一、前言

上一篇文章咱们残缺实现了一个柱状图,并且提到了一个比例尺的概念。这篇文章咱们持续来学习 D3.js,然而呢是用它来绘制一个饼图。那么废话不多说,咱们正式开始

二、注释

2.1、API

函数用法
d3.pie利用给定的数据生成饼图,并且返回一个对象数组
d3.arc圆弧生成器

2.2.、绘制圆弧

后面咱们提到了 d3.arc 是一个圆弧生成器。举个例子:

const arc = d3.arc()    .innerRadius(0)    .outerRadius(100)    .startAngle(0)    .endAngle(Math.PI / 2);arc(); // "M0,-100A100,100,0,0,1,100,0L0,0Z"

咱们能够看到最终 arc() 返回了这样串后果:"M0,-100A100,100,0,0,1,100,0L0,0Z"

那么这串后果是啥意思呢?咱们把这串后果合成为如下命令:

联合起来就是:将以后地位挪到了 $(0, -100)$ ,基于以后地位绘制一条椭圆曲线,它的 $rx$ 和 $ry$ 都为 $100$,起点地位为 $(100, 0)$。后果就是绘制了一个 $1/4$ 圆。


可想而知,咱们将利用这个属性来绘制咱们的饼图。

2.3、绘制饼图

首先咱们先来生成一个数组,数字范畴轻易设置

const data = Array.from({length: 10}).map((v, i) => i * 100 + 10)

接着再增加 svg & 设置容器

const svg = d3.select('#pie')    .append('svg')    .attr('width', 600)    .attr('height', 600)    .attr('class', 'svg')        // 使得视图居中    .attr('viewBox', '-300 -300 600 600')

而后咱们须要做两件事件:

  1. 配置一个圆弧生成器
  2. 依据 data 生成每个数据对应的 startAngleendAngle
  3. 联合下面两步生成一个饼图

那么下面咱们提到了用 d3.arcd3.pie 就能够实现这两件事

const arcs = d3.pie()(data)const arc = d3.arc()  .outerRadius(100)  .innerRadius(0)svg.selectAll('path')    .data(arcs)    .enter()    .append('path')    .attr('d', arc)

最终咱们失去了一个黑压压的饼图

2.4、增加色彩

如上所示,咱们最终的饼图是没有色彩的,那么怎么给它上色呢?有一个办法是咱们本人来手动配色,然而这略微显得有点蛋疼。另外一种办法就是应用 D3.js 中内置的办法 —— d3.schemePaired

代码如下:

svg.selectAll('path')    .data(arcs)    .enter()    .append('path')    .attr('d', arc)    .attr('fill', (d, i) => d3.schemePaired[i])

这样咱们就失去了一个颇有颜值的饼图

2.5、增加标注

2.5.1、数据结构

为了给饼图中的每一块减少标注,首先咱们要把数据结构稍稍调整一下:

const datasets = [    {name: 'cat', value: 100},    {name: 'dog', value: 100},    {name: 'pig', value: 100},    {name: 'cow', value: 100},    {name: 'bird', value: 100},    {name: 'fish', value: 100},    {name: 'snake', value: 100},    {name: 'mouse', value: 100},    {name: 'monkey', value: 100},    {name: 'elephant', value: 100},  ]

2.5.2、确定坐标

为了确保咱们画出一条完满的折线,接下来咱们须要确定几个坐标:

  1. 起始坐标
  2. 起点坐标
  3. 文本坐标

D3.js 提供了一个函数 —— arc.centroid,通过它咱们能够失去一个以后扇形的两头坐标。以咱们以后所画的饼图为例,咱们把这些点标注为一个个红色的圆。如下图所示

咱们察看图中的原点,联合控制台能够发现,饼图右边的点横坐标区间为 $x < 0$,饼图左边的点横坐标区间为 $x ≥ 0$。

因而,咱们就能够得出一个绘制标注的逻辑:

  1. 终点坐标: arc.centroid
  2. 起点坐标:沿着圆心绘制一条线穿过以后扇形的 arc.centroid ,它的长度为 $r - arc.centroid 间隔圆心的直线间隔 + offsetY$($offsetY$ 为自定义的偏移量)
  3. 文本坐标:以起点坐标为终点,横坐标减少 $offsetX$($offsetX$ 为自定义的偏移量)

当指标扇形的中点处于 $x$ 轴上方时,如下图所示:


咱们把 A 点认为是扇形的「中点」,易知:

$OD = cos∠AOB * OC$ ,$CD = sin∠AOB * OC$

其中: $cos∠AOB = x / z$ ,$sin∠AOB = y / z$ ,$z = √(x^2 + y^2)$

当指标扇形的中点处于 $x$ 轴下方时,如下图所示:

此时:$cos∠AOB = y / z$ ,$sin∠AOB = x / z$,$z = √(x^2 + y^2)$

2.5.3、代码实现

晓得如何绘制之后咱们就来入手写代码吧,首先是先绘制折线

const generatePolyline = selection => {    const offsetY = 10;    const distance = r + 50;    const points = []    const r1 = 20    // 生成折线    selection.append('polyline')      .attr('class', 'polyline')      .attr('points', (d, i) => {        const centerX = arc.centroid(d)[0]        const centerY = arc.centroid(d)[1]        const centerZ = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2))        const offsetX = getOffsetX(datasets[i].name)        // 以后处于第一、四象限        if (centerY <= 0) {          const cos = Math.abs(centerX / centerZ)          const sin = Math.abs(centerY / centerZ)          const X = centerX >= 0 ? cos * distance : -cos * distance          const Y = -sin * distance          points.push({[datasets[i].name]: [X, Y]})          if (centerX >= 0) {            return `${centerX},${centerY} ${X},${Y}, ${X + offsetX},${Y}`          }          return `${centerX},${centerY} ${X},${Y}, ${X - offsetX},${Y}`        }        // 以后处于第二、三象限        const cos = Math.abs(centerY / centerZ)        const sin = Math.abs(centerX / centerZ)        const X = centerX > 0 ? sin * distance : -sin * distance        const Y = cos * distance        points.push({[datasets[i].name]: [X, Y]})        if (centerX > 0) {          return `${centerX},${centerY} ${X},${Y}, ${X + offsetX},${Y}`        }        return `${centerX},${centerY} ${X},${Y}, ${X - offsetX},${Y}`      })      .attr('stroke', '#6F68A7')      .attr('fill', 'none')}svg    .selectAll('.path-group')  .call(generatePolyline)

此时饼图产生了变动


紧接着咱们把残余的代码也加进来 generatePolyline 函数中

// 增加文字selection.append('text')  .attr('class', 'text')  .attr('x', (d, i) => {    const {name} = datasets[i]    const [x] = points[i][name]    if (x > 0) {      return x + 5    }    return x - getWordWidth(name) - 5  })  .attr('y', (d, i) => {    const {name} = datasets[i]    const [, y] = points[i][name]    return y - offsetY  })  .attr('fill', '#6F68A7')  .text((d, i) => datasets[i].name)  .style('font-size', '14px')// 增加百分比selection.append('text')  .attr('class', 'text')  .attr('x', (d, i) => {    const {name} = datasets[i]    const [x] = points[i][name]    const offsetX = getOffsetX(name)    if (x > 0) {      return x + offsetX + r1 - 10    }    return x - offsetX - r1 - 10  })  .attr('y', (d, i) => {    const {name} = datasets[i]    const [, y] = points[i][name]    return y + 4  })  .attr('fill', '#6F68A7')  .style('font-size', '12px')  .text((d, i) => {    const sum = datasets.reduce((acc, cur) => acc + cur.value, 0)    return `${datasets[i].value / sum * 100}%`  })// 增加圆点selection.append('circle')  .attr('class', 'circle')  .attr('cx', (d, i) => {    const {name} = datasets[i]    const [x] = points[i][name]    const offsetX = getOffsetX(name)    if (x >0) {      return x + offsetX + r1    }    return x - offsetX - r1  })  .attr('cy', (d, i) => {    const {name} = datasets[i]    const [, y] = points[i][name]    return y  })  .attr('r', r1)  .attr('fill', 'none')  .attr('stroke', '#6F68A7')

刷新页面后咱们就能看到一个带有残缺标注的饼图了

2.6、增加动画

能够应用attrTween函数增加动画成果,管制动画速度、持续时间及属性变动,实现简单变动,达到准确管制成果。

因为要加的中央还挺多的,这里就不一一展现了,所以咱们间接来看最终成果

残缺代码参考:[](https://github.com/pigpigever...)https://github.com/pigpigever...

三、总结

本文介绍了如何应用 D3.js 绘制一个饼图,并具体解释了饼图的绘制原理,以及绘制过程中所需的根本步骤,从绘制底层圆环开始,再绘制扇形、增加标注,最初还增加了动画成果。

想要理解更多前端常识,欢送关注我的公众号:tony老师的前端补习班

参考资料:[](https://developer.mozilla.org...)https://developer.mozilla.org...