D3 插值器
插值器是用于计算过渡值的,以便实现补间动画。
在 D3 中创建插值器时需要指定它的起始值和目标值/结束值,即设置插值范围。然后调用插值器函数时,它接受一个标准时间 t
作为入参,然后结合起始值和结束值,返回该时间点的过渡值,例如 D3 内置插值器 d3.interpolateNumber(a, b)
其源码如下
export default function(a, b) {
return a = +a, b = +b, function(t) {
return a * (1 - t) + b * t;
};
}
// 创建一个插值器,配置插值范围
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
的值类型自动调用相应的数据类型插值器,选择算法如下:
- 当
b
数据类型是null
、undefined
或布尔值,始终使用常量b
作为过渡值 - 当
b
数据类型是数值, 使用d3.interpolateNumber
插值器 - 当
b
数据类型是颜色(或可以转换为颜色的字符串),使用d3.interpolateRgb
插值器 - 当
b
数据类型是日期,使用d3.interpolateDate
插值器 - 当
b
数据类型是字符串,使用d3.interpolateString
插值器 - 当
b
数据类型是声明类型的数组,使用d3.interpolateNumberArray
插值器 - 当
b
数据类型是数组,使用d3.interpolateArray
插值器 - 当
b
数据类型是可以转换为数值的类型,使用d3.interpolateNumber
插值器 - 当
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
的文本部分(保留不变)作为模板,再计算出数值部分的过渡值,构成完整的字符串。jsconst 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
为模板,得出「中间状态」的数组。jsconst 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
为模板。jsconst 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 设置插值范围。
jsconst 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
获取,可以基于该时间(或乘以任何系数)来设置过渡的持续时间jsconst 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
的每个元素之间创建插值器以上示例创建的分段插值器,当标准时间jsconst 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.interpolateArray
、d3.interpolateDate
和 d3.interpolateObject
,该方法无法正常采样,必须先对这些插值器进行二次封装,让它们的返回的是过渡值的拷贝,而不是对象的引用。