I am searching for a way to bind a d3 plot to a shiny app.
The hintch is: The plot gets created dynamically on the javascript side. The trigger for the creation of the plot on the other side comes from the shiny app. How it works.
That means, something in the ui.R like includeHTML("someJsWithPlot.js") won't work (as described here), because the plot will be displayed upon user interaction, after the index.html is created.
I tried this on the ui.R side
tags$div(id = "#fromJs")
And this on the .js side
var w = 100;
var h = 100;
var test = d3.select("#fromJs").append("text")
.attr("width", w)
.attr("height", h);
test.append("text")
.text("Foobar")
.attr("x",10)
.attr("y",15);
That works! The text gets displayed in the div I created!
My idea was to replace the text with the plot. For a starter, I tried to replace it with a simple circle, but that does not work. Nothing gets displayed (nor an error thrown in the console).
var w = 100;
var h = 100;
var test = d3.select("#fromJs").append("svg")
.attr("width", w)
.attr("height", h);
test.append("svg")
.attr("x", 10)
.attr("y", 15)
.attr("r", 20);
I also found this, but I do not find much on shiny.OutputBinding(), except this or this. The latter one is not understandable to me.
Bottom line: How do I access a costum div from the js side? There seems to be a workaround, but that looks like a hack...
I am bit clueless where to start. Any hints?
Related
I can recreate the following 1000 times and have enough of an understanding to do so. But I'm trying to get my head around a few specific bits that I just 'do', rather than understand:
var w = 900,
h = 500;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.attr("style", "border: 1px solid grey;")
.on("mousemove", fn)
var force = d3.layout.force()
.size([w, h])
.on("tick", tick)
.gravity(0)
.charge(0)
.start()
function fn() {
var m = d3.mouse(this);
var point = {x: m[0], y: m[1]};
d3.select("#output").text(force.nodes().length)
var node = svg
.append("circle")
.data([point])
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
.attr("r", 0.1)
.transition().ease(Math.sqrt)
.attr("r", 5)
.transition().delay(1000)
.each("end", function() {
force.nodes().shift()
})
.remove()
force.nodes().push(point)
force.start()
}
function tick() {
svg.selectAll("circle")
.attr("cx", function(d) {return d.x})
.attr("cy", function(d) {return d.y})
}
In particular it's the data binding part I'm not sure about.
In function fn() (on mousemove of svg space) we define a new point and we need to do two things with it; push it into force.nodes() so that the x and y coordinates of the point can be manipulated by forces configured in the force layout, and we need to use the coordinates of the point to create and manipulate the visualisation.
So we create the point first off. We then build a circle to represent this point. We push the point into force.nodes() and after a short delay, we remove both the visualisation and the point from the force.nodes() array.
The bit I don't understand is how the visualisation and the point in the array stay "connected"?
Conjecture: The data point is an object which the force layout is constantly updating the x and y properties of. There is a "link" to this object bound to the circle element. The object is therefore easily accessed and used by the circle object, but not without us controlling that process. The circle is defined as having a cx and cy at point of its creation, but we need to keep accessing the underlying data to update its cx and cy?
If that's the case, how is the object "shared" by both force.nodes() and the circle element?
Or am I miles off the mark?
Also I have read a lot of documentation on this but I feel this is something more intrinsic to javascript rather than d3 necessarily, so it's not elaborated on in any literature I've so far read.
The link between the data structures that the force layout updates and the visualization (i.e. the DOM elements) is the tick event handler function. The tick event is generated by the force layout to signify that the force simulation has progressed another step (i.e. tick) and its internal state has changed. This signals that the visualization needs to be updated.
There are two parts to making this link happen. First, the data operated on by the force layout (i.e. the links and nodes) needs to be bound to DOM elements. This is done using the usual .selectAll().data().enter().append() pattern, usually in the initialisation code, sometimes in the tick event handler function. This establishes the link between data and DOM elements.
The second part to this is the code that updates the DOM elements when the force layout changes their positions. This is what happens in the tick event handler function. If you're not adding or removing elements, there's usually no need to rebind data and often you won't see the .selectAll().data() pattern, but only the code that actually updates the positions based on the data already bound to the elements (in your case this works even though you're changing the elements because the data binding happens in the function that updates the data for the force layout as well).
As an experiment, take an arbitrary force layout example and delete the tick event handler function -- you'll see that nothing happens at all even though the force layout is running.
I'm helping guide a project that's combining some visualizations in D3. In our specific example we are producing a graph of pies by using a regular D3 Pie chart with the sankey layout visualization. The affect of this is to produce something like:
The development is aimed to try and keep this as modular as possible, therefore the very first step was to produce an updating pie chart that could be used stand alone or plugged into another visualization. This is currently encapuslated into a pieObject which looks something like this:
var pieObject = function( d, target ){
var pie = {};
// other code to handle init
pie.update = function(data) {
// render code
};
};
Where it gets a little confusion is in the tree visualization, when I need to start handling updates. Here is how a new pie is added:
sankey.nodes(data.nodes)
.links(data.links)
.layout(32);
var node = svg.append("g")
.selectAll(".node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
.each( function( d ) {
var pie = new pieObject( d, this );
})
If I however want to deal with an existing node, I'm not sure how I should go about accessing the pieObject? There are a couple of options I can think of, but I'm wondering if there's a general approach commonly used?
Store the pieObject on the element d
Store the pieObject in an array or JavaScript object with a lookup from a field on the d
Out of the examples I've outlined, I prefer option #1. But I'm also re-loading my entire data-set from JSON (e.g. new JSON request returns existing data + new data) so I believe when I call sankey.nodes(data.nodes).links(data.links).layout(32); that this is going to lose any additional information I've stored on the d object?
EDIT
I've put together a JSFiddle to help illustrate my problem. The code is a little lengthy and I don't own it as yet so don't know all the ins & outs but here's a breakdown:
1-#214 - Sankey code which produces the Tree layout
215-#451 - Code for a Pie Chart
453-#475 - Code to add the viz to the page
Specifically the area of creating the pies, and trying to figure out how to update them is in the render function between lines #129-#149
I'd recommend starting with Mike Bostock's tutorial on re-usable charts, which does pretty much what you are looking for: http://bost.ocks.org/mike/chart/
Specifically, the key to this type of thing is to use the selection.call() function to insert your chart into another chart/layout in a re-usable way. Then when you need to update your embedded chart, just do the same selection.call() again. Hopefully that gets you started.
I'm trying to draw a straight line of circles using d3 at the bottom of the browser window. I am not sure how this would be accomplished. I know I could create a bunch of circles using the SVG tag, but there's probably a better way using a for loop with an array.
I would like the circles to appear in a straight line at the bottom of the browser window. I would also like the circles to fill the width of the browser window as well. Any help would be greatly appreciated.
d3 has a functional style with the concept of selectors. If you are thinking of using a loop then you are probably using the tool wrong. The functional style allows you to instead concentrate on what you want to do with each item of data instead of how to process the data. There are also a number of helper functions.
Lets take Adam's solution
d3.select('body')
We are using CSS style selectors to select one object from the DOM. In this case it is the body of the document. We can do a number of things with this selection but first we append using
append('svg')
in
d3.select('body').append('svg')
This could be written to differently if we needed to reuse these selections
var body = d3.select('body');
var svg = body.append('svg');
We can the define the attributes of the object just defined
.attr('width', width)
.attr('height', height)
Now comes the interesting bit. D3 operates by binding data to selections so add data we first need a (probably) empty selection.
.selectAll('circle')
Note the use of selectAll not select.
Adam creates an array of data with
d3.range(0, width, width/10)
This uses one of d3's helper functions which behaves like the range function found in many languages with functional support (examples of use in F# & Python)
> d3.range(5)
[0, 1, 2, 3, 4]
> d3.range(0,5)
[0, 1, 2, 3, 4]
> d3.range(4,5)
[4]
// At intervals of 2
>d3.range(0,5,2)
[0, 2, 4]
Anyway we have a list of number which gets bound using
.data()
Which returns a selection. We the define what happens within the life cycle events of this selection. Because we are only dealing with data entering we can just go
.enter()
Anything under this selection will be applied to any datum entering (which in this case will be all the elements from the list). You should be able to understand what is happening until
.attr('cx', function(d){ return d; })
What is happening hear is the attribute cx is dependent on the data from the list we supplied earlier. We can provide a function which will be executed which gets passed the datum and the index of the current item.
Using some more of the helpers D3 brings
Typically you will need to use the scale helper when using D3. This allows us to abstract out the concept of pixels and instead concentrate on a fixed range.
Slightly changing the example given by Adam. Lets say we want to show 5 evenly spaced circles at the end of the document.
We can define the data like
var data = d3.range(0, 5);
And set up a scale like
var x = d3.scale.linear()
.domain([0, data.length])
.range([0,width])
With the domain (that is the input) been 0 to the max number of our data.
.domain([0, d3.max(data)])
and the range (that is what we want to output) as been 0 up to the maximum number of pixals
.range([0,width])
The example code would then look like
var width = window.innerWidth;
var height = 100;
var data = d3.range(0, 5);
var x = d3.scale.linear()
.domain([0, data.length-1])
.range([0,width])
d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(data ).enter()
.append('circle')
.style('fill', 'red')
.attr('r', height/4)
.attr('cy', height/2)
.attr('cx', function(d){ return x(d);})
We can even change that last line to
.attr('cx', function(d, i){ return x(i);})
Whilst in this example the index and the data is the same this allows us to to space out the items whilst keeping the data simple. Say if the array actually was the values which r should be
...
var data = [4,1,20,5,7];
...
.attr('r', function(d){ return d;})
...
.attr('cx', function(d, i){ return x(i+0.5);})
var width = window.innerWidth;
var height = 100;
d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(d3.range(0, width, width/10)).enter()
.append('circle')
.style('fill', 'red')
.attr('r', height/4)
.attr('cy', height/2)
.attr('cx', function(d){ return d; })
I re-factored the d3.layout.pack graph example here into a reusable module. Now I want to update the graph when the data updates. But when I call the graph with the new data the new graph gets rendered on top of the old graph. You can find a demo of the issue here.
Basically, to simulate the data update I am calling a function with setinterval this way:
function test(){
d3.select('#vis')
.datum(data2)
.call(cluster);
}
setInterval(test, 1500);
you can find the data update section in the bottom of the file.
Could you please check what's wrong?
There are a few problems with your code. First, your check whether the SVG exists already doesn't work because of scoping issues. The better way to do it is to select the element you want and check whether your selection is empty.
var svg = d3.select("svg > g");
if(svg.empty()){
svg = d3.select(this).append("svg:svg").attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("transform", "translate(" + (width - r) / 2 + "," + (height - r) / 2 + ")");
}
Note that I've merged the appending of the g element into this, as that is what you're operating on.
Second, you need to handle the update and exit selections in addition to the enter selection. I've added that to your jsfiddle here.
Below is a bit of code that I've written and I'm hoping someone can help me out and explain why it's not responding the way I imagined it would.
I have (quite obviously) pieced this together from a number of examples, docs, etc. online and am using d3.v3.js. I want to better understand exactly what the center, scale, and translate 'attributes' of a projection do, so in addition to the large quantity of reading I've done, I thought I'd write a brief script that allows the user to click a new 'center' for the map - so in effect, you should be able to click this map (made with some data available in the gallery) and recenter the map on a new state/location/etc.
The issue is that every time I set a new center for the data as the inversely projected point that was clicked (that is, invert the point to get the new coordinates to set the center to), the map centers on alaska, then I can click that general area a few times and the world 'rotates' back into view.
What am I doing wrong? I thought this script would help me gain a better understanding of what's going on, and I'm getting there, but I would like a little help from you if at all possible.
<script>
var w = 1280;
var h = 800;
var proj = d3.geo.albers();
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("body")
.insert("svg:svg")
.attr("height", h)
.attr("width", w);
var states = svg.append("svg:g")
.attr("id", "states");
d3.json("./us-states.json", function(d) {
states.selectAll("path")
.data(d.features).enter()
.append("svg:path")
.attr("d", path)
.attr("class", "state");
});
svg.on("click", function() {
var p = d3.mouse(this);
console.log(p+" "+proj.invert(p));
proj.center(proj.invert(p));
svg.selectAll("path").attr("d", path);
});
</script>
You should be able to use projection.rotate()
svg.on("click", function() {
p = proj.invert(d3.mouse(this));
console.log(p);
proj.rotate([-(p[0]), -(p[1])]);
svg.selectAll("path").attr("d", path);
});