首页
/ d3 中的 d3-interpolate:数值、颜色、变换与缩放的插值器完全指南

d3 中的 d3-interpolate:数值、颜色、变换与缩放的插值器完全指南

2026-09-04 23:36:54作者:咎竹峻Karen

本篇指南围绕 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 的类型为准依次判断:

  1. b 为 null、undefined 或布尔值时,返回常数 b
  2. b 是数字,用 interpolateNumber
  3. b 是颜色或可强制转换为颜色的字符串,用 interpolateRgb
  4. b 是 Date 对象,用 interpolateDate
  5. b 是字符串,用 interpolateString
  6. b 是数字 TypedArray,用 interpolateNumberArray
  7. b 是普通数组,用 interpolateArray
  8. b 可强制转换为数字,用 interpolateNumber
  9. 其余情况一律用 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) 会找出 ab 中内嵌的数字(形式与 JavaScript 理解的数字一致,如 -1423.141596.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 的静态值。注意模板数组以及参数 ab 均不做防御性拷贝。
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)"]

注意:对返回非防御性拷贝输出的插值器(interpolateArrayinterpolateDateinterpolateObject)不可直接使用,需要自行包装并对每个返回值做拷贝。

  • piecewise(interpolate, values):分段插值器,为每对相邻值组合一个插值器;t ∈ [0, 1/(n−1)] 对应 interpolate(values[0], values[1]),[1/(n−1), 2/(n−1)] 对应下一对,依此类推,相当于一个轻量 linear scaleinterpolate 缺省为泛型 interpolate
d3.piecewise(d3.interpolateRgb.gamma(2.2), ["red", "green", "blue"])
d3.piecewise(["red", "green", "blue"])

颜色插值:在正确的色彩空间里混合

Color interpolation 提供各色彩空间的双色插值器与多色样条插值器。所有双色插值器的 ab 不要求已经是目标空间的颜色——会先经过 d3-color 转换(如 d3.rgbd3.hsld3.labd3.hcld3.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 校正

interpolateRgbinterpolateCubehelix / 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

单独处理两个色相角 ab(度数)的插值器。若某一方为 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() 内部的插值器选择规则是:

  1. 值是数字,用 interpolateNumber
  2. 值是颜色或可强制转换为颜色的字符串,用 interpolateRgb
  3. 否则用 interpolateString

需要更精细控制时,可以使用 attrTween / styleTween / textTween 显式提供插值器工厂。工厂函数在过渡开始时对每个被选元素求值(接收当前数据 d、索引 i 和当前组 nodesthis 为当前 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.jsexport * from "d3-interpolate"; 使 d3.interpolateNumberd3.interpolateLab 等 API 直接挂在 d3 命名空间下。从 package.json 可以看到该仓库(d3 v7.9.0)依赖 "d3-interpolate": "^3.0.1",官方 API 细节见 docs/d3-interpolate.md 及其四个子页:Value interpolationColor interpolationTransform interpolationZoom interpolation

选型速查

  • 两个数字:interpolateNumber;要整数用 interpolateRound
  • 两个颜色:默认 interpolateRgb(感知要求高时用 interpolateLab / interpolateHcl,需要色相绕环时用 interpolateHsl);多色色带用 interpolateRgbBasispiecewise
  • 含数字的字符串/CSS 值:interpolateString 或泛型 d3.interpolate
  • 复杂形状过渡:interpolateObject 做数据空间插值 + shape 生成器重算属性;
  • CSS/SVG 变换:interpolateTransformCss / interpolateTransformSvg
  • 地图/图表视口飞行:interpolateZoom(配合其 duration 属性设定过渡时长);
  • 从连续插值器取离散档位:quantizeinterpolateDiscrete
登录后查看全文
热门项目推荐
相关项目推荐