I have a d3 (v7) visualization where I have a variable number of circles being drawn on the screen depending on my data set.
How can I get arrows connecting these circles? I'm trying to follow this guide: https://observablehq.com/@harrylove/draw-an-arrow-between-circles-with-d3-links
However, this is only for a set number of circles (2) and I will have a variable number of them depending on my dataset.
Below is my current d3 code that draws circles:
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 800)
.attr("height", 200);
var circles = svgContainer.selectAll("circle")
.data(nodeObjs)
.enter()
.append("circle");
circles
.attr("cx", function (d, i) {return i * 100 + 30})
.attr("cy", 60)
.attr("r", 30)
.style("fill", "steelblue");
To make the observable example dynamic there are a few factors to take into account:
you need 2 link functions; 1 for horizontal and 1 for vertical - below I have linkH
and linkV
instead of just link
the link function doesn't need to be called immediately so lose the ({ source: linkSource, target: linkTarget});
- you are going to need an array of links
instead
some choice between linkH
and linkV
- you can test if the x-gap is greater than the y-gap between two circles and choose a horizontal link; and vice versa
in the example I've made the horizontal vs vertical decision in the link creation; then call linkH
or linkV
in the .attr("d", ...)
section
in the case that the arrow runs right to left or top to bottom you need to reverse the sign on the adjustment of the link x
and y
See the working example below:
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 => dx) .y(d => dy); // vertical link const linkV = d3 .linkVertical() .x(d => dx) .y(d => dy); // 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) => dx) .attr("cy", (d, i) => dy) .attr("r", d => dr); 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>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.