chart = {
const links = data.links.map(d => Object.create(d));
const nodes = data.nodes.map(d => Object.create(d));
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody().strength(-1500))
.force("center", d3.forceCenter(width / 2, height / 2));
const view = svg`<svg viewBox="0 0 ${width} ${height}">
<defs><marker id="arrow" viewBox="0 0 30 5" refX="15" refY="5"
markerWidth="10" markerHeight="10"
orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
`;
const $svg = d3.select(view);
const arc = (x1, y1, x2, y2, dist) => {
const dx = x2-x1, dy = y2-y1;
const mx = x1+(x2-x1)/2, my = y1+(y2-y1)/2;
const s = dist;
return [x1+dx/2 - dy*s, y1+dy/2 + dx*s];
};
const stroke = v => 2 + v*.2;
const $link = $svg.append("g")
.attr("stroke-opacity", .8)
.attr("fill", "none")
.selectAll("path")
.data(links)
.join("path")
.attr("stroke-width", d => stroke(d.value))
.attr("stroke", d => color(d.source));
const $node = $svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", 10)
.attr("fill", color)
.call(drag(simulation));
const $text = $svg.append("g")
.attr("stroke", "#fff3")
.attr("stroke-width", .5)
.attr("fill", "black")
.style("pointer-events", "none")
.selectAll("text")
.data(nodes)
.join("text")
.text(d => d.id)
.attr("font-family", "sans-serif")
.attr("dx", 15)
.attr("alignment-baseline", "middle");
const selection = new Set(nodes.slice(-3));
const refs = new Map(nodes.map(n => [n, new Set()]));
links.map(({source:s, target:t}) => {refs.get(t).add(s)});
const selected = d => selection.has(d);
const selectable = d => {
if(selected(d)) return true;
for(const s of selection) if(!refs.get(d).has(s)) return false;
return true;
}
const updateSelected = () => {
$node.style('opacity', d=> selected(d) ? 1 : selectable(d) ? .1 : 0);
$text.style('opacity', d=> selected(d) ? 1 : selectable(d) ? .5 : 0);
$link.style('opacity', ({source: s, target: t}) => {
return !(selectable(s) && selectable(t))
? 0
: selected(s) && selected(t) ? 1 : .1
});
};
updateSelected();
$node.on('click', (e,d) => {
console.log(d);
if(selection.has(d)) selection.delete(d);
else if(selectable(d)) selection.add(d);
updateSelected();
});
simulation.on("tick", () => {
$link.attr("d", ({source: {x:x1, y:y1}, target: {x:x2, y:y2}, value}) => {
const d = stroke(value)/2+1, a = Math.atan2(y2-y1, x2-x1)+Math.PI/2, dx = d*Math.cos(a), dy = d*Math.sin(a);
//return `M${x1},${y1} Q${arc(x1,y1,x2,y2,.2)} ${x2},${y2}`;
return `M${x1+dx},${y1+dy} L${x2+dx},${y2+dy}`;
});
$node.attr("cx", d => d.x).attr("cy", d => d.y);
$text.attr("x", d => d.x).attr("y", d => d.y);
});
invalidation.then(() => simulation.stop());
return $svg.node();
}