I have just finished a rough implementation of a force directed graph that emits particles from nodes when clicking on a node. However, the performance is worse than I expected and because I'm new to d3js, I was wondering whether there's a better way to do this.
Relevant code:
Timer to call the ticker method, which updates the particles:
let t3 = d3.timer(this.#particleTicked, 1000);
Ticker method:
#particleTicked = () => {
let n = d3.selectAll(".nodes").selectAll("g");
const newPos = [];
// retrieve node positions
n.each(function (d) {
newPos.push({ id: d.id, pos: { x: d.x, y: d.y } });
});
// update particle positions
this.#particles.forEach((p) => {
const originPos = newPos.find((d) => d.id === p.originNode.id),
targetPos = newPos.find((d) => d.id === p.targetNode.id);
p.updatePos(
Math.round(originPos.pos.x),
Math.round(originPos.pos.y),
Math.round(targetPos.pos.x),
Math.round(targetPos.pos.y)
);
});
// update particles in svg
this.#updateParticles(self.svg, self.#particles);
};
Method that removes and appends all particle SVG elements:
#updateParticles = (svg, particles) => {
let g = svg.select(".particles");
if (g.empty()) {
g = svg.append("g").attr("class", "particles");
}
let p = g
.selectAll("circle")
.data(particles, (d) => d.originNode.id + d.targetNode.id);
p.exit().remove();
return p
.enter()
.append("circle")
.attr("class", "particle")
.attr("cx", (d) => d.position.x)
.attr("cy", (d) => d.position.y)
.attr("r", (d) => d.radius)
.attr("fill", function (d) {
return "#000";
});
};