Highlighting in D3.js forceSimulation

Explains how to highlight clicked elements and their related elements and links in D3.js forceSimulation.

Example program: click a node

This example adds click highlighting to this example. This page explains only the highlight-related code. See this page for the other parts.

View code

Example code

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 v5 force simulation node click response</title>
  <!-- 0. Configure styles -->
  <style type="text/css">
    .selected {
      fill: tomato;
    }

    .linkSelected {
      stroke: tomato;
    }

    .conected {
      fill: orange;
    }
  </style>
</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 = [];
    for (var i = 0; i < nodeNumber; i++) {
      for (var j = i + 1; j < nodeNumber; j++) {
        if (Math.random() > 0.9) {
          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", 2)
      .attr("stroke", "black");

    var d3_drag = d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

    var node = d3.select("svg")
      .selectAll("circle")
      .data(nodesData)
      .enter()
      .append("circle")
      .attr("r", function (d) { return d.r })
      .attr("fill", "Gold")
      .attr("stroke", "black")
      .call(d3_drag)
      .on("click", clicked);

    function clicked(event, d) {

      d3.selectAll(".selected").classed("selected", false);
      d3.selectAll(".conected").classed("conected", false);
      d3.selectAll("line").classed("linkSelected", false);

      d3.select(this).classed("selected", true);

      d3.selectAll("line")
        .filter(function (v, i) {
          if (d == v.source) {
            node.each(function (vj, j) {
              if (v.target == vj) d3.select(this).classed("conected", true);
            });
            return true;
          } else if (d == v.target) {
            node.each(function (vj, j) {
              if (v.source == vj) d3.select(this).classed("conected", true);
            });
            return true;
          }
        }).classed("linkSelected", true);

    }

    // 3. Configure forceSimulation
    var simulation = d3.forceSimulation()
      .force("link",
        d3.forceLink()
          .distance(function (d) { return d.l; })
          .iterations(2))
      .force("collide",
        d3.forceCollide()
          .radius(function (d) { return d.r; })
          .strength(0.7)
          .iterations(2))
      .force("charge", d3.forceManyBody().strength(-100))
      .force("x", d3.forceX().strength(0.01).x(width / 2))
      .force("y", d3.forceY().strength(0.01).y(height / 2))
      .force("center", d3.forceCenter(width / 2, 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();
      event.subject.fx = event.subject.x;
      event.subject.fy = event.subject.y;
    }

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

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

</html>

Explanation

0. Configure styles

<style type="text/css">
.selected {
  fill: tomato;
}

.linkSelected {
  stroke: tomato;
}

.conected {
  fill: orange;
}
</style>

Configure the styles to add on click.

2. Place SVG elements and register the click event

selection.on("click", clicked);

function clicked(event, d) {

  d3.selectAll(".selected").classed("selected", false);
  d3.selectAll(".conected").classed("conected", false);
  d3.selectAll("line").classed("linkSelected", false);

  d3.select(this).classed("selected", true);

  d3.selectAll("line")
    .filter(function (v, i) {
      if (d == v.source) {
        node.each(function (vj, j) {
          if (v.target == vj) d3.select(this).classed("conected", true);
        });
        return true;
      } else if (d == v.target) {
        node.each(function (vj, j) {
          if (v.source == vj) d3.select(this).classed("conected", true);
        });
        return true;
      }
    }).classed("linkSelected", true);

}

Register the click event on node elements with the on method. The "click" event is registered, but it also automatically supports "touch" events on tablets and smartphones.

The click event function first clears all highlights with the classed method. classed takes two arguments: a class name, without a leading ., and a Boolean. If the value is false, the class is removed; if it is true, the class is added. If the same class name is already registered when true is specified, nothing happens.

After adding the selected class to the clicked node element, the code uses the filter and each methods to find links and nodes connected to the clicked node and add classes to them.