iT邦幫忙

2021 iThome 鐵人賽

DAY 23
0
Modern Web

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

Day23-D3 基礎圖表:複數長條圖

  • 分享至 

  • xImage
  •  

本篇大綱:開放資料下載、本次範例的畫面與互動效果、複數長條圖的繪製關鍵、繪製複數長條圖

今天的一天一圖表,我們要來畫圖表長條圖第二部曲 — 複數長條圖!
https://ithelp.ithome.com.tw/upload/images/20211005/201349307BuJaRleJg.jpg

複數長條圖主要是用來比較 某項目在不同時間的變化,或是 同一時間不同項目的差異。由於有多項資料要比較,因此除了XY軸之外,最好還要有資料的標籤,才能讓看圖表的人更了解這張圖想比較什麼東西。

https://ithelp.ithome.com.tw/upload/images/20211005/20134930HrVjCUBiYN.jpg

開放資料下載

為了更貼近真實,我們這次一樣使用政府提供的公開資料:臺南市勞動人口-依年齡別區分 來當作此次圖表的資料!這次的資料一樣是csv檔案,打開檔案的結構如下

https://ithelp.ithome.com.tw/upload/images/20211005/20134930KW86JjxeGy.jpg

由於是 csv 檔,所以我們不用進行其他處理,直接拿來用就可以了 (耶比~)

本次範例的畫面與互動效果

這次我們要做的範例畫面與互動效果有:

  • 多條直條圖呈現
  • 滑鼠 hover 到長條圖時,會從此長條圖拉出一條水平線,連到最左邊的Y軸
  • 滑鼠 hover 到長條圖時,Y軸會呈現此條資料的數值

https://i.imgur.com/4ZFQ1Zd.gif

複數長條圖的繪製關鍵

繪製複數長條圖的關鍵是 多加一條X軸的比例尺 。一般的長條圖只有一條X軸跟一個X軸的比例尺,所有的資料會根據這條X軸去排列;但複數長條圖的關鍵是另外整理一個新的資料陣列放要比較的資料,接著用這個新的資料陣列再多設定一個新的X軸比例尺。這樣講可能很模糊,我們直接來看程式碼比較清楚。

繪製複數長條圖

首先,我們先將資料取回來,並且建立svg畫面

// css
.chart{
    width: 100%;
    min-width: 300px;
    margin: auto;
}
// html
<div class="chart"></div>
let data = []
async function getData() {
    // 取資料
    dataGet = await d3.csv('./data/tainan_labor_force_population.csv')
    data = dataGet
    console.log(data)
    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,
          margin = 20,
          marginBottom = 100

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

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

d3.select(window).on('resize', drawBarChart);

接著,我們先把基本的XY軸、以及XY軸的比例尺建立出來

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

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

const xAxis = d3.axisBottom(xScale)

// 呼叫繪製x軸、調整x軸位置
const xAxisGroup = svg.append("g")
                      .call(xAxis)
                      .attr("transform", `translate(0,${rwdSvgHeight - marginBottom})`)

// 設定要給 Y 軸用的 scale 跟 axis
const yScale = d3.scaleLinear()
                .domain([0, 600])
                .range([rwdSvgHeight - marginBottom, 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)`)

再來!最重要的部分來了!我們把要比較的資料抓出來建立新的陣列,並且建立新的X軸比例尺

const subgroups =  Object.keys(data[0]).slice(1)

// 第二條X軸的比例尺,用來設定多條bar的位置
const xSubgroup = d3.scaleBand()
                    .domain(subgroups)
                    .range([0, xScale.bandwidth()])
                    .padding([0.05])

我們可以把新的資料陣列 console 出來看看
https://ithelp.ithome.com.tw/upload/images/20211005/20134930IiDAioWbIi.jpg

這個就是我們要分組的陣列啦~~接著,我們來設定一下每個組別的顏色

// 設定不同 subgorup bar的顏色
const color = d3.scaleOrdinal()
  .domain(subgroups)
  .range(['#ff2d85','#4a4ae0','#4daf4a', '#f29909'])

然後就是建立長條圖啦!

// 開始建立長條圖
const bar = svg.append('g')
               .selectAll('g')
               .data(data)
               .join('g')
               .attr('transform',  d => `translate(${xScale(d['年度'])}, 0)`)
               .selectAll('rect')
               .data(d => {
                  return subgroups.map(key=>{
                      return {key:key, value:d[key]};})
                 })
               .join('rect')
                 .attr('x', d => xSubgroup(d.key))
                 .attr("y", d => yScale(d.value))
                 .attr("width", xSubgroup.bandwidth())
                 .attr("height", d =>{
                    return (rwdSvgHeight-marginBottom) - yScale(d.value)})
                 .attr("fill", d => color(d.key))
                 .style('cursor', 'pointer')

這樣就建立好長條圖囉~剛剛有說過,複數長條圖的標籤很重要,因此我們還要加上最下方的標籤

// 加上下方分類標籤
const tagsWrap =  svg.append('g')
     .selectAll('g')
     .attr('class', 'tags')
     .data(subgroups)
     .enter()
     .append('g')
     .attr('transform', "translate(-70,0)")
    
tagsWrap.append('rect')
     .attr('x', (d,i)=> (i+1)*marginBottom*1.3)
     .attr('y', rwdSvgHeight-marginBottom/2)
     .attr('width', 20)
     .attr('height', 20)
     .attr('fill', d => color(d))

tagsWrap.append('text')
        .attr('x', (d,i)=> (i+1)*marginBottom*1.3)
        .attr('y', rwdSvgHeight-10)
        .style('fill', '#000')
        .style('font-size', '12px')
        .style('font-weight', 'bold')
        .style("text-anchor", 'middle')
        .text(d=>d)

這樣基本的圖表就好囉
https://ithelp.ithome.com.tw/upload/images/20211005/201349308C29fg510e.jpg

再來我們來綁定滑鼠的事件,同時使用 d3.pointer( ) 的方法來建立水平軸線跟資料的標示


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

function handleMouseOver(d, i){
      // console.log(d.target.__data__)
      const pt = d3.pointer(event, svg.node())

      // 加上文字標籤
      svg.append('text')
         .attr('class', 'infoText')
         .attr('y', yScale(d.target.__data__['value']))
         .attr("x", margin*2)
         .style('fill', '#000')
         .style('font-size', '18px')
         .style('font-weight', 'bold')
         .style("text-anchor", 'middle')
         .text(d.target.__data__['value'] + '千人')
    
      // 加上軸線
      svg.append('line')
         .attr('class', 'dashed-Y')
         .attr('x1', margin*2)
         .attr('y1', yScale(d.target.__data__['value']))
         .attr('x2', pt[0])
         .attr('y2', yScale(d.target.__data__['value']))
         .style('stroke', 'black')
         .style('stroke-dasharray', '3' )
 }

function handleMouseLeave(){
  // 移除文字、軸線標籤
  svg.select('.infoText').remove()
  svg.select('.dashed-Y').remove()
}

大功告成~


Github Page 圖表與 Github 程式碼

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


上一篇
Day22-D3 基礎圖表:長條圖
下一篇
Day24-D3 基礎圖表:堆疊長條圖
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言