一、D3的基本使用
柱形图是一种最简单的可视化图标,主要有矩形、文字标签、坐标轴组成。那么如何使用 D3 在 SVG 画布中绘图呢?
画布是什么?
之前处理对象都是 HTML 的文字,没有涉及图形的制作。要绘图,首要需要的是一块绘图的“画布”。HTML 5 提供两种强有力的“画布”:SVG 和 Canvas。
- SVG 绘制的是矢量图,因此对图像进行放大不会失真,可以为每个元素添加 JavaScript 事件处理器。每个图形均视为对象,更改对象的属性,图形也会改变。要注意的是,在 SVG 中的坐标轴,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的。
- 在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。
具体使用
添加画布
D3 虽然没有明文规定一定要在 SVG 中绘图,但是 D3 提供了众多的 SVG 图形的生成器,它们都是只支持 SVG 的。因此,建议使用 SVG 画布。
- 使用 D3 在 body 元素中添加 svg 的代码如下:
var width = 300; //画布的宽度 var height = 300; //画布的高度 var svg = d3.select("body") //选择文档中的body元素 .append("svg") //添加一个svg元素 .attr("width", width) //设定宽度 .attr("height", height); //设定高度
- 先在html中设置好svg的宽度和高度之后,在使用选择器将画布选中。
<svg id="chart" class="svgs" width="1000" height="900"></svg>
// 获得画布 const svg = d3.select('#mainsvg');
有了画布,接下来就可以在画布上作图了。
1、柱状图
制作步骤:
- 使用html创建一个容器用来存放图表 :
<div id="chart"></div>
- 使用css的样式调整最后生成的柱形图的样式
#chart div {
display: inline-block;
background: #4285F4;
width: 30px;
margin-right: 20px;
}
- 使用js生成每个柱形图
d3.select('#chart')
.selectAll("div")
.data([100, 80, 150, 160, 230, 120])
.enter()
.append("div")
.style("height", (d) => d + "px")
上述代码中的输入数据是一组数组:[10, 8, 15, 16, 23, 42],对应的输出 HTML 结构是:
所以不需要通过 JS 控制的视觉层内容都写到 CSS 中
2、GitHub 贡献表
我们只需要更改柱状图代码中的几行,我们就能得到一个 GitHub 贡献表。它和柱状图不同地方在于,图表中根据数据变化的不再是元素的高度,而是元素的背景色。
var colorMap = d3.interpolateRgb(
d3.rgb('#d6e685'),
d3.rgb('#1e6823')
);
d3.select('#chart')
.selectAll("div")
.data([.2, .4, 0, 0, .13, .92])
.enter()
.append("div")
.style("background-color", (d) => {
return d == 0 ? '#eee' : colorMap(d);
})
colorMap 函数接收的输入值要在0到1之间,返回的是一个颜色值,这个值是在以输入值中两两数据为颜色值之间的渐变色值,插值法是图形编程和动画的关键点。
3、圆形
D3 还可以操作更复杂的数据类型。
var data = [{
label: "one",
sales: 20
}, {
label: "two",
sales: 12
}, {
label: "three",
sales: 8
}, {
label: "four",
sales: 27
}]
var g = d3.select('#chart')
.selectAll("g")
.data(data)
.enter()
.append('g')
g.append("circle")
.attr('cy', 40)
.attr('cx', (d, i) => (i + 1) * 50)
.attr('r', (d) => d.sales)
g.append("text")
.attr('y', 90)
.attr('x', (d, i) => (i + 1) * 50)
.text((d) => d.label)
对每一个数据点,我们都将有一个 g (组)元素在 #chart 中,根据对象的属性,每个组里会有一个 元素和一个 元素。生成的html代码如下:
<svg height="100" width="250" id="chart">
<g>
<circle cy="40" cx="50" r="20"/>
<text y="90" x="50">one</text>
</g>
<g>
<circle cy="40" cx="100" r="12"/>
<text y="90" x="100">two</text>
</g>
<g>
<circle cy="40" cx="150" r="8"/>
<text y="90" x="150">three</text>
</g>
<g>
<circle cy="40" cx="200" r="27"/>
<text y="90" x="200">four</text>
</g>
</svg>
二、坐标轴与比例尺
坐标轴
是可视化图表中经常出现的一种图形,由一些列线段和刻度组成。坐标轴在 SVG 中是没有现成的图形元素的,需要用其他的元素组合构成。D3 提供了坐标轴的组件,这样在 SVG 画布中绘制坐标轴变得像添加一个普通元素一样简单。
比例尺
比例尺
很像数学中的函数。D3 中的比例尺,也有定义域和值域,分别被称为 domain 和 range。我们可以指定 domain 和 range 的范围,就可以得到他们直接的关系。
range
: 定义了可以用来绘图的区域domain
: 定义了轴上的最大和最小刻度值。
线性比例尺
线性比例尺,能将一个连续的区间,映射到另一区间。要解决柱形图宽度的问题,就需要线性比例尺。
例子
: 有以下数组,要求:将 dataset 中最小的值,映射成 0;将最大的值,映射成 300。代码如下:
var dataset = [1.2, 2.3, 0.9, 1.5, 3.3];
var min = d3.min(dataset); //0.9
var max = d3.max(dataset); //3.3
var linear = d3.scale.linear() //绘制X,Y 轴的刻度,可以当做函数来使用,返回一个线性比例尺
.domain([min, max])
.range([0, 300]);
linear(0.9); //返回 0
linear(2.3); //返回 175
linear(3.3); //返回 300
由此可以看出:比例尺的定义域 domain 为:[0.9, 3.3],值域 range 为:[0, 300]。
因此,当输入 0.9 时,返回 0;当输入 3.3 时,返回 300。当输入 2.3 时呢?返回 175,这是按照线性函数的规则计算的。
绘制带坐标轴的柱形图
- 首先,我们使用一个svg元素来绘制我们的柱形图,这个元素在 html中。给他定义长和宽等一些常量。
<svg id="mainsvg" class="svgs" width="1100" height="600"></svg>
- 这些是我们将要描述的简单的数据
var data = [{
name: "wen-yan",
value: 20
}, {
name: "anww-ji",
value: 6
}, {
name: "dre-er",
value: 20
}, {
name: "drf-ff",
value: 27
}, {
name: "der-ff",
value: 30
}];
- 我们用d3.select 来选择index.html 中的svg 元素。
// 获得画布
const svg = d3.select('#mainsvg');
// 定义比例尺
const width = +svg.attr('width');
const height = +svg.attr('height');
- 建立X轴和Y轴要用到的数据。SVG对于D3.js来说就是一个“画布”,在SVG范围外的任何内容都属于画布之外,浏览器就不会显示。
// 用来控制表格边框线的位置
const margin = {
top: 60,
right: 30,
bottom: 60,
left: 80
};
// 计算实际操作的inner长/宽
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
-
基于上面这些数据,我们要建立X轴和Y轴的刻度,我们需要获取到定义这两个轴的最大和最小值。我们使用
d3.scaleLinear
的方法来绘制X,Y 轴的刻度,这个方法使用range
和domain
属性来创建刻度。range 的值已经在上面被定义出来了,能够使图像不会在边缘过于拥挤。最大和最小值则基于之前的数据。
// 横轴纵轴的比例尺
const xScale = d3.scaleLinear()
.domain([0, d3.max(data, d => d.value)]) //数据的最大值和最小值的设置
.range([0, innerWidth]) //映射的部分
const yScale = d3.scaleBand()
.domain(data.map(d => d.name)) //取到data数据中的人名
.range([0, innerHeight])
// 设置柱状图之间的间隔
.padding(0.1)
- 虽然已经定义好了刻度,但是还没有把他显示出来。D3为画轴提供了一个叫做
d3.svg.axis
的API。我们就可以使用用上面定义好的刻度来绘制X,Y轴。
const g = svg.append('g').attr('id', 'maingroup')
.attr('transform', `translate(${margin.left},${margin.top})`) //根据上左边距离把maingroup移动到对应的位置
// 定义坐标轴
// 告诉坐标轴你的取值范围是如何映射
const yAxis = d3.axisLeft(yScale)
g.append('g').call(yAxis)
// 横轴默认情况下会在上面展示,使用transform来把它移动到下面
const xAxis = d3.axisBottom(xScale)
g.append('g').call(xAxis).attr('transform', `translate(0,${innerHeight})`)
此时,就可以看到带有刻度的X轴和Y轴已经被绘制出来了,但是我们会发现X轴默认的会显示在最上面。想让它显示在最下面,可以在渲染X轴的时候添加translate属性。
7. 绘制柱状图片
// 画柱状图
data.forEach(d => {
g.append('rect')
.attr("width", xScale(d.value))
//在d3中,可以返回两个带之间的宽度 band.bandwidth()
.attr("height", yScale.bandwidth())
.attr("fill", 'green')
.attr("opacity", '.8')
.attr("y", yScale(d.name))
})
- 坐标轴上面显示的字体很小,下面就是修改坐标轴上面的字体的大小。先看看html中的文本是处在.tick 下的text中,因此使用selectAll来选中它,然后修改它的字体大小即可。
// 修改柱状头的文字
d3.selectAll('.tick text').attr('font-size', '1.5em')
- 为图表添加标题
// 添加标题
g.append('text').text('Member of class')
.attr('font-size', '3em')
.attr('transform', `translate(${innerWidth/2},0)`) //文字向中间移动
.attr("text-anchor", 'middle') //使文字居中显示