昨天的文章講完如何繪製長條圖後,今天要來講點進階版的長條圖了,其實說進階也沒到多進階就只是讓同一個 band 可以多畫一些 rect
出來形成多重長條圖而已XD
廢話不多說馬上開始今天的文章內容吧!
這邊先稍微提一下等等流程需要的小知識,既然現在要做的是多重長條圖,可以知道我們在 x 軸上一定會有不只一個輸入區域,這樣才能輸入多筆資料,這邊一共會需要兩個輸入區域,第一個是負責用來設定 band 的輸入區域,另一個則是負責該 band 上每條長條圖自己的輸入區域,而 y 軸就一樣維持不變了,畢竟 y 軸就是負責數值而已。
再來就是會進行兩次的 d3.data().enter()
,第一次的 d3.data().enter()
是用來設定 band 用的,第二次的 d3.data().enter()
就是要用來繪製每個 band 裡面的長條圖啦!
有了以上的兩個觀念後再來看底下的繪製流程就會比較容易懂了~
一開始還是一樣初始化 svg ,作法就跟昨天的文章一樣,只是這邊筆者會再用一個變數來代表 rect
的寬度。
const margin = { top: 10, right: 35, bottom: 20, left: 40 },
width = document.querySelector(`#${root}`).clientWidth,
height = width,
rectWidth = 24
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 了,作法也跟昨天的文章一樣,不同的地方在於今天的 x 軸會有兩種不同的比例尺以及輸出區域。
// x0 代表的是一個一個的 band
const x0 = d3
.scaleBand()
// 為了讓輸出區域即便再平移之後也可以完整顯示出 x 座標,扣掉左右兩邊的邊界
.range([0, width - margin.left - margin.right])
// x1 代表的是 band 中的每個 rect
const x1 = d3
.scaleBand()
.range([0, 2 * rectWidth + 5])
const y = d3.scaleLinear().range([height, 0])
解決了比例尺以及輸出區域後,再來就是要解決輸入區域了,這邊因為上面 x 軸的輸出區域寫了兩種,所以底下的輸入區域也要寫想兩種出來喔!
// 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
}
x0.domain(data.map(el => el.name))
x1.domain(data[0].series.map(el => el.name))
y.domain([0, endPoint])
完成了 svg 的最初始設定後接下來就是要傳資料進去了,這邊跟昨天的文章不一樣的地方就在於會傳兩次 d3.data()
進去,兩次都是做不同的事情,第一次的 d3.data().enter()
是用來設定 band 用的,第二次的 d3.data().enter()
就是要用來繪製每個 band 裡面的長條圖。
svg
.selectAll('.slice')
.data(data)
.enter()
.append('g')
.attr('class', 'g')
.attr('transform', d => `translate(${x0(d.name)}, 0)`)
.selectAll('rect')
.data(d => d.series)
.enter()
.append('rect')
.attr('x', d => x1(d.name))
.attr('width', rectWidth)
.attr('y', d => y(d.value))
.attr('height', d => height - y(d.value))
.attr('fill', d => chartColor[d.name].color)
.attr('transform', `translate(${x0.bandwidth() / 2 - rectWidth - 2}, 0)`)
解決了資料問題之後,最後就是要畫 Axis 了,這樣就可以把圖表順利繪製完成了~~
// add the x Axis
svg
.append('g')
.attr('transform', `translate(0, ${height})`)
.attr('class', 'xaxis')
.call(d3.axisBottom(x0).tickSizeOuter(0))
// add the y Axis
svg
.append('g')
.attr('class', 'yaxis')
.call(d3.axisLeft(y).ticks(5))
今天一樣在 GitHub 上也有完整的程式碼,有興趣的讀者歡迎點選下面的連結參考範例碼。
今天介紹了多重長條圖,沒想到可以用昨天的觀念再加一些設定進去就跑出一張全新的圖表了,有沒有突然覺得好像沒有 D3 做不出來的圖表了XD
如果有什麼問題歡迎在下面留言給我,沒問題的話明天要來介紹折線圖了。