首页
/ d3.line 折线生成器详解:d3-shape 中折线图的数据访问器、曲线控制与 Canvas 渲染

d3.line 折线生成器详解:d3-shape 中折线图的数据访问器、曲线控制与 Canvas 渲染

2026-09-06 13:06:52作者:姚月梅Lane

本文围绕 d3-shape 模块中的折线生成器 d3.line 展开,系统讲解其构造函数、x/y 数据访问器、缺失值处理(defined)、曲线插值(curve)、Canvas 上下文(context)与小数精度(digits)等全部核心 API。读完之后,你可以独立编写可复制运行的折线图代码,并理解每个生成器方法在底层是如何影响 SVG path 数据或 Canvas 路径指令的。

d3.line 在 d3-shape 中的位置

折线生成器是 d3-shape 模块提供的形状生成器之一。d3-shape 模块文档将可视化标记分为多种离散图形——符号(symbols)、扇形(arcs)、折线(lines)、面积(areas)等,并指出这些形状与 D3 其他部分一样由数据驱动:每个形状生成器都暴露一组访问器(accessor),控制输入数据如何映射为视觉表示。

折线生成的结果是样条曲线(spline)或折线(polyline),典型应用是折线图;它同样出现在其他可视化类型中,例如分层边捆绑(hierarchical edge bundling)中的连线。极坐标系下的对应版本是 radial line 文档所讲的 d3.lineRadial

关于当前仓库的版本前提,需要说明两点:

  • 本仓库是 d3 元包(meta-package),package.json"version": "7.9.0",并通过 "d3-shape": "^3.2.0" 依赖独立的 d3-shape 子包。因此本文描述的是 d3 v7 / d3-shape v3.x 的行为。
  • 从源码结构看,src/index.js 只做了 export * from "d3-shape" 这类再导出,d3.line 的实际实现位于 d3-shape 子包的 src/line.js 中(各文档条目的 Source 链接均指向该文件),本仓库不包含该实现源码,只负责将其统一暴露在 d3 命名空间下。test/d3-test.js 中的测试也印证了这一点:它遍历 package.json 的全部依赖模块,断言 d3 对象导出了每个子模块(除 version 外)的所有导出属性,保证 d3.lined3.curveStep 等符号在元包上确实可用。

api.md 的函数索引中也列出了 d3.line(创建新的 line generator)及其径向变体 d3.lineRadial

创建生成器:d3.line(x, y)

d3.line(*x*, *y*) 使用给定的 xy 访问器构造一个新的折线生成器:

const line = d3.line((d) => x(d.Date), (d) => y(d.Close));

如果调用时不指定 xy,则分别使用默认值。上面一行可以写成更显式的链式形式:

const line = d3.line()
    .x((d) => x(d.Date))
    .y((d) => y(d.Close));

这里的 xy 通常指通过 d3-scale 创建的刻度,把数据中的日期字段(d.Date)映射为水平像素位置、把收盘价字段(d.Close)映射为垂直像素位置。d3-shape 模块文档给出的时序折线示例正是这一模式:

const line = d3.line()
    .x((d) => x(d.date))
    .y((d) => y(d.value));

生成路径:line(data)

调用生成器本身即触发渲染:*line*(*data*) 对给定的数据数组 data 生成一条折线。在 SVG 中的典型用法是把它作为 path 元素的 d 属性值:

svg.append("path").attr("d", line(data)).attr("stroke", "currentColor");

这里有一个关键的分叉点,决定了返回值是什么:

  • 若生成器设置了 context,折线会被渲染为该 context 上的一连串 Canvas path method 调用,函数返回 void
  • 否则,函数返回一个 SVG path data 字符串(形如 M...L... 的指令序列)。

官方文档中还给出了一个等价的简洁写法——借助 datum 把数据挂到节点上,让 d3-selection 在设置 d 属性时自动以数据作为参数调用生成器:

path.datum(data).attr("d", line);

数据排序警告:文档特别指出,取决于生成器关联的 curve,输入的 data 在传入生成器之前可能需要按 x 值排序。例如使用单调插值曲线(如 curveMonotoneX)时,x 值乱序会导致曲线出现非预期的弯折,这是使用折线图时最常见的"隐性坑"之一。

数据访问器:line.x(x) 与 line.y(y)

两个访问器既是 setter 也是 getter:

// 设置
const line = d3.line().x((d) => x(d.Date));
const line2 = d3.line().y((d) => y(d.Close));

// 读取当前访问器
line.x() // (d) => x(d.Date)
line.y() // (d) => y(d.Close)

调用约定与默认值

当折线被生成时,访问器会对输入数组中每个有定义的元素被调用,并接收三个参数:元素 d、索引 i、数组 data

x 访问器的默认实现是:

function x(d) {
  return d[0];
}

y 访问器的默认实现是:

function y(d) {
  return d[1];
}

也就是说,默认访问器假设输入数据是两元素数字数组,形如 [[x0, y0], [x1, y1], ...]。如果你的数据是对象(如 {Date, Close} 这样的股票行情记录),或者希望在渲染前对数据做变换,就必须指定自定义访问器。

处理缺失值:line.defined(defined)

defined 访问器决定哪些数据点是"有效的":

const line = d3.line().defined((d) => !isNaN(d.Close));

生成折线时,defined 访问器会对输入数组的每个元素被调用,参数同样是 (d, i, data)

  • 若返回真值(该点有定义),随后会计算该点的 xy 值,并把该点加入当前线段;
  • 若返回假值,该元素被跳过,当前线段在此结束,下一个有定义的点会开启一条新的线段。

不指定参数时返回当前的 defined 访问器:

line.defined() // (d) => !isNaN(d.Close)

默认值是恒为 true 的函数,即假设输入数据始终有定义:

function defined() {
  return true;
}

这意味着默认情况下,NaN 坐标值不会被自动过滤,而是由浏览器在渲染时决定如何处理(通常是断开路径)。对含缺失值的时序数据(例如某些交易日没有收盘价记录),显式设置 defined 是让折线"断而不错连"的关键手段。

两个细节值得注意:

  1. 如果某条线段只包含单个点,除非使用圆头或方头(rounded/square)的 stroke-linecap,否则该点可能看起来不可见;
  2. 某些曲线(如 curveCardinalOpen)只有在线段包含多个点时才渲染出可见部分。

控制曲线形态:line.curve(curve)

curve 方法用于设置 curve factory

const line = d3.line().curve(d3.curveStep);

不指定参数时返回当前曲线工厂,默认是 curveLinear(线性连接,即普通折线):

line.curve() // d3.curveLinear(默认值)

d3-shape 提供了完整的曲线族(见 curve 文档),常用的有:

曲线工厂 效果
curveLinear 默认,直线段连接各点
curveStep / curveStepBefore / curveStepAfter 阶梯式连接
curveBasis / curveBasisOpen / curveBasisClosed B 样条(B-spline)平滑
curveCardinal / curveCardinalOpen / curveCardinalClosed 中心差法(cardinal)样条,可用 curveCardinal.tension(t) 调节张力
curveCatmullRom 系列 Catmull–Rom 样条,可用 curveCatmullRom.alpha(a) 调节
curveMonotoneX / curveMonotoneY 单调插值,保证不越过数据点,常用于函数图
curveNatural 自然三次样条
curveBundle 分层边捆绑用的束状曲线,可用 curveBundle.beta(b) 调节
curveBumpX / curveBumpY 单点"鼓包"曲线,常用于甘特图等

注意:曲线只影响点与点之间的插值方式,不改变访问器给出的数据点位置;且正如前文所述,部分曲线(单调、自然样条)要求数据按 x 值排序。

渲染到 Canvas:line.context(context)

context 方法切换折线的输出目标:

const context = canvas.getContext("2d");
const line = d3.line().context(context);

// 读取当前 context
line.context() // context
  • context 默认为 null
  • context 为 null 时,*line*(*data*) 返回表示该折线的 SVG path data 字符串
  • context 非 null 时,生成的折线以一系列 Canvas path method 调用(moveTo/lineTo/bezierCurveTo 等)绘制到该 context 上,函数返回 void

d3-shape 模块文档给出的 Canvas 用法一行即达:

line.context(context)(data);

同一套生成器配置(访问器、curve、defined)在 SVG 字符串输出与 Canvas 指令输出之间是完全对等的,这使得同一个生成器可以无缝用于两种渲染后端——这正是 d3-shape"生成器与渲染目标解耦"设计的核心价值。

小数精度控制:line.digits(digits)

digits 方法设置 path data 中小数点后的最大位数:

const line = d3.line().digits(3);

// 读取当前值,默认 3
line.digits() // 3

适用范围限制:该选项仅在关联的 contextnull 时生效,即生成 SVG path data 字符串的场景。Canvas 路径指令走的是浮点参数直接传入 context 方法,不存在"格式化为文本"的问题,因此 digits 对其无意义。把精度调低(例如 digits(2) 甚至 digits(0))可以显著缩短生成的 path 字符串,在点数众多的场景下减小 DOM 中的字符串体积。

完整实战示例:一个可运行的折线图

综合以上 API,下面是一个符合 d3-shape 模块文档推荐模式的完整示例,可直接在任意引入 d3 v7 的页面中运行:

// 1. 准备比例尺(以 d3-scale 文档 docs/d3-scale.md 为参考)
const x = d3.scaleTime().domain(d3.extent(data, (d) => d.Date)).range([margin.left, width - margin.right]);
const y = d3.scaleLinear().domain([0, d3.max(data, (d) => d.Close)]).range([height - margin.bottom, margin.top]);

// 2. 构造生成器:访问器 + 缺失值过滤 + 曲线
const line = d3.line()
    .x((d) => x(d.Date))          // 默认是 d[0],此处改为对象字段
    .y((d) => y(d.Close))         // 默认是 d[1]
    .defined((d) => !isNaN(d.Close)) // 缺失值处断开线段
    .curve(d3.curveMonotoneX)     // 平滑且不越过数据点;要求数据按 Date 有序
    .digits(3);                   // path 字符串小数位

// 3a. SVG 渲染:返回 path data 字符串
svg.append("path")
    .attr("d", line(data))
    .attr("fill", "none")
    .attr("stroke", "currentColor");

// 或者等价地用 datum 模式:
path.datum(data).attr("d", line);

// 3b. Canvas 渲染:返回 void,直接画到 context
line.context(canvas.getContext("2d"))(data);

这个示例覆盖了文档中的全部配置面:两种访问器、defined 断点、曲线选择、digits 精度以及 SVG/Canvas 双后端输出。运行环境的前提是安装 d3 v7(本仓库 package.json 对应的版本线),其中 d3.linesrc/index.jsexport * from "d3-shape" 统一暴露。

小结与延伸阅读

d3.line 的 API 面虽小,但每个方法都对应一条明确的渲染决策链:

  1. d3.line(x, y) 构造生成器,默认访问器假设数据为 [[x, y], ...] 数组;
  2. *line*(data) 生成路径,有 context 输出 Canvas 指令(返回 void),无 context 输出 path 字符串;
  3. .x() / .y() 自定义数据到像素的映射,访问器接收 (d, i, data) 三参数;
  4. .defined() 控制线段断点,默认恒真、不自动过滤 NaN;
  5. .curve() 选择插值算法,默认 curveLinear,部分曲线要求数据按 x 排序;
  6. .context() 决定 SVG/Canvas 输出后端;
  7. .digits() 仅在 path 字符串模式下限制小数位,默认 3。

仓库内可继续深入的文档:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393