d3 中的 d3-interpolate:数值、颜色、变换与缩放的插值器完全指南
本篇指南围绕 d3 官方文档 d3-interpolate 展开,讲清楚 d3 插值器(interpolator)的统一模型:给定起始值 a 和结束值 b,插值器接收参数 t(通常取值 [0, 1])并返回中间过渡值。读完本文,你将能够熟练选用数值、字符串、数组、对象、各类颜色空间、CSS/SVG 变换以及视口缩放等十余种插值器,并理解它们在 d3-transition 动画管线中的实际工作方式。
插值器(Interpolator)的统一模型
d3-interpolate 模块提供了一组"在两个值之间混合"的方法。值可以是数字、颜色、字符串、数组,甚至是深层嵌套的对象。官方文档给出的基础示例如下:
const i = d3.interpolateNumber(10, 20);
i(0.0); // 10
i(0.2); // 12
i(0.5); // 15
i(1.0); // 20
返回的函数 i 就是一个插值器:给定起始值 a 与结束值 b,它对参数 t(典型取值 [0, 1])求值,通常 t = 0 时返回与 a 等价的值,t = 1 时返回与 b 等价的值。
颜色是最直观的应用场景。要找到 steelblue 与 brown 之间的感知中点:
d3.interpolateLab("steelblue", "brown")(0.5); // "rgb(142, 92, 109)"
而把同一个插值器当作从 t = 0 到 t = 1 的连续色带(color ramp)使用也是常见做法——官方文档站点正是用 ColorRamp 组件把插值器在 256 个采样点上逐一求值,绘制成 256×1 的 canvas 色带:
// ColorRamp 组件的核心渲染逻辑(节选自 docs/components/ColorRamp.vue)
function render(canvas, color, n) {
canvas.width = n;
canvas.height = 1;
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
}
泛型插值器 d3.interpolate 的类型推断
d3 最通用的入口是 d3.interpolate(a, b)。它不仅识别嵌套对象和数组,还能识别字符串中内嵌的颜色和数字:
const i = d3.interpolate({colors: ["red", "blue"]}, {colors: ["white", "black"]});
i(0.0); // {colors: ["rgb(255, 0, 0)", "rgb(0, 0, 255)"]}
i(0.5); // {colors: ["rgb(255, 128, 128)", "rgb(0, 0, 128)"]}
i(1.0); // {colors: ["rgb(255, 255, 255)", "rgb(0, 0, 0)"]}
根据 Value interpolation 文档,该算法以结束值 b 的类型为准依次判断:
- b 为 null、undefined 或布尔值时,返回常数 b;
- b 是数字,用
interpolateNumber; - b 是颜色或可强制转换为颜色的字符串,用
interpolateRgb; - b 是 Date 对象,用
interpolateDate; - b 是字符串,用
interpolateString; - b 是数字 TypedArray,用
interpolateNumberArray; - b 是普通数组,用
interpolateArray; - b 可强制转换为数字,用
interpolateNumber; - 其余情况一律用
interpolateObject。
选定插值器后,a 会被强制转换为对应类型。这意味着你只需调用 d3.interpolate(a, b),无需自己判断两端值是什么类型。
值插值:从数字到分段插值
Value interpolation 覆盖了最通用的插值场景,核心 API 如下。
interpolateNumber 与 interpolateRound
d3.interpolateNumber(a, b) 返回两个数字之间的插值器,等价于:
function interpolator(t) {
return a * (1 - t) + b * t;
}
d3.interpolateNumber(20, 620)(0.8) // 500
d3.interpolateRound(a, b) 与其类似,但会将结果四舍五入到最近整数:
d3.interpolateRound(20, 620)(0.821) // 513
注意事项:当插值器用于生成字符串时,应避免以 0 为起点或终点。极小值被字符串化时会进入科学计数法(如 0.0000001 → "1e-7"),这在旧浏览器中是非法的属性/样式值,在插值 opacity 时尤为明显。官方建议以 1e-6(不会触发科学计数法的最小值)作为起点或终点。
interpolateString:模板化字符串插值
d3.interpolateString(a, b) 会找出 a 与 b 中内嵌的数字(形式与 JavaScript 理解的数字一致,如 -1、42、3.14159、6.0221413e+23),为 b 中每个数字在 a 中寻找对应项并用 interpolateNumber 创建数值插值器;b 的其余静态部分作为模板保持不变:
d3.interpolateString("20px", "32px")(0.5) // "26px"
官方文档给出更复杂的例子:a 为 "300 12px sans-serif",b 为 "500 36px Comic-Sans",t = 0.5 时得到 "400 24px Comic-Sans"。
interpolateDate、interpolateArray 与 interpolateObject
interpolateDate(a, b):两个 Date 之间的插值器。
d3.interpolateDate(new Date("2014-01-01"), new Date("2024-01-01"))(0.5) // 2019-01-01
注意:为性能考虑,返回的 Date 不做防御性拷贝——每次求值都返回同一个 Date 实例(插值器通常是过渡动画内层的循环体)。
interpolateArray(a, b):两个数组之间的插值器,内部构建与 b 等长的数组模板,对每个位置存在的对应元素用泛型interpolate创建插值器,其余用 b 的静态值。同样不做防御性拷贝。
d3.interpolateArray([0, 0, 0], [1, 2, 3])(0.5) // [0.5, 1, 1.5]
// a=[0,1]、b=[1,10,100] 时 t=0.5 得 [0.5, 5.5, 100]
若 b 是 TypedArray(如 Float64Array),则改为调用 interpolateNumberArray。
interpolateNumberArray(a, b):面向两个数字数组(含 TypedArray),模板与 b 同类型同长度,对应元素直接在模板内插值,无对应元素则复制 b 的静态值。注意模板数组以及参数 a、b 均不做防御性拷贝。
d3.interpolateNumberArray([0, 1], Float64Array.of(1, 3))(0.5) // [0.5, 2]
interpolateObject(a, b):两个对象之间的插值器,构建与 b 同属性的对象模板,逐属性用interpolate创建内嵌插值器。它特别适合数据空间插值(dataspace interpolation)——先插值数据对象,再用 shape 生成器计算新的属性值。例如插值饼图扇区的描述对象,再交给 arc 计算新的 SVG path 数据。
d3.interpolateObject({x: 0, y: 1}, {x: 1, y: 10, z: 100})(0.5) // {x: 0.5, y: 5.5, z: 100}
分段与离散插值:Basis、Discrete、quantize、piecewise
interpolateBasis(values)/interpolateBasisClosed(values):一维的均匀非有理 B 样条插值器(要求 values 是数字)。前者隐式生成控制点使 t = 0 时返回 values[0]、t = 1 时返回最后一个值;后者隐式重复控制点,使样条在 [0,1] 上循环重复时具有周期 C² 连续性(适合循环色标):
d3.interpolateBasis([0, 0.1, 0.4, 1])(0.5) // 0.2604166666666667
d3.interpolateBasisClosed([0, 0.1, 0.4, 1])(0.5) // 0.45
interpolateDiscrete(values):离散插值器,t ∈ [0, 1/n) 映射 values[0],[1/n, 2/n) 映射 values[1],依此类推,相当于一个域固定为 [0, 1] 的轻量 quantize scale:
d3.interpolateDiscrete(["red", "blue", "green"])(0.5) // "blue"
quantize(interpolator, n):从任意插值器均匀采样 n 个值(n 为大于 1 的整数),第一个样本总在 t = 0,最后一个总在 t = 1。常用于从连续插值器派生 quantize scale 的 range:
d3.quantize(d3.interpolate("red", "blue"), 4)
// ["rgb(255, 0, 0)", "rgb(170, 0, 85)", "rgb(85, 0, 170)", "rgb(0, 0, 255)"]
注意:对返回非防御性拷贝输出的插值器(interpolateArray、interpolateDate、interpolateObject)不可直接使用,需要自行包装并对每个返回值做拷贝。
piecewise(interpolate, values):分段插值器,为每对相邻值组合一个插值器;t ∈ [0, 1/(n−1)] 对应interpolate(values[0], values[1]),[1/(n−1), 2/(n−1)] 对应下一对,依此类推,相当于一个轻量 linear scale。interpolate 缺省为泛型interpolate:
d3.piecewise(d3.interpolateRgb.gamma(2.2), ["red", "green", "blue"])
d3.piecewise(["red", "green", "blue"])
颜色插值:在正确的色彩空间里混合
Color interpolation 提供各色彩空间的双色插值器与多色样条插值器。所有双色插值器的 a、b 不要求已经是目标空间的颜色——会先经过 d3-color 转换(如 d3.rgb、d3.hsl、d3.lab、d3.hcl、d3.cubehelix),返回值统一为 RGB 字符串。
| 插值器 | 色彩空间 | 色相处理 |
|---|---|---|
interpolateRgb(a, b) |
RGB(可配 gamma) | — |
interpolateHsl(a, b) / interpolateHslLong(a, b) |
HSL | 短路径 / 不取短路径 |
interpolateLab(a, b) |
CIELAB | — |
interpolateHcl(a, b) / interpolateHclLong(a, b) |
CIELChab | 短路径 / 不取短路径 |
interpolateCubehelix(a, b) / interpolateCubehelixLong(a, b) |
Cubehelix(可配 gamma) | 短路径 / 不取短路径 |
要点:
- 对于 HSL 与 Hcl 插值器,若某一端的色相(或彩度)为 NaN,则取另一端的通道值;色相变化默认走最短路径。
- 多色样条插值器
interpolateRgbBasis(colors)与interpolateRgbBasisClosed(colors)接收一个颜色数组,返回在 RGB 空间穿过这些颜色的均匀非有理 B 样条插值器。前者隐式生成控制点,保证 t = 0 返回首色、t = 1 返回末色;后者隐式重复控制点,使样条循环时具有周期 C² 连续性,适合做循环色标。两者目前均不支持不透明度插值。它们与数值版interpolateBasis/interpolateBasisClosed对应,d3-scale-chromatic 中的色标正是基于这类插值器构建:
d3.interpolateRgbBasis(["purple", "green", "orange"])
d3.interpolateRgbBasisClosed(["purple", "green", "orange"])
gamma 校正
interpolateRgb 与 interpolateCubehelix / interpolateCubehelixLong 支持通过 .gamma(g) 设置新的插值器工厂:
d3.interpolateRgb.gamma(2.2)("purple", "orange")
d3.interpolateCubehelix.gamma(3)("purple", "orange")
gamma 未指定时默认为 1.0。在 gamma = 1(线性 RGB)下做插值,混合结果与人眼感知更贴近;设置 gamma = 2.2 则模拟显示器伽马,让色带在明度上更均衡。
interpolateHue
单独处理两个色相角 a、b(度数)的插值器。若某一方为 NaN,取另一方;色相之间走最短路径,返回值是 [0, 360) 内的数字:
d3.interpolateHue(20, 340)(0.5) // 0
变换插值:CSS 与 SVG 的矩阵分解
Transform interpolation 面向 CSS 和 SVG 的 2D 变换,插值方法遵循 CSS 标准定义的矩阵分解。两个插值器各自把输入变换分解为 translate、rotate、x-skew、scale 四个标准分量,再对分量分别插值:
d3.interpolateTransformCss("translateY(12px) scale(2)", "translateX(30px) rotate(5deg)")(0.5)
// "translate(15px,6px) rotate(2.5deg) scale(1.5,1.5)"
d3.interpolateTransformSvg("skewX(-60)", "skewX(60) translate(280,0)")
// "translate(140,0) skewX(0)"
interpolateTransformCss(a, b):两端是 2D CSS 变换字符串;interpolateTransformSvg(a, b):两端是 2D SVG 变换字符串。
按分量插值而非按矩阵元素线性混合,能保证中间的 rotate/scale 语义正确,不会出现"元素先翻转再转回来"这类线性混合矩阵的怪象。
缩放插值:平滑的视口飞行
Zoom interpolation 基于 Jarke J. van Wijk 与 Wim A.A. Nuij 的 "Smooth and efficient zooming and panning" 论文,用于在两个二维视口之间沿曲线路径平滑飞行:
d3.interpolateZoom([30, 30, 40], [135, 85, 60])(0.5) // [72, 52, 126.04761005270991]
每个"视图"是一个三元素数组 [cx, cy, width]:前两项是视口中心坐标,第三项是视口宽度。返回的插值器额外暴露 duration 属性(毫秒),它由 xy 空间的曲线路径长度推算出推荐过渡时长;想要更快或更慢,可按论文中的 V 参数乘以任意缩放系数。
还可以用 interpolateZoom.rho(rho) 返回指定曲率 rho 的新插值器:rho 越接近 0,轨迹越接近直线;默认曲率是 sqrt(2):
d3.interpolateZoom.rho(0.5)([30, 30, 40], [135, 85, 60])(0.5) // [72, 52, 51.09549882328188]
在 d3-transition 中使用插值器
插值器是 d3 动画管线的基本单元。从 d3-transition 文档(见 docs/d3-transition/modifying.md)可以看到,transition.attr() / transition.style() 内部的插值器选择规则是:
- 值是数字,用
interpolateNumber; - 值是颜色或可强制转换为颜色的字符串,用
interpolateRgb; - 否则用
interpolateString。
需要更精细控制时,可以使用 attrTween / styleTween / textTween 显式提供插值器工厂。工厂函数在过渡开始时对每个被选元素求值(接收当前数据 d、索引 i 和当前组 nodes,this 为当前 DOM 元素),返回的插值器随后在每一帧被调用,传入 缓动后的时间 t(通常在 [0, 1]):
// 从当前 fill 过渡到蓝色
transition.styleTween("fill", function() {
return d3.interpolateRgb(this.style.fill, "blue");
});
// 文本按整数从 0 插值到 100
transition.textTween(() => d3.interpolateRound(0, 100));
文档还特别推荐数据插值技巧:用 interpolateObject 直接插值数据对象,再用 shape 生成器(如 d3-shape)从插值结果计算属性值,从而对复杂形状(路径、扇区等)做自然过渡。
仓库中的组织方式
在本仓库中,插值能力以 d3-interpolate 子包的形式提供并在顶层重新导出:src/index.js 中 export * from "d3-interpolate"; 使 d3.interpolateNumber、d3.interpolateLab 等 API 直接挂在 d3 命名空间下。从 package.json 可以看到该仓库(d3 v7.9.0)依赖 "d3-interpolate": "^3.0.1",官方 API 细节见 docs/d3-interpolate.md 及其四个子页:Value interpolation、Color interpolation、Transform interpolation 和 Zoom interpolation。
选型速查
- 两个数字:
interpolateNumber;要整数用interpolateRound; - 两个颜色:默认
interpolateRgb(感知要求高时用interpolateLab/interpolateHcl,需要色相绕环时用interpolateHsl);多色色带用interpolateRgbBasis或piecewise; - 含数字的字符串/CSS 值:
interpolateString或泛型d3.interpolate; - 复杂形状过渡:
interpolateObject做数据空间插值 + shape 生成器重算属性; - CSS/SVG 变换:
interpolateTransformCss/interpolateTransformSvg; - 地图/图表视口飞行:
interpolateZoom(配合其duration属性设定过渡时长); - 从连续插值器取离散档位:
quantize、interpolateDiscrete。
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 StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00