D3 入门
D3(Data Driven Docuemtns)是一套非常优秀的数据可视化库, 它可以帮助开发者在浏览器中直观地展现各种数据.
虽然这个工具本身非常强大, 但是其学习门槛并不低. 其中一个原因在于教程的不友好, 新手学习起来很容易没有头绪. 官方给出的 tutorials 一方面更新不及时, 导致和 API 文档对不上号, 用 v3 的教程对照 v5 的 API 文档看的一头雾水. 二来很多例子也相对复杂, 缺少一些循序渐进的讲解.
这里将会分享如何实现一些简单的数据可视化图形.
Fundamentals 入门教学
首先推荐一篇优秀的 d3 基础教程
如何制作一个饼图 / 环形图(pie/donut)
我们可以把一个数组 [110, 12, 18] , 用环形图的方式展示.
donut.PNG
源代码在这里查看.
像这样的饼图, 可以分成三个部门: 环形 (Arc), 文字(Label) 和连接环形文字之间的连线(Line).
Arc
在这里每一条数据对应的环形, 可以使用 SVG 里的 Path 元素来表示, 通过给 Path 传递描述图形形状的参数, 便可以在浏览器中渲染出我们需要的图形. 为了画出环形, 我们需要几个基本信息:
环内径 (innerRadius)
环外经 (outerRadius)
起始弧度 (startAngle)
终止弧度 (endAngle)
这里使用弧度来表示环的起始和终止位置, 而不是角度. 长度等于半径的弧长对应的弧度定义为 1, 所以对于一个完整的圆, 弧度是 2 * PI. 后面我们会用到中间弧度 (midAngle) 来判断环在左半边圆还是右半边圆, 这可以帮助我们定位数据标签 (label).
d3-shape 可以帮我们计算需要的图形参数. 调用 d3.pie() 会返回一个 pie 生成器 (generator). 这个生成器可以把传入的数据转化成扇形 / 环形相应的弧度. 而为了得到环形的形状描述(path), 我们还要用到 d3.arc 生成器, 并传入内外径参数, 有了弧度和半径参数之后, 才能生成实际的图形.
- // 指定了两个相关函数: value accessor 和 sort
- // value 函数表示如何从单个 data 中取取到值
- // sort 函数如果没有专门指定的话, 会默认降序排列
- const pie = d3.pie().value((d) => d).sort(null);
- const slices = pie(data);
- // slices 的结构如下, 注意 slices 的排序和给定的 data 排序一致, index 对应 sort 之后的顺序.
- [
- {
- data: number,
- value: number,
- index: number,
- startAngle: number,
- endAngle: number,
- padAngle: number,
- },
- ...
- ]
- // 带有内外径的 arc 生成器
- const innerArc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius);
- // D3 的选择 (selection) 部分
- const slice = d3.select('.slices').selectAll('path').data(slices);
- slice.enter()
- .append('path')
- .attr('d', (d, i) => innerArc(slices[i]))
- .attr('transform', `translate(${width / 2}, ${height / 2})`)
- .attr('fill', (d, i) => colorArray[i % (colorArray.length)]);
- slice.exit().remove();
- Line
Line 是连接环形图心和文字标签之间的连线, 可以用 Polyline 元素渲染. 为了绘制连线, 我们需要知道起点, 转折点和终点.
连线的起点是环的圆心, 转折点是外层一个不可见的同弧度环的圆心, 终点根据环的位置判断, 如果环在左半边圆, 那么从转折点往左平移一段距离, 在右边则向右平移一段距离.
- const endPoints = []; // 记录下终点, 为文字标签定位
- const pivotArc = d3.arc().innerRadius(outerRadius).outerRadius(pivotRadius);
- const line = d3.select('.lines').selectAll('polyline').data(slices);
- line.enter()
- .append('polyline')
- .attr('points', (d, i) => {
- const slice = slices[i];
- const innerCentroid = innerArc.centroid(slice);
- const x1 = innerCentroid[0] + width / 2;
- const y1 = innerCentroid[1] + height / 2;
- const pivotPoint = pivotArc.centroid(slice);
- const x2 = pivotPoint[0] + width / 2;
- const y2 = pivotPoint[1] + height / 2;
- const midAngle = getMidAngle(slice);
- const x3 = x2 + (midAngle> Math.PI ? -20 : 20);
- const y3 = y2;
- endPoints[i] = [x3, y3];
- return `${x1},${y1} ${x2},${y2} ${x3},${y3}`;
- })
- .attr('fill', 'none')
- .attr('stroke', (d, i) => colorArray[i % (colorArray.length)]);
- line.exit().remove();
- Label
定位到终点之后, 我们可以添加文字标签.
- const label = svg.select('.labels').selectAll('text').data(slices);
- label.enter()
- .append('text')
- .text((d) => d.value)
- .attr('transform', (d, i) => {
- const x = endPoints[i][0] + (getMidAngle(d)> Math.PI ? -10 : 10);
- const y = endPoints[i][1] + 5;
- return `translate(${x}, ${y})`;
- })
- .attr('text-anchor', (d) => {
- const midAngle = getMidAngle(d);
- return midAngle> Math.PI ? 'end' : 'start';
- });
- label.exit().remove();
来源: http://www.jianshu.com/p/5804e69fa3d4