d3-shape 曲线插值完全指南:d3 中 17 种内置 Curve 的原理、参数与自定义接口
本篇技术指南聚焦 d3 官方文档中的 Curves 页面(docs/d3-shape/curve.md),系统讲解曲线(curve)如何将离散的 [x, y] 点集插值为连续形状:全部内置曲线类型的行为差异、beta / tension / alpha 三个可调参数的取值范围与默认值,以及曲线底层暴露的 areaStart / lineStart / point 五方法接口。读完本文,你可以为折线图、面积图选择合适的插值方式,正确配置曲线参数,并在内置曲线不满足需求时实现自己的自定义曲线。
一、曲线是什么:从离散点到连续形状
曲线(curve)的核心职责,是把折线(line)或面积(area)的离散(逐点)表示转换为连续形状:曲线规定了如何在二维 [x, y] 点之间进行插值。
理解曲线的两个关键点(来自 curve 文档 开头的定义):
- 曲线通常不直接构造或直接使用,而是作为曲线工厂函数传给 line.curve 或 area.curve;
d3.line()与d3.area()的默认曲线都是 curveLinear,即直接以折线连接各点。
标准用法如下:
const line = d3.line()
.x((d) => x(d.date))
.y((d) => y(d.value))
.curve(d3.curveCatmullRom.alpha(0.5));
在本仓库中,d3 作为伞包通过 src/index.js 重新导出 d3-shape 模块(export * from "d3-shape"),而 package.json 声明的依赖为 "d3-shape": "^3.2.0",因此上述 d3.curveCatmullRom 等 API 来自 d3-shape 3.x 系列。官方文档站点中每个曲线类型都配有基于 Observable Plot 的动态演示组件(见 docs/components/ExampleCurve.vue),该组件用同一组示例点集分别绘制多种曲线,并对 ticks = [0, 0.25, 0.5, 0.75, 1] 做参数扫描,直观对比不同参数取值下的曲线形态。
二、内置曲线全览
d3-shape 内置 17 种曲线,可按插值算法分为 8 个家族。下表先给出总览,后文逐族详解:
| 曲线 | 插值方式 | 是否穿过数据点 | 默认参数 |
|---|---|---|---|
| curveLinear | 折线 | 是 | — |
| curveLinearClosed | 闭合折线 | 是 | — |
| curveStep | 阶梯(中点切换) | 部分 | — |
| curveStepBefore / curveStepAfter | 阶梯(前/后切换) | 部分 | — |
| curveBasis / curveBasisOpen / curveBasisClosed | 三次 B 样条 | 首尾点(非 open) | — |
| curveBumpX / curveBumpY | 三次贝塞尔 | 是(切线水平/垂直) | — |
| curveBundle | 拉直的 B 样条 | 否 | beta = 0.85 |
| curveCardinal / curveCardinalOpen / curveCardinalClosed | 三次 Cardinal 样条 | 是(非 open) | tension = 0 |
| curveCatmullRom / curveCatmullRomOpen / curveCatmullRomClosed | 三次 Catmull–Rom 样条 | 是(非 open) | alpha = 0.5 |
| curveMonotoneX / curveMonotoneY | 保单调三次样条 | 是 | — |
| curveNatural | 自然三次样条 | 是 | — |
2.1 Basis 家族:不穿过数据点的 B 样条
- curveBasis(context):用指定控制点生成三次 B 样条。首点和尾点会被三重复制,使样条从第一个点开始、到最后一个点结束,并且在端点处分别与"第一、二点连线"和"倒数第二、点连线"相切。
- curveBasisClosed(context):闭合三次 B 样条。线段结束时重复前三个控制点,生成具有 C2 连续性(二阶导连续)的闭合回路。
- curveBasisOpen(context):与 basis 相同,但首尾点不重复,因此曲线通常不穿过首尾点。
B 样条的整体特征是用"逼近"而非"穿过"控制点,曲线平滑但会偏离数据点,适合控制点本身不要求被精确绘制、只作为造型引导的场景。
2.2 Bump 家族:贝塞尔凸起曲线
- curveBumpX(context):在每对点之间生成三次贝塞尔曲线,且在每个点处切线为水平方向——曲线呈现沿 x 轴方向的平滑起伏。
- curveBumpY(context):同理,但每个点处切线为垂直方向,适合 y 方向单调推进的布局(如自下而上的层级连线)。
2.3 curveBundle:层级边缘束带
curveBundle(context) 生成一条"被拉直"的三次 B 样条,拉直程度由参数 beta 控制,默认 0.85。它典型用于**层级边缘束带(hierarchical edge bundling)**技术,通过让同源同目的的连线在公共路径上贴合来消除视觉交叉——这一方法由 Danny Holten 在其论文《Hierarchical Edge Bundles: Visualization of Adjacency Relations in Hierarchical Data》中提出。
需要注意两个限制(原文档明确标注):
- curveBundle 不实现
areaStart/areaEnd,即只能配合 d3.line 使用,不能配合 d3.area; - 从源码结构看,它不实现面积所需的区段接口,这正是它与其它曲线在接口层面的关键差异。
curveBundle.beta(beta)
const line = d3.line().curve(d3.curveBundle.beta(0.5));
- 取值范围 [0, 1],表示束带强度,默认 0.85;
beta = 0:退化为首尾点之间的直线;beta = 1:退化为标准 curveBasis 样条。
CHANGES.md 中记载,d3 4.0 将 bundle 的默认 beta 从 0.7 调整为 0.85,以匹配 Holten 论文中使用的取值。
2.4 Cardinal 家族:带张量的三次样条
- curveCardinal(context):用指定控制点生成三次 Cardinal 样条,首尾两段使用单侧差分(one-sided differences)。默认 tension 为 0。
- curveCardinalClosed(context):闭合三次 Cardinal 样条。线段结束时重复前三个控制点,生成闭合回路。默认 tension 为 0。
- curveCardinalOpen(context):不使用单侧差分,因此曲线从第二个点开始、在倒数第二个点结束,不穿过首尾点。默认 tension 为 0。
curveCardinal.tension(tension)
const line = d3.line().curve(d3.curveCardinal.tension(0.5));
- 取值范围 [0, 1],决定切线长度:
tension = 1:所有切线长度为零,等价于 curveLinear;tension = 0:生成均匀 Catmull–Rom 样条(即curveCatmullRom.alpha(0))。
CHANGES.md 还记载:d3 4.0 修正了 Cardinal 样条 tension 参数的解释方式——现在以 cardinal.tension 显式指定且默认为 0(均匀 Catmull–Rom),tension 为 1 时得到直线曲线;同时 basis 与 cardinal 曲线首尾段的行为也在 4.0 中一并修正。
2.5 Catmull–Rom 家族:推荐默认的曲线
- curveCatmullRom(context):用指定控制点与参数 alpha(默认 0.5)生成三次 Catmull–Rom 样条,首尾两段使用单侧差分。参数化方案来自 Yuksel 等人的研究《On the Parameterization of Catmull–Rom Curves》。
- curveCatmullRomClosed(context):闭合版本,线段结束时重复前三个控制点形成闭合回路,alpha 默认 0.5。
- curveCatmullRomOpen(context):不使用单侧差分,曲线从第二个点开始、在倒数第二个点结束。alpha 默认 0.5。
curveCatmullRom.alpha(alpha)
const line = d3.line().curve(d3.curveCatmullRom.alpha(0.5));
- 取值范围 [0, 1]:
alpha = 0:均匀(uniform)样条,等价于 tension 为 0 的 curveCardinal;alpha = 1:弦长(chordal)样条;alpha = 0.5:向心(centripetal)样条。
官方文档明确建议:优先使用向心样条(alpha = 0.5,即默认值),以避免自交(self-intersection)与过冲(overshoot)——这是选择曲线参数时最重要的实战经验之一。
2.6 Linear 家族:折线
- curveLinear(context):生成穿过指定点的折线。这是 line 与 area 生成器的默认曲线。
- curveLinearClosed(context):在线段结束时重复第一个点,生成闭合折线。
2.7 Monotone 家族:保单调插值
- curveMonotoneX(context):在 x 单调的前提下生成y 方向保持单调性的三次样条(Steffen 算法)。其引文概括了该曲线的性质:"一条一阶导数连续的光滑曲线,穿过给定的任意数据点且不产生虚假振荡;局部极值只能出现在数据给定的网格点上,而不会出现在相邻两个网格点之间。"
- curveMonotoneY(context):对称版本——在 y 单调的前提下保持 x 方向的单调性,适合纵向图表。CHANGES.md 记载 curveMonotoneY 是 4.0 新引入的,同时 4.0 也修复了 monotone 曲线实现中的多个 bug。
保单调曲线适合"值不应在相邻数据点之间反向波动"的场景,例如累积量、占比、排名类时间序列。
2.8 curveNatural:自然三次样条
curveNatural(context) 生成自然三次样条:样条在两端点的二阶导数为零(即端点处曲率为零)。它同样在 d3 4.0 中引入。相比 monotone,natural 不承诺单调性,适合需要全局"最平滑"外观、且端点弯曲自然收尾的曲线。
2.9 Step 家族:阶梯函数
三者都生成由水平线与垂直线交替组成的分段常数(阶梯)函数,区别只在 y 值切换的时机:
- curveStep(context):y 值在每对相邻 x 值的中点处切换;
- curveStepAfter(context):y 值在 x 值之后切换;
- curveStepBefore(context):y 值在 x 值之前切换。
阶梯曲线常用于直方图轮廓、离散状态的时间线(如订单状态随时间的变化)。
三、regular / open / closed 三种形态的区别
cardinal、catmull-rom、basis 三个家族各有三个变体,其行为差异是选型时的常见困惑点:
| 维度 | regular(如 curveCardinal) | open(如 curveCardinalOpen) | closed(如 curveCardinalClosed) |
|---|---|---|---|
| 首尾处理 | 首尾两段用单侧差分,从第一个点开始、到最后一个点结束 | 不用单侧差分,从第二个点开始、到倒数第二个点结束 | 线段结束时重复前三个控制点,生成闭合回路 |
| 典型用途 | 开放折线/面积边线 | 需要端点"收缩"效果的曲线 | 环形图边线、闭合区域轮廓 |
另外注意:使用 open 变体时,若某个线段只含一个点,曲线可能不产生可见输出——line 文档 在 line.defined 一节明确提示,诸如 curveCardinalOpen 这类曲线只有在包含多个点的线段中才渲染可见段。
四、自定义曲线接口
当内置曲线均不满足需求时,可以基于以下接口实现自定义曲线(curveLinear 的实现是最简示例)。这套接口还可以直接与内置曲线配合使用,作为 line / area 生成器的底层替代。
| 方法 | 语义 |
|---|---|
curve.areaStart() |
标记一个新的面积段开始。每个面积段恰好由两个线段组成:topline 在前,baseline 在后,且 baseline 的点逆序排列 |
curve.areaEnd() |
标记当前面积段结束 |
curve.lineStart() |
标记一个新线段开始,其后跟随零个或多个点 |
curve.lineEnd() |
标记当前线段结束 |
curve.point(x, y) |
在当前线段中追加一个新点,取值为给定的 x、y |
从这套接口可以看出 line 与 area 生成器驱动曲线的方式:生成器在遍历数据时按 areaStart → lineStart → point* → lineEnd →(第二次 lineStart/lineEnd,点逆序)→ areaEnd 的顺序回调曲线对象,曲线则负责把回调序列翻译成 context(SVG 或 Canvas 2D)上的路径调用。这也解释了为何 curveBundle 只需不实现 areaStart / areaEnd 两个方法即可表达"不支持面积"。
关于 context 的行为(见 line 文档 与 area 文档):
- 若生成器设置了 context(如
canvas.getContext("2d")),形状以路径方法调用序列渲染到该 context,调用返回 void; - 若 context 为 null(默认),则返回 SVG path data 字符串,此时
digits选项(默认 3 位小数)控制路径字符串的小数位数。
五、实战注意事项
结合 curve、line、area 三篇文档的警告与限制,实际选型时建议核对以下要点:
- 输入数据可能需要按 x 排序:line 与 area 文档都以警告框明确指出——"取决于该生成器关联的曲线,传入的输入数据可能需要先按 x 值排序"。monotone 系列曲线对此尤其敏感(其正确性以单调性为前提)。
- area 兼容性:curveBundle 不支持面积(无
areaStart/areaEnd实现),只能配合 d3.line;其余内置曲线均支持 line 与 area。 - 端点是否被穿过:basis 家族(除首尾处理外)、bundle 不穿过内部控制点;open 变体不穿过首尾点;cardinal、catmull-rom(非 open)、monotone、natural 穿过所有数据点。选择曲线前先确认业务上"曲线必须经过每个数据点"是否成立。
- 参数默认值:bundle.beta 默认 0.85、cardinal.tension 默认 0、catmullRom.alpha 默认 0.5(向心)。除非有明确理由,建议保留默认值;向心参数化是官方推荐的防自交/防过冲方案。
- 单点线段:任何曲线下,仅含单点的线段可能不可见(需配合圆头或方头线帽渲染);open 系曲线在单点线段下则完全不产生可见段。
六、在仓库中继续深入
- docs/d3-shape/curve.md:本文主体文档,含各曲线动态演示;
- docs/d3-shape/line.md 与 docs/d3-shape/area.md:line/area 生成器的 accessors、
curve/context/digits配置及数据排序警告; - docs/d3-shape.md:d3-shape 模块总览(line、area、arc、pie、stack 等生成器索引);
- docs/components/ExampleCurve.vue:曲线对比演示组件的实现,展示了如何用一组固定示例点渲染并对比多条曲线;
- CHANGES.md:d3 4.0 版本中 curve API 从旧
interpolate接口迁移的历史、各曲线命名映射(如monotone ↦ curveMonotoneX)、以及 curveMonotoneY / curveNatural 的引入与 bundle 默认 beta 的调整; - test/docs-test.js:文档锚点链接校验测试,保证文档内
{#anchor}形式的接口锚点可被稳定引用。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00