Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update bound data in d3.js?

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?

like image 407
keisuke Avatar asked Nov 27 '14 11:11

keisuke


2 Answers

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.

like image 95
Mark Avatar answered Oct 14 '22 00:10

Mark


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

like image 29
Henry S Avatar answered Oct 13 '22 23:10

Henry S