Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 v4: Update force layout

Mike Bostock has an example regarding updating a force layout. The example is based on v3 — how can the same functionality be replicated in v4?

Here's my (pitiful) attempt.

I've read the changes to selections in the v4 Changelog, but the merge call is still confusing. In particular, it's not clear to me how the data join interacts with the simulation nodes() and links() call.

like image 973
David Chouinard Avatar asked Jul 27 '16 21:07

David Chouinard


1 Answers

var width = 300,
    height = 200;

var color = d3.scaleOrdinal(d3.schemeCategory20);

var nodes = [],
    links = [];

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

var svg = d3.select("svg");

var linkLayer = svg.append('g').attr('id','link-layer');
var nodeLayer = svg.append('g').attr('id','node-layer');

// 1. Add three nodes and three links.
setTimeout(function() {
  var a = {id: "a"}, b = {id: "b"}, c = {id: "c"};
  nodes.push(a, b, c);
  links.push({source: a, target: b}, {source: a, target: c}, {source: b, target: c});
  start();
}, 0);

// 2. Remove node B and associated links.
setTimeout(function() {
  nodes.splice(1, 1); // remove b
  links.shift(); // remove a-b
  links.pop(); // remove b-c
  start();
}, 2000);

// Add node B back.
setTimeout(function() {
  var a = nodes[0], b = {id: "b"}, c = nodes[1];
  nodes.push(b);
  links.push({source: a, target: b}, {source: b, target: c});
  start();
}, 4000);


function start() {
  var link = linkLayer.selectAll(".link")
    .data(links, function(d) { return d.source.id + "-" + d.target.id; });
  
  
  link.enter().append("line")
    .attr("class", "link");

  link.exit().remove();

  var node = nodeLayer.selectAll(".node")
      .data(nodes, function(d) { return d.id;});
  
  node.enter().append("circle")
      .attr("class", function(d) { return "node " + d.id; })
      .attr("r", 8);

  node.exit().remove();

  simulation
    .nodes(nodes)
    .on("tick", tick);

  simulation.force("link")
    .links(links);
}

function tick() {
  nodeLayer.selectAll('.node').attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })

  linkLayer.selectAll('.link').attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });
}
.link {
  stroke: #000;
  stroke-width: 1.5px;
}

.node {
  fill: #000;
  stroke: #fff;
  stroke-width: 1.5px;
}

.node.a { fill: #1f77b4; }
.node.b { fill: #ff7f0e; }
.node.c { fill: #2ca02c; }
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="300px" height="200px"></svg>

So you don't actually need a d3-selection-merge to make your example work. The reason is that your node positions and links are being updated by the simulation. So you will want to add the nodes and links on start, but any updating to the positions will happen at the end of the start method when the simulation is started.

One major flaw with your original code was that you called svg.selectAll('.node') & svg.selectAll('.link') at the initial stages of the script. When you do this, there aren't any nodes or links bound to the svg, so you get a d3-selection with empty DOM elements. This is fine when you want to add elements with enter().append() - however when removing elements this will not work. Using that same, stale d3-selection to remove elements with exit().remove() does not work b/c there aren't any DOM elements in the d3-selection to remove. You need to call svg.selectAll() each time to get the DOM elements that are currently in the svg.

I also made a few minor changes in your code. You usually want the links to always show below the nodes. As you're adding elements to an SVG, the most recently added nodes are placed at the top. However if you add groups before hand (as I did with linkLayer & nodeLayer in the code), any newly added links will appear below any items in the nodeLayer group.

like image 106
John Thompson Avatar answered Oct 27 '22 04:10

John Thompson