Highlighting 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.
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.