猿问

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

我在 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其终点的距离的值”)


如果我有那个点,我可以把它变成箭头的顶点。


(更好的是,如果我在那个点上有贝塞尔曲线的斜率,这样我就可以真正对齐它,但我认为我可以通过将它对齐到中点和锚点之间的线来摆脱......)


暮色呼如
浏览 116回答 1
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 HTMLElementpositionLinkAtEdges = 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>
随时随地看视频慕课网APP

相关分类

JavaScript
我要回答