比例尺是比较重要的工具,可以实现抽象数据的一个维度到一个可视化表示的映射(mapping)。
可以把比例尺想像成一个函数,它能将“一个区间”的数据映射到“另一个区间”。
要理解比例尺,就先需要理解定义域(domain)、值域(range)和对应法则这三个概念。 例如[20, 80]对应到[0, 120],当输入50时,输出60。上述示例中的[20, 80]称为定义域,[0, 120]称为值域,定义域和值域之间的映射方法称为对应法则。
比例尺的分类
比例尺有连续比例尺、序列比例尺、发散比例尺、量化比例尺、分位数比例尺、阈值比例尺和序数比例尺、分段比例尺这几种。
比例尺分类 | 子类 | 简介 | D3函数 |
---|---|---|---|
连续比例尺 | 线性比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleLinear() |
指数比例尺 | 是将连续的定义域映射到连续的值域 | d3.scalePow() | |
对数比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleLog() | |
定量恒等比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleIdentity() | |
线性时间比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleTime() | |
线性颜色比例尺 | 是将连续的定义域映射到连续的值域 | ||
序列比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleSequential() | |
发散比例尺 | 是将连续的定义域映射到连续的值域 | d3.scaleDiverging() | |
量化比例尺 | 是将连续的定义域映射到离散的值域 | d3.scaleQuantize() | |
分位数比例尺 | 是将离散的定义域映射到离散的值域 | d3.scaleQuantile() | |
阈值比例尺 | 是将连续的定义域映射到离散的值域 | d3.scaleThreshold() | |
序数比例尺 | 是将离散的定义域映射到离散的值域 | d3.scaleOrdinal() | |
分段比例尺 | 是将离散的定义域映射到离散的值域 | d3.scaleBand() |
以下为连续比例尺 Continuous Scales的通用方法示例,以线性比例尺为方法载体进行测试:
<div id="view010901"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
// 线性比例尺
xScale = d3.scaleLinear()
.domain( [1, 5] ) // 通常连续比例尺中的domain只包含两个值,但如果指定多个值时就会生成一个分位数的比例尺,例如创建一个分位数的颜色比例尺
.range( [0, 300] )
//.clamp( true );
d3.select("#view010901")
.append("p")
.text(xScale(3)) // 150
.append("p")
.text(xScale.invert(100)) // 2.333333333333333
.append("p")
.text(xScale(-10)) // -825,如果设定clamp( true ),则此时返回值为0
.append("p")
.text(xScale(10)) // 675,如果设定clamp( true ),则此时返回值为300
</script>
结果输出如下:
150
2.333333333333333
-825
675
创建一个线性分位数颜色比例尺,传入比例尺函数的值为0.5时,返回的值是在白色和绿色之间的插值
<div id="view010902"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
xScale = d3.scaleLinear()
.domain( [-1, 0, 1] )
.range( ["red", "white", "green"] )
d3.select("#view010902")
.append("p")
.text(xScale(0.5)) // rgb(128, 192, 128)
.style("color", xScale(0.5))
</script>
结果输出如下:
rgb(128, 192, 128)
定义指数比例尺,当没有定义指定exponent时,默认指数为1,此时功效与线性比例尺一样
<div id="view010903"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
xScale = d3.scalePow() // 默认定义域domain为[0, 1],值域range为[0, 1]
d3.select("#view010903")
.append("p")
.text(xScale(2)) // 2
xScale.exponent(0.5); // 指数为0.5,其实就是求平方根
d3.select("#view42")
.append("p")
.text(xScale(2)) // 1.4142135623730951
</script>
结果输出如下:
2
1.4142135623730951
时间比例尺是线性比例尺的一种变体。它的输入被强制转为日期类型而不是数值类型,并且invert返回的也是date类型。
<div id="view010904"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
xScale = d3.scaleTime()
.domain( [new Date(2000, 0, 1), new Date(2000, 0, 2)] )
.range( [0, 960] )
d3.select("#view010904")
.append("p")
.text(xScale( new Date(2000, 0, 1, 5))) // 200
.append("p")
.text(xScale.invert( 200 )) // Sat Jan 01 2000 05:00:00 GMT+0800 (中国标准时间)
</script>
结果输出如下:
200
Sat Jan 01 2000 05:00:00 GMT+0800 (中国标准时间)
序列比例尺类似于连续比例尺,也是将连续的定义域domain映射到连续的值域range。但与连续比例尺不同的是,序列比例尺的值域是根据指定的插值器内置且不可配置,并且它的插值方式也不可配置。
<div id="view010905"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
// 实现一个 HSL 具有周期性的颜色插值器
xScale = d3.scaleSequential( function( t ){
return d3.hsl( t*360, 1, 0.5 ) + "";
} )
d3.select("#view010905")
.append("p")
.text(xScale(0.8)) // rgb(204, 0, 255)
.style("color", xScale(0.8))
</script>
结果输出如下:
rgb(204, 0, 255)
使用 d3.interpolateRainbow 实现一种更优雅并且更高效的周期性颜色插值器
<div id="view010906"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
// 实现一个 HSL 具有周期性的颜色插值器
xScale = d3.scaleSequential( d3.interpolateRainbow )
d3.select("#view010906")
.append("p")
.text(xScale(0.8)) // rgb(35, 171, 216)
.style("color", xScale(0.8))
</script>
结果输出如下:
rgb(35, 171, 216)
量化比例尺类似于线性比例尺,其定义域也是连续的,但值域是离散的,连续的定义域值会被分割成均匀的片段。下面给个量化比例尺的坐标轴实例,有几个圆,圆的半径越小,颜色越深:
<div id="view010907"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
// 定义量化比例尺
quantizeScale = d3.scaleQuantize()
.domain( [ 0, 50 ] )
.range( ["#888", "#666", "#444", "#222", "#000"] );
// 定义圆的半径
r = [ 45, 35, 25, 15, 5 ];
var svg = d3.select("#view010907")
.append("svg")
.attr("width", 550)
.attr("height", 100)
svg.selectAll( "circle" )
.data( r )
.enter()
.append( "circle" )
.attr( "cx", function( d, i ){ return 50 + i * 100 } )
.attr( "cy", 50 )
.attr( "r", function( d ){ return d } )
.attr( "fill", function(d){ return quantizeScale(d) } )
</script>
关于代码运行效果
由于在简书上不能展示代码的运行结果,我在网上搭建了一个 jupyter notebook http://jupyter.cyber-life.cn/lab,我将本套教程的笔记和源代码写在了上面,可以在线查看代码运行效果,还可以调试代码,有需要的同学私信我。