d3.js 绘制柱形图

d3.js 绘制力导向图中已经说明了如何绘制力导向图以及数据更新。本次将说明如何绘制带坐标轴的柱形图。

确定初始数据

初始数据如下:

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
var nodes = [{'label': 'Sort', 'title': 'lslamic'}, {
'label': 'GOODS',
'title': 'VINTAGE OLD HAND MADE BEAUTY SILVER BROOCH PIN RED ENAMEL LEAF SIAM STERLING'
}, {'label': 'Website', 'title': 'ebay'}, {'label': 'Sort', 'title': 'Antiques'}, {
'label': 'Sort',
'title': 'Baby'
}, {'label': 'Sort', 'title': 'Books'}, {'label': 'Sort', 'title': 'Business & Industrial'}, {
'label': 'Sort',
'title': 'Cameras & Photo'
}, {'label': 'Sort', 'title': 'Cell Phones & Accessories'}, {
'label': 'Sort',
'title': 'Clothing, Shoes & Accessories'
}, {'label': 'Sort', 'title': 'Coins & Paper Money'}, {'label': 'Sort', 'title': 'Collectibles'}, {
'label': 'Sort',
'title': 'Computers Tablets & Networking'
}, {'label': 'Sort', 'title': 'Consumer Electronics'}, {'label': 'Sort', 'title': 'Crafts'}, {
'label': 'Sort',
'title': 'eBay Motors'
}, {'label': 'Sort', 'title': 'Everything Else'}, {'label': 'Sort', 'title': 'Health & Beauty'}, {
'label': 'Sort',
'title': 'Home & Garden'
}, {'label': 'Sort', 'title': 'Jewelry & Watches'}, {
'label': 'Sort',
'title': 'Musical Instruments & Gear'
}, {'label': 'Sort', 'title': 'Sporting Goods'}, {'label': 'Sort', 'title': 'Antiquities'}, {
'label': 'Sort',
'title': 'Architectural & Garden'
}, {'label': 'Sort', 'title': 'Asian Antiques'}, {'label': 'Sort', 'title': 'Decorative Arts'}, {
'label': 'Sort',
'title': 'Linens & Textiles (Pre-1930)'
}, {'label': 'Sort', 'title': 'Maritime'}, {'label': 'Sort', 'title': 'Periods & Styles'}, {
'label': 'Sort',
'title': 'Primitives'
}, {'label': 'Sort', 'title': 'Science & Medicine (Pre-1930)'}, {'label': 'Sort', 'title': 'Silver'}];

初始数据中每个数据具有不同的类别,本次的目的是统计不同类别的数据出现次数并进行绘制,数据处理代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var dataset = {};
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
dataset[node['label']] = 0;
for (var j = 0; j < nodes.length; j++) {
if (nodes[j]['label'] == node['label'])
dataset[node['label']] += 1;
}
}
var data = [];
var labels = [];
for (var key in dataset) {
data.push(dataset[key]);
labels.push(key);
}
  • dataset 用于将数据进行分类统计。
  • data 保存每个分类出现次数。
  • labels 保存分类名称,作为 x 轴的刻度。

创建 SVG

1
2
3
4
var width = 420, height = 420;
var svg = d3.select("#graph").append("svg")
.attr("width", width)
.attr("height", height);

绘制坐标轴

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
var padding = {top: 20, right: 20, bottom: 20, left: 30};
var xAxisWidth = width - 2 * padding.left;
var yAxiWidth = height - 2 * padding.bottom;
var xAxisScale = d3.scale.ordinal()
.domain(d3.range(labels.length))
.rangeRoundBands([0, xAxisWidth]);

var yAxisScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([yAxiWidth, 0]);

var xAxis = d3.svg.axis()
.scale(xAxisScale)
.orient("bottom")
.tickFormat(function (d) {
return labels[d];
});

var yAxis = d3.svg.axis()
.scale(yAxisScale)
.orient("left");

svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding.left + "," +
(height - padding.bottom) + ")")
.call(xAxis);

svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding.left + "," +
(height - yAxiWidth - padding.bottom) + ")")
.call(yAxis);
  • xAxisWidth,yAxiWidth 代表 x 轴和 y 轴的长度。
  • padding x 轴和 y 轴的外边距。
  • xAxisScale,yAxisScale x 轴和 y 轴的比例尺。
  • ordinal().domain() 设定或获取定义域。
  • ordinal.rangeRoundBands(interval[, padding[, outerPadding]]) 接收一个连续的区间,然后根据定义域中离散值的数量将其分段,分段结果会取整。
  • tickFormat() 用于设定文字格式的刻度。

这样添加之后的坐标轴,样式会比较丑,所以还需要给坐标轴增加如下样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<style>
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}

.axis text {
font-family: sans-serif;
font-size: 11px;
}
</style>

绘制矩形

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
//0.05 用于设置矩形之间的间隔,由于 rangeRoundBands 选取的范围远小于 xAxisWidth,所以矩形之间的间隔远大于 0.05,该参数未起作用。
var xScale = d3.scale.ordinal()
.domain(d3.range(labels.length))
.rangeRoundBands([0, 100], 0.05);

var yScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, yAxiWidth]);

var rect = svg.selectAll("rect")
.data(data);

rect.enter()
.append("rect")
.attr("x", function (d, i) {
x = xAxisWidth / (2 * labels.length) * (2 * i + 1)
- xScale.rangeBand() / 2 + padding.left;
return x;
})
.attr("y", function (d, i) {
return height - padding.bottom - yScale(d);
})
.attr("width", function (d, i) {
return xScale.rangeBand();
})
.attr("height", yScale)
.attr("fill", "steelblue");
  • xScale 用于控制矩形宽度, yScale 用于控制矩形高度。这样做可以使得矩形的宽度和高度自由伸缩。

给矩形增加文字描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var text = svg.selectAll("text")
.data(data);
text.enter().append("text")
.attr("x", function (d, i) {
x = xAxisWidth / (2 * labels.length) * (2 * i + 1)
- xScale.rangeBand() / 2 + padding.left;
return x;
})
.attr("y", function (d, i) {
return height - padding.bottom - yScale(d);
})
.attr("dx", function (d, i) {
return xScale.rangeBand() / 4;
})
.attr("dy", -2)
.attr("text-anchor", "begin")
.attr("font-size", 14)
.attr("fill", "#000000")
.text(function (d, i) {
return d;
});
  • text 元素的坐标是矩形的坐标,dx,dy 设定了方向上的偏移量。所以最终文字位于矩形正上方。

柱形图动画

给柱形图增加动画,需要将代码修改为:

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
var rect = svg.selectAll("rect")
.data(data);
rect.enter()
.append("rect")
.attr("y", function (d, i) {
return height - padding.bottom;
})
.attr("height", 0)
.attr("fill", "red")
.transition()
.duration(3000)
.ease("bounce")
.delay(function (d, i) {
return 200 * i;
})
.attr("x", function (d, i) {
x = xAxisWidth / (2 * labels.length) * (2 * i + 1)
- xScale.rangeBand() / 2 + padding.left;
return x;
})
.attr("y", function (d, i) {
return height - padding.bottom - yScale(d);
})
.attr("width", function (d, i) {
return xScale.rangeBand();
})
.attr("height", yScale)
.attr("fill", "steelblue");
  • 上述代码设置了两个 fill 属性,在添加了 transition() 之后,D3 会自动对颜色之间的值进行线性插值,最终在动画中会看到平滑的颜色变化。
  • duration() 动画持续时间。
  • ease() 指定转变方式,常用的转变方式有:
    • linear 普通的线性变化。
    • circle 慢慢地到达变换的最终状态。
    • elastic 带有弹跳的到达最终状态。
    • bounce 在最终状态处弹跳几次。
  • delay() 指定延时。

最终效果

柱形图效果 坐标轴


参考资料

如何让图表动起来 d3-3.x-api-reference