D3 插值器

d3

D3 插值器

插值器是用于计算过渡值的,以便实现补间动画。

在 D3 中创建插值器时需要指定它的起始值和目标值/结束值,即设置插值范围。然后调用插值器函数时,它接受一个标准时间 t 作为入参,然后结合起始值和结束值,返回该时间点的过渡值,例如 D3 内置插值器 d3.interpolateNumber(a, b) 其源码如下

js
export default function(a, b) {
  return a = +a, b = +b, function(t) {
    return a * (1 - t) + b * t;
  };
}
js
// 创建一个插值器,配置插值范围
const i = d3.interpolateNumber(10, 20);

// 调用插值器,入参是标准时间 t,范围是 [0, 1]
i(0.0); // 10
i(0.2); // 12
i(0.5); // 15
i(1.0); // 20

D3 在 d3-interpolate 模块中提供了多种插值器,针对不同的数据类型:

提示

实际上这些插值器最终都是针对数值部分进行插值,例如对颜色进行插值,是将颜色解构转换为相应的数值参数;对于 CSS 变换样式插值,是对 transform matrix 操作。

通用类型插值器

使用方法 d3.interpolate(a, b) 创建一个通用类型的插值器。 实际上,它会根据 b 的值类型自动调用相应的数据类型插值器,选择算法如下:

  1. b 数据类型是 nullundefined 或布尔值,始终使用常量 b 作为过渡值
  2. b 数据类型是数值, 使用 d3.interpolateNumber 插值器
  3. b 数据类型是颜色(或可以转换为颜色的字符串),使用 d3.interpolateRgb 插值器
  4. b 数据类型是日期,使用 d3.interpolateDate 插值器
  5. b 数据类型是字符串,使用 d3.interpolateString 插值器
  6. b 数据类型是声明类型的数组,使用 d3.interpolateNumberArray 插值器
  7. b 数据类型是数组,使用 d3.interpolateArray 插值器
  8. b 数据类型是可以转换为数值的类型,使用 d3.interpolateNumber 插值器
  9. b 是其他数据类型,使用 d3.interpolateObject 插值器

特定数据类型插值器

  • d3.interpolateNumber(a, b) 创建一个插值范围为 [a, b] 的数值插值器
    提示

    避免使用 0 作为范围的端点,因为当过渡值靠近 0 时,它们会被转换为科学计数法(以字符串表示数字),例如数值 0.0000001 会被转换为字符串 1e-7。如果插值范围的端点要使用 0,应该使用 0.000001 来代替(它是不会被转换为科学计数法表示的最小数值)

  • d3.interpolateRound(a, b) 插值范围为 [a, b] 的数值插值器,但是过渡值会被修约为最近的整数
  • d3.interpolateString(a, b) 针对字符串的插值器,它会自动分析出字符串,b 的文本部分(保留不变)作为模板,再计算出数值部分的过渡值,构成完整的字符串。
    js
    const a="300 12px sans-serif";
    const b="500 36px Comic-Sans";
    const interpolator = d3.interpolateString(a, b);
    
    interpolator(0.5); // "400 24px Comic-Sans"
    
  • d3.interpolateDate(a, b) 创建一个日期范围是 [a, b] 的插值器
    注意

    出于性能的考虑,该插值器返回的过渡值采用非防御性拷贝 no defensive copy,即返回的过渡值其实都指向同一个日期对象,所以该日期对象在过渡过程中一直变换,如果希望基于该日期对象进行额外的操作,请先对该对象进行拷贝。

  • d3.interpolateArray(a, b) 对数组进行插值,实际是对两个数组的配对元素(基于索引)分别创建插值器,以目标数组 b 为模板,得出「中间状态」的数组。
    js
    const a=[0, 1];
    const b=[1, 10, 100];
    const interpolator = d3.interpolateArray(a, b);
    
    interpolator(0.5); // [0.5, 5.5, 100]
    
    注意

    出于性能的考虑,该插值器返回的过渡值也是采用非防御性拷贝 no defensive copy

  • d3.interpolateNumberArray(a, b) 对数组进行插值,数组的元素的数据类型都是数值
  • d3.interpolateObject(a, b) 为对象的进行插值,实际上是对两个对象的配对属性(基于属性名)分别创建插值器,以目标对象 b 为模板。
    js
    const a={x: 0, y: 1};
    const b={x: 1, y: 10, z: 100};
    const interpolator = d3.interpolateObject(a, b);
    
    interpolator(0.5); // {x: 0.5, y: 5.5, z: 100}
    
    注意

    出于性能的考虑,该插值器返回的过渡值也是采用非防御性拷贝 no defensive copy

  • d3.interpolateTransformCss(a, b) 创建一个 2D CSS 变换属性 transform 插值器,过渡值会被解构为一个标准的变换属性(字符串),根据起始值和目标值,过渡值可能包括平移translate、旋转rotate、沿水平轴的扭曲 x-skew、缩放 scale
    提示

    实际上是基于 transform matrix 进行插值,因此创建插值器时也支持使用 transform matrix 设置插值范围。

    js
    const a = "translateY(12px) scale(2)";
    const b = "translateX(30px) rotate(5deg)";
    const interpolator = d3.interpolateTransformCss(a, b);
    
    interpolator(0.5); // "translate(15px, 6px) rotate(2.4999999999999996deg) scale(1.5,1.5)"
    
    const c = "matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)";
    const d = "translate(3px,90px)";
    const interpolator = d3.interpolateTransformCss(c, d);
    
    interpolator(0.5); // "translate(4px, 48px) rotate(-58.282525588538995deg) skewX(-39.847576765616985deg) scale(-0.6180339887498949,0.9472135954999579)"
    
  • d3.interpolateTransformSvg(a, b) 创建一个 SVG 变换 transform 插值器
  • d3.interpolateZoom(a, b) 创建一个二维平面视图缩放切换插值器。每一个视图都有三个元素构成的数组来定义 [cx, cy, width],其中 (cx, cy) 表示视图的中点 viewport center,width 表示视图的尺寸。
    提示

    该插值器会根据两个视图的切换路径长度,计算得出推荐的过渡时间(单位是毫秒),可以通过属性 interpolateZoom.duration 获取,可以基于该时间(或乘以任何系数)来设置过渡的持续时间

    js
    const start = [30, 30, 40];
    const end = [135, 85, 60];
    const interpolator = d3.interpolateZoom(start, end);
    const duration = interpolator.duration * 1.5 // 33% slower than the recommendation
    
    // set transition duration
    transition()
      .duration(duration)
    
    提示

    可以设置插值器的曲率参数 interpolateZoom.rho(rho) 其中参数 rho 默认值是 sqrt(2)。当入参 rho 越接近 0 时,视图切换轨迹越接近线性。

  • d3.interpolateDiscrete(values) 创建一个离散插值器,其入参 values 是一个数组,它的各个元素会被作为过渡值。当调用插值器时,标准时间会根据数组 values 的长度 n=values.length 进行划分,即当 t[0, 1/n] 范围中时,插值器返回的过渡值是 values[0];当 t[1/n, 2/n] 范围中时,插值器返回的过渡值是 values[1];依此类推。
    提示

    可以将它用作分层比例尺 Quantize Scale,定义域范围是 [0, 1]

颜色插值器

  • d3.interpolateRgb(a, b) 插值器可以配置 gamma 参数 interpolate.gamma(gamma)
  • d3.interpolateRgbBasis(colors)
  • d3.interpolateRgbBasisClosed(colors)
  • d3.interpolateHsl(a, b)
  • d3.interpolateHslLong(a, b)
  • d3.interpolateLab(a, b)
  • d3.interpolateHcl(a, b)
  • d3.interpolateHclLong(a, b)
  • d3.interpolateCubehelix(a, b) 插值器可以配置 gamma 参数 interpolate.gamma(gamma)
  • d3.interpolateCubehelixLong(a, b) 插值器可以配置 gamma 参数 interpolate.gamma(gamma)
  • d3.interpolateHue(a, b)

其他插值器

  • d3.interpolateBasis(values)
  • d3.interpolateBasisClosed(values)
  • d3.piecewise([interpolate, ]values) 创建分段的插值器,它会为数组 values 的每个元素之间创建插值器
    js
    const interpolate = d3.piecewise(d3.interpolateRgb, ["red", "green", "blue"]);
    
    以上示例创建的分段插值器,当标准时间 t[0, 1/(3-1)] 范围中的时候,会使用插值器 d3.interpolateRgb("red", "green");当标准时间 t[1/(3-1), 2/(3-1)] 范围中的时候,会使用插值器 d3.interpolateRgb("green", "blue")

采样

插值器一般用于过渡中,接收标准时间 t 作为入参,计算出相应的中间值,以实现补间动画。

D3 还提供了一种方法 d3.quantize(interpolator, n) 以使用插值器对其插值的范围 [a, b] 进行采用

该方法的第二个参数 n 是正整数且大于 1 表示需要采样的数量,它实际是通过 n 计算出相应的标准时间 t=k/n(其中 k 依次从 1 取到 n),再传递给指定的插值器 interpolator 获取相应的值。

该方法可以从一个连续的范围中通过插值器获取一些离散的值,用于构建分层比例尺 Quantize Scale(作为值域)

注意

对于采用非防御性拷贝 no defensive copy 方式工作的插值器,例如 d3.interpolateArrayd3.interpolateDated3.interpolateObject,该方法无法正常采样,必须先对这些插值器进行二次封装,让它们的返回的是过渡值的拷贝,而不是对象的引用。


Copyright © 2024 Ben

Theme BlogiNote

Icons from Icônes