{
const width = 500;
const height = 200;
const svg = d3.create("svg").attr("width", width).attr("height", height),
link = svg
.selectAll(".link")
.data(graph.links)
.join("line")
.classed("link", true),
node = svg
.selectAll(".node")
.data(graph.nodes)
.join("circle")
.attr("r", (d) => (d.type === "origin" ? 1 : 5))
.attr("fill", (d) => (d.type === "origin" ? "red" : "blue"))
.classed("node", true);
const drag = d3
.drag()
.on("start", dragstart)
.on("drag", dragged)
.on("end", dragend);
node.call(drag);
yield svg.node();
const simulation = d3
.forceSimulation()
.nodes(graph.nodes)
.force(
"charge",
d3.forceManyBody().strength((x) => (x.type === "origin" ? 0 : -30))
)
.force("collide", d3.forceCollide(10))
.force(
"link",
d3
.forceLink(graph.links)
.id((x) => x.id)
.strength(2)
)
.on("tick", tick);
function tick() {
for (let node of graph.nodes) {
node.x = clamp(node.x, 0, width);
node.y = clamp(node.y, 0, height);
}
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
}
function dragstart() {
d3.select(this).classed("fixed", true);
}
function dragged(event, d) {
d.fx = clamp(event.x, 0, width);
d.fy = clamp(event.y, 0, height);
simulation.alpha(1).restart();
}
function dragend(event, d) {
d.x = d.fx;
d.y = d.fy;
d.fx = d.fy = undefined;
if (d.type === "label") {
d.fx = labelX;
}
if (d.type === "origin") {
d.fx = originX;
}
simulation.alpha(1).restart();
}
}