{
const { width, height, dag } = laidout;
const svgNode = svg`<svg width=${width} height=${height} transform="scale(1)"></svg>`;
const svgSelection = d3.select(svgNode);
const defs = svgSelection.append("defs");
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = {};
for (const [i, node] of [...dag].entries()) {
let nodeType = findNodeType(node.data.id);
colorMap[nodeType] = interp(i / steps);
}
const curve =
splines.get(spline) ??
(coord === "Simplex (medium)" ? d3.curveMonotoneY : d3.curveCatmullRom);
const line = d3
.line()
.curve(curve)
.x((d) => d.x)
.y((d) => d.y);
svgSelection
.append("g")
.selectAll("path")
.data(dag.links())
.enter()
.append("path")
.attr("d", ({ points }) => {
return line(points);
})
.attr("fill", "none")
.attr("stroke-width", 1)
.attr("stroke", ({ source, target }) => {
// encodeURIComponents for spaces, hope id doesn't have a `--` in it
const gradId = encodeURIComponent(
`${source.data.id.replaceAll(" ", "")}--${target.data.id.replaceAll(
" ",
""
)}`
);
const grad = defs
.append("linearGradient")
.attr("id", gradId)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", source.x)
.attr("x2", target.x)
.attr("y1", source.y)
.attr("y2", target.y);
grad
.append("stop")
.attr("offset", "0%")
.attr("stop-color", colorMap[findNodeType(source.data.id)]);
grad
.append("stop")
.attr("offset", "100%")
.attr("stop-color", colorMap[findNodeType(target.data.id)]);
return `url(#${gradId})`;
});
// Select nodes
const nodes = svgSelection
.append("g")
.selectAll("g")
.data(dag.descendants())
.enter()
.append("g")
.attr("transform", ({ x, y }) => `translate(${x}, ${y})`);
// Plot node circles
nodes
.append("circle")
.attr("r", nodeRadius)
.attr("fill", (n) => colorMap[findNodeType(n.data.id)]);
// Plot Arrows
if (arrows) {
const arrowSize = (nodeRadius * nodeRadius) / 5.0;
const arrowLen = Math.sqrt((4 * arrowSize) / Math.sqrt(3));
const arrow = d3.symbol().type(d3.symbolTriangle).size(arrowSize);
svgSelection
.append("g")
.selectAll("path")
.data(dag.links())
.enter()
.append("path")
.attr("d", arrow)
.attr("transform", ({ source, target, points }) => {
const [end, start] = points.slice().reverse();
// This sets the arrows the node radius (20) + a little bit (3) away from the node center, on the last line segment of the edge. This means that edges that only span ine level will work perfectly, but if the edge bends, this will be a little off.
const dx = start.x - end.x;
const dy = start.y - end.y;
const scale = (nodeRadius * 1.15) / Math.sqrt(dx * dx + dy * dy);
// This is the angle of the last line segment
const angle = (Math.atan2(-dy, -dx) * 180) / Math.PI + 90;
console.log(angle, dx, dy);
return `translate(${end.x + dx * scale}, ${
end.y + dy * scale
}) rotate(${angle})`;
})
.attr("fill", ({ target }) => colorMap[findNodeType(target.data.id)])
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("stroke-dasharray", `${arrowLen},${arrowLen}`);
}
// Add text to nodes
nodes
.append("text")
.text((d) => d.data.id)
.attr("font-size", "9")
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("fill", "black")
.attr("paint-order", "stroke")
.attr("stroke", "white");
yield svgNode;
}