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?
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With