iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
0
Modern Web

React + D3 的正確姿勢系列 第 22

Day22-bar chart(using D3.js)

  • 分享至 

  • xImage
  •  

前言

從今天開始的文章就正式進入實作的過程啦~ 這次的系列文預計會帶給大家 5 種不一樣的圖表,視覺化是個非常深奧的世界,筆者我也只會一點皮毛而已XD

所以這次系列文帶給大家的圖表都會偏向是日常最常使用,然後也會稍微講一下這些圖表適用在哪些場景,畢竟在視覺化的世界中是沒辦法一張圖表打天下的XD

今天要為各位帶來的是長條圖這個圖表。

長條圖

長條圖通常是用來反映事物分布、集中情況,所以可以推知統計資料通常都是不連續的數據,這樣才能比較各類量的大小,如果是上述的這種情況,就比較推薦使用長條圖進行視覺化的圖表。

長條圖一般適合比較各項目類別差異量或隨時間數量變化,數據之間利用量化或長度的圖形來呈現,例如,年度間每月降雨量情形,或者每季各項商品銷售量的高低差異。

rect

想要在圖表中顯示直方圖必需要用到 svg 中的 rect 這個元素, rect 是代表長方形的意思,其實長條圖簡單來看就是把多個長方形拼在一起,所以會用到 rect 好像也是蠻正常的XD

要如何設定 rect 的大小呢?很簡單只要設定好 x 以及 y 就好了,這邊的 x 就是 x 軸中的資料 y 則是 y 軸上的資料,由於 d3.data() 會把陣列中的資料一筆一筆傳進去,所以在設定 x 跟 y 的時候也只要把當前的資料利用 callback 的方式傳進去 x 軸以及 y 軸即可,寫法會像這樣:

svg
  .data(data)
  .enter()
  .attr(x, d => x(d.name))
  .attr(y, d => y(d.value))

繪製流程

講完長條圖的相關基本知識以及繪製需要用到的元素後,接下來就跟著筆者的腳步一步一步的繪製出圖表來吧!接下來的動作都會使用到以前文章的技巧,對於 D3 整個繪製方法還不懂的讀者歡迎閱讀筆者之前的文章喔!

首先是初始化 svg ,還記得昨天文章教的如何完整顯示圖表嗎?今天的文章也會用到這個觀念,寫法如下:

const margin = { top: 10, right: 35, bottom: 20, left: 40 },
  width = document.querySelector(`#${root}`).clientWidth,
  height = width
    
const svg = d3
  .select(document.querySelector(`#${root}`))
  .append('svg')
  .attr('width', width)
  .attr('height', height + margin.top + margin.bottom)
  .append('g')
  .attr('transform', `translate(${margin.left}, ${margin.top})`)

初始化 svg 後接下來就是要設定 scale 以及 range 了,由於長條圖適用的數據為不連續數據,所以這邊的比例尺使用的是 d3.scaleBand() 而不是 d3.scaleLinear() 喔!

Day13-D3基本介紹(scale、range)

// 設定 x 以及 y 座標的比例尺以及輸出區域
const x = d3
  .scaleBand()
  .range([0, width])

const y = d3.scaleLinear().range([height, 0])

解決了比例尺以及輸出區域後,再來就是要解決輸入區域了,由於長條圖適用的數據為不連續數據,所以 x 軸的輸入區域要填入完整的資料不能只填最大最小值喔!但 y 軸由於都是數值的關係所以就可以填入最大最小值了,這裡 y 軸會帶入筆者之前分享給大家參考用的 getSmartEndpoint() 來進行 y 軸的最大值調整。

Day14-D3基本介紹(domain、endpoint)

// val 為 d3.max() 得到的資料最大值
function getSmartEndpoint(val) {
  // 先取得最大值的位數,並算出這個位數的最小值
  let count = Math.floor(val).toString().length - 1
  let step = Math.pow(10, count)

  // 以 5 的倍數為基準,假如最大值除以此位數的最小值小於 5
  if (val / step < 5) {
  // 將這個位數最小值砍半,這樣之後就會是以 5 為基準了
    step = step / 2
  } 
  
  count = Math.ceil(val / step)
  
  return count * step
}

x.domain(data.map(d => d.name))
y.domain([0, endPoint])

完成了 svg 的最初始設定後接下來就是要傳資料進去了,這邊為了要讓圖表看起來更好看一些,筆者會把每個 rect 平移到相對應座標點的中間,這邊為了要算出中心點到 rect 的距離會用到 x.bandwidth() 這個 API , band 可以用 scaleBand 的方式來思考, scaleBand 會把我們剛剛 domain 傳進去的資料切成一個一個的 band ,所以 bandwidth 就是這幾個 band 的寬度啦!為了要對應到中間所以這裡就會把 bandwidth / 2 。

Day18-D3基本介紹(data)

svg
  .selectAll('rect')
  .data(data)
  .enter()
  .append('rect')
  .attr('x', d => x(d.name))
  .attr('width', 24)
  .attr('y', d => y(d.value))
  .attr('height', d => height - y(d.series[0].value))
  .attr('fill', d => chartColor[d.name].color)
  .attr('transform', `translate(${x.bandwidth() / 2 - 12}, 0)`)

解決了資料問題之後,最後就是要畫 Axis 了,這樣就可以把圖表順利繪製完成了~~

Day19-D3基本介紹(Axis)

// add the x Axis
svg
  .append('g')
  .attr('transform', `translate(0, ${height})`)
  .attr('class', 'xaxis')
  .call(d3.axisBottom(x).tickSizeOuter(0))

// add the y Axis
svg
  .append('g')
  .attr('class', 'yaxis')
  .call(d3.axisLeft(y).ticks(5))

組合起來

最後筆者有把上面流程所講的程式碼都丟到 GitHub 上,有興趣想要參考的讀者歡迎上去參考這些範例碼。

bar chart

總結

今天介紹了長條圖的做法以及適用時機,希望在視覺化的世界中能幫助到大家有初步的了解。

如果有什麼問題歡迎在下面留言給我,沒問題的話明天要來介紹比較進階一點的長條圖了。


上一篇
Day21-D3基本介紹(transform)
下一篇
Day23-multi bar chart(using D3.js)
系列文
React + D3 的正確姿勢30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言