iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
Modern Web

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

Day27-D3 進階圖表:甜甜圈圖

本篇大綱:範例一、範例二

昨天我們講完了基礎圖表的章節,學會圓餅圖、散點圖、直條圖跟折線圖等等基礎常見的圖表,今天我們要進到進階的圖表啦!進階的圖表主要是較少用到或是寫法較困難的圖表,有些進階圖表是從基礎圖表演化而來,例如:甜甜圈圖、氣泡圖等;有些則是由完全不同的方法撰寫而成的圖表,例如階層圖、地圖等等。我們就先從延伸而來的圖表開始講起吧!今天~就讓我們來看看從圓餅圖延伸而來的甜甜圈圖

我們今天的範例有兩個,第一個是基礎的甜甜圈圖:
https://ithelp.ithome.com.tw/upload/images/20211009/20134930ZZAFsAmx9N.jpg

第二個是加上標示線段的甜甜圈圖
https://ithelp.ithome.com.tw/upload/images/20211009/20134930Dco36qFtrq.jpg

甜甜圈圖的關鍵

甜甜圈圖其實是從圓餅圖延伸而來,它的關鍵就在於設定圓餅內圈的半徑。使用 d3.arc 設定圓弧的形狀時,可以分別設定內弧跟外弧的半徑

  • arc.innerRadius ⇒ 內弧
  • arc.outerRadius ⇒ 外弧
// Creating arc
const arc = d3.arc()
				.innerRadius(50)
				.outerRadius(100);

https://ithelp.ithome.com.tw/upload/images/20211009/20134930x6MxdBbWMH.jpg

如果把內弧半徑設為0的話,就會形成圓餅圖;但如果內弧半徑設定成不同數值,就能任意調整內圈半徑,進而形成甜甜圈圖。現在我們就來看看範例一要怎麼做吧!

範例一

我們先來畫一個基本的甜甜圈圖加上資訊。首先,一樣先取資料並建立svg

// html 
<div class="basicDonut"></div>

// js
const data = [
    { "value": 1, "property": "p1" },
    { "value": 2, "property": "p2" },
    { "value": 3, "property": "p3" },
    { "value": 4, "property": "p4" },
    { "value": 5, "property": "p5" },
    { "value": 6, "property": "p6" }
]

// 建立svg
const svg = d3.select(".basicDonut")
            .append('svg')
            .attr('width', 300)
            .attr('height', 300);

再來,我們用 d3.pie( ) 方法來建立生成圓餅圖的方法,然後用 d3.arc( ) 繪製弧度

// 用 pie()建立圓餅圖 generator
const pie = d3.pie()
    .value((d) => { return d.value })
    (data);

// 建立圓弧
const arc = d3.arc()
    .innerRadius(50)
    .outerRadius(100);

接著就是簡單的綁定資料並加上路徑啦

// 綁定資料
const arcs = svg.append("g")
    .attr("transform", "translate(150, 120)")
    .selectAll("arc")
    .data(pie)
    .enter()
    .append("g");

// 加上路徑 
arcs.append("path")
    .attr("fill", (data, i) => {
        return d3.schemeSet2[i];
    })
    .attr("d", arc);

如果想要加上資料標示,就一樣用 append.text的 方式去處理

// 加上內部文字標示
arcs.append("text")
    .attr("transform", (d) => {
        return "translate(" +
            arc.centroid(d) + ")";
    })
    .text(function (d) {
        return d.value;
    });

這樣就完成啦~寫起來是不是幾乎跟圓餅圖一模一樣呢?
https://ithelp.ithome.com.tw/upload/images/20211009/20134930kC1ByoYWIy.jpg

範例二

我們接著再來看看範例二,這個就比較有趣~是加上外拉的標籤線段來標示每個區塊的資料,並在內圈放上圖表的主題
https://ithelp.ithome.com.tw/upload/images/20211009/20134930dSQJr73Ywx.jpg

首先,我們一樣先把甜甜圈圖建立出來

// html
<div class="advancedDonut"></div>

// js
// 建立資料
const data = [{city:'台北', data:30},{city:'新北', data:45},
        {city:'台中', data:9},{city:'嘉義', data:67},{city:'台南', data:22}]  

// RWD 的svg 寬高
const svgWidth = parseInt(d3.select('.basicDonut').style('width')),
      svgHeight = svgWidth*0.8,
      margin = 60

var svg = d3.select(".advancedDonut").append("svg")
  .attr("width", svgWidth)
  .attr("height", svgHeight)
  .append("g");

// 設定圖表寬高與圓弧半徑
const width = svgWidth-margin*2;
const height = svgHeight-margin*2;
// radius設定圓餅圖的圓弧大小,是區域的一半
const radius = Math.min(width, height)/2;

// 設定顏色
const color = d3.scaleOrdinal()
                .range(["#4BEFCF","#2c9af7","#F96262", '#910842', '#b054e5']);
  
// 圓餅、線段、標籤
svg.append("g")
    .attr("class", "slices");

svg.append("g")
  .attr("class", "labels");

svg.append("g")
  .attr("class", "lines");

// 建立圓餅圖
const pie = d3.pie().sort(null).value(d => d.data);
const arc = d3.arc().innerRadius(radius*0.3).outerRadius(radius*0.6);

// 設定弧度
const outerArc = d3.arc()
            .outerRadius(radius * 0.9)
            .innerRadius(radius * 0.9);

// 建立甜甜圈圖 
svg.attr("transform", "translate(" + svgWidth / 2 + "," + svgHeight / 2 + ")");

svg.selectAll('path')
    .data(pie(data))
    .enter()
    .append('path')
    .attr('d', arc)
    .attr('fill', (d,i)=> color(i));

再來建立標示的線段跟文字標籤。這邊的關鍵是線段跟標籤的位置,我們要根據外弧的位置去設定,因此會用outerArc.centroid 去處理

// 建立線段跟標示
svg.append('g').classed('labels',true);
svg.append('g').classed('lines',true);
 

// 設定線段
const polyline = svg.select('.lines')
            .selectAll('polyline')
            .data(pie(data))
            .enter()
            .append('polyline')
            // 這邊是關鍵,要根據外弧的位置去調整線段位置
            .attr('points', function(d) {
                const pos = outerArc.centroid(d);
                pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
                return [arc.centroid(d), outerArc.centroid(d), pos]
            });

// 設定文字標籤
const label = svg.select('.labels').selectAll('text')
            .data(pie(data))
            .enter()
            .append('text')
            .attr('dy', '.35em')
            .html(d => `${d.data.city}:${d.data.data}萬度`)
            // 這邊是關鍵,要根據外弧的位置去調整標籤位置
            .attr('transform', function(d) {
                const pos = outerArc.centroid(d);
                pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
                return 'translate(' + pos + ')';
            })
            .style('text-anchor', function(d) {
                return (midAngle(d)) < Math.PI ? 'start' : 'end';
            });

// 綁定文字標籤要顯示的內容
svg.append('text')
    .attr('class', 'toolCircle')
    .attr('dy', 0) 
    .html('用電量')
    .style('font-size', '.9em')
    .style('text-anchor', 'middle');

function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; }

然後我們可以將線段跟文字標籤設定一些樣式

/*資料軸線*/
.advancedDonut polyline {
    opacity: .3;
    stroke: black;
    stroke-width: 2px;
    fill: none;
}

/* 字體加粗*/
.labelName tspan {
    font-style: normal;
    font-weight: 700;
}

/* 字形 */
.labelName {
    font-size: 0.9em;
    font-style: italic;
}

這樣就完成啦!是不是也沒多困難呢?今天的甜甜圈圖就講到這邊~明天要來講氣泡圖囉!


Github Page 圖表與 Github 程式碼

最後附上本章的程式碼:想看完整程式碼的請上 Github,想直接操作圖表的則去 Github Page 吧!請自行取用~


上一篇
Day26-D3 基礎圖表:多線折線圖
下一篇
Day28-D3 進階圖表:氣泡圖
系列文
三十天成為D3.js v7 好手30

尚未有邦友留言

立即登入留言