←首页

很早之前就听说过 D3 (Data Driven Documents)大名 ,它是一个数据驱动的可视化前端库,使用 D3 可以方便地实现数据可视化。今天学习了一下,并用 D3 来绘制常用的图表。

数据可视化的意义: 一图胜过千言万语。直接阅读大量文字或者是数据,需要花费大量的时间,而且相关研究也表明,数据映射成图片更容易被人脑记住。绘制逻辑清晰,结构简单的图表比提供一个全是数据的 Excel 更具有说服力。

这篇文章里默认使用的版本是 4.8.0

操控元素

D3 提供了 jQuery Like Api,选中元素,通过 api 更改元素属性,样式等等,D3 同样支持链式操作。

使用 select 选中元素,使用 text 方法更改文本

1
2
3
4
import * as d3 from "d3"
d3.select('p')
.text('Hello D3')

selectAll 选中一类元素,并通过 style 方法更改样式

1
2
3
4
d3.selectAll('.box')
.style('background-color', 'red')
.style('width', '50px')
.style('height', '50px')

使用 append 添加元素,attr 方法修改 attributes。

1
2
3
4
d3.select('body')
.append('a')
.text('w3ctrain')
.attr('href', 'https://www.w3ctrain.com')

有一点可能不太一样的地方是,d3 的 append 方法返回的是新添加的元素,而不是原来的选择器。jQ 返回的是原来的选择器。这时候拆分开来写会直观一些。

1
2
3
4
5
let link = d3.select('body')
.append('a')
link.text('w3ctrain')
.attr('href', 'https://www.w3ctrain.com')

SVG - 绘制图形

绘制图标的时候经常会绘制一些不规则的图形,这时候 Dom 是难以实现的,但是 SVG 就很方便了。使用 D3 可以很方便地操作 SVG 元素,这里使用 D3 来绘制简单图形。

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
36
37
38
39
40
41
42
43
let width = 100
let height = width
let radius = width / 2
let canvas = d3.select('#graph')
let svg = canvas.append('svg')
.attr('width', 500)
.attr('height', 500)
// 绘制 rect
svg.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height)
.attr('fill', '#2ecc71')
// 绘制 circle
svg.append('circle')
.attr('r', radius)
.attr('cx', width + radius)
.attr('cy', height / 2)
.attr('fill', '#3498db')
// 绘制 line
svg.append('line')
.attr('x1', width * 2)
.attr('y1', 0)
.attr('x2', width * 3)
.attr('y2', height)
.attr('stroke-width', 2)
.attr('stroke', '#e74c3c')
// 创建 arc
var arc = d3.arc()
.innerRadius(radius - 20)
.outerRadius(radius)
.startAngle(0)
.endAngle(1.2 * Math.PI)
// 使用 arc 创建 path
svg.append('g')
.attr("transform", `translate(${width * 3 + radius},${radius})`)
.append('path')
.attr("class", "arc")
.attr("d", arc)
.attr('fill', '#ecf0f1')

效果

数据绑定

前面提到了 D3 是数据驱动的,提供了一个 data 方法用于绑定数据。
先绘制一个柱状图再说吧。

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
let dalet dataArray = [100, 20, 40, 100, 80, 50, 70, 85, 120, 98, 12, 11]
let width = 50
let svg = d3.select('#graph')
.append('svg')
.attr('width', 600)
.attr('height', 400)
.style('border', '1px solid black')
let group = svg.append('g')
group.selectAll('rect')
.data(dataArray)
.enter()
.append('rect')
.attr('height', function(d) {
return d
})
.attr('width', width - 1)
.attr('x', function(d, i) {
return i * width
})
.attr('y', 400)
.attr('transform', function (d) {
return `translate(0, ${-d})`
})
.attr('fill', '#2ecc71')

柱状图

d3 大部分设置属性的地方都支持传一个 Function ,Function 的第一个参数是 d3 处理后的单个元素,这里的数据很简单只是一个数字, Function 需要返回一个给前面属性用的数值。
简单几行代码就可以生成一个柱状图~
你可能对 selectAll(‘rect’) 这里感到奇怪,这时候 group 里面并没有 rect,data 方法用于绑定数据,那绑定的数据绑给谁了?enter 方法是干什么用的?为了 selectAll(‘rect’) 下面又要 append(‘rect’) 什么意思?

再看几个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
group.append('rect')
.attr('width', width - 1)
.attr('height', 60)
.attr('x', 0)
.attr('y', 400)
.attr('transform', `translate(0,${-60})`)
.attr('fill', '#e74c3c')
group.selectAll('rect')
.data(dataArray)
.enter()
.append('rect')
.attr('height', function(d) {
return d
})
.attr('width', width - 1)
.attr('x', function(d, i) {
return i * width
})
.attr('y', 400)
.attr('transform', function (d) {
return `translate(0,${-d})`
})
.attr('fill', '#2ecc71')

效果

你会发现原本的第一个元素高度和填充色都变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
group.append('rect')
.attr('width', 60)
.attr('height', height - 1)
.attr('x', 0)
.attr('y', 0)
.attr('fill', '#e74c3c')
group.selectAll('rect')
.data(dataArray)
.attr('fill', '#9b59b6')
.enter()
.append('rect')
.attr('width', function(d) {
return d
})
.attr('height', height - 1)
.attr('x', 0)
.attr('y', function(d, i) {
return i * height
})
.attr('fill', '#2ecc71')

enter 之前再修改填充色,发现还是只有第一个元素变化了。那修改一下数据呢?

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
let dataArray = [100]
// , 20, 40, 100, 80, 50, 70, 85, 120, 98, 12, 11
let width = 50
let svg = d3.select('#graph')
.append('svg')
.attr('width', 600)
.attr('height', 400)
.style('border', '1px solid black')
let group = svg.append('g')
group.append('rect')
.attr('width', width - 1)
.attr('height', 60)
.attr('x', 0)
.attr('y', 400)
.attr('transform', `translate(0,${-60})`)
.attr('fill', '#e74c3c')
group.append('rect')
.attr('width', width - 1)
.attr('height', 20)
.attr('x', width)
.attr('y', 400)
.attr('transform', `translate(0,${-20})`)
.attr('fill', '#e74c3c')
group.append('rect')
.attr('width', width - 1)
.attr('height', 40)
.attr('x', width * 2)
.attr('y', 400)
.attr('transform', `translate(0,${-40})`)
.attr('fill', '#e74c3c')
group.selectAll('rect')
.data(dataArray)
.attr('fill', '#9b59b6')
.enter()
.append('rect')
.attr('height', function(d) {
return d
})
.attr('width', width - 1)
.attr('x', function(d, i) {
return i * width
})
.attr('y', 400)
.attr('transform', function (d) {
return `translate(0,${-d})`
})
.attr('fill', '#2ecc71')

效果

第一个变成紫色了,另外两个还是红色,其他数据消失了,想让另外两个也变成紫色需要用 exit()

1
2
3
4
5
6
7
8
group.selectAll('rect')
.data(dataArray)
// updated here
.attr('fill', '#9b59b6')
// enter or exit
.exit()
.attr('fill', '#9b59b6')
.append('rect')

效果

根据上面的实验,我们可以得到这样的结论,enter 和 exit 在 d3 里面相当于过滤器,过滤的条件跟 selectAll 选中的元素数量以及绑定的 data 长度有关。data 数据是和选中的 d3 元素做绑定,如果已存在元素数量小于 data 长度,d3 会为多出来的 data 生成占位符,通过 enter() 处理之后就可以为这些占位符 append 元素。如果已存在元素数量大于 data 长度,那就通过 exit() 处理之后返回多出来那部分数据的元素选择器(这时候接着执行 append 那就是在 rect 上 append rect 了)。等于那部分呢,可以在 enter () 或者是 exit() 之前修改属性。

条件 过滤
DOM elements length < data elements length enter
DOM elements length > data elements length exit
DOM elements length = data elements length updated

比例尺缩放

你肯定觉得上面的柱状图很大部分都是空的,不够饱满,想让它灵活一些,根据显示刻度灵活变化宽高,而不是定死~ 特别数据差异性很大的时候,我们希望图表显示范围都在画布里面。

比例尺

d3 提供了比例尺缩放(scale)通过调整 scale 来实现需求。
domain 输入,range 输出,scale 的作用就是把输入转成输出。

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
let dataArray = [100, 20, 40, 100, 80, 50, 70, 85, 120, 98, 12, 11]
let canvasWidth = 600
let canvasHeight = 400
let width = 50
let svg = d3.select('#graph')
.append('svg')
.attr('width', canvasWidth)
.attr('height', canvasHeight)
.style('border', '1px solid black')
let yScale = d3.scaleLinear()
.domain([0, d3.max(dataArray, function(d) {
return d
})])
.range([0, canvasHeight])
let group = svg.append('g')
group.selectAll('rect')
.data(dataArray)
.enter()
.append('rect')
.attr('height', function(d) {
return yScale(d)
})
.attr('transform', function(d) {
return `translate(0, ${yScale(-d)})`
})
.attr('width', width - 1)
.attr('y', canvasHeight)
.attr('x', function(d, i) {
return i * width
})
.attr('fill', '#2ecc71')

效果

scaleLinear d3 提供的线性比例尺,其他的还有 scalePow, scaleQuantise, scaleOrdinal, scaleSqrt, scaleLog, scaleSequential 等等。
d3 提供了挺多数据处理的方法,除了 max 还有 min extent sum median mean shuffle 之类的。

坐标轴

有了 图标就该想到坐标轴了,添加坐标轴也很简单。

1
2
3
svg.append("g")
.attr('transform', 'translate(0, ' + (canvasHeight - 20) + ')')
.call(d3.axisBottom(xScale).ticks(20))

axisTop, axisRight, axisBottom, axisLeft 分别创建的是四个朝向的坐标轴,ticks 方法设置坐标轴的刻度数。
通过 call 方法来添加到元素上。

坐标轴

过渡

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
36
let width = 100
let height = width
let radius = width / 2
let canvas = d3.select('#graph')
let svg = canvas.append('svg')
.attr('width', 500)
.attr('height', 500)
var arc = d3.arc()
.innerRadius(radius - 20)
.outerRadius(radius)
.startAngle(0)
.endAngle(1.5 * Math.PI)
// 使用 arc 创建 path
let group = svg.append('g')
.attr('transform', `translate(${radius}, ${radius})`)
let path = group.append('path')
.attr("class", "arc")
.attr("d", arc)
.attr("x", width / 2)
.attr('fill', '#ecf0f1')
function repeat() {
path.transition()
.duration(1000)
.attr("transform", 'rotate(180)')
.transition()
.delay(500)
.duration(1000)
.attr("transform", 'rotate(0)')
.on("end", function() {
console.log('end')
repeat()
})
}
repeat ()

d3 元素上使用 transition 创建一个 过渡效果,duration 设置过渡时间,delay 方法设置延时,on 方法可以监听过渡开始(start)和结束(end)

异步请求

d3 自带可以发起异步请求的 requert 方法,json, xml, csv 等等都可以,具体使用自行查看
d3 request

layout

d3 提供了很多布局,不怎么想了解~ 用到的时候再说吧。再写文章就超长了。

最后再用一个综合点的例子结束这篇文章~

例子

代码

链接

如需转载,请注明出处: http://w3ctrain.com/2017/04/27/d3-tutorial/