昨天写了一篇介绍 D3.js 的入门级文章,以示例的方式由浅入深的讲述了如何用 D3 绘制出一个带坐标轴的柱形图。今天继续这一话题,来看看如何画饼图。
本文中的图表代码和运行环境和上一篇绘制柱形图的环境相似,所以这里就不再重复叙述。有需要可以通过查看上一篇文章中的「准备工作」部分来了解。
使用 D3.js 绘制柱形图D3.js 的入门级指南,总结了如何实现绘制一个柱形图表的过程。
另外,本文延续了上一篇文章的风格,「由浅入深」的总结了如何绘制出一个有不同颜色,以及带图例和鼠标交互功能的饼图。
简单的饼图
先来看看要画出一个最简单的饼图所需要的代码:
var dataset = [66, 10, 9]
var svg = d3.select('svg')
var g = svg.append('g').attr('transform', 'translate(75, 75)')
var pie = d3.pie().value(v => { return v })
g.selectAll('path').data(pie(dataset)).enter()
.append('path')
.attr('d', d3.arc().innerRadius(0).outerRadius(70))
.attr('stroke', 'white')
.attr('stroke-width', 2)
解释一下上面代码中的流程和关键点:
dataset
是要图形化的数据。- 饼图通过 SVG 的路径标签
d
来绘制,并使用了 D3 提供的pie()
函数来创建路径显示数据。 stroke
和stroke-width
分别设置了路径边框和宽度。
来看看上面代码画出的饼图:
使用不同的颜色
上面的饼图每块都是全黑的,虽然有白色的分割线来区分,但不够显眼。通常的做法是让每一块都拥有不同的颜色。来看看添加顔色后的代码:
var dataset = [66, 10, 9]
var svg = d3.select('svg')
var g = svg.append('g').attr('transform', 'translate(75, 75)')
var pie = d3.pie().value(v => { return v })
var colors = d3.scaleOrdinal(['#69C', '#FC9', '#9C6'])
g.selectAll('path').data(pie(dataset)).enter()
.append('path')
.attr('d', d3.arc().innerRadius(0).outerRadius(70))
.attr('stroke', 'white')
.attr('stroke-width', 2)
.attr('fill', colors)
主要有两处调整:
- 使用 D3 提供的
scaleOrdinal
方法创建了颜色迭代方法。 - 使用
fill
属性代入颜色。
来看看添加颜色后的饼图效果:
添加图例
饼图图例的作用和柱形图的坐标轴一样,是锦上添花,或者说是不可或缺的元素。能显著提高图表的直观体验。添加图例后的饼图代码如下:
var dataset = [66, 10, 9]
var svg = d3.select('svg')
var g = svg.append('g').attr('transform', 'translate(75, 75)')
var pie = d3.pie().value(v => { return v })
var colors = d3.scaleOrdinal(['#69C', '#FC9', '#9C6'])
g.selectAll('path').data(pie(dataset)).enter()
.append('path')
.attr('d', d3.arc().innerRadius(0).outerRadius(70))
.attr('stroke', 'white')
.attr('stroke-width', 2)
.attr('fill', colors)
// 绘制图例区块
var legend = svg.append('g').attr('transform', 'translate(200, 50)')
// 绘制图例圆点图标
legend.selectAll('rect').data(dataset).enter()
.append('circle')
.attr('r', 7)
.attr('cy', (v, i) => { return i*20+5 })
.attr('fill', colors)
// 绘制图例文字数值
legend.selectAll('text').data(dataset).enter()
.append('text')
.text((v, i) => { return v })
.attr('x', 15)
.attr('y', (v, i) => { return i*20+10 })
.attr('font-size', '0.8em')
.attr('text-auchor', 'middle')
新增的代码主要在后半段,为了方便了解相关代码的作用,我在每段代码的起始处添加了注释。代码看起来很多,其实就干了两件事:
- 用圆形标签显示对应数据集的颜色。
- 用文本标签显示对应数据集的数字。
有了图例后的效果:
添加鼠标事件
最后给这个饼图添加一点简单的鼠标效果,当鼠标移入饼图时,显示对应区块的具体数值。来看看最新的代码:
var dataset = [66, 10, 9]
var svg = d3.select('svg')
var g = svg.append('g').attr('transform', 'translate(75, 75)')
var pie = d3.pie().value(v => { return v })
var colors = d3.scaleOrdinal(['#69C', '#FC9', '#9C6'])
// 鼠标移入饼图区域
var handleMouseOver = function (e, v) {
d3.select(this).attr('opacity', 1)
var pos = d3.pointer(e)
var x = pos[0]+95
var y = pos[1]+75
var tooltip = svg.append('g').attr('class', 'tooltip').attr('transform', 'translate('+x+','+y+')')
var text = tooltip.append('text').text(v.value).attr('text-anchor', 'middle')
var box = text.node().getBBox()
tooltip.insert('rect', 'text')
.attr('x', box.x)
.attr('y', box.y)
.attr('width', box.width)
.attr('height', box.height)
.attr('fill', 'white')
.attr('opacity', 0.7)
}
// 鼠标在饼图区域移动
var handleMouseMove = function (e) {
var pos = d3.pointer(e)
var x = pos[0]+95
var y = pos[1]+75
d3.select('.tooltip')
.attr('transform', 'translate('+x+','+y+')')
}
// 鼠标移出饼图区域
var handleMouseOut = function () {
d3.select(this).attr('opacity', 0.8)
d3.select('.tooltip').remove()
}
g.selectAll('path').data(pie(dataset)).enter()
.append('path')
.attr('d', d3.arc().innerRadius(0).outerRadius(70))
.attr('stroke', 'white')
.attr('stroke-width', 2)
.attr('fill', colors)
.attr('opacity', 0.8)
.on('mouseover', handleMouseOver) // 绑定鼠标移入事件处理方法
.on('mousemove', handleMouseMove) // 绑定鼠标移动事件处理方法
.on('mouseout', handleMouseOut) // 绑定鼠标移出事件处理方法
var legend = svg.append('g').attr('transform', 'translate(200, 50)')
legend.selectAll('rect').data(dataset).enter()
.append('circle')
.attr('r', 7)
.attr('cy', (v, i) => { return i*20+5 })
.attr('fill', colors)
legend.selectAll('text').data(dataset).enter()
.append('text')
.text((v, i) => { return v })
.attr('x', 15)
.attr('y', (v, i) => { return i*20+10 })
.attr('font-size', '0.8em')
添加的代码看起来有点长,主要看有注释的部分。也就关联了 3 个鼠标事件,分别如下:
- 在
mouseover
鼠标移入事件中,创建文本提示框,并根据鼠标位置设置初始坐标。并调整所在区块的透明度,让其看起来更显眼。 - 在
mousemove
鼠标移动事件中,随时更新提示框的显示位置,实现鼠标跟随效果。 - 在
mouseout
鼠标移出事件中,销毁提示框。另外还原饼图区块的透明度。
这个饼图最终的效果如下:
最后提供个小彩蛋:把 d3.arc().innerRadius(0)
这个方法中的 0
改成更大的数字,会得到一个环状图哦。