Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding javascript (d3.js) to shiny

First, I am fairly unfamiliar with javascript and its library d3.js, but I am familiar with R. Creating dashboards using Shiny has been fun and easy (thanks to stackoverflow). Now I want to expand it by connect d3 elements to it.

I'm looking for information sources on how to actually bind javascript to Shiny (R dashboard) and explain what is actually going on.

Background: I did the tutorial on js and jquery on w3schools and learned (a bit) about d3 using Scott Murray's book (Interactive Data visualization for the web). I hoped this would be enough to make me understand the examples and explanation concerning how to build custom input/output bindings on the Shiny website:

http://shiny.rstudio.com/articles/building-inputs.html

But unfortunately I don't and I can't seem to find any examples which are in minimal working code. Many examples on github are to complex for me to dissect, most probably because of my little experience with javascript. Here is an examples of custom input binding with javascript:

https://github.com/jcheng5/shiny-js-examples/tree/master/input

Here is an example of an input & output binding I try to unfold:

<script src="http://d3js.org/d3.v3.js"></script> <script type="text/javascript"> (function(){   // Probably not idiomatic javascript.    this.countValue=0;    // BEGIN: FUNCTION   updateView = function(message) {      var svg = d3.select(".d3io").select("svg")      svg.append("text")       .transition()       .attr("x",message[0])       .attr("y",message[1])       .text(countValue)       .each("end",function(){         if(countValue<100) {           countValue+=1;           $(".d3io").trigger("change");         }       })   }   // END: FUNCTION    //BEGIN: OUTPUT BINDING   var d3OutputBinding = new Shiny.OutputBinding();   $.extend(d3OutputBinding, {     find: function(scope) {       return $(scope).find(".d3io");     },     renderError: function(el,error) {       console.log("Foe");     },     renderValue: function(el,data) {       updateView(data);       console.log("Friend");     }   });   Shiny.outputBindings.register(d3OutputBinding);   //END: OUTPUT BINDING    //BEGIN: INPUT BINDING   var d3InputBinding = new Shiny.InputBinding();   $.extend(d3InputBinding, {     find: function(scope) {       return $(scope).find(".d3io");     },     getValue: function(el) {       return countValue;     },     subscribe: function(el, callback) {       $(el).on("change.d3InputBinding", function(e) {         callback();       });     }   });   Shiny.inputBindings.register(d3InputBinding);  //END: OUTPUT BINDING  })() </script> 

Where "d3io" is a div element in the ui, updateView() is a function. Here is the ui:

#UI library(shiny)  d3IO <- function(inputoutputID) {   div(id=inputoutputID,class=inputoutputID,tag("svg","")) #; eerst zat ; erbij, maar werkt blijkbaar ook zonder }  # Define UI for shiny d3 chatter application shinyUI(pageWithSidebar(    # Application title   headerPanel("D3 Javascript chatter",               "Demo of how to create D3 I/O and cumulative data transfer"),    sidebarPanel(     tags$p("This widget is a demonstration of how to wire shiny direct to javascript, without any input elements."),     tags$p("Each time a transition ends, the client asks the server for another packet of information, and adds it             to the existing set"),     tags$p("I can't claim this is likely to be idiomatic javascript, because I'm a novice, but it allows d3 apps             to do progressive rendering.  In real use, a more complex request/response protocol will probably be             required.  -AlexBBrown")   ),    mainPanel(     includeHTML("d3widget.js"),     d3IO("d3io") #Creates div element that d3 selects     ) )) 

Here is the server file:

# SERVER library(shiny) # Define server logic required to respond to d3 requests shinyServer(function(input, output) {    # Generate a plot of the requested variable against mpg and only    # include outliers if requested   output$d3io <- reactive(function() {     if (is.null(input$d3io)) {       0;     } else {       list(rnorm(1)*400+200,rnorm(1)*400+200);     }   }) }) 

Specific questions:

1) The server.r seems to get input called "d3io" (input$d3io) since this is not defined in ui.r, I reasoned it must come from the javascript file. Which element does it actually refer to?

2) I have trouble understanding the custom binding part:

var d3OutputBinding = new Shiny.OutputBinding();   $.extend(d3OutputBinding, {     find: function(scope) {       return $(scope).find(".d3io");     },     renderError: function(el,error) {       console.log("Foe");     },     renderValue: function(el,data) {       updateView(data);       console.log("Friend");     }   });   Shiny.outputBindings.register(d3OutputBinding); 

My understanding is:

Create a new shiny outputbinding, first find the class .d3io (div element), if error then write to console "Foe" (is this special code?), if not error then renderValue using the function updateView using data (Where does it receive this value from?) and write to console "Friend". Finally register output.

Hope you guys can help! I'm creating a document with the steps on "The necessary steps to learn how to implement javascript into shiny when you don't know any javascript", I would love that!:)

Cheers, Long

like image 967
Sweetbabyjesus Avatar asked Oct 30 '14 10:10

Sweetbabyjesus


1 Answers

Hi Sweetbabyjesus (so fun to say). You had two questions:

1) The server.r seems to get input called "d3io" (input$d3io) since this is not defined in ui.r, I reasoned it must come from the javascript file. Which element does it actually refer to?

That phrase input$d3io has the following components:

  • input is a parameter passed into the function - it's a list that stores the current values of all the widgets in the app.
  • $ is the member selector.
  • d3io refers to the content of the div element with that id ('d3IO("d3io")') in the mainPanel of the UI.

2) I have trouble understanding the custom binding part:

var d3OutputBinding = new Shiny.OutputBinding(); 

That's right, this creates an instance of Shiny.OutputBinding and assigns it to the variable d3OutputBinding.

$.extend(d3OutputBinding, {   find: function(scope) {     return $(scope).find(".d3io");   },   renderError: function(el,error) {     console.log("Foe");   },   renderValue: function(el,data) {     updateView(data);     console.log("Friend");   } }); 

This code extends the behaviour of d3OutputBinding with three functions called find, renderError and renderValue. Those three functions are required for a Shiny.OutputBinding.

find is the key because it returns a list of elements that should be passed into the two render functions via their el parameter. Notice it's returning elements whose css class is "d3io" - that's the same div mentioned earlier.

Note that extend() is a function of jQuery javascript library, and the $ in this context is an alias for the jQuery object.

Shiny.outputBindings.register(d3OutputBinding); 

Lets Shiny know that this newly configured object should be put to use now.

Cheers, Nick

like image 69
gknicker Avatar answered Oct 17 '22 20:10

gknicker