Day20-D3 基础图表:圆饼图

本篇大纲:选择最合适的图表、圆饼图、本次范例的画面与互动效果、pie( ) 与 arc( )、绘制圆饼图

终於要正式进到一天一张图的篇章了!在开始绘制图表之前,很重要的一点是:如何为资料选择最合适的图表呈现

选择最合适的图表

图表分成很多种,有圆饼图、长条图、折线图、散点图等等,为什麽不乾脆用一种图表呈现资料就好吗?这是因为每一种图表适合呈现的资料不一样。当我们拿到一组资料後,就要依照我们的目的,找到最适合的图表来将这组资料呈现给别人看。

下面这张图将各种图表适合用来表达的事情整理出来
https://ithelp.ithome.com.tw/upload/images/20211002/201349308NDhNI3cVV.png

( 图片来源 )

我这边用文字简单整理常用的几种图表,以及它们适合呈现的资料

适合用在 比较资料 整体占比 了解分布 分析趋势
图表 长条图、折线图 圆饼图、长条堆积图、面积图 散点图、气泡图 折线图

想更了解该怎麽选择图表的人,可以另外看看这几篇不错的文章


圆饼图

了解完该选择哪种图表之後,开始来画第一张图吧!圆饼图适合用来表达 每笔资料於整体的占比,生活中最常见的地方就是记帐App啦~常用App来记帐的人一定都看过这种图
https://ithelp.ithome.com.tw/upload/images/20211002/20134930KzGTbLG1wG.jpg

透过这种圆饼图甜甜圈图,我们就能弄清楚自己的钱都花到哪里、哪边的支出占最多,今天就来练习画一张圆饼帐本图吧!

本次范例的画面与互动效果

这次我们要做的范例画面与互动效果有:

  • 圆饼图要呈现各别单项目跟%百分占比
  • 滑鼠hover到单项目区块时,圆饼图会放大+加阴影
  • 切换1月、2月按钮时,圆饼图的资料会更动

实际范例如下
https://i.imgur.com/3DnCNms.gif

pie( ) 与 arc( )

绘制圆饼图之前,我们要先知道 d3.arc( )d3.pie( ) 这两个方法跟它们细节设定。关於 d3.arc( ) 的用法在 Day9 讲过了,今天再来讲讲 d3.pie 的用法吧!

d3.arc( ) 跟 a3.pie( ) 通常都是都配一起使用。当我们用arc( )建立好圆弧之後,就能将资料带进 pie( )的方法中去建立圆饼图。pie( ) 包含以下几种API:
https://ithelp.ithome.com.tw/upload/images/20211002/201349304FpivLIgH9.jpg

很多设定跟 arc( ) 的设定类似,像是两者都有 startAngle、endAngle、padding等API,它们的功能也是一样的,我们就直接进入下方的范例做一次吧!

绘制圆饼图

绘制圆饼图之前,我们先把要进行的步骤稍微拆解一下:

  1. 设定一月、二月按钮要搭配的资料
  2. 设定画圆饼图的方法、建立svg、添加饼图、线条、标签的DOM元素
  3. 设定 color 的方法 scaleOrdinal( )
  4. 设定arc( )、pie( ) 方法
    每一块饼图都是用 arc( ) 建立的路径
  5. 计算每块饼图的%百分占比
  6. 资料绑定上DOM 去建立饼图
  7. 加上文字资讯
  8. 加上滑鼠互动效果

接着就按照这些顺序来撰写程序码吧!首先,先建立资料

// css
.chartContainer {
    margin: auto;
    width: 80%;
    min-width: 200px;
    /* height: 600px; */
    margin: auto;
}
//html
<h4 class="text-center mt-5">金金的帐本</h4>
<div class="chartContainer"></div>
<div>
  <button type="button" class="btn btn-primary January">1月</button>
  <button type="button" class="btn btn-primary Feburary">2月</button>
</div>
// JS
// Data
  let data = [{item:'交通', data:30},{item:'房租', data:45},
            {item:'其他', data:9},{item:'吃饭', data:67},{item:'娱乐', data:22}]; 

  // 切换一二月资料
  d3.select('.Feburary').on('click', function(){
    data = [{item:'交通', data:50},{item:'房租', data:65},
            {item:'其他', data:39},{item:'吃饭', data:17},{item:'娱乐', data:72}];  
		drawPie()
  })

  d3.select('.January').on('click', function(){
    data = [{item:'交通', data:30},{item:'房租', data:45},
            {item:'其他', data:9},{item:'吃饭', data:67},{item:'娱乐', data:22}];   
    drawPie()
  })

  1. 设定画圆饼图的方法 drawPie( ),建立svg并添加饼图、线条跟标签的< g >元素

const drawPie = () => {
  // RWD 清除原本的图型
  d3.select('svg').remove()

  // svg图形区大小、边界
  const svgWidth = parseInt(d3.select(".chartContainer").style("width")),
        svgHeight = svgWidth*0.8,
        margin = 40;

  // 先设定 svg 大小
  const svg = d3.select(".chartContainer")
                  .append("svg")
                  .attr("width", svgWidth)
                  .attr("height", svgHeight);

  // 图表与线条、标签
  svg.append("g")
     .attr("class", "slices")
     .attr('transform', `translate(${svgWidth / 2}, ${svgHeight / 2})`);
    
  svg.append("g")
     .attr("class", "labels");
    
  svg.append("g")
     .attr("class", "lines");

  // 接下来的程序码都放这边哦.....
  // 接下来的程序码都放这边哦.....
  // 接下来的程序码都放这边哦.....
}

drawPie()
  1. 再来我们使用 scaleOrdinal( ) 这个API 来设定颜色,scaleOrdinal ( ) 的用法 Day17 有讲解,这边不需要设定domain,只要把range 按照资料的数量填上想要的颜色就好
// 设定颜色
const color = d3.scaleOrdinal()
                .range(["#4BEFCF","#0bbc17","#F96262", '#ffbe32', '#e271fc']);
  1. 设定arc( )、pie( ) 方法
// radius 用来设定半径,圆饼图的圆弧大小是区域的一半
const radius = Math.min(svgWidth, svgHeight) / 2 - margin;

// 设定每个资料在圆饼图上:
const piechart = d3.pie()
                   .value(d => d.data)
                   .sort(function(a,b){
                     console.log(a,b) // 固定圆饼图的项目排序
                     return d3.ascending(a.key, b.key)
                   });

// innerRadius 跟 outerRadius 决定圆饼内圈外圈的大小 radius
const arc = d3.arc()
              .innerRadius(0)
              .outerRadius(radius)
              .padAngle(.02),
      outerArc = d3.arc()
                   .outerRadius(radius * 0.9)
                   .innerRadius(radius * 0.9),
      data_ready = piechart(data)
  1. 再来,因为我们想呈现每个区块的%占比,所以要进行百分比换算
// 计算每块资料的占比%
// 先用 d3.sum 加总全部资料,再将资料一一除上总数
const total = d3.sum(data, d => d.data)
data.forEach(d => {
  d.percentage = Math.round((d.data/total)*100)
})
  1. 然後就可以先前用 arc( ) 跟 pie( ) 写好的设定绑定到DOM元素上啦!!
// 建立pie
const cutePie = svg.select('.slices')
        .selectAll('g')
        .data(data_ready)
        .enter()
        .append('g')
        .attr('class', 'arc')

 cutePie.append('path')
        .attr('d', arc)
        .attr('fill', color)
        .attr("stroke", "#fff")
        .style("stroke-width", "2px")
        .style("opacity", 1);
  1. 接着我们加上每个区块的文字跟%占比,这边会运用到 arc.centroid( ) 的方法把文字标签调整到中间
// 加上每个区块的标示
// 控制文字的位置
const arcText = d3.arc()
                  .innerRadius(radius)
                  .outerRadius(radius - 10)

const itemText = cutePie.append('text')
                        .attr('transform', d => `translate(${arcText.centroid(d)})`)
                        .text(d => d.data.item + d.data.percentage + '%')
                        .style('text-anchor', 'middle')
                        .style('font-size', 16)
                        .style('fill', 'black')
  1. 加上滑鼠互动
// 滑鼠互动 mouseover、mouseleave
d3.selectAll('.arc path')
  .style('cursor', 'pointer')
  .on('mouseover', function(){
     d3.select(this)
       .transition()
       .duration(500)
       .style("filter", "drop-shadow(2px 4px 6px black)")
       .style('transform', 'scale(1.1)')
  })
  .on('mouseleave', function(){
    d3.select(this)
      .transition()
      .duration(500)
      .style("filter", "drop-shadow(0 0 0 black)")
      .style('transform', 'scale(1)')
  })

这样就完成啦!最後的最後,别忘了要用 window resize 把完成的圆饼图调整为 RWD 圆饼图唷

// RWD
  d3.select(window).on("resize", drawPie);

https://i.imgur.com/3DnCNms.gif


Github Page 图表与 Github 程序码

想看完整的程序码请往这走 Github ,想看完成的实际画面请往这走 Github Page,需要的人请自行取用~


<<:  [day17]Vue实作-浏览列加入登入及注册钮

>>:  人脸辨识-day17 应用层面--3

Dot NET Core Host - 运作概述图解

Net Core 为一跨平台的应用开发,其此框架上可以加上许多自制服务,称之为容器也不为过。 如果...

学习Python纪录Day6 - String type和Container type的运算子

String type和Container type的运算子 连接运算子 重复运算子 成员运算子 关...

Day 24非同步程序设计

前言 非同步程序设计基本上就是没有等待或非阻塞程序的设计模型,在Flutter中,非同步是用Futu...

D13 删除特定的使用者文件

已经先有测试资料了 来试试看删除文件的方法 doc_info/views.py 一样使用修饰器来验证...

Day01-为什麽我要学Vue/Vue简介

我的梦想就是带这一台笔电走遍全世界,「成为一个工程师似乎可以完成这个梦想」,於是在去年底毅然决然的投...