简体   繁体   中英

Dynamically Create Arrows between Circles (D3)

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM