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

如何使用D3.js生成沿曲线分布的分段自定义样式Path?

使用D3.js实现沿曲线分布的独立分段路径

核心思路

d3.arc确实仅适用于圆弧/扇形场景,自定义曲线的分段需要基于基础曲线生成两侧偏移路径,再将每段的上下边缘闭合为独立的path元素,这样每个分段就能单独设置fill、stroke样式。

具体实现步骤

1. 定义基础曲线

先用d3.line生成你需要的基础曲线(支持各种曲线插值,比如curveBasis、curveCatmullRom等),得到曲线的坐标点数组或path字符串。

// 示例:生成一条基础平滑曲线
const points = [
  [80, 220],
  [180, 120],
  [280, 320],
  [380, 170],
  [480, 270]
];
const lineGen = d3.line().curve(d3.curveBasis); // 启用平滑插值
const basePathStr = lineGen(points);

2. 拆分曲线为均匀分段

如果需要按长度均匀拆分,用d3.lineLength获取总长度,再计算每个分段的起止比例;如果按点数量拆分更简单,直接切割坐标点数组即可。

const segmentCount = 5; // 分成5段
const totalLength = d3.lineLength(basePathStr);
const segmentLen = totalLength / segmentCount;

// 生成每个分段的起止比例(0~1范围)
const segments = Array.from({ length: segmentCount }, (_, idx) => ({
  start: idx / segmentCount,
  end: (idx + 1) / segmentCount
}));

3. 生成分段的闭合路径

为每个分段生成上下偏移的边缘,再拼接成闭合路径。这里提供两种方案:

方案A:使用d3-path-offset插件(简单快捷)

这个插件可以直接对path字符串进行偏移,无需手动计算法线:

// 引入插件后初始化偏移器
const offset = d3.offsetCurve();
const offsetDistance = 15; // 曲线两侧的偏移距离

// 提取基础曲线中指定比例范围的分段path
function getSegmentSubPath(startRatio, endRatio) {
  // 采样基础曲线的密集点,筛选出目标比例范围内的点
  const sampledPoints = d3.lineSample(basePathStr, 200); // 采样200个点保证平滑
  const filteredPoints = sampledPoints.filter((_, idx) => {
    const ratio = idx / (sampledPoints.length - 1);
    return ratio >= startRatio && ratio <= endRatio;
  });
  return lineGen(filteredPoints);
}

// 为每个分段创建独立path元素
segments.forEach((seg, idx) => {
  const subPath = getSegmentSubPath(seg.start, seg.end);
  // 生成上下偏移的路径
  const upperEdge = offset(subPath, offsetDistance);
  const lowerEdge = offset(subPath, -offsetDistance);
  // 拼接成闭合路径:上边缘 + 反转下边缘 + 闭合指令Z
  const closedPath = `${upperEdge} ${lowerEdge.split('L').reverse().join('L')}Z`;

  // 添加到SVG并设置样式
  d3.select('svg')
    .append('path')
    .attr('d', closedPath)
    .attr('fill', `hsl(${idx * 72}, 70%, 60%)`) // 每个分段不同颜色
    .attr('stroke', '#222')
    .attr('stroke-width', 1.5);
});
方案B:手动计算法线偏移(无外部依赖)

如果不想用插件,可通过计算曲线每个点的法线方向实现偏移:

const offsetDistance = 15;

// 计算点的法线方向(单位向量)
function getNormal(pPrev, pCurr, pNext) {
  // 计算切线向量
  const dx = pNext[0] - pPrev[0];
  const dy = pNext[1] - pPrev[1];
  // 法线向量(垂直于切线),单位化
  const len = Math.sqrt(dx * dx + dy * dy);
  return [-dy / len, dx / len];
}

// 生成分段的闭合路径
segments.forEach((seg, idx) => {
  const sampledPoints = d3.lineSample(basePathStr, 200);
  const filtered = sampledPoints.filter((_, i) => {
    const ratio = i / (sampledPoints.length - 1);
    return ratio >= seg.start && ratio <= seg.end;
  });

  // 生成上下偏移点数组
  const upperPoints = [];
  const lowerPoints = [];
  filtered.forEach((p, i) => {
    const pPrev = i === 0 ? p : filtered[i - 1];
    const pNext = i === filtered.length - 1 ? p : filtered[i + 1];
    const [nx, ny] = getNormal(pPrev, p, pNext);
    // 上下偏移点
    upperPoints.push([p[0] + nx * offsetDistance, p[1] + ny * offsetDistance]);
    lowerPoints.push([p[0] - nx * offsetDistance, p[1] - ny * offsetDistance]);
  });

  // 构建闭合路径
  const closedPath = `${lineGen(upperPoints)} ${lineGen(lowerPoints.reverse())}Z`;

  // 添加到SVG
  d3.select('svg')
    .append('path')
    .attr('d', closedPath)
    .attr('fill', `hsl(${idx * 72}, 70%, 60%)`)
    .attr('stroke', '#222')
    .attr('stroke-width', 1.5);
});

关键注意事项

  • 采样点数量越多,分段边缘越平滑,可根据需求调整d3.lineSample的第二个参数;
  • 偏移距离不宜过大,否则可能出现路径自相交的情况;
  • 如果需要非均匀分段,只需调整segments数组中的起止比例即可。

内容的提问来源于stack exchange,提问作者console.log

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:35:37