chart = {
const height =400;
const svg = d3.create("svg").attr("viewBox", [-width / 2, -height / 2, width, height]);
const edges = graph.edges
.map(e => ({...e}))
;
const depthToY = d => d.depth * range + (d.randId * range / 2) - height/3;
function getDepth(node) {
if (isRoot(node)) { return 0; }
const incomingEdge = graph.edges.find(e => e.target === node.id);
const parent = graph.nodes.find(n => n.id === incomingEdge.source);
return getDepth(parent) + 1;
}
function isRoot(node) {
return graph.edges.every(e => e.target !== node.id);
}
const randoms = Array.from({length: graph.nodes.length}, () => Math.random());
const nodes = graph.nodes
.map(n => ({...n}))
.map((n,i) => ({
...n,
incomingEdges: graph.edges.filter(e => e.target === n.id).length,
outgoingEdges: graph.edges.filter(e => e.source === n.id).length,
isRoot: isRoot(n),
isLeaf: graph.edges.every(e => e.source !== n.id),
onlyHasOneOutput: graph.edges.filter(e => e.source === n.id).length === 1,
depth: getDepth(n),
randId: randoms[i]
}));
const linkSelection = svg
.append("g")
.attr("stroke", "#999")
.selectAll("line")
.data(edges)
.join("g")
.call(edge)
;
const nodeSelection = svg
.append("g")
.selectAll("g")
.data(nodes)
.join("g")
.call(node)
;
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(edges).id(d => d.id).distance(d => (d.source.outgoingEdges + 1)*50))
.force("collide", d3.forceCollide().radius(80))
.force("x", d3.forceX(-100).strength(.01))
.alphaDecay(0)
;
let time = 0;
simulation.on("tick", () => {
time++;
nodeSelection
.attr("transform", d => `translate(${d.x}, ${depthToY(d)})`)
;
linkSelection.selectAll("line")
.attr("x1", d => d.source.x)
.attr("y1", d => depthToY(d.source))
.attr("x2", d => d.target.x)
.attr("y2", d => depthToY(d.target))
const prog = 1 - (time/47 % 1);
linkSelection.selectAll("circle")
.attr("r", 2)
.attr("cx", d => d.target.x + (d.source.x - d.target.x) * prog)
.attr("cy", d => depthToY(d.target) + (depthToY(d.source) - depthToY(d.target)) * prog)
.attr("fill", d => "black");
if (time === 100) {
simulation.force("collide", null);
simulation.force("x", null);
}
});
invalidation.then(() => simulation.stop());
return { simulation, svg, edges, nodes, time };
}