D3库实践笔记之比例尺与坐标轴 |可视化系列35

对数据进行可视化时,我们可以直接把数据值映射为像素值,但是如果数值过小或过大直接用像素得到的图形就很难看甚至无法辨别,例如不能值是10000就绘制1万像素长的矩形,数值全是[0,1]的值不能全部绘制小于1个像素的图,d3提供了比例尺(scale)来解决这个问题。从数据到屏幕图形的像素有一个数据变换的过程,在输入值范围(值域)不确定的情况,我们限定输出的范围,这就是比例尺的作用。

比例尺生成器

D3 提供了比例尺函数生成器。例如var scale = d3.scaleLinear().domain([100, 500]).range([0, 100]);比例尺scale将输入数据从[100,500]转换在[0,100]之间,并且保持数据的大小关系。

比例尺将“一个区间”的数据映射到“另一个区间”。线性比例尺可以理解为我们熟悉的一次函数f(x)=ax+b。通过d3.scaleLinear()可以初始化一个线性比例尺,在d3的3.x版本及之前版本中使用的是d3.scale.linear(),在v5.x及之后的v6版本中,都用的d3.scaleLinear(),原先的d3.scale已经undefined未定义了。

定义一个比例尺后可以传入数值,会输出转换后的值,就类似于一个映射函数y=f(x)的使用。

1
2
3
4
5
6
var scale = d3.scaleLinear()
.domain([30, 80]) //设置定义域
.range([0,1000]); //值域

scale(76); //输出 920
scale(80); //结果是1000

比例尺经常需要和坐标轴结合使用。

坐标轴

通过append()、attr()、style()等接口只是将数据映射为图形,离统计图还有些差距。比如我们需要有标识数据大小的数轴、标题、坐标轴标签等。其中标题通过text来绘制,图形颜色等通过style设置,数轴(坐标轴)可以拆解为线段+文本的组合,可以通过svg的line和text来画,需要注意的是坐标原点的位置以及y轴方向的问题。实际上d3提供了绘制坐标轴的接口,省去了很多工作量。在D3的v5.x及之后的版本中,通过d3.axisBottom(scale)绘制x轴(水平方向)、d3.axisLeft(scale)绘制y坐标轴,其中调用了我们定义的比例尺scale。在v3.x版本使用的是 xAxis = d3.svg.axis().scale(xScale).orient("bottom");

基于比例尺和坐标轴绘制一个柱状图如下:

d3绘制柱状图

将坐标轴的绘制封装为renderXAxis()renderYAxis()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function renderXAxis(){
var axis_len=w-2*m;
var scale=d3.scaleLinear().domain([0,60]).range([0,axis_len]);
var xaxis=d3.axisBottom(scale); //.tickFormat(function(d){ return d.x;});
svg.append("g").attr("transform",function(){return "translate("+m+","+(h-m)+")"})
.attr("class",'x-axis').call(xaxis);
d3.selectAll("g.x-axis g.tick").append("line")
.classed("grid-line",true).attr('x1',0).attr('y1',0)
.attr('x2',0).attr('y2',-(h-2*m));
}
function renderYAxis(){
var axis_len=h-2*m;
var scale=d3.scaleLinear().domain([100,0]).range([0,axis_len]);
var yaxis=d3.axisLeft(scale);
svg.append("g").attr("transform",function(){return "translate("+m+","+m+")"})
.attr("class",'y-axis').call(yaxis);
d3.selectAll("g.y-axis g.tick").append("line").classed("grid-line",true).attr('x1',0)
.attr('y1',0).attr('x2',axis_len).attr('y2',0);
}

具体来看坐标轴的绘制,首先根据数据集的范围(包括最大最小值和数据行数)确定定义域,根据建立的svg长宽确定值域,然后初始化一个线性比例尺,scale=d3.scaleLinear().domain([100,0]).range([0,460]),因为SVG的坐标原点在左上角(具体描述可以参考SVG篇),,在建立y轴时生成的比例尺是将[n,0]映射到[0,460],而x轴用的比例尺是从[0,m]映射到[0,460]。生成比例尺后使用d3.axisBottom(scale)生成x轴对象,再通过d3.selectAll().append("line")绘制x坐标轴的具体线段,用d3.axisLeft(scale)生成y轴再绘制y轴的具体线段。

d3-renderXAxis

坐标轴在后续的绘图中还会经常用到,写法都是这一套路。

D3不仅提供了线性比例尺可用,还有序数比例尺(实现{1:’r’,2:’b’,3:’g’})、对数比例尺、平方根比例尺等。对于连续值,可以使用的比例尺有线性比例尺、对数(.scaleLog)、指数(.scalePow)、时间序列(.scaleTime)以及弧度(scaleRadial)。

比例尺初始化除了写scale=d3.scaleLinear().domain([100,0]).range([0,460]),还可以简写为d3.scaleLinear([10, 130], [0, 960]),两者效果一致,颜色值也可以和数值建立映射,在填充颜色中经常需要用到,比例尺的一些示例可以看以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var color = d3.scaleLinear()
.domain([10, 100])
.range(["brown", "steelblue"]);
color(20); //输出是 "#9a3439"
var ssqrt=d3.scaleSqrt() //对数比例尺的具体形式,平方根
.domain([0,800000]).range([0,30]);
ssqrt(100020) //输出 10.60766
//平方根比例尺在把数据映射为散点图中经常会用到,数据变成半径前经过平方根运算;
var tscale = d3.scaleTime() //时间序列和像素值的对应
.domain([new Date(2010,0,1), new Date(2020,10,4)])
.range([0,480]);
tscale(new Date(2019,5,18)) //得到 418.788
var color = d3.scaleQuantize() //分段比例尺
.domain([0, 1])
.range(["brown", "steelblue"]);

color(0.49); // "brown"
color(0.51); // "steelblue"

var width = d3.scaleQuantize()
.domain([10, 100])
.range([1, 2, 4]);
width(50); // 2

var color = d3.scaleThreshold() //阈值比例尺
.domain([0, 1])
.range(["red", "white", "green"]);

color(-1); // 区间外的值映射到边界"red"
color(0.5); // "white"
color(1000); // "green"

d3.schemeCategory10
//输出10个预设颜色的数组 在v3.x版本是写 d3.scale.category10()
#["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728",…]

总结

比例尺解决的是输入数据过大或过小无法直接对应到像素值的问题,比例尺可以将数据从一个区间(定义域)映射到另一个区间(值域),在实际的可视化中线性比例尺和平方根比例尺使用最广泛,通过scale = d3.scaleLinear().domain([1,2]).range([0,10])可以初始化一个线性比例尺。坐标轴的绘制需要依赖比例尺的建立,再通过d3.axisBottom(scale)生成x轴对象,通过d3.axisLeft(scale)绘制y坐标轴。

有了比例尺和svg的各种点线面形状,我们已经可以绘制各种统计图表了,但现在绘制的图表都是静态的,作为一个优秀的前端可视化库,d3对动态交互的支持也非常厉害。下一篇我们实践d3绘制交互可视化图表。