原文(持续更新):datavis-note.benbinbin.com/article/d3/…
系列文章可以查看《数据可视化》专栏
参考:
- Learn D3: Scales
- Scales (d3-scale)
- d3-scale(非官方中译版)
- Axes (d3-axis)
- d3-axis(非官方中译版)
- Interpolators (d3-interpolate)
- d3-interpolate(非官方中译版)
- Color Schemes (d3-scale-chromatic)
- d3-scale-chromatic(非官方中译版)
- Introducing d3-scale
- Scales(中译版)⚠️ 文章基于 D3.js v3.x 版本
- Introducing d3-scale
- d3: scales, and color.
本文主要介绍 Scales 模块。
Jacques Bertin 在《Semiology of Graphics》中描述了图形符号的不同属性(例如位置、尺寸、颜色等)在展示数据时的适用性。而将两者联系起来的关键是比例尺,它将数据(某个维度)按照一定的规则映射到图形符号的(某个特定)属性上,这样就可以使用具体可视的图形符号表示抽象的数据了。
比例尺实际是将一个类型的数据转换为另一个数据的规则方法,例如通过线性比例尺,将将一年的每月的收益转换为条形图在 Y 坐标轴的高度值。D3 在模块 d3-scale 中提供了多种类型的比例尺,适用于将不同类型的抽象数据转换为图形符号的不同属性:
💡 在 D3 说明文档中,将输入数据的范围称为定义域 domain
,转换后输出数据的范围称为值域 range
以下比例尺适用于定义域为连续型
连续型比例尺 Continuous Scales
连续型比例尺 Continuous Scales 用于将连续型的定义域映射到连续型的值域
该类型的比例尺(以下的 continuous
)的常用方法:
-
continuous.domain(domainArr)
设置或返回定义域。如果没有传递参数,则返回当前比例尺的定义域;如果传递数组,则将定义域范围设置为该数组。💡 一般传递的数组必须包含两个元素,而且元素的类型是数值。如果数组的元素有多个(元素依此是递增或递减的),则可以创建映射关系更复杂的比例尺。⚠️ 如果定义域 domain 数组长度
M
(元素个数)和值域 range 数组长度N
(元素个数) 不同,则映射的关系按照min(N, M)
构建,超出范围的无法进行转换// 创建一个线性比例尺 const color = d3.scaleLinear() .domain([-1, 0, 1]) // 定义域是 -1 到 1,其中 -1 到 0 是一部分(负值),0 到 1 是另一部分(正值) .range(["red", "white", "green"]); // 负值映射的值域是从红色渐变到白色的 rgb 值;正值映射的值域是从白色渐变到绿色的 rgb 值 color(-0.5); // "rgb(255, 128, 128)" color(+0.5); // "rgb(128, 192, 128)"
-
continuous.range(rangeArr)
设置或返回值域。在传递数组设置值域时,数组的元素不必是数值,只要是插值器支持的类型就可以,例如颜色值。⚠️ 如果希望比例尺支持continuous.invert(value)
则值域的类型需要是数值。💡 如果希望同时设置值域和插值方法为
d3.interpolateRound
(通过四舍五入插入整数),可以使用continuous.rangeRound(rangeArr)
,那么此时传递的数组的元素类型只能是数值 -
continuous(value)
向比例尺传递一个定义域 domain 的值,返回相应的值域 range 的值 -
continuous.invert(value)
向比例尺传递一个值域的 range 的值,反过来得到定义域 domain 的值。这对于交互很有用,例如根据鼠标在图表的位置,反向求出并显式对应的数据。⚠️ 该方法只支持值域为数值类型的比例尺,否则返回NaN
-
continuous.clamp(clampState)
当continuous(value)
或continuous.invert(value)
入参值超出定义域(或值域)的范围时,比例尺的行为会根据clamp
而定。- 如果开启 clamping 夹具功能
clampState=true
,比例尺返回的值会限制在相应范围内 - 如果不开启 clamping 夹具功能
clampState=false
,比例尺会进行推断得出超出范围的值
const x = d3.scaleLinear() .domain([10, 130]) .range([0, 960]); // 默认不开启 clamping 夹具功能 x(-10); // -160 超出值域范围 x.invert(-160); // -10 超出定义域范围 // 开启 clamping 夹具功能 x.clamp(true); x(-10); // 0 结果被限制在值域中 x.invert(-160); // 10 结果被限制在定义域中
- 如果开启 clamping 夹具功能
-
continuous.unknown(value)
设置当比例尺接受的入参为undefined
或NaN
时,应该返回的值。这对于数据集中存在部分缺失时很有用(当然最好是在数据清洗中进行处理),可以将数据项映射到一个特定的可视化属性值 -
continuous.nice()
编辑定义域的范围,通过四舍五入使其两端的值更「整齐」nice,例如对于定义域的原本范围是[0.201479…, 0.996679…]
可以扩展为[0.2, 1.0]
-
continuous.interpolate(interpolate)
自定义值域的插值函数
线性比例尺 Linear Scales
线性比例尺 Linear Scales 值域中的值 与定义域中的值 通过表达式 联系起来,这种映射方式可以在视觉元素的变量中保留数据的原始差异比例
使用方法 d3.scaleLinear(domain, range)
构建一个线性比例尺,入参是可选的,如果忽略则定义域和值域范围默认是 [0, 1]
,也可以在之后通过 continuous.domain(value)
和 continuous.range(value)
设置定义域和值域。
const x = d3.scaleLinear([10, 130], [0, 960]);
const color = d3.scaleLinear([10, 100], ["brown", "steelblue"]);
<!-- 等价的方法 -->
const x = d3.scaleLinear()
.domain([10, 130])
.range([0, 960]);
x(20); // 80
x(50); // 320
const color = d3.scaleLinear()
.domain([10, 100])
.range(["brown", "steelblue"]);
color(20); // "#9a3439"
color(50); // "#7b5167"
💡 恒等比例尺 Identity Scales 是线性比例尺的一种特例,其定义域和值域相同,使用方法 d3.scaleIdentity(range)
构建
幂比例尺 Power Scales
幂比例尺 Power Scales 会对定义域的值 进行幂运算,再与值域中的值 联系起来 ,其中 是幂
使用方法 const pow = d3.scalePow(domain, range)
构建一个幂比例尺,默认的幂为 (此时是线性比例尺)
使用比例尺方法 pow.exponent(k)
用来设置幂
💡 幂比例尺 pow(value)
可以接受负值的输入,此时入参的值和转换后得到的值都会乘以
💡 D3 还提供了一个方法 d3.scaleSqrt(domain, range)
方便地生成幂为 的幂比例尺,等价于以下生成的幂比例尺
d3.scalePow()
.exponent(0.5)
对数比例尺 Log Scales
对数比例尺 Log Scales 会对定义域的值 进行对数运算,再与值域中的值 联系起来
⚠️ 由于对数的限制 ,对数比例尺的定义限范围是正值。如果需要支持负值,则需要对该比例尺进行封装,显式地对入参值和输出值都乘以 进行预转换
使用方法 const log = d3.scaleLog(domain, range)
构建一个对数比例尺,默认以 作为底数
使用比例尺方法 log.base(N)
用来设置底数
💡 类似地,D3 还提供双对称的对数比例尺 Symlog Scales d3.scaleSymlog(domain, range)
径向比例尺 Radial Scales
径向比例尺 Radial Scales 是线性比例尺的一种变体,它将定义域的值与值域的值的平方构成线性关系,例如在径向条形图将数据映射为半径,而页面展示的图形元素则是面积
使用方法 d3.scaleRadial(domain, range)
构建一个径向比例尺
时间比例尺 Time Scales
时间比例尺 Time Scales 是线性比例尺的一种变体,它以时间对象 Date 作为定义域
使用方法 d3.scaleTime(domain, range)
构建一个时间比例尺,如果省略 domain
则默认定义域为 [2000-01-01, 2000-01-02]
const x = d3.scaleTime()
.domain([new Date(2000, 0, 1), new Date(2000, 0, 2)])
.range([0, 960]);
x(new Date(2000, 0, 1, 5)); // 200
x(new Date(2000, 0, 1, 16)); // 640
x.invert(200); // Sat Jan 01 2000 05:00:00 GMT-0800 (PST)
x.invert(640); // Sat Jan 01 2000 16:00:00 GMT-0800 (PST)
顺序比例尺 Sequential Scales
它和连续型比例尺类似,也是将连续型的定义域映射到连续型的值域,但该比例尺的值域一般是指定一个插值器
使用方法 const sequential = d3.scaleSequential(domain, interpolator)
构建一个顺序比例尺,如果值域的插值器省略,则默认使用恒等函数 identity function
const rainbow = d3.scaleSequential(d3.interpolateRainbow);
💡 如果值域/插值器是两个元素构成的数组,表示插值的范围,D3 会调用方法 d3.interpolate()
将它转换为一个插值器
该比例尺(以下的 sequential
)除了有连续型比例尺的常用方法以外,还有一些不同的方法:
sequential.interpolator(interpolator)
设置比例尺的插值器sequential.range(rangeArr)
设置插值的范围,D3 会将它转换为一个插值器
💡 和连续型比例尺类似,顺序比例尺有一些衍生的比例尺,可以先对定义域的值进行幂、对数等运算,进行转换后再传递给插值器处理
d3.scaleSequentialLog(domain, interpolator)
和d3.scaleSequentialSqrt(domain, interpolator)
d3.scaleSequentialPow(domain, interpolator)
和d3.scaleSequentialSymlog(domain, interpolator)
d3.scaleSequentialQuantile(domain, interpolator)
和分位数型比例尺类似
发散比例尺 Diverging Scales
它和连续型比例尺类似,也是将连续型的定义域映射到连续型的值域,但该比例尺的定义域一般是由三个元素组成的数组,值域一般是指定一个插值器
使用方法 const diverging= d3.scaleDiverging(domain, interpolator)
构建一个发散比例尺,如果没有设置定义域,默认值为 [0, 0.5, 1]
;如果没有设定插值器,则默认使用恒等函数 identity function
const spectral = d3.scaleDiverging(d3.interpolateSpectral);
分层比例尺 Quantize Scale
分层比例尺 Quantize Scale 用于将连续型的定义域映射到离散型值域,一般通过四舍五入等修约的方法将数据进行分层转换映射,以便将数据进行归类区分。
定义域的范围会根据离散型值域中可取值的数量划分为等距的片段,即每一个值域中离散值 ,都可以代表一段定义域范围 ,例如等值域图/分级统计地图
使用方法 d3.scaleQuantize(domain, range)
构建一个分层比例尺,如果省略了定义域或值域,则它默认范围是 [1, 0]
,其作用就等价于 Math.round
const color = d3.scaleQuantize()
.domain([0, 1])
.range(["brown", "steelblue"]);
color(0.49); // "brown"
color(0.51); // "steelblue"
该类型的比例尺(以下的 quantize
)的常用方法:
-
quantize.domain(domainArr)
设置定义域,数组由两个元素组成一个范围,而且元素的类型是数值,而且按升序排列 -
quantize.range(rangeArr)
设置值域,数组包含一些列离散的值 -
quantize(value)
向比例尺传递一个定义域 domain 的值,返回相应的值域 range 的值 -
quantize.invertExtent(value)
向比例尺传递一个值域的 range 的值,反过来得到定义域 domain 对应的片段const width = d3.scaleQuantize() .domain([10, 100]) .range([1, 2, 4]); width.invertExtent(2); // [40, 70]
-
quantize.nice()
分位数比例尺 Quantile Scales
分位数型比例尺 Quantile Scales 将输入的数据作为总体(一堆通过采样获取的离散值),这样就可以接受在该总体范围中的任意值输入(即定义域是连续型),可以计算出它在总体中的分位数,然后基于分位数再找出在值域中一些列离散值中的那个对应值。
使用方法 d3.scaleQuantile(domain, range)
构建一个分位数比例尺
该类型的比例尺(以下的 quantile
)的常用方法:
-
quantile.domain(domainArr)
设置定义域,数组由一堆通过采样获取的离散值,而且元素的类型是数值,然后 D3 会对数组进行拷贝并对元素进行排序,作为总体用于计算分位数 -
quantile.range(rangeArr)
设置值域,数组包含一些列离散的值 -
quantile(value)
向比例尺传递一个定义域 domain 的值,返回相应的值域 range 的值 -
quantile.invertExtent(value)
向比例尺传递一个值域的 range 的值,反过来得到定义域 domain 对应的片段 -
quantile.quantiles()
根据值域 range 的离散值数量n
,对分位数进行分段,再根据各段的分位数可以计算出对应的定义域的 domain 的值,该方法就是返回n-1
个阈值构成的数组。
阈值比例尺 Threshold Scales
阈值比例尺 Threshold Scales 和分层型比例尺 Quantize Scale 类似,不过映射规则更自由,定义域数组中各元素是阈值,可以更灵活地对定义域进行任意的划分,然后 D3 将各段定义域分别映射到值域各个离散值
使用方法 d3.scaleThreshold(domain, range)
构建一个阈值比例尺
const color = d3.scaleThreshold()
.domain([0, 1])
.range(["red", "white", "green"]);
color(-1); // "red"
color(0); // "white"
color(0.5); // "white"
color(1); // "green"
color(1000); // "green"
该类型的比例尺(以下的 threshold
)的常用方法:
threshold.domain(domainArr)
设置定义域,数组由一系列阈值组成,然后元素按升序排列threshold.range(rangeArr)
设置值域,数组包含一些列离散的值
⚠️ 如果值域离散值数量是 N+1
,则定义域数组中的阈值数量需要是 N
。如果阈值数量少于期望的值,则相应的值域离散值会被忽略;如果阈值的数量多于期望的值,则在调用阈值比例尺时可能返回 undefined
,因为在值域没有相应的离散值与该段定义域相对应。
-
threshold(value)
向比例尺传递一个定义域 domain 的值,返回相应的值域 range 的值 -
threshold.invertExtent(value)
向比例尺传递一个值域的 range 的值,反过来得到定义域 domain 对应的片段const color = d3.scaleThreshold() .domain([0, 1]) .range(["red", "white", "green"]); color.invertExtent("red"); // [undefined, 0] color.invertExtent("white"); // [0, 1] color.invertExtent("green"); // [1, undefined]