You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在D3.js中调用函数为.attr()提供值以消除重复逻辑

D3.js 消除链接路径坐标计算的重复逻辑

问题说明

在D3.js中通过.attr()绘制链接路径时,原代码直接返回源节点与目标节点的x、y坐标。为实现缩短链接路径的效果,修改后的代码在四个.attr()回调中存在大量重复的坐标计算逻辑,尝试用.call()或自定义函数调用均无法正确传递返回值,需要找到消除重复的方法。

原代码

link.each(function (d){})
  .attr('x1', function (d) {
    return d.source.x;
  })
  .attr('y1', function (d) {
    return d.source.y;
  })
  .attr('x2', function (d) {
    return d.target.x;
  })
  .attr('y2', function (d) {
    return d.target.y;
  });

存在重复逻辑的修改后代码

link.attr("x1", function(d) {
    let diffX = d.target.x - d.source.x;
    let diffY = d.target.y - d.source.y;
    let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
    let offsetX = (diffX * 40) / pathLength;
    return d.source.x + offsetX;
  }).attr("y1", function(d) {
    let diffX = d.target.x - d.source.x;
    let diffY = d.target.y - d.source.y;
    let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
    let offsetY = (diffY * 40) / pathLength;
    return d.source.y + offsetY;
  }).attr("x2", function(d) {
    let diffX = d.target.x - d.source.x;
    let diffY = d.target.y - d.source.y;
    let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
    let offsetX = (diffX * 40) / pathLength;
    return d.target.x - offsetX;
  }).attr("y2", function(d) {
    let diffX = d.target.x - d.source.x;
    let diffY = d.target.y - d.source.y;
    let pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));
    let offsetY = (diffY * 40) / pathLength;
    return d.target.y - offsetY;
  })

尝试过的无效调用方式

.attr('x1', function (d) {
  return d.source.x; // 期望从.call()获取返回值
})
.attr('x1', function (d) {
  return this.myNewPathFunction(d); 
})

解决方案

方法1:提前计算偏移量复用结果

通过.each()一次性计算每个链接的偏移量并存储到数据对象中,后续.attr()直接取用,彻底消除重复计算:

link.each(function(d) {
  const diffX = d.target.x - d.source.x;
  const diffY = d.target.y - d.source.y;
  const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
  // 将偏移量存入数据对象
  d.offsetX = (diffX * 40) / pathLength;
  d.offsetY = (diffY * 40) / pathLength;
})
.attr('x1', d => d.source.x + d.offsetX)
.attr('y1', d => d.source.y + d.offsetY)
.attr('x2', d => d.target.x - d.offsetX)
.attr('y2', d => d.target.y - d.offsetY);

方法2:封装自定义函数返回坐标对象

编写统一的计算函数,返回包含四个坐标值的对象,.attr()回调直接调用函数获取对应值,逻辑统一便于维护:

function getShortenedCoords(d) {
  const diffX = d.target.x - d.source.x;
  const diffY = d.target.y - d.source.y;
  const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
  const offsetX = (diffX * 40) / pathLength;
  const offsetY = (diffY * 40) / pathLength;
  
  return {
    x1: d.source.x + offsetX,
    y1: d.source.y + offsetY,
    x2: d.target.x - offsetX,
    y2: d.target.y - offsetY
  };
}

// 调用函数获取对应坐标
link.attr('x1', d => getShortenedCoords(d).x1)
    .attr('y1', d => getShortenedCoords(d).y1)
    .attr('x2', d => getShortenedCoords(d).x2)
    .attr('y2', d => getShortenedCoords(d).y2);

注:此方法函数会被调用四次,性能略逊于方法1,但代码结构更清晰。

方法3:使用.attr()的对象参数形式

D3.js的.attr()支持传入包含属性键值对的对象,写法上更紧凑,但仍存在重复计算,适合快速整理代码:

link.attr({
  x1: d => {
    const diffX = d.target.x - d.source.x;
    const diffY = d.target.y - d.source.y;
    const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
    return d.source.x + (diffX * 40)/pathLength;
  },
  y1: d => {
    const diffX = d.target.x - d.source.x;
    const diffY = d.target.y - d.source.y;
    const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
    return d.source.y + (diffY * 40)/pathLength;
  },
  x2: d => {
    const diffX = d.target.x - d.source.x;
    const diffY = d.target.y - d.source.y;
    const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
    return d.target.x - (diffX * 40)/pathLength;
  },
  y2: d => {
    const diffX = d.target.x - d.source.x;
    const diffY = d.target.y - d.source.y;
    const pathLength = Math.sqrt(diffX * diffX + diffY * diffY);
    return d.target.y - (diffY * 40)/pathLength;
  }
});

内容的提问来源于stack exchange,提问作者Tom Rudge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 13:35:18