const svgWidth = 480;
const svgHeight = 180;
const svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight);
// Define the arrowhead marker variables
const markerBoxWidth = 8;
const markerBoxHeight = 8;
const refX = markerBoxWidth / 2;
const refY = markerBoxHeight / 2;
const markerWidth = markerBoxWidth / 2;
const markerHeight = markerBoxHeight / 2;
const arrowPoints = [[0, 0], [0, 8], [8, 4]];
// Add the arrowhead marker definition to the svg element
svg
.append("defs")
.append("marker")
.attr("id", "arrow")
.attr("viewBox", [0, 0, markerBoxWidth, markerBoxHeight])
.attr("refX", refX)
.attr("refY", refY)
.attr("markerWidth", markerBoxWidth)
.attr("markerHeight", markerBoxHeight)
.attr("orient", "auto-start-reverse")
.append("path")
.attr("d", d3.line()(arrowPoints))
.attr("stroke", "black");
// horizontal link
const linkH = d3
.linkHorizontal()
.x(d => d.x)
.y(d => d.y);
// vertical link
const linkV = d3
.linkVertical()
.x(d => d.x)
.y(d => d.y);
// circle data
const n = (Math.floor(Math.random() * 12) * 2) + 2;
const circleRadius = 10;
const nodes = [];
const links = [];
for (let i=0; i<n; i++) {
nodes.push({
x: Math.floor(Math.random() * (svgWidth - 20)) + 20,
y: Math.floor(Math.random() * (svgHeight - 20)) + 20,
r: circleRadius
});
}
for (let i=0; i<n; i+=2) {
const xdelta = Math.abs(nodes[i + 1].x - nodes[i].x);
const ydelta = Math.abs(nodes[i + 1].y - nodes[i].y);
links.push({
source: { x: nodes[i].x, y: nodes[i].y },
target: { x: nodes[i + 1].x, y: nodes[i + 1].y },
arrowDirection: ydelta >= xdelta ? "V" : "H"
});
}
const circles = svg.selectAll(".node")
.data(nodes)
.enter()
.append("circle")
.attr("class", "node");
circles
.attr("cx", (d, i) => d.x)
.attr("cy", (d, i) => d.y)
.attr("r", d => d.r);
const arrows = svg.selectAll(".arrow")
.data(links)
.enter()
.append("path")
.attr("class", "arrow");
arrows
.attr("d", (d, i) => {
let reversed;
if (d.arrowDirection === "H") {
reversed = d.source.x < d.target.x ? 1 : -1;
d.source.x += circleRadius * reversed;
d.target.x -= (circleRadius + markerWidth) * reversed;
return linkH(d);
} else {
reversed = d.source.y > d.target.y ? 1 : -1;
d.source.y -= circleRadius * reversed;
d.target.y += (circleRadius + markerWidth) * reversed;
return linkV(d);
}
})
.attr("marker-end", "url(#arrow)");
.node {
fill: green;
stroke: steelblue;
}
.arrow {
stroke: black;
fill: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>