svg = (nodes) => {
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height]);
nodes.forEach((node) => {
node._x = x(node.distance);
node._y = y;
node.x = x(node.distance);
node.y = node.type > 0 ? y : y / 2;
node.type = Math.floor(Math.random() * 3);
});
const colors = ["#66c2a5", "#fc8d62", "#8da0cb"];
const nodesCircle = svg
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", (node) => node.x)
.attr("cy", (node) => node.y)
.attr("r", r)
.attr("fill", (node) => colors[node.type])
.attr("opacity", 0.5);
const simulation = d3
.forceSimulation(nodes)
.force(
"collision",
d3
.forceCollide()
.radius(r + 0.5)
.strength(1)
)
.force(
"y",
d3
.forceY()
.y((d) => d._y)
.strength((d) => 0.02 + 0.3 * d.type)
)
.force(
"x",
d3
.forceX()
.x((d) => d._x)
.strength(1)
)
.on("tick", () => {
nodes.forEach((node) => {
if (node.type <= 1) node.y = Math.min(node.y, y - 0.5 * r);
else node.y = Math.max(node.y, y + 0.5 * r);
});
nodesCircle.attr("cx", (d) => d.x);
nodesCircle.attr("cy", (d) => d.y);
})
.on("end", () => {
console.log(
nodes
.map((node) => Math.abs(node.x - node._x))
.reduce((acc, cur) => acc + cur) / nodes.length
);
});
return svg.node();
}