用D3实现有动画效果的饼状图

饼状图是数据可视化中最为常见的一种图形了,尤其适合展示多个分类所占百分比。饼状图用D3很容易实现,但是如何给饼状图加入动画效果,则具有一定的难度。本文将介绍如何用D3(v4)实现饼状图的动画效果。请先点击此处,预览最终的效果。

First things first

首先,我们需要准备好第4版的d3.js(这份教程中的代码未在第5版的d3中进行测试)。除了d3.js本身以外,因为用到了额外的d3配色,因此还需准备好d3-scale-chromatic.js。这两个Javascript框架既可以下载到本地引用,也可以使用CDN中的文档。

我们要可视化的数据存储在一个csv文档中,因此,受浏览器读取本地文件的权限限制,如果要在本地查看效果,需要用node或Python等工具搭建一个简单的本地服务器。好一些代码编辑器的插件也能实现本地服务器的效果,也可以使用。

在我们的index.html中,加入引用d3库和我们自己所写的Javascript代码的几行命令:

<script src="../common/js/lib/d3.v4.min.js"></script>
<script src='../common/js/lib/d3-scale-chromatic.v1.min.js'></script>
<script src="js/draw_canvas1.js"></script>

你需要根据自己文档的存储位置,调整js文件的路径和文件名。下一步,我们在index.html中加入一个h1元素,以及一个供d3操纵的SVG元素:

<h1>An Animated Pie Chart with D3.js</h1>
<div id="visualization">
    <svg id="canvas1"></svg>
</div>

装载有数据的csv文档内容如下,第一列为书籍的主题,第二列为该主题书籍的收藏量:

subject,item_no
History,25156
Social Sciences,12546
Political Science,15145
Law,54154
Education,41654
Music,50551
Fine Arts,51516

编辑绘制饼图的draw_canvas1.js

draw_canvas1.js中的全部代码,都被包含在一个叫show()的函数之中。最终在html中,我们会加入代码,直接调用show()函数,然后就能绘制出饼图。我们先添加一段读取csv文档的代码:

var data1;
d3.csv(
  'data/data1.csv',
  function(d) {
    return {
      subject: d.subject,
      item_no: +d.item_no //“+”将字符变量转化为数字变量
    };
  },
  function(data) {
    data1 = data;
    exec(); // 调用“exec()”函数执行我们的绘制
  }
);

下一步,是编写exec()函数,它同样被包含在show()函数里面。exec()函数的第一部分,是为svg元素设置尺寸和边距等数值,然后添加一个g元素。我们绘制的饼状图就会被包含在g元素中。

var margin = { top: 20, bottom: 20, right: 20, left: 45 },
  width = 800 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;
var canvas1 = d3
  .select('#canvas1')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom)
  .append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

接着,计算出我们数组data1的长度,存储在length_data1变量里面,以便稍后利用d3的组件计算出饼状图各部分的颜色。

var length_data1 = data1.reduce(function(p, el) {
  if (p) {
    return 1 + parseInt(p);
  } else {
    return 1;
  }
});

colors1()函数能计算出饼图各部分的颜色。将d3.interpolatePuBuGn替换为其它的d3预设,可以实现不同的颜色效果。

function colors1(i) {
  return d3.interpolatePuBuGn(i / length_data1);
}

饼状图实际上是通过SVG的path元素来绘制,我们首先需要用到d3.arc()方法,来将饼状图中各个部分的角度转化为path元素的d属性。我们绘制的是中间空心的饼状图,因此需要设置outerRadiusinnerRadius里、外两个半径。

var arc = d3
  .arc()
  .outerRadius((height / 2) * 0.7)
  .innerRadius((height / 2) * 0.33);

d3.pie()能根据数据每一行的具体数字,计算出饼状图中每一牙圆弧对应的角度。使用padAngle()可以在每一牙之间设置一个间距。

var pie = d3
  .pie()
  .sort(null)
  .padAngle(0.04)
  .value(function(d) {
    return d.item_no;
  });

接着用d3.transition()设置动画的时间间隔,和间隔中动画速度的变化。

var t = d3
  .transition()
  .ease(d3.easeQuad) //动画的速度由慢变快
  .duration(2800);

使用定义好的pie()函数和data1中存储的数据,计算出饼状图各个部分的弧形的角度,存储在变量arcs1中。在canvas1中增加一个g元素,然后用设置transform属性,让其位置居中,同时加入左右翻转,已实现动画从左往右的效果。

var arcs1 = pie(data1);

var pieContainer = canvas1
  .append('g')
  .attr('transform', 'translate(' + width / 2 + ' ' + height / 2 + ') scale(-1, 1)');

exec()函数中加入一个plot_pie()函数,绘制出饼图。其中transition()方法之后的attrTween()方法,为path元素的d属性设置了动画效果。具体计算d属性值的tweenArcs()函数,会在下一步设置。

function plot_pie() {
  var arcElements = pieContainer.selectAll('.pieChart').data(arcs1, function(d, i) {
    return i;
  });
  arcElements.exit().remove();
  var enterArcElements = arcElements
    .enter()
    .append('path')
    .attr('class', 'pieChart')
    .merge(arcElements)
    .attr('fill', function(d, i) {
      return colors1(i);
    })
    .transition(t)
    .attrTween('d', tweenArcs);
}

tweenArcs()是一个二阶函数,它的输入值是和SVG绑定的夹角数据,然后返回一个根据动画过渡进度变量t计算出d属性值的函数。

function tweenArcs(d) {
  var interpolator = getArcInterpolator(this, d);
  // this是当前d3在处理的圆弧对象
  return function(t) {
    return arc(interpolator(t));
    //arc()函数的输入值是圆弧的角度,因此interpolator()函数计算出的夹角需要随着t而变化
  };
}

d3.interpolate()方法实现了getArcInterpolator()函数。

function getArcInterpolator(el, d) {
  var oldValue = el._oldValue;
  var interpolator = d3.interpolate(
    {
      startAngle: oldValue ? oldValue.startAngle : 0,
      endAngle: oldValue ? oldValue.endAngle : 0
    },
    d
  );
  el._oldValue = interpolator(0);

  return interpolator;
}

最后,在exec()函数中调用plot_pie()函数,即可实现绘图。同时也别忘了在index.html中加入调用show()函数的Javascript代码哦。

本示例的完整代码,请在这里查看。

Written on September 26, 2018