從今天開始的文章就正式進入實作的過程啦~ 這次的系列文預計會帶給大家 5 種不一樣的圖表,視覺化是個非常深奧的世界,筆者我也只會一點皮毛而已XD
所以這次系列文帶給大家的圖表都會偏向是日常最常使用,然後也會稍微講一下這些圖表適用在哪些場景,畢竟在視覺化的世界中是沒辦法一張圖表打天下的XD
今天要為各位帶來的是長條圖這個圖表。
長條圖通常是用來反映事物分布、集中情況,所以可以推知統計資料通常都是不連續的數據,這樣才能比較各類量的大小,如果是上述的這種情況,就比較推薦使用長條圖進行視覺化的圖表。
長條圖一般適合比較各項目類別差異量或隨時間數量變化,數據之間利用量化或長度的圖形來呈現,例如,年度間每月降雨量情形,或者每季各項商品銷售量的高低差異。
想要在圖表中顯示直方圖必需要用到 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()
喔!
// 設定 x 以及 y 座標的比例尺以及輸出區域
const x = d3
.scaleBand()
.range([0, width])
const y = d3.scaleLinear().range([height, 0])
解決了比例尺以及輸出區域後,再來就是要解決輸入區域了,由於長條圖適用的數據為不連續數據,所以 x 軸的輸入區域要填入完整的資料不能只填最大最小值喔!但 y 軸由於都是數值的關係所以就可以填入最大最小值了,這裡 y 軸會帶入筆者之前分享給大家參考用的 getSmartEndpoint()
來進行 y 軸的最大值調整。
// 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 。
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 了,這樣就可以把圖表順利繪製完成了~~
// 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 上,有興趣想要參考的讀者歡迎上去參考這些範例碼。
今天介紹了長條圖的做法以及適用時機,希望在視覺化的世界中能幫助到大家有初步的了解。
如果有什麼問題歡迎在下面留言給我,沒問題的話明天要來介紹比較進階一點的長條圖了。