【问题标题】:D3.js force-directed graph with dynamically offset arrows?D3.js 具有动态偏移箭头的力导向图?
【发布时间】:2020-10-23 00:39:24
【问题描述】:

我在 D3.js 中有一个力导向图,其中节点半径与该数据的属性(例如网页浏览量)成正比,链接宽度与链接数据的属性(例如点击次数)成正比。我想给链接曲线一个方向的指标。问题是链接会到达数据节点的center,所以如果我使用marker-end,我会得到:

(数据节点通常用链接到另一个数据类别的颜色填充...)

我使用以下方法创建我的 ~~arcs~~ 曲线:

positionLink = (d) => {
    const offset = 100;
    const midpoint_x = (d.source.x + d.target.x) / 2;
    const midpoint_y = (d.source.y + d.target.y) / 2;
  
    const dx = d.source.x - d.target.x;
    const dy = d.source.y - d.target.y;
  
    // Perpendicular vector 
    const nx = -dy;
    const ny = dx;
    const norm_length = Math.sqrt((nx*nx)+(ny*ny));
    const normx = nx / norm_length;
    const normy = ny / norm_length;
  
    const offset_x = parseFloat(midpoint_x + offset * normx.toFixed(2));
    const offset_y = parseFloat(midpoint_y + offset * normy.toFixed(2));
    const arc = `M ${d.source.x.toFixed(2)} ${d.source.y.toFixed(2)} S ${offset_x} ${offset_y} ${d.target.x.toFixed(2)} ${d.target.y.toFixed(2)}`;

    return arc;
  };

我的代码调用 arc 是一个 SVG“S”路径,它是一个“平滑曲线”,但我并没有特别关注它:我只需要将弧彼此分开,这样我可以显示一个方向和另一个方向的数据之间的差异。

如何定位贝塞尔曲线与圆的交点?

(由于曲线的目标是圆心,我想这可以改写为“距离终点r 处的贝塞尔曲线的值”)

如果我有这一点,我可以让它成为一个箭头的顶点。

(如果我在那个点有贝塞尔曲线的斜率会更好,这样我就可以真正对齐它,但我想我可以将它与中点和锚点之间的线对齐......)

【问题讨论】:

    标签: javascript d3.js geometry observablehq cubic-bezier


    【解决方案1】:

    考虑以下迭代方法:

    使用path.getPointAtLength,您可以遍历路径,直到找到距圆心正好为r 的点,然后使用这些坐标重新绘制路径。

    const data = [{
      x: 50,
      y: 100,
      r: 20
    }, {
      x: 100,
      y: 30,
      r: 5
    }];
    const links = [{
        source: data[0],
        target: data[1]
      },
      {
        source: data[1],
        target: data[0]
      }
    ];
    
    positionLink = (source, target) => {
      const offsetPx = 100;
      const midpoint = {
        x: (source.x + target.x) / 2,
        y: (source.y + target.y) / 2
      };
    
      const dx = source.x - target.x;
      const dy = source.y - target.y;
    
      // Perpendicular vector 
      const nx = -dy;
      const ny = dx;
      const norm_length = Math.sqrt((nx * nx) + (ny * ny));
      const normx = nx / norm_length;
      const normy = ny / norm_length;
    
      const offset = {
        x: parseFloat(midpoint.x + offsetPx * normx.toFixed(2)),
        y: parseFloat(midpoint.y + offsetPx * normy.toFixed(2)),
      };
    
      const arc = `M ${source.x.toFixed(2)} ${source.y.toFixed(2)} S ${offset.x} ${offset.y} ${target.x.toFixed(2)} ${target.y.toFixed(2)}`;
    
      return arc;
    };
    
    euclidean = (point, other) => Math.sqrt(point.x * other.x + point.y * other.y);
    
    findPointAtLength = (path, point, fromEnd) => {
      // For the target we need to start at the other side of the path
      let offset = point.r;
      if (fromEnd) {
        const totalLength = path.getTotalLength();
        offset = totalLength - offset;
      }
    
      let current = path.getPointAtLength(offset);
    
      // Gradually increase the offset until we're exactly 
      // `r` away from the circle centre
      while (euclidean(point, current) < point.r) {
        offset += 1;
        current = path.getPointAtLength(offset);
      }
    
      return {
        x: current.x,
        y: current.y
      };
    };
    
    // Use function because we want access to `this`,
    // which points to the current path HTMLElement
    positionLinkAtEdges = function(d) {
      // First, place the path in the old way
      d3.select(this).attr("d", positionLink(d.source, d.target));
    
      // Then, position the path away from the source
      const source = findPointAtLength(this, d.source, false);
      const target = findPointAtLength(this, d.target, true);
    
      return positionLink(source, target);
    }
    
    const svg = d3.select("svg").append("g");
    
    svg
      .selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)
      .attr("r", d => d.r);
    
    svg
      .selectAll("path")
      .data(links)
      .enter()
      .append("path")
      .attr("d", positionLinkAtEdges)
      .attr("marker-end", "url(#triangle)");
    g circle,
    g path {
      fill: none;
      stroke: black;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg>
      <defs>
        <marker id="triangle" viewBox="0 0 10 10"
              refX="10" refY="5" 
              markerUnits="strokeWidth"
              markerWidth="10" markerHeight="10"
              orient="auto">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#f00"/>
        </marker>
      </defs>
    </svg>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-24
      • 1970-01-01
      相关资源
      最近更新 更多