Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use index.html and ui.r for my r shiny interface?

Tags:

r

d3.js

shiny

In reference to this on how to build your shiny app completely in HTML, I'm wondering if there is any way to use this approach in conjunction with using the traditional ui.r approach.

Reason: This approach of using D3 with R Shiny seems to require putting all D3 code into the index.html file. But I also want some interactivity (selectInputs, dateRangeInputs, multiple tabs, etc.). Any advice?

like image 788
sbanders Avatar asked Apr 29 '15 14:04

sbanders


Video Answer


2 Answers

You can use tags to add custom HTML to your ui.R.

For more complicated outputs, you can build custom shiny outputs while using an app with ui.R and server.R. This page has info on how to do this for any code.

Here's an example using the javascript code from the D3 example you posted. The app just generates the plot, I added a selectInput where you can select the data it plots to show how thing integrate. All of the javascript code is by mbostock. (Can be found here).

I added the shiny binding parts and changed a few lines to adapt it to the app, I commented above the changes so you can track them.

ui.R

library(shiny)

shinyUI(
  fluidPage(singleton(tags$head(
    #adds the d3 library needed to draw the plot
    tags$script(src="http://d3js.org/d3.v3.min.js"),

    #the js script holding the code to make the custom output
    tags$script(src="HierarchicalEdgeBundling.js"),

    #the stylesheet, paste all that was between the <style> tags from your example in the graph_style.css file
    tags$link(rel = "stylesheet", type = "text/css", href = "graph_style.css")
  )),
    mainPanel(

      #this select input allows the user to choose json files in the www directory
      selectInput("data_files", "JSON files:" ,  as.matrix(list.files(path="www",pattern="json"))),

      #this div will hold the final graph
      div(id="graph", class="HierarchicalEdgeBundling")
    )        
  )
)

server.R

shinyServer(function(input, output, session) {

  #output to the graph div
  output$graph <- reactive({
    #get the selected file
    input$data_files
  })
})

HierarchicalEdgeBundling.js

//shiny output binding
var binding = new Shiny.OutputBinding();

binding.find = function(scope) {
        return $(scope).find(".HierarchicalEdgeBundling");
};



binding.renderValue = function(el, data) {
//empty the div so that it removes the graph when you change data
  $(el).empty()

if(data!=null){
  var diameter = 960,
      radius = diameter / 2,
      innerRadius = radius - 120;

  var cluster = d3.layout.cluster()
      .size([360, innerRadius])
      .sort(null)
      .value(function(d) { return d.size; });

  var bundle = d3.layout.bundle();

  var line = d3.svg.line.radial()
      .interpolate("bundle")
      .tension(.85)
      .radius(function(d) { return d.y; })
      .angle(function(d) { return d.x / 180 * Math.PI; });

  //select the div that has the same id as the el id 
  var svg = d3.select("#" + $(el).attr('id')).append("svg")
      .attr("width", diameter+300)
      .attr("height", diameter+300)
    .append("g")
      .attr("transform", "translate(" + radius + "," + radius + ")");

  var link = svg.append("g").selectAll(".link"),
      node = svg.append("g").selectAll(".node");

  //add the data from the user input
  d3.json(data, function(error, classes) {
    var nodes = cluster.nodes(packageHierarchy(classes)),
        links = packageImports(nodes);

    link = link
        .data(bundle(links))
      .enter().append("path")
        .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
        .attr("class", "link")
        .attr("d", line);

    node = node
        .data(nodes.filter(function(n) { return !n.children; }))
      .enter().append("text")
        .attr("class", "node")
        .attr("dy", ".31em")
        .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
        .style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
        .text(function(d) { return d.key; })
        .on("mouseover", mouseovered)
        .on("mouseout", mouseouted);
  });

  function mouseovered(d) {
    node
        .each(function(n) { n.target = n.source = false; });

    link
        .classed("link--target", function(l) { if (l.target === d) return l.source.source = true; })
        .classed("link--source", function(l) { if (l.source === d) return l.target.target = true; })
      .filter(function(l) { return l.target === d || l.source === d; })
        .each(function() { this.parentNode.appendChild(this); });

    node
        .classed("node--target", function(n) { return n.target; })
        .classed("node--source", function(n) { return n.source; });
  }

  function mouseouted(d) {
    link
        .classed("link--target", false)
        .classed("link--source", false);

    node
        .classed("node--target", false)
        .classed("node--source", false);
  }

  d3.select(self.frameElement).style("height", diameter + "px");

  // Lazily construct the package hierarchy from class names.
  function packageHierarchy(classes) {
    var map = {};

    function find(name, data) {
      var node = map[name], i;
      if (!node) {
        node = map[name] = data || {name: name, children: []};
        if (name.length) {
          node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
          node.parent.children.push(node);
          node.key = name.substring(i + 1);
        }
      }
      return node;
    }

    classes.forEach(function(d) {
      find(d.name, d);
    });

    return map[""];
  }

  // Return a list of imports for the given array of nodes.
  function packageImports(nodes) {
    var map = {},
        imports = [];

    // Compute a map from name to node.
    nodes.forEach(function(d) {
      map[d.name] = d;
    });

    // For each import, construct a link from the source to target node.
    nodes.forEach(function(d) {
      if (d.imports) d.imports.forEach(function(i) {
        imports.push({source: map[d.name], target: map[i]});
      });
    });

    return imports;
  }
}
};

//register the output binding
Shiny.outputBindings.register(binding, "HierarchicalEdgeBundling");

To make the app work you need to create a www folder in the same directory as your ui.R and server.R and put in it the HierarchicalEdgeBundling.js file, the css found here in a graph_style.css file, and json data file (for example data1.json or data2.json).

like image 164
NicE Avatar answered Sep 27 '22 22:09

NicE


My understanding is that using index.html is a replacement for ui.R, giving more functionality at the "cost" of needing to use HTML code (vs. the "out-of-the-box" shiny functions like selectInputs etc... these really just convert a series of inputs into HTML code anyway).

To find the equivalent functions in index.html, one tip would be to create your ui.R, then view page source in your browser. This will give you the right HTML code to implement in your shiny app. For example, a tab might look like:

<div class="container-fluid">
    <div class="tabbable tabs-above">
      <ul class="nav nav-tabs">
        <li class="active">
          <a href="#tab-5303-1" data-toggle="tab" data-value="Graphs">Graphs</a>
        </li>
        <li>
          <a href="#tab-5303-2" data-toggle="tab" data-value="Size Data">Size Data</a>
        </li>
        <li>
          <a href="#tab-5303-3" data-toggle="tab" data-value="Data Bins">Data Bins</a>
        </li>
      </ul>

...

<div class="tab-content">
        <div class="tab-pane active" data-value="Graphs" id="tab-5303-1">

...

    </div>
  </div>

Once you figure out how to translate the UI arguments to HTML code, it is fairly straightforward. The link you provided gives a good summary of how to connect to your server file.

like image 41
Chris Avatar answered Sep 27 '22 23:09

Chris