iT邦幫忙

2021 iThome 鐵人賽

DAY 22
1
Modern Web

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

Day22-D3 基礎圖表:長條圖

本篇大綱:開放資料下載、繪製長條圖

今天的一天一圖表,我們來畫圖表世界中最常見的 長條圖 吧!長條圖系列總共分成三部曲,分別是「基礎長條圖、複數長條圖、堆積長條圖」,今天先來看看基礎的第一部曲~

開放資料下載

為了讓我們畫的圖表更接近真實社會的資料,我這次選用台灣電力公司提供的開放資料 — 各縣市售電資訊。台電目前只提供 xls 檔案,但由於D3無法接受這種檔案格式,因此我們要把載下來的 xls 檔另存成 csv 檔。

我們先把2021/07跟2021/08的售電資訊載下來並打開,檔案裡面第一行跟最後一行使用了合併儲存格的方式去記錄資料,但這樣的方式另存成csv檔時會出錯,所以我們先將第一行跟最後一行都刪掉
https://ithelp.ithome.com.tw/upload/images/20211004/20134930A5bu9hibo6.jpg

接著再把這兩份資料另存成csv檔。其實這個不是很好的做法,如果你要替公司的專案製作圖表的話,記得跟後端溝通好你需要的資料格式,而不是用這種直接修改資料的方式。這邊因為只是示範,所以就先用偷吃步的方式來處理~

本次圖表的畫面與互動效果

確定了要使用哪些資料後,我們先來看看這次要做的圖表以及互動效果吧!我們這次要做的圖表如下:
https://i.imgur.com/h9kDRSg.gif

畫面與互動效果包含:

  • 滑鼠 hover 時,長條圖會變色+呈現用電度數
  • 資料切換時圖表更改

繪製長條圖

現在我們開始畫圖吧!首先,一樣先建立圖表區塊與svg,並且將剛剛下載的資料用 d3.csv的方式取得

// css
.chart{
    width: 100%;
    min-width: 300px;
    margin: auto;
}
//html
<h4 class="mt-3">台灣各縣市每月住宅售電</h4>
<div class="chart"></div>
<div class="btnWrap">
  <button class="btn btn-primary July">2021 7月</button>
  <button class="btn btn-primary August">2021 8月</button>
</div>
// js

let data = []
async function getData() {
  // 取資料
  dataGet = await d3.csv('./data/202107_Electric.csv')
  console.log(dataGet)
  data = dataGet
  drawBarChart()
};
getData()

// RWD
function drawBarChart(){
  // 刪除原本的svg.charts,重新渲染改變寬度的svg
  d3.select('.chart svg').remove();

  // RWD 的svg 寬高
  const rwdSvgWidth = parseInt(d3.select('.chart').style('width')),
        rwdSvgHeight = rwdSvgWidth*0.8,
        margin = 40;

  const svg = d3.select('.chart')
                .append('svg')
                .attr('width', rwdSvgWidth)
                .attr('height', rwdSvgHeight);

	// 接下來的程式碼放這邊...
	// 接下來的程式碼放這邊...
	// 接下來的程式碼放這邊...
}

我們先把目前得到的資料印出來看一下
https://ithelp.ithome.com.tw/upload/images/20211004/20134930xqhgfMUn5d.jpg

這是我們拿到的資料~A 代表的是「家用住宅的售電度數」,我們的圖表就先來比較各縣市家用售電度數吧!要畫圖表前要先確定XY軸分別使用哪些資料,我決定X軸放縣市,Y軸就放家用售電度數,因此我要把XY軸需要的資料都分別拉出來整理成陣列,這樣才能帶給比例尺去做運算

// map 資料集
const  xData = data.map((i) => i['縣市']);

//由於台電提供的A售電數度是帶有千分號的字串,我們要先把它處理成數字
const  yData = data.map((i) => parseInt(i['A.售電量(度)'].split(',').join('')));

整理出XY軸的資料集後,接著來畫XY軸的比例尺與軸線吧

// 設定要給 X 軸用的 scale 跟 axis
const xScale = d3.scaleBand()
                .domain(xData)
                .range([margin*2, rwdSvgWidth - margin/2]) // 寬度
                .padding(0.2)

const xAxis = d3.axisBottom(xScale)

// 呼叫繪製x軸、調整x軸位置
const xAxisGroup = svg.append("g")
                      .call(xAxis)
                      .attr("transform", `translate(0,${rwdSvgHeight - margin})`)
                      .selectAll("text") // 調整刻度文字標籤傾斜
                      .attr("transform", "translate(-10,0)rotate(-45)")
                      .style("text-anchor", "end");

// 設定要給 Y 軸用的 scale 跟 axis
const yScale = d3.scaleLinear()
                .domain([0, d3.max(yData)])
                .range([rwdSvgHeight - margin, margin]) // 數值要顛倒,才會從低往高排
                .nice() // 補上終點值

const yAxis = d3.axisLeft(yScale)
                .ticks(5)
                .tickSize(3)

// 呼叫繪製y軸、調整y軸位置
const yAxisGroup = svg.append("g")
                      .call(yAxis)
                      .attr("transform", `translate(${margin*2},0)`)

再來就是畫長條圖啦!長條圖我們會使用< rect >來建立,要特別注意的是 height 的部分。由於svg都是由原點從上而下繪製,如果我們想繪製正確高度的長條圖,就要把高度設定成從「圖表高度 — 直條圖高度」的地方開始繪製,才能畫出正確高度的圖表
https://ithelp.ithome.com.tw/upload/images/20211004/20134930efYrrWCFZ0.jpg

// 開始建立長條圖
const bar = svg.selectAll("rect")
              .data(data)
              .join("rect")
              .attr("x", d => xScale(d['縣市'])) // 讓長條圖在刻度線中間
              .attr("y", d => yScale(parseInt(d['A.售電量(度)'].split(',').join(''))))
              .attr("width", xScale.bandwidth())
              .attr("height", d => {
                  return (rwdSvgHeight - margin) - yScale(parseInt(d['A.售電量(度)'].split(',').join('')))
                })
              .attr("fill", "#69b3a2")
              .attr('cursor', 'pointer')

這樣就建立完長條圖啦!長條圖真的算是最基本間單的圖表~
https://ithelp.ithome.com.tw/upload/images/20211004/20134930KzI1qlzi77.jpg

為了增加點難度,我們來加上 mousehover 跟 mouseleave 的效果。 < rect > 綁定的資料藏在 d.target.__data__ 中,找到之後就能把文字標籤設定成每個DOM元素各別綁定的資料

bar.on("mouseover", handleMouseOver)
   .on("mouseleave", handleMouseLeave)

function handleMouseOver(d, i){
      // console.log(d)
      // console.log(d.target.__data__)
      d3.select(this)
        .attr('fill', 'red')
      
      // 加上文字標籤
      svg.append('text')
         .attr('class', 'infoText')
         .attr('y', yScale(parseInt(d.target.__data__['A.售電量(度)'].split(',').join('')))-20)
         .attr("x", xScale(d.target.__data__['縣市']) + 50)
         .style('fill', '#000')
         .style('font-size', '18px')
         .style('font-weight', 'bold')
         .style("text-anchor", 'middle')
         .text(d.target.__data__['A.售電量(度)'] + '度');
    }

    function handleMouseLeave(){
      d3.select(this)
        .attr('fill', '#69b3a2')

      // 移除文字標籤
      svg.select('.infoText').remove()
    }

這樣就完成啦!基礎的長條圖範例就到這邊~明天再來看看長條圖二部曲:複數長條圖吧!


Github Page 圖表與 Github 程式碼

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


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

尚未有邦友留言

立即登入留言