I want to update a network graph dynamically in D3.js. Now my code is:
var color = d3.scale.category20();
var my_nodes = [{"cluster": 0, "x": 50, "y": 50},
{"cluster": 0, "x": 100, "y": 50},
{"cluster": 1, "x": 100, "y":100}];
var vis = d3.select("body").append("svg").attr("width", 500).attr("height", 500);
var nodes = vis.selectAll("circle.node").data(my_nodes).enter().append("g")
.attr("class", "node");
var circles = nodes.append("svg:circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.style("fill", function(d) {return color(d.cluster)});
This code works. But when I update data like:
var new_nodes = [{"cluster": 0, "x": 50, "y": 50},
{"cluster": 2, "x": 100, "y": 50},
{"cluster": 2, "x": 100, "y":100}];
nodes.data(new_nodes);
doesn't work.
How can I update bound data?
EDIT: What I want to do is replacing old data my_nodes
with new data new_nodes
. Is there any way to update the attribute cluster
of each bound data?
EDIT2: Suppose I do:
d3.select("body").select("svg").selectAll("circle").data(mydata).enter().append("svg:circle");
Can I modify mydata
?
There's no data-binding magic à la angular that's going to trigger a "redraw". Just call .data
and then re-set the attributes:
function update(){
nodes
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", 5)
.style("fill", function(d) {
return color(d.cluster)
});
}
var nodes = vis.selectAll("circle.node").data(my_nodes)
.enter()
.append("g")
.attr("class", "node")
.append("svg:circle");
update();
// some time later
nodes.data(new_nodes);
update();
Example here.
Not sure how you'd want this to look, but I've created a fiddle here: http://jsfiddle.net/henbox/8ua144p4/4/
Clicking the update
button will update to the new data.
I've based the changes off the General Update Patterns and this article from Mike on Joins
I've put a transition on the fill
attribute for each circle, so you can hopefully see that the nodes are being updated in this case rather than new nodes being added. I've also shown a 4th new node being added, to demo the difference.
Finally, I've simplified things a little by removing the node
(g
) elements and just using circle
. This is the important code:
// DATA JOIN
// Join new data with old elements, if any.
var circle = vis.selectAll("circle").data(data);
// ENTER
// Create new elements as needed.
circle.enter().append("svg:circle").attr("r", 5);
// UPDATE
// Update old elements as needed.
circle.attr("cx", function (d) {return d.x;})
.attr("cy", function (d) {return d.y;})
.transition().duration(750)
.style("fill", function (d) {
return color(d.cluster)
});
// EXIT
// Remove old elements as needed.
circle.exit().remove();
When the data is updated, force.start();
is run everytime so it looks like new data. If you remove that, it's easier to see what's happening but you lose the animation. What you probably want is to only animate entry for the new nodes (and maybe exiting nodes), but that would be a separate question
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