本篇大綱:開放資料下載、繪製長條圖
今天的一天一圖表,我們來畫圖表世界中最常見的 長條圖
吧!長條圖系列總共分成三部曲,分別是「基礎長條圖、複數長條圖、堆積長條圖」,今天先來看看基礎的第一部曲~
為了讓我們畫的圖表更接近真實社會的資料,我這次選用台灣電力公司提供的開放資料 — 各縣市售電資訊。台電目前只提供 xls 檔案,但由於D3無法接受這種檔案格式,因此我們要把載下來的 xls 檔另存成 csv 檔。
我們先把2021/07跟2021/08的售電資訊載下來並打開,檔案裡面第一行跟最後一行使用了合併儲存格的方式去記錄資料,但這樣的方式另存成csv檔時會出錯,所以我們先將第一行跟最後一行都刪掉
接著再把這兩份資料另存成csv檔。其實這個不是很好的做法,如果你要替公司的專案製作圖表的話,記得跟後端溝通好你需要的資料格式,而不是用這種直接修改資料的方式。這邊因為只是示範,所以就先用偷吃步的方式來處理~
確定了要使用哪些資料後,我們先來看看這次要做的圖表以及互動效果吧!我們這次要做的圖表如下:
畫面與互動效果包含:
現在我們開始畫圖吧!首先,一樣先建立圖表區塊與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);
// 接下來的程式碼放這邊...
// 接下來的程式碼放這邊...
// 接下來的程式碼放這邊...
}
我們先把目前得到的資料印出來看一下
這是我們拿到的資料~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都是由原點從上而下繪製,如果我們想繪製正確高度的長條圖,就要把高度設定成從「圖表高度 — 直條圖高度」的地方開始繪製
,才能畫出正確高度的圖表
// 開始建立長條圖
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')
這樣就建立完長條圖啦!長條圖真的算是最基本間單的圖表~
為了增加點難度,我們來加上 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,想直接操作圖表的則去 Github Page 吧!請自行取用~