为了账号安全,请及时绑定邮箱和手机立即绑定

具有动态偏移箭头的 D3.js 力导向图?

具有动态偏移箭头的 D3.js 力导向图?

暮色呼如 2023-06-09 10:39:57
我在 D3.js 中有一个力导向图,其中节点半径与该数据的属性(例如,页面浏览量)成正比,链接宽度与链接数据的属性(例如,点击)成正比。我想给链接曲线一个方向指示器。问题是链接会到达数据节点的中心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其终点的距离的值”)如果我有那个点,我可以把它变成箭头的顶点。(更好的是,如果我在那个点上有贝塞尔曲线的斜率,这样我就可以真正对齐它,但我认为我可以通过将它对齐到中点和锚点之间的线来摆脱......)
查看完整描述

1 回答

?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

考虑以下迭代方法:

使用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>


查看完整回答
反对 回复 2023-06-09
  • 1 回答
  • 0 关注
  • 115 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信