D3.js forceSimulation Link Force

Explains interactions caused by links in D3.js forceSimulation programs.

Example program

Try dragging the nodes, shown as circles.

View code

Example code

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 v7 force simulation node detail</title>
</head>

<body>
  <svg width="800" height="600"></svg>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <script>
    // 1. Prepare the data to draw
    var width = document.querySelector("svg").clientWidth;
    var height = document.querySelector("svg").clientHeight;
    var nodeNumber = 30;
    var nodesData = [];
    for (var i = 0; i < nodeNumber; i++) {
      nodesData.push({
        "index": i,
        "x": width * Math.random(),
        "y": height * Math.random(),
        "r": 10
      });
    }
    var linksData = [];
    var i = 0;
    for (var j = i + 1; j < nodeNumber; j++) {
      linksData.push({
        "source": i,
        "target": j,
        "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r
      });
    }

    // 2. Add SVG elements
    var link = d3.select("svg")
      .selectAll("line")
      .data(linksData)
      .enter()
      .append("line")
      .attr("stroke-width", 1)
      .attr("stroke", "black");

    var node = d3.select("svg")
      .selectAll("circle")
      .data(nodesData)
      .enter()
      .append("circle")
      .attr("r", function (d) { return d.r })
      .attr("fill", "LightSalmon")
      .attr("stroke", "black")
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

    // 3. Configure forceSimulation
    var simulation = d3.forceSimulation()
      .force("link",
        d3.forceLink()
          .distance(function (d) { return d.l; })
          .strength(0.03)
          .iterations(16))
      .force("collide",
        d3.forceCollide()
          .radius(function (d) { return d.r; })
          .strength(0.7)
          .iterations(16))
      .force("charge", d3.forceManyBody().strength(-200))
      .force("x", d3.forceX().strength(0.02).x(width / 2))
      .force("y", d3.forceY().strength(0.02).y(height / 2));

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

    simulation.force("link")
      .links(linksData)
      .id(function (d) { return d.index; });

    // 4. forceSimulation drawing update function
    function ticked() {
      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; });
      node
        .attr("cx", function (d) { return d.x; })
        .attr("cy", function (d) { return d.y; });
    }

    // 5. Drag event functions
    function dragstarted(event, d) {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(event, d) {
      d.fx = event.x;
      d.fy = event.y;
    }

    function dragended(event, d) {
      if (!event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }
  </script>
</body>

</html>

Explanation

1. Prepare the data to draw

var width = document.querySelector("svg").clientWidth;
var height = document.querySelector("svg").clientHeight;
var nodeNumber = 30;
var nodesData = [];
for (var i = 0; i < nodeNumber; i++) {
  nodesData.push({
    "index": i,
    "x": width * Math.random(),
    "y": height * Math.random(),
    "r": 10
  });
}

Use document.querySelector(...) to get the width and height of the svg.

The ID of a node connected by a link can use any key name. In the code above, index is stored in the node data array and specified later. IDs can also be strings instead of numbers.

var linksData = [];
var i = 0;
for (var j = i + 1; j < nodeNumber; j++) {
  linksData.push({
    "source": i,
    "target": j,
    "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r
  });
}

Create the link data array, linksData. In the code above, l is defined so each link can have a different length.

2. Add SVG elements

Add SVG elements for links and nodes. The event functions that run when dragging occurs are registered with call(...).

3. Configure forceSimulation

var simulation = d3.forceSimulation()
  .force("link",
    d3.forceLink()
    .distance(function(d) { return d.l; })
    .strength(0.03)
    .iterations(16)) // <<< Interaction through links
  .force("collide",
    d3.forceCollide()
    .radius(function(d) { return d.r; })
    .strength(0.7)
    .iterations(16))
  .force("charge", d3.forceManyBody().strength(-200))
  .force("x", d3.forceX().strength(0.02).x(width / 2))
  .force("y", d3.forceY().strength(0.02).y(height / 2));

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

simulation.force("link")
  .links(linksData)
  .id(function(d) { return d.index; }); // <<< Interaction through links

The following explains the link-based interactions that can be configured in forceSimulation.

.force("link",
  d3.forceLink()
  .distance(function(d) { return d.l; })
  .strength(0.03)
  .iterations(16))
Function Description
distance Sets the link length.
The link always tries to return to the configured length.
The default is 30.
strength Sets the link strength.
The default is 1/Math.min(count(link.source), count(link.target)).
If each node has one connection, the value is 1.
Even if there are two links between the same nodes, the reaction force remains the same.
iterations Sets the number of simulation iterations.
Increasing the number of iterations stabilizes the calculation, but increases calculation time.
The default is 1.
  simulation.force("link")
    .links(linksData)
    .id(function(d) { return d.index; });

Specify the indexed key name with id(...). The default is node.index.

4. forceSimulation drawing update function

This function is called for each simulation step. It reflects the calculation result in the SVG element positions so the SVG elements move.

5. Drag event functions

These are the event functions used during dragging.