梦入琼楼寒有月,行过石树冻无烟

D3 饼布图

饼状图(Pie Chart)通过使用布局可以方便计算出饼图或圈外的开始以及结束的角度,饼布图还可以用于绘制弧形。当开始绘制的时候布局会将数据数组转换成一个对象数据,在这个对象数据中包含了开始角度以及结束角度,之后传递给弧形生成器。

ID DA FA
d3.layout.pie() 创建一个新的饼图
pie(value[,index]) 转换数据,转换后的每个对象都包含有起始角度终止角度
pie.value([accessor]) 设置或获取值访问器,即从接收的数据中提取初始值
pie.sort([comparator]) 设置或获取比较器,主要用于进行排序
pie.startAngle([angle]) 如果指定angle,那么将会来设置饼布图所有起始弧度,默认为0,单位为弧度
pie.endAngle([angle]) 如果设置angle,那么将会设置饼布图的所有终止弧度,默认为,单位为弧度

布局运用


在下述 code 中,主要定义了基本的饼布图数据,之后通过使用d3.layout.pie创建布局,共过使用值访问将传入的数组提取出需要转换的数据方法,因此所有数据都会被调用一次此方法,通过使用值访问器,那么将会分别返回没一项的对应数据,如[2015,1000],那么通过值访问器将会返回1000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var database = [
[2015,1000],
[2016,2000],
[2017,3000],
[2018,4000],
[2019,5000],
[2020,6000],
[2021,7000]
]
var pie = d3.layout.pie()
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)
console.log(pieEcho)

Start and EndAngle


通过使用布局(layout),可以不需要开发者手动进行计算来根据布局得出结果,在下述 code 中主要通过使用 startAngle以及endAngle来分别指定圆弧的开始以及结束半径,整个过程都无需开发者进行手动计算,直接根据布局运算的出结果。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/* 基本 svg 样式 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)

/* 饼布图数据 */
var database = [
[2015,1000],
[2016,2000],
[2017,3000],
[2018,4000],
[2019,5000],
[2020,6000],
[2021,7000]
]

/*
/- 值访问器 function
/——— 输出经过布局计算出的值 pie(databases)
/———— 自定义设置圆弧的开始半径以及结束半径
*/
var pie = d3.layout.pie()
.startAngle(Math.PI * 0.2)
.endAngle(Math.PI * 1.5)
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)

/*
/—— 半径设置 arc
/———— 外半径 outerRadius
/———— 内半径 innerRadius(为区分这里设置200,饼状图应该设置为0)
*/
var outerRadius = padding.width / 3
var innerRadius = 0

var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)

/* 设置颜色 */
var color = d3.scale.category20c()

/* 为对应数据添加弧的基本位置 */
var arcs = svg.selectAll("g")
.data(pieEcho) /* 绑定计算后的数据 */
.enter()
.append("g")
.attr("transform",
"translate(" + (padding.width /2) +"," + (padding.height /2) + ")")

/* 添加路径元素 */
arcs.append("path")
.attr("fill",function(d,i) {
return color(i)
})
.attr("d",function (d) {
return arc(d)
})

/*
/—— 文字
/———— 问别定义文文字的xy坐标
/———— 计算出数据的百分比
*/
arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0]
var y = arc.centroid(d)[1]
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.attr("fill","white")
.text(function (d) {
var percent = Number(d.value)/d3.sum(database, function (d) {
return d[1]
}) * 100
return percent.toFixed(1) + "%"
})

/*
外部文字及线条
*/
arcs.append("line")
.attr("stroke","black")
.attr("x1", function (d) {
return arc.centroid(d)[0] * 2.3
})
.attr("y1", function (d) {
return arc.centroid(d)[1] * 2.3
})
.attr("x2", function (d) {
return arc.centroid(d)[0] * 2.2
})
.attr("y2", function (d) {
return arc.centroid(d)[1] * 2.2
})

arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0] * 2.5
var y = arc.centroid(d)[1] * 2.5
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.text(function (d) {
return d.data[0]
})

绘制弧

通常来讲布局与弧生成器来进行绘制的图被称之为饼布图,与弧生成器来直接绘制饼图类似,因此startAngle以及endAngle都将适用与饼布图。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
/* 基本 svg 样式 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)

/* 饼布图数据 */
var database = [
[2015,1000],
[2016,2000],
[2017,3000],
[2018,4000],
[2019,5000],
[2020,6000],
[2021,7000]
]

/*
/- 值访问器 function
/——— 输出经过布局计算出的值 pie(databases)
*/
var pie = d3.layout.pie()
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)

/*
/—— 半径设置 arc
/———— 外半径 outerRadius
/———— 内半径 innerRadius(为区分这里设置200,饼状图应该设置为0)
*/
var outerRadius = padding.width / 3
var innerRadius = 200

var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)

/* 设置颜色 */
var color = d3.scale.category20c()

/* 为对应数据添加弧的基本位置 */
var arcs = svg.selectAll("g")
.data(pieEcho) /* 绑定计算后的数据 */
.enter()
.append("g")
.attr("transform",
"translate(" + (padding.width /2) +"," + (padding.height /2) + ")")

/* 添加路径元素 */
arcs.append("path")
.attr("fill",function(d,i) {
return color(i)
})
.attr("d",function (d) {
return arc(d)
})

内部文字


在下述的 code 中,我们通过使用 arc.centroid来计算出弧的中心坐标,之后在通过计算数据的百分比最终得出一个完整的饼图内部数据。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* 基本 svg 样式 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)

/* 饼布图数据 */
var database = [
[2015,1000],
[2016,2000],
[2017,3000],
[2018,4000],
[2019,5000],
[2020,6000],
[2021,7000]
]

/*
/- 值访问器 function
/——— 输出经过布局计算出的值 pie(databases)
*/
var pie = d3.layout.pie()
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)

/*
/—— 半径设置 arc
/———— 外半径 outerRadius
/———— 内半径 innerRadius(为区分这里设置200,饼状图应该设置为0)
*/
var outerRadius = padding.width / 3
var innerRadius = 200

var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)

/* 设置颜色 */
var color = d3.scale.category20c()

/* 为对应数据添加弧的基本位置 */
var arcs = svg.selectAll("g")
.data(pieEcho) /* 绑定计算后的数据 */
.enter()
.append("g")
.attr("transform",
"translate(" + (padding.width /2) +"," + (padding.height /2) + ")")

/* 添加路径元素 */
arcs.append("path")
.attr("fill",function(d,i) {
return color(i)
})
.attr("d",function (d) {
return arc(d)
})

/* 内部文字 */
arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0]
var y = arc.centroid(d)[1]
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.attr("fill","white")
.text(function (d) {
var percent = Number(d.value)/d3.sum(database, function (d) {
return d[1]
}) * 100
return percent.toFixed(1) + "%"
})

外部文字


外部文字依然可以通过使用arc.centroid来直接根据圆弧从中心点进行计算,从而分别出的线以及文字的路径和x,y轴的坐标。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* 基本 svg 样式 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)

/* 饼布图数据 */
var database = [
[2015,1000],
[2016,2000],
[2017,3000],
[2018,4000],
[2019,5000],
[2020,6000],
[2021,7000]
]

/*
/- 值访问器 function
/——— 输出经过布局计算出的值 pie(databases)
*/
var pie = d3.layout.pie()
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)

/*
/—— 半径设置 arc
/———— 外半径 outerRadius
/———— 内半径 innerRadius(为区分这里设置200,饼状图应该设置为0)
*/
var outerRadius = padding.width / 3
var innerRadius = 0

var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)

/* 设置颜色 */
var color = d3.scale.category20c()

/* 为对应数据添加弧的基本位置 */
var arcs = svg.selectAll("g")
.data(pieEcho) /* 绑定计算后的数据 */
.enter()
.append("g")
.attr("transform",
"translate(" + (padding.width /2) +"," + (padding.height /2) + ")")

/* 添加路径元素 */
arcs.append("path")
.attr("fill",function(d,i) {
return color(i)
})
.attr("d",function (d) {
return arc(d)
})

/*
/—— 文字
/———— 问别定义文文字的xy坐标
/———— 计算出数据的百分比
*/
arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0]
var y = arc.centroid(d)[1]
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.attr("fill","white")
.text(function (d) {
var percent = Number(d.value)/d3.sum(database, function (d) {
return d[1]
}) * 100
return percent.toFixed(1) + "%"
})

/*
外部文字
*/
arcs.append("line")
.attr("stroke","black")
.attr("x1", function (d) {
return arc.centroid(d)[0] * 2.3
})
.attr("y1", function (d) {
return arc.centroid(d)[1] * 2.3
})
.attr("x2", function (d) {
return arc.centroid(d)[0] * 2.2
})
.attr("y2", function (d) {
return arc.centroid(d)[1] * 2.2
})

arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0] * 2.5
var y = arc.centroid(d)[1] * 2.5
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.text(function (d) {
return d.data[0]
})

增加交互


在下述的 code 中,我们主要通过getPixelLength以及分别定义两个连接弧文字直线,将直线变得斜上一点,之后通过使用d3所提供的behavior增加鼠标的交互。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/* 基本 svg 样式 */
var padding = {
width: 800,
height: 800
}

var svg = d3.select("body")
.append("svg")
.attr("width",padding.width)
.attr("height",padding.height)

/* 饼布图数据 */
var database = [
["2015",1000],
["2016",2000],
["2017",3000],
["2018",4000],
["2019",5000],
["2020",6000],
["2021",7000]
]

/*
/- 值访问器 function
/——— 输出经过布局计算出的值 pie(databases)
*/
var pie = d3.layout.pie()
.value(function(d) {
return d[1]
})

var pieEcho = pie(database)

/*
/—— 半径设置 arc
/———— 外半径 outerRadius
/———— 内半径 innerRadius(为区分这里设置200,饼状图应该设置为0)
*/
var outerRadius = padding.width / 3
var innerRadius = 0

var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)

/* 设置颜色 */
var color = d3.scale.category20c()

/* 为对应数据添加弧的基本位置 */
var arcs = svg.selectAll("g")
.data(pieEcho) /* 绑定计算后的数据 */
.enter()
.append("g")
.attr("transform",
"translate(" + (padding.width /2) +"," + (padding.height /2) + ")")

/* 添加路径元素 */
arcs.append("path")
.attr("fill",function(d,i) {
return color(i)
})
.attr("d",function (d) {
return arc(d)
})

/*
/—— 文字
/———— 问别定义文文字的xy坐标
/———— 计算出数据的百分比
*/
arcs.append("text")
.attr("transform", function (d) {
var x = arc.centroid(d)[0]
var y = arc.centroid(d)[1]
return "translate(" + x + "," + y + ")"
})
.attr("text-anchor","middle")
.attr("fill","white")
.text(function (d) {
var percent = Number(d.value)/d3.sum(database, function (d) {
return d[1]
}) * 100
return percent.toFixed(1) + "%"
})


/*
外部文字
*/
var fontsize = 14

arcs.append("line")
.attr("stroke","black")
.attr("x1", function (d) {
return arc.centroid(d)[0] * 2
})
.attr("y1", function (d) {
return arc.centroid(d)[1] * 2
})
.attr("x2", function (d) {
return arc.centroid(d)[0] * 2.2
})
.attr("y2", function (d) {
return arc.centroid(d)[1] * 2.2
})

/*
分别定义两个连接弧文字直线
*/
arcs.append("line")
.style("stroke","black")
.each(function(d){
d.textLine = {x1:0, y1:0, x2:0, y2:0};
})
.attr("x1",function(d){
d.textLine.x1 = arc.centroid(d)[0] * 2.2;
return d.textLine.x1;
})
.attr("y1",function(d){
d.textLine.y1 = arc.centroid(d)[1] * 2.2;
return d.textLine.y1;
})
.attr("x2",function(d){
var strLen = getPixelLength(d.data[0],fontsize) * 1.5;
var bx = arc.centroid(d)[0] * 2.2;
d.textLine.x2 = bx >= 0 ? bx + strLen : bx - strLen;
return d.textLine.x2;
})
.attr("y2",function(d){
d.textLine.y2 = arc.centroid(d)[1] * 2.2;
return d.textLine.y2;
});

arcs.append("text")
.attr("transform", function (d) {
var x = 0
var y = 0
x = (d.textLine.x1 + d.textLine.x2) /2
y = d.textLine.y1
y = y > 0 ? y + fontsize * 1.1 : y - fontsize * 0.4
return "translate(" + x + "," + y + ")"
})
.style("text-anchor","middle")
.style("font-size",fontsize)
.text(function (d) {
return d.data[0]
})

/*
添加提示
默认的情况下透明度为 0,
当鼠标经过的时候将透明度设置为 0.9
移出时设在将透明度设置为0
当鼠标移动的时候,计算出当前所在位置
*/
var givecuo = d3.select("body")
.append("div")
.attr("class","style")
.style("opacity",0.0)

arcs.on("mouseover", function (d) {
givecuo.html(d.data[0] + "当前数据为" + "<br/>" + d.data[1] + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY + 20) + "px")
.style("opacity",0.9)
})
.on("mousemove", function (d) {
givecuo.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY + 20) + "px")
})
.on("mouseout", function (d) {
givecuo.style("opacity",0.0)
})
/*
将直线稍微斜一点
*/
function getPixelLength(str,fontsize) {
var curLen = 0;
for (var i = 0;i<str.length;i++) {
var code = str.charCodeAt(i)
var pixelLen = code > 255 ? fontsize : fontsize / 2
curLen += pixelLen
}
return curLen
}

css

1
2
3
4
5
6
7
8
9
.style {
font-family: simsun;
font-size: 14px;
width: 120px;
height: auto;
position: absolute;
text-align: center;
background-color: white;
}
⬅️ Go back