iT邦幫忙

2021 iThome 鐵人賽

DAY 20
1
Modern Web

三十天成為D3.js v7 好手系列 第 20

Day20-D3 基礎圖表:圓餅圖

  • 分享至 

  • twitterImage
  •  

本篇大綱:選擇最合適的圖表、圓餅圖、本次範例的畫面與互動效果、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,需要的人請自行取用~


上一篇
Day19-D3 的 RWD 圖表
下一篇
Day21-D3 基礎圖表:散點圖/散佈圖
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言