【问题标题】:Dynamically Create Arrows between Circles (D3)在圆之间动态创建箭头 (D3)
【发布时间】:2021-09-22 22:37:00
【问题描述】:

我有一个 d3 (v7) 可视化,根据我的数据集,我在屏幕上绘制了可变数量的圆圈。

如何获得连接这些圆圈的箭头?我正在尝试遵循本指南:https://observablehq.com/@harrylove/draw-an-arrow-between-circles-with-d3-links

但是,这仅适用于固定数量的圆圈 (2),根据我的数据集,我将拥有可变数量的圆圈。

以下是我当前绘制圆圈的 d3 代码:

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");

【问题讨论】:

  • 你使用的是什么版本的 D3?
  • @AlfonsoIrarrázaval 7.0.0

标签: javascript typescript d3.js


【解决方案1】:

要使observable example 动态化,需要考虑以下几个因素:

  • 您需要 2 个链接功能; 1 表示水平,1 表示垂直 - 下面我有 linkHlinkV 而不仅仅是 link

  • 不需要立即调用链接函数,因此请不要使用 ({ source: linkSource, target: linkTarget}); - 您将需要一个 links 数组来代替

  • linkHlinkV 之间进行选择 - 您可以测试 x 间距是否大于两个圆圈之间的 y 间距并选择水平链接;反之亦然

  • 在示例中,我在链接创建中做出了水平与垂直的决定;然后在.attr("d", ...) 部分调用linkHlinkV

  • 在箭头从右到左或从上到下的情况下,您需要将链接xy的调整符号反转

请参阅下面的工作示例:

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;
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"&gt;&lt;/script&gt;

【讨论】:

    猜你喜欢
    • 2017-08-30
    • 1970-01-01
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2018-04-07
    • 1970-01-01
    相关资源
    最近更新 更多