How to update reusable d3.js graph when data updates? - javascript

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.

Related

How to sort a grouped bar-chart d3js v4

In this block (FIXED) I've tried to do a sorting function in a similar fashion to this.
It technically sorts the bars but not in the expected way, if you check the sorting checkbox and shift years you can see what I mean.
I thought that it had something to do with the fact that it's only sorting based on data and not keys and/or the copy variable but I've tried sorting in all kinds of ways based on the mentioned variables without any success.
Not sure what I'm missing, appreciate any help!
Here you go! There was not much change required in your previous code.
Plunker
So this was related to the data binding to the barGroups. Every time the data was sorted or changed, new data was bound to the "g.layer" and with d3's update methodology, this would how it would work.
Changes in the new code:
Moved barGroups code above the data sorting with no transform attribute.
Added transform attribute to the groups after x0 domain is defined.
Relevant code:
Above the sort function:
// bars
let barGroups = g.selectAll("g.layer").data(data);
barGroups.enter().append("g")
.classed('layer', true);
barGroups.exit().remove();
Once x0 domain is set:
g.selectAll("g.layer").transition().duration(Globalvar.durations)
.attr("transform", function(d, i) {
return "translate(" + x0(d.State) + ",0)";
});
Hope this helps! :)

Bind d3 plot to shiny ui.R

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?

How to go about updating modular visualizations

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.

Can I create a Javascript chart from this table?

I just started programming with Highcharts, and wonder if it's possible to get any sort of line chart based on this table: http://www2.nve.no/h/hd/plotreal/Q/0027.00025.000/knekkpunkt.html
The table updates each half hour, and I want to program a line chart that updates accordingly.
My problem is that I have no idea if this is possible. The table data are remote, on a server I don't have access to. The data is public and open, though.
Would be grateful for some help!
One great open source project you'd want to check out is D3.js. It's a feature-rich library for generating complex visualizations (using SVG elements and the DOM) from raw data. Here is a demo I made using a snippet of your data that is powered by D3:
http://jsfiddle.net/2s6Y3/
//Define two functions that will 'map' x- and y-values into their respective axis domain
var x_scale_fn = d3.scale.linear().domain([Math.min.apply(null, times), Math.max.apply(null, times)]).range([0, width]);
var y_scale_fn = d3.scale.linear().domain([Math.max.apply(null, levels), Math.min.apply(null, levels)]).range([0, height]);
//Create a 'line' object that can read our records and turn them into x-y coordinates
var line = d3.svg.line()
.x_scale_fn(function(d, i) { return x(times[i]); })
.y_scale_fn(function(d, i) { return y(levels[i]); });
//Create a new SVG element (our chart) and give it some basic attributes
var graph = d3.select(".chart").append("svg:svg")
.attr("width", width + margins[1] + margins[3])
.attr("height", height + margins[0] + margins[2])
.append("svg:g")
.attr("transform", "translate(" + margins[3] + ", " + margins[0] + ")");
//Create our chart axes
var x_axis = d3.svg.axis().scale(x),
y_axis = d3.svg.axis().scale(y).orient("left");
//Render the x-axis
graph.append("svg:g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(x_axis)
.selectAll("text")
.attr("transform", function(d) { return "rotate(45deg)"; });
//Render the y-axs
graph.append("svg:g")
.attr("transform", "translate(-25,0)")
.attr("class", "y axis")
.call(y_axis);
//Render our data (line) onto the chart
graph.append("svg:path").attr("d", line(data));
If you want to create a project that offers a persistent, up-to-date visualization of the water data, you'll need to set up a scraper that will periodically read data from the page and format it to be used by D3 (i.e. - JSON). This could be done with a number of different tools, but one suggestion would be using Python and urllib2.
This will be possible, but not fun at all. That data is all one big preformatted string. Getting access to a table of those values will make things way easier for you. Otherwise, you'll need to look into writing some RegEx to parse that monster node's text.
Your link seems to not be working.
If you are talking about an HTML table, take a look at the demo here:
http://www.highcharts.com/demo/column-parsed
Thanks for all the help, this made me realize that my skills doesn't add up to what it takes to get this working.

D3 Tree Layout Separation Between Nodes using NodeSize

Right now I am trying to separate my rectangle nodes because they overlap as shown in the picture below:
I took a look and found out that D3 offers a nodeSize and separation method but for some reason it did not work.
I found this blog post talking about the issue but he says
The size property doesn’t exist in nodes, so it will be whatever property you want to control the size of them.
but clearly there is a nodeSize method so I feel like I am simply using the method incorrectly and/or the blog post is out-of-date. I want to shape my nodes to be the size of the rectangle and space them out evenly so they do not overlap each other. Does anyone know how to use the methods properly? The documentation about these methods isn't explained very well and it isn't yielding any difference. I also couldn't find many examples where people changed the nodeSize of trees or needed separation for rectangular objects (there were some examples regarding circular ones but I feel that's too different...)
Here is the relevant code. I will try to prepare a JSFiddle.
var margin = {top: 20, right: 120, bottom: 20, left: 120},
height = 960 - margin.right - margin.left,
width = 800 - margin.top - margin.bottom,
rectW = 70;
rectH = 30;
//bbox = NaN,
maxTextLength = 0;
var i = 0,
duration = 750,
root;
//paths from each node drawn initially here
//changed to d.x, d.y
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x+rectW/2, d.y+rectH/2];
//.projection(function(d) { return [d.x+bbox.getBBox().width/2, d.y+bbox.getBBox().height/2];
});
var tree = d3.layout.tree()
.nodeSize([30,70])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2); })
.size([width, height]);
var svg = d3.select("body")
.append("svg")
.attr("height","100%").attr("width","100%")
.call(d3.behavior.zoom().on("zoom", redraw))
.append("g")
.attr("transform", "translate(" + margin.top + "," + margin.left + ")");
UPDATE 05/04/2018: It is my understanding d3 has changed a lot (for the better) to be a lot more modular. For those who are looking towards this answer, this was using a much older version of d3 (specifically v3).
A lot of the findings are still relevant for the d3-hierarchy package under cluster.size() and cluster.nodeSize() and I am planning to potentially update my example to use that. For historical reference though, I'm leaving the bottom untouched.
Here is a jsFiddle: http://jsfiddle.net/augburto/YMa2y/
EDIT: Updated and move the example to Codepen. The example still exists on jsFiddle but Codepen seems to have a nicer editor and allows you to easily fork. I'll also try to add the example directly to this answer once I've reduced the amount of content in it.
http://codepen.io/augbog/pen/LEXZKK
Updating this answer. I talked with my friend and we looked at the source for size and nodeSize
tree.size = function(x) {
if (!arguments.length) return nodeSize ? null : size;
nodeSize = (size = x) == null;
return tree;
};
tree.nodeSize = function(x) {
if (!arguments.length) return nodeSize ? size : null;
nodeSize = (size = x) != null;
return tree;
};
When you set a size for the tree, you are setting a fixed size so that the tree has to conform to that width and height. When you set a nodeSize, the tree has to be dynamic so it resets the size of the tree.
When I specified size after nodeSize, I was pretty much overriding what I wanted haha...
Bottom line: If you want nodeSize to work, you can't have a fixed tree size. It will set the size to null. Do not declare a size if you are declaring a nodeSize.
EDIT: D3.js actually updated the documentation. Thanks to whoever did that because it is way clearer now!
The nodeSize property is exclusive with tree.size; setting
tree.nodeSize sets tree.size to null.
This is what my tree looks like now. I have also added zoom functionality as well as how to center text within the rectangle.
I didn't quite understand the accepted answer until I did some digging of my own, so I thought I'd share what I found as well...
If you are using .size() and your nodes are overlapping, use .nodeSize() instead
As explained in the accepted answer, .size() sets the tree's available size, and so depending on the spacing between cousin nodes, second cousins, etc. they may get squished together and overlap. Using .nodeSize() simply says each node should get this much space, so they will never overlap!
The code that ended up working for me was
var nodeWidth = 300;
var nodeHeight = 75;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
Without horizontalSeparationBetweenNodes and verticalSeparationBetweenNodes the nodes edges were touching each other. I also added this .separation() to decrease the amount of space between cousin nodes, as my nodes are pretty wide and lots of space was getting wasted.
Note: This is for d3 v3, not v4
First, thanks to all those who posted before, pure gold.
I wanted to add to this post for those that might be struggling with the offset problem associated with a horizontally drawn tree.
The key is, if you switch from .size() to .nodeSize() on a horizontal tree, you'll notice your root node seems to jump/reorient to be located at (0,0).
And per the d3.js documentation this is actually the case (see https://github.com/d3/d3-hierarchy/blob/master/README.md#tree_nodeSize )
However, to adjust you just need to make sure to reorient your viewBox.
That is to say, when you .append your svg you need to explicitly set your viewBox. Here's my hacky little line where it worked for me...
svg = d3.select("#tree").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + 0 + 0)
.attr("viewBox", "0 "+(-1*(height-margin.top-margin.bottom)/2)+" "+width+" "+height)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + 0 + ")");
D3 now does things through Observable.
To set the nodeSize look for the line:
main.variable(observer("tree")).define("tree", ["d3","dx","dy"],
function(d3,dx,dy){return(
d3.tree()
And set nodeSize with added constants:
main.variable(observer("tree")).define("tree", ["d3","dx","dy"],
function(d3,dx,dy){return(
d3.tree().nodeSize([dx + 10, dy + 10])
Or use a function to set values wrt chart size as discussed in other answer using the older D3 approach.

Categories

Resources