Home > Software design >  Updating d3.layout.force v3 to d3.forceSimulation v7
Updating d3.layout.force v3 to d3.forceSimulation v7

Time:08-08

I am trying to update a force-directed graph written using d3js version 3 to d3js version 7.

The following code snippet is the working implementation using d3js v3:

var svg = d3.select("svg"),
  width =  svg.attr("width"),
  height =  svg.attr("height");

graph = {
  nodes: [],
  links: [],
}

var simulation = d3.layout.force()
  .size([width, height])
  .nodes(graph.nodes)
  .links(graph.links)
  .on("tick", function() {
    svg.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 })
      
    svg.selectAll('.node')
      .attr("cx", function (d) { return d.x })
      .attr("cy", function (d) { return d.y })
      .attr("transform", function (d) {
        return "translate("   d.x   ","   d.y   ")";
      })
  });
  
function update() {
  // update links
  var link = svg.selectAll('.link').data(graph.links);
  link.enter()
    .insert('line', '.node')
    .attr('class', 'link')
    .style('stroke', '#d9d9d9');
  link
    .exit()
    .remove()
    
  // update nodes
  var node = svg.selectAll('.node').data(graph.nodes);
  var g = node.enter()
            .append('g')
            .attr('class', 'node');
  g.append('circle')
    .attr("r", 20)
    .style("fill", "#d9d9d9");
  g.append('text')
    .attr("class", "text")
    .text(function (d) { return d.name });
  node
    .exit()
    .remove();
    
  // update simulation
  simulation
    .linkDistance(100)
    .charge(-200)
    .start();
};

function addNode(node) {
  graph.nodes.push(node);
  update();
};

function connectNodes(source, target) {
  graph.links.push({
    source: source,
    target: target,
  });
  update();
};

addNode({
  id: "you",
  name: "you",
});

let index = 1;

// add a new node every three seconds and connect to 'you'
const interval = window.setInterval(() => {
  let id = Math.random().toString(36).replace('0.','');
  id = id.slice(0,4);
  addNode({
      id: id,
      name: id
  });
  
  connectNodes(0, index);
  index  ;
}, 3000);

// no more than 8 nodes
setTimeout(() => {
  clearInterval(interval)
}, 3000 * 8);
<html>
<head>
  <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
  <svg width="400" height="200"></svg>
</body>
</html>

The following code snippet my attempt of implementing the above code snippet using d3js v7:

var svg = d3.select("svg"),
  width =  svg.attr("width"),
  height =  svg.attr("height");

graph = {
  nodes: [],
  links: [],
}

var simulation = d3.forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.01))
  .nodes(graph.nodes)
  .force("link", d3.forceLink(graph.links).distance(100))
  .on("tick", function() {
    svg.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 })
      
    svg.selectAll('.node')
      .attr("cx", function (d) { return d.x })
      .attr("cy", function (d) { return d.y })
      .attr("transform", function (d) {
        return "translate("   d.x   ","   d.y   ")";
      })
  });
  
function update() {
  // update links
  var link = svg.selectAll('.link').data(graph.links);
  link.enter()
    .insert('line', '.node')
    .attr('class', 'link')
    .style('stroke', '#d9d9d9');
  link
    .exit()
    .remove()
    
  // update nodes
  var node = svg.selectAll('.node').data(graph.nodes);
  var g = node.enter()
            .append('g')
            .attr('class', 'node');
  g.append('circle')
    .attr("r", 20)
    .style("fill", "#d9d9d9");
  g.append('text')
    .attr("class", "text")
    .text(function (d) { return d.name });
  node
    .exit()
    .remove();
    
  // update simulation
  simulation
    .nodes(graph.nodes)
    .force("link", d3.forceLink(graph.links).distance(100))
    .force("charge", d3.forceManyBody().strength(-200))
    .restart()
};

function addNode(node) {
  graph.nodes.push(node);
  update();
};

function connectNodes(source, target) {
  graph.links.push({
    source: source,
    target: target,
  });
  update();
};

addNode({
  id: "you",
  name: "you",
});

let index = 1;

// add a new node every three seconds and connect to 'you'
const interval = window.setInterval(() => {
  let id = Math.random().toString(36).replace('0.','');
  id = id.slice(0,4);
  addNode({
      id: id,
      name: id
  });
  
  connectNodes(0, index);
  index  ;
}, 3000);

// no more than 8 nodes
setTimeout(() => {
  clearInterval(interval)
}, 3000 * 8);
<html>
<head>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
  <svg width="400" height="200"></svg>
</body>
</html>

The d3js v7 code snippet does not produce the same results as d3js v3 - why is this? The exact changes I have done are seen in this diff: https://www.diffchecker.com/wdq7AFbU.

Even without adding any connections, there is a difference between the two implementations. The v3 implementation makes the "you" node fly in from random directions, whilst with the v7 implementation the "you" node always flies in from the same direction.

There also seems to be some discrepancy on how the force is being applied since the new nodes in the v7 implementation get stuck in the top-left corner.

CodePudding user response:

I've noticed the attributes of DOMs are reflecting the status alright. It's just that the simulation just stopped prematurely.

In short, the default value of d3.force.alphaDecay is too short for the intended result; alphaDecay dictates the end of simulation. Try expand the value a little bit. The latest default value for alphaDecay is 0.001, according to d3-force github readme. In my testing session, setting the value to 1/5(0.0002) seems to be enough for the same result.

try run the code below. it works fine.

Tips

When working with DOMs and SVGs, try add matching data-ooo tag to see if the d3.selection is working properly. I've added properties of node data such as .index and .target, .source to attributes like data-index,data-id,data-target,data-source... and noticed that everything is in place.

var svg = d3.select("svg"),
  width =  svg.attr("width"),
  height =  svg.attr("height");

graph = {
  nodes: [],
  links: [],
}

var simulation = d3.forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.01))
  .nodes(graph.nodes)
  .force("link", d3.forceLink(graph.links).distance(100))
  .on("tick", function() {
    svg.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 })
      
    svg.selectAll('.node')
      .attr("cx", function (d) { return d.x })
      .attr("cy", function (d) { return d.y })
      .attr("transform", function (d) {
        return "translate("   d.x   ","   d.y   ")";
      })
  }).alphaDecay(0.0002) // just added alpha decay to delay end of execution
  
function update() {
  // update links
  var link = svg.selectAll('.link').data(graph.links);
  link.enter()
    .insert('line', '.node')
    .attr('class', 'link')
    .style('stroke', '#d9d9d9');
  link
    .exit()
    .remove()
    
  // update nodes
  var node = svg.selectAll('.node').data(graph.nodes);
  var g = node.enter()
            .append('g')
            .attr('class', 'node');
  g.append('circle')
    .attr("r", 20)
    .style("fill", "#d9d9d9");
  g.append('text')
    .attr("class", "text")
    .text(function (d) { return d.name });
  node
    .exit()
    .remove();
    
  // update simulation
  simulation
    .nodes(graph.nodes)
    .force("link", d3.forceLink(graph.links).distance(100))
    .force("charge", d3.forceManyBody().strength(-200))
    .restart()
};

function addNode(node) {
  graph.nodes.push(node);
  update();
};

function connectNodes(source, target) {
  graph.links.push({
    source: source,
    target: target,
  });
  update();
};

addNode({
  id: "you",
  name: "you",
});

let index = 1;

// add a new node every three seconds and connect to 'you'
const interval = window.setInterval(() => {
  let id = Math.random().toString(36).replace('0.','');
  id = id.slice(0,4);
  addNode({
      id: id,
      name: id
  });
  
  connectNodes(0, index);
  index  ;
}, 3000);

// no more than 8 nodes
setTimeout(() => {
  clearInterval(interval)
}, 3000 * 8);
<html>
<head>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
  <svg width="400" height="200"></svg>
</body>
</html>

  • Related