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,该方法无法正常采样,必须先对这些插值器进行二次封装,让它们的返回的是过渡值的拷贝,而不是对象的引用。