Data visualization 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/data-visualization/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Tue, 20 Jul 2021 09:10:55 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png Data visualization 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/data-visualization/ 32 32 用D3.js製作視覺化的日常作息方塊圖(直播筆記) https://creativecoding.in/2021/07/23/%e7%94%a8d3-js%e8%a3%bd%e4%bd%9c%e8%a6%96%e8%a6%ba%e5%8c%96%e7%9a%84%e6%97%a5%e5%b8%b8%e4%bd%9c%e6%81%af%e6%96%b9%e5%a1%8a%e5%9c%96/ Fri, 23 Jul 2021 02:03:00 +0000 https://creativecoding.in/?p=1308 D3.js工具可以把大筆的資料視覺化呈現於網頁上,本篇文章利用淺顯易懂的解說,一步步寫出如同Google日曆般的日常生活作息方塊圖。

這篇文章 用D3.js製作視覺化的日常作息方塊圖(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
本文翻自 [週四寫程式系列] – 來做視覺化日常作息的方塊圖吧!,若是對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片跟著動手做,也附上這次成品

D3.js視覺化日常作息方塊圖成品
D3.js視覺化日常作息方塊圖成品

在資料科學中經常會面臨到的難關,就是資料看起來髒亂必須要重新整理,僅是物件或陣列,無法快速了解資料內容,透過資料視覺化,可以將資料轉換成另一種形式呈現,方便使用者閱讀。想做這個主題的緣由是因為,老闆會去紀錄每天的工作項目,想要利用這個工具,瞭解每天的工作內容及分布。

接下來老闆就要帶大家使用 D3 來達成資料視覺化,將原本無趣的資料,透過 D3 變成七個長條圖 ,一眼看出一周行事曆中每天各時段的工作分配。

製作的過程中可以分為兩個層次去思考:

  1. 一天的工作分配要怎麼畫?在繪製一天的行程中,帶大家認識 D3 繪製資料的方式。
  2. 一周的行程怎麼繪製?當完成一天後,我們只要將這個流程重複執行,就能產出一周的畫面。

此外,老闆也出了一個小題目給大家挑戰:如何實現多重選單中,項目不能重複。有興趣的同學可以在影片中,看看老闆如何解決這個問題。希望透過這篇文章讓大家發現 D3 真正的魅力,接著就讓我們拿起神奇的木槌 D3,將資料變得有趣吧!

如果對於老闆的教學方式有興趣,或是想看看老闆其他創作內容,歡迎大家去支持老闆的網頁程式入門或是網頁特效入門課程。

這次直播筆記會帶大家學會以下內容:

  • 利用 D3 繪製視覺化資料
  • 利用 js api ( map, filter )進行資料再處理
  • 製作一周工作項目產生器

事前準備

資料處理

《D3視覺化日常作息方塊圖》資料處理概念說明
《D3視覺化日常作息方塊圖》資料處理概念說明

資料視覺化之前,必須先準備好資料才能繼續,這邊會需要一周的行程,記錄每小時的工作項目。

第一步:先理解單日的行程,完成後就能將這個流程重複七次,達成需求。

第二步:將每天的行程(上圖左)群組化(上圖右),例如時段 1~3 都在做 b 這件事,則產出的資料會有類似以下結構。

var day = [
  {
    name: 'b',
    startTime: 1,
    endTime: 3,
  },
  ...
]

了解資料處理的邏輯後,需要原始的資料檔才能有下一步的畫面處理與呈現,可以利用關鍵字查詢 csv to json,將完成的一周行事曆檔案(excel)匯出成 csv 檔,再透過線上工具轉成 json 格式,這邊提供老闆使用的線上工具。如果想直接進入開發,除了可以使用下方老闆提供的24小時 raw_data 外,後面的內容老闆也會教大家亂數產生資料的方法。

var raw_data = [
  {
    "time": 0,
    "thing": "工作"
  },
  {
    "time": 1,
    "thing": "工作"
  },
  {
    "time": 2,
    "thing": "工作"
  },
  {
    "time": 3,
    "thing": "睡覺"
  },
  {
    "time":4,
    "thing": "睡覺"
  },
  {
    "time": 5,
    "thing": "睡覺"
  },
  {
    "time": 6,
    "thing": "睡覺"
  },
  {
    "time": 7,
    "thing": "工作"
  },
  {
    "time": 8,
    "thing": "睡覺"
  },
  {
    "time": 9,
    "thing": "睡覺"
  },
  {
    "time": 10,
    "thing": "做作業"
  },
  {
    "time": 11,
    "thing": "做作業"
  },
  {
    "time": 12,
    "thing": "工作"
  },
  {
    "time": 12,
    "thing": "工作"
  },
  {
    "time": 13,
    "thing": "工作"
  },
  {
    "time": 14,
    "thing": "睡覺"
  },
  {
    "time": 15,
    "thing": "睡覺"
  },
  {
    "time": 16,
    "thing": "工作"
  },
  {
    "time": 17,
    "thing": "工作"
  },
  {
    "time": 18,
    "thing": "睡覺"
  },
  {
    "time": 19,
    "thing": "散步"
  },
  {
    "time": 20,
    "thing": "工作"
  },
  {
    "time": 21,
    "thing": "工作"
  },
  {
    "time": 22,
    "thing": "散步"
  },
  {
    "time": 23,
    "thing": "工作"
  }]

開發環境

開發使用 codepen 線上撰寫程式碼,大家可以先將環境設定成跟老闆一樣,如果想知道較詳細的設定,可以參考成品老闆的設定。

會使用到 ES6 的語法,增加開發的效率

  • html: Pug
  • javascript:
    • Babel:為了加速開發的時間,我們會使用到 ES6 的語法
    • D3.js

接下來會使用到的 API:

這次專案會使用以下的 api,先重點整理給大家,不清楚的地方,可以一步步跟著老闆操作,了解每個 api 使用時機。

D3

  • .range(1, 5):產出陣列,陣列內為 [ 1, 2, 3, 4 ]
  • 選擇器
    • .select(“body”):取得 body 這個 tag (可填入其他值來取得其他目標物)。
    • .selectAll(“rect.num”):選擇畫面全部的 rect 且含有 class=”num” 的 tag。
  • .data(data1):將資料 data1 填入。
  • 資料綁定 tag 的方式:
    • .enter():綁定資料時,選取的元素不夠綁定時,該筆資料會被分配為 enter 類型,不足的 tag 可以搭配 .append 使用。
      • .append(“svg”):插入一個 svg tag (可選擇其他 tag)
    • .exit():綁定資料時,選取的元素多於資料時,該元素會被分配為 exit 類型。
      • .remove():刪除符合條件的元素,可以搭配 .exit() 刪除多餘的元素。
    • .update() 將新的資料直接覆蓋到對應的元素上。
  • .attr(“width”, 700) 為目標物賦予寬 700px 的屬性 (可填入其他值,賦予其他動態屬性,例如:邊框、顏色、x 座標…等)。
  • .domain(ary1):可以與 .range(ary2) 搭配,將定義的資料 ary1 對應到 range 中的 ary2。

javascript

  • .filter(d, i, arr):將原本陣列中符合條件的值回傳成新陣列,文件
    • d 為每次要處理的該筆資料
    • i 為該筆資料的索引值
    • arr 為原始的整串陣列
  • .map():把原本陣列中每個物件再處理後回傳組成新陣列。
  • .reduce():將一組陣列資料經由累加器,利用回呼函式,再處理成為單一值,文件

跟著老闆開始動手做

D3 是資料導向的工具,會針對一筆資料去做事情,也可以將 D3 理解成是一個工具,能對一組資料用同樣的顯示方法去展示資料,所以最後的程式的流程會是,將處理後的一周行事曆陣列資料,分成七天的群組資料渲染,再針對每一天的工作內容進行渲染,就能顯示視覺化的資料。

處理資料前,老闆先帶大家將 D3 這把槌子準備好,了解基本操作後,只要將資料填入,就能跑出我們要的結果。

D3 小試身手

進入製作前,老闆先帶大家畫出七個方塊,來熟悉 D3 的操作。

產出陣列 1~7

透過 D3 的 API 我們能夠快速產出一組陣列資料,如果使用 javascript 會寫了一大段 code。

// D3
var dataD3 = d3.range(1,8)
console.log(dataD3) // [1,2,3,4,5,6,7]

// javascript
var data = Array.from({length: 7}, function (d, i) {
  return i + 1
})
console.log(data) // [1,2,3,4,5,6,7]

// ES6
var dataES6 = Array.from({length: 7}, (d, i) =>i + 1)
console.log(dataES6) // [1,2,3,4,5,6,7]

資料與畫面綁定邏輯

在繪製畫面之前,讓我們先了解 D3 資料繪製到畫面的操作邏輯,假設今天有多筆資料,想要綁定到畫面中 class=”target” 的 div 上,第一步驟會先取得畫面符合規則的 DOM,接著會出現以下三種狀況與處理方式:

  • 指定 div 數量與資料數量相同:直接使用 update() 更新畫面
  • 指定 div 不存在或數量不夠:將未放入畫面的資料利用 enter() 存著,再使用 .append() 增加不足的 tag
  • 指定 div 數量多於資料數量:將多餘的 DOM 利用 .exit() 存著,使用 remove() 移除多餘的 DOM

產出 svg 圖表

有了資料後,我們也要產出畫面來將資料顯示,這邊要提醒大家 D3 是動態屬性,不是使用 css 改變外觀,而是使用 attr 來改變屬性。我們先存一個變數 svg 作為待會繪製圖表的位置,使用 api 選到 body,插入一個 DOM svg,將這個 DOM 的寬高皆設定成 700,畫面上看不到,這時去檢查開發者工具,會發現跑出一個新的 svg tag。

var data = d3.range(1,8)
var svg = d3.select("body")
            .append("svg")
            .attr("width", 700)
            .attr("height", 700)

有了資料和放這些畫面的位置(var = svg)後,我們來試著將前面產出的七筆資料放到畫面中,首先選擇畫面全部的 rect 且含有 class=”num” 的 tag,將前面產出的 data 使用 .data() 這個 api 插入畫面中。由於 svg 中目前完全沒有符合的 tag 來展示這些資料,我們使用 .enter() 定義這些新資料,並透過 .append() 來增加不足數量的 rect,最後賦予這些 tag 動態屬性。

其中比較特別的是 .attr(“x”, (d, i) => d*100 ),傳入的 function 第一個參數 d 是這筆資料內容,第二個參數 i 是這個參數的索引值。例如以陣列 [ 1, 2, 3 ] 來說,第一個傳入的 (d, i) 參數值分別是 d = 1,i =0。大家也可以將這些值 print 出來了解,譬如在這邊回傳 d * 50 作為 x 的值,就會做出七個水平間距為 50 的方塊了。

影片中老闆是使用 d * 100,由於一開始的 svg 寬度設定只有 700 ,會導致後面的方塊無法顯示,大家在實作上不用擔心。

svg.selectAll("rect.num")
  .data(data)
  .enter().append("rect")
  .attr("width", 50)
  .attr("height", 50)
  .attr("x", (d, i) => d * 100 ) // 與左上角的距離與方塊間距
  .attr("fill", "transparent") // 填色
  .attr("stroke", "black") // 框線
產出SVG七方塊
產出SVG七方塊

處理重複的標籤

我們來製作一整天幾種不同的工作項目,利用這個目標來練習 js 中的 filter 與 map,後面會頻繁地使用到這兩個功能。

我們可以拿前面老闆提供的 raw_data 來加工,首先只需要 raw_data 中做了什麼事情,什麼時間點做的對這個需求來說不重要。透過 map 會回傳新的陣列,其中的條件我們只要改成回傳 thing 即可。map 裡面的 function 為 ES6 的寫法,傳入參數為 o,為當下正在處裡的單筆資料,每次都會回傳 o.thing,所以會產出一個只有工作項目的陣列。

接著利用 filter 來篩掉重複的項目,filter 也會利用傳入的 function 作為規則,回傳一個新的陣列,傳入的三個參數分別為:

  • d : 目前處理的單筆資料
  • i :該資料的 index 值
  • arr:原始的陣列

回傳的條件為arr.indexOf(d) === i,indexOf 會從該陣列中找到第一個符合傳入條件的 index 值,所以當每筆資料在原始陣列搜尋時,和他自己的 index 值一樣,表示它是第一個出現的獨特值。利用這兩個 api 就能獲得所有工作的單一標籤。大家如果不清楚原因,可以在回傳的條件 return 前,console.log 將值 print 出來。

var new_data = raw_data.map((o) => o.thing )
                       .filter((d, i, arr) => arr.indexOf(d) === i)

相同工作的片段時間組合在一起

接下來要處理重複工作的時間段,將相同工作項目的連續時間段再處理成一個一個的群組。我們會使用到 reduce 將一天的行程處理成塊狀的陣列。首先讓我們了解 reduce 傳入的參數以及用法:

.reduce(回傳函式(累加器, 傳入的值, 傳入值的 index, 初始陣列), 初始值)

reduce 能將一個累加器及 array 中的每一個元素,透過回傳函式化為單一值。從下面的程式碼可以知道,我們將一個原始陣列,透過 reduce(),每次傳入兩個值,一開始累加器沒有值,b 為 1,第二次則處理累加器 1 與 2 後為 3,以此類推。

累加器圖示說明
累加器圖示說明
const ary = [1, 2, 3, 4, 5]
const result = ary.reduce((a, b) => a+b)
console.log(result) // 15

傳入 reduce 的參數除了處理的函數外,也可以傳入第二個參數 – 累加器的初始值。跟前面不同的地方是,我們最後想獲得一個陣列,所以將累加器初始值設定為 array。先取得累加器陣列中最後一個值。使用 slice 能將原始陣列依照傳入的數值進行陣列的切割,若是傳入 -1 ,則從原始陣列中的最後一個開始切割,並產生一個新的陣列包含原始陣列的最後一個值。

接著判斷最後做的事情 last_thing 的值,如果 last_el 存在,則最後做的事就是這件事情的 thing 值,也就是 last_el.thing ,否則代表這件事情不存在,也就是這個事件是新的群組。再來該如何判斷與前面的事件不同及如何處理呢?

  • 下一個時段的工作項目與目前時段工作項目一樣,結束時間增加
  • 上一個工作項目不存在或是與目前的工作項目不一樣,新增一筆新的資料,並將它組合進累加器

工作項目不同,表示這是一個新的工作項目,我們必須將它新增到陣列中,這邊使用到 .concat() 來將 accumulator array 與新的陣列合併。如果不清楚中間的操作,可以在處理過程中使用 console.log 去了解每個步驟值的變化。

var join_data = raw_data.reduce((accumulator, el, idx)=>{
  var last_el = accumulator.slice(-1)[0]
  var last_thing = last_el ? last_el.thing : null
  if (last_thing === el.thing) { // 上一件事情與現在的事情相同,調整結束時間
    last_el.end_hour = el.time
    return accumulator
  } else { // 上一件事情與現在的事情不同或是上一件事情不存在,新增一筆新的工作資料到累加器中
    return accumulator.concat([
      {
        thing: el.thing,
        start_hour: el.time,
        end_hour: el.time
      }
    ])
  }
}, []) // 累加器初始值為陣列

畫出一天的行程

有了處理過後的資料,就能夠將資料轉視覺化。前面有提到 D3 畫圖的方式,這邊依序跟大家解釋下面的程式碼:

  • 變數設定:將常用的單位設定成變數,包含單日圖表的寬度、單位小時的高度
  • 準備 svg 畫布:宣告畫布的方式如下,在 body 新增 svg ,並設定長寬
  • 資料填入:先選擇畫面中 svg 中所有有 hour class 的 rect 元件,將資料填入後,因為資料過多, rect 數量不夠,利用 enterappend 來增加數量,並賦予 class="hour" 的屬性
  • y 的起點:每個工作項目 y 座標的起始點都不同,利用開始時間 d.start_hour乘上 hour_height 決定每個 rect 的起始位置
  • rect 的寬高:有了初始位置,我們要將這些 rect 加上寬度及高度,寬度使用我們前面設定的 day_width,高度則是用結束時間 d.end_hour 減掉起始時間d.start_hour乘上單位小時高度 hour_height 即可,要注意的是,相減之後的數值要加上 1,因為若是結束時間和起始時間一樣,表示這個工作維持了一小時
  • 填色:將時段的 rect 加上黑色邊框
var day_width = 30
var hour_height = 20

var svg = d3.select("body")
            .append("svg")
            .attr("width", 700)
            .attr("height", 700)

var hour_rect = svg.selectAll('rect.hour')
                   .data(join_data)
                   .enter().append("rect")
                     .attr("class", "hour")
                     .attr("y", (d) => d.start_hour * hour_height)
                     .attr("width", day_width )
                     .attr("height", (d) => (d.end_hour - d.start_hour + 1) * hour_height )
                     .attr("stroke", "black")

完成之後就可以看到一條全黑的圖,接下來我們要來教大家如何填加上不同顏色

不同工作項目填上不同色

老闆這邊介紹兩個產出色碼的方式給同學

  • scaleLinear:將數值轉換為視覺變量,domain 內容為這個變量的值從多少到多少,range 的內容則是使用什麼值去對應,這邊是利用顏色去對應(圖左)。
  • scaleOrdinal:一個資料對應一個值,不用使用 domain 是因為他會直接依照順序,一個一個對應到後面 range 中的顏色(圖中),超過的值再從頭算起。大家也可以使用這個工具,直接使用 D3 提供的顏色填入 range (圖右)。
var colorize1 = d3.scaleLinear().domain([0,5]).range(["#f22", "#000"])
var colorize2 = d3.scaleOrdinal().range(["#f22", "#000"])
var colorize3 = d3.scaleOrdinal().range(d3.schemePaired)

var hour_rect = svg.selectAll('rect.hour')
  .data(join_data)
  .enter().append("rect")
    .attr("class", "hour")
    .attr("y", (d) => d.start_hour * hour_height)
    .attr("width", day_width )
    .attr("height", (d) => (d.end_hour - d.start_hour + 1) * hour_height )
    .attr("stroke", "black")
    .attr("fill", (d,i) => colorize3(i)) // 將填入的顏色換成變數代入
《D3視覺化日常作息方塊圖》三種不同的填色方式圖示
《D3視覺化日常作息方塊圖》三種不同的填色方式圖示

想達成「同種工作會對應到同一個顏色」,針對這個需求,我們要使用第二種方法。還記得前面有將工作整理成唯一值的陣列 new_data 嗎?利用 indexOf 就能取得這個值在 new_data 中的 index 值,再將這個值傳進前面使用 scaleOrdinal 做出來的工具,就能填上對應的顏色。

接著要在畫面中顯示文字,用前面產出 rect 方式,改產出一樣數量的 text 的元素。將 text 放到畫面中之後,會發現第一筆文字資料消失在畫面中了,因為 svg 文字對齊的屬性與一般文字的樣式不同,只要調整 y 的定位和 sass 樣式,按照下面去操作,文字就會在時段的框框中出現,大家也能按照自己的喜好去調整字級與位置。

JavaScript

var day_width = 80 // 調整單日寬度,讓文字能夠在 rect 內
var hour_height = 20

var hour_rect = svg.selectAll('rect.hour')
  .data(join_data)
  .enter().append("rect")
    .attr("class", "hour")
    .attr("y", (d) => d.start_hour * hour_height)
    .attr("width", day_width )
    .attr("height", (d) => (d.end_hour - d.start_hour + 1) * hour_height )
    .attr("stroke", "black")
    .attr("fill", (d,i) => colorize3(new_data.indexOf(d.thing))) // 改使用 indexOf 來作為 i值傳入

var hour_text = svg.selectAll('text.hour')
  .data(join_data)
  .enter().append("text")
    .attr("class", "hour")
    .attr("y", (d) => d.start_hour * hour_height + 5)
    .attr("x", 5)
    .attr("width", day_width )
    .attr("fill","black")
    .text(d => (d.thing))

CSS(Sass)

text
  alignment-baseline: hanging
  font-size: 12px
《D3視覺化日常作息方塊圖》完成一天的作息方塊圖
《D3視覺化日常作息方塊圖》完成一天的作息方塊圖

產生七天亂數資料

前面帶大家成功跑出一天的資料了,緊接著來準備七天的資料,讓神奇的 D3 槌子敲一下,產出一週的工作視覺化資料。

如何產出一天的亂數資料呢?先列出總共有哪些工作類別,並設定最後一件事為 null 值,在 generate_day_data 中,要亂數產生一組當天的工作項目。

首先, 利用 d3.range(0,24) 可以產出一個陣列內容涵蓋 0~23,利用 map 對這個陣列做加工,if 判斷式為,如果沒有上一件事情或是當下產生的 random 值大於 0.5,就從 catas 隨機中取得一個值做為 lastThing,這會讓下一件事情有一半的機會換值。利用 Math.random()parseInt() 可以產出新的事情,此時 raw_data 可以拿掉固定值,使用這個亂數產生器來產出一天的資料。

  • Math.random() 可以產出 0~1之間的亂數
  • parseInt() 可以將含有小數點的數值轉成整數
var catas = ["工作", "睡覺", "做專案", "吃東西", "散步"]
var lastThing = null
function generate_day_data () {
  return d3.range(0, 24).map((o)=>{
      if(!lastThing || Math.random() > 0.5) {
        lastThing = catas[parseInt(Math.random()*catas.length)]
      }
      return {
        time: o,
        thing: lastThing
      }
  })
}

能夠產生一天的資料後,接著應用前面學到的內容,就能產生一週的工作項目。使用 d3.range() 產出七個值,再利用 map 對每個值去操作,把前面處理 join_data 的內容改成 reduce_time() 傳入的參數為 arr ,之後就能將 generate_day_data 的值傳入,並將這組原始的 data,改裝成有分組的資料。

function reduce_time (arr) {
  return arr.reduce((accumulator, el, idx)=>{
    var last_el = accumulator.slice(-1)[0]
    var last_thing = last_el ? last_el.thing : null
    if (last_thing === el.thing) {
      last_el.end_hour = el.time
      return accumulator
    } else {
      return accumulator.concat([
        {
          thing: el.thing,
          start_hour: el.time,
          end_hour: el.time
        }
      ])
    }
  }, [])
}

var data_week = d3.range(0,7).map( () => reduce_time(generate_day_data()) )

繪製七天的行事曆

七天的資料準備好了,只要將這些資料繪製到畫面上就完成今天的任務。這邊我們增加一個變數 day_group,來做為七天的資料放置位置。並改寫後面的 hour_recthour_text,除了將 svg 換成 day_group 外,也將傳入的資料改成 (d) ⇒ d,讓七筆資料分別填入。D3 槌子一敲,繪製完成了。

var day_group = svg.selectAll("g")
  .data(data_week)
  .enter().append("g")
  .attr("transform",(d,i) => `translate(${ i * 100 })`)

var hour_rect = day_group.selectAll('rect.hour')
  .data((d) => d) // 改使用單日的資料傳入
  .enter().append("rect")
    .attr("class", "hour")
    .attr("y", (d) => d.start_hour * hour_height)
    .attr("width", day_width )
    .attr("height", (d) => (d.end_hour - d.start_hour + 1) * hour_height )
    .attr("stroke", "black")
    .attr("fill", (d,i) => colorize3(new_data.indexOf(d.thing)))

var hour_text = day_group.selectAll('text.hour')
  .data((d) => d) // 改使用單日的資料傳入
  .enter().append("text")
    .attr("class", "hour")
    .attr("y", (d) => d.start_hour * hour_height + 5)
    .attr("x", 5)
    .attr("width", day_width )
    .attr("fill","black")
    .text(d => (d.thing))
《D3視覺化日常作息方塊圖》繪製完成七天的作息方塊圖
《D3視覺化日常作息方塊圖》繪製完成七天的作息方塊圖

老闆來結語

回顧

再附上這次範例的成果,讓大家在實作時參考,讓我們來快速回顧一次製作流程:

  1. 了解 D3 繪製方法 filter, map, reduce 的用法
  2. 處理重複標籤,作為後續亂數產生一周行事曆使用
  3. 處理單日工作事項,相同工作的片段時間組合在一起
  4. 繪製一天的行程、不同工作項目對應不同顏色、加上文字
  5. 產生七天亂數資料
  6. 繪製七天的行事曆

萬事起頭難,一個作品不可能一步到位,將最終目標拆分成不同階段任務,從熟悉工具,繪製單日的行程到最後的七天行程,最後將這些工具組裝在一起,才有我們最後的成品。這次帶大家利用 D3 來將資料視覺化,大家也可以想想,自己手邊有什麼資料可以來讓 D3 這隻槌子敲一敲,變成一目了然的視覺圖,期待大家能用這次的教學做出有趣的應用。

如果你喜歡老闆的教學,歡迎加入老闆開的課程中一起學習,順便支持一下老闆,課程會帶你看看不一樣的作品,並引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓大家覺得寫 code 不是這麼難的事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠在網頁上做出與眾不同的創作。

動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。長達3085分鐘,超過60個精緻範例與400張的投影片以上,以及四個加碼單元vue-cli、GSAP、D3、Three.js的投影片,成為hahow上最長的課程。

此篇直播筆記由幫手 H 協助整理

墨雨設計banner

這篇文章 用D3.js製作視覺化的日常作息方塊圖(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化的動態長條圖製作(下):Vue/ D3(直播筆記) https://creativecoding.in/2021/01/12/%e8%b3%87%e6%96%99%e8%a6%96%e8%a6%ba%e5%8c%96%e7%9a%84%e5%8b%95%e6%85%8b%e9%95%b7%e6%a2%9d%e5%9c%96%e8%a3%bd%e4%bd%9c%ef%bc%88%e4%b8%8b%ef%bc%89%ef%bc%9avue-d3%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86/ Tue, 12 Jan 2021 15:44:10 +0000 https://creativecoding.in/?p=405 在上篇我們講解了如何使用jQuery製作網頁上常用的動態長條圖,在本篇文章中我們將實際操作另外兩種框架:Vue/ D3的製作方法。還沒看上一篇的快快補唷👉 資料視覺化的動態長條圖製作(上):…

這篇文章 資料視覺化的動態長條圖製作(下):Vue/ D3(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
在上篇我們講解了如何使用jQuery製作網頁上常用的動態長條圖,在本篇文章中我們將實際操作另外兩種框架:Vue/ D3的製作方法。還沒看上一篇的快快補唷👉 資料視覺化的動態長條圖製作(上):jQuery(直播筆記)

https://i.imgur.com/cEEkxZo.gif

主要會用到的知識:

  1. HTML (Pug), CSS (Sass)與JavaScript/ jQuery的基礎觀念
  2. Vue框架概念
  3. D3框架概念

如果想搭配直播影片一起實作,請往這邊走👉 https://www.youtube.com/watch?v=o50XYczmoP0


框架二:Vue

Vue就像你雇用了一位負責代換抄寫的小幫手,你所需要做的就是提供資料的類型和處理方式,小幫手就會自動化地填入。

首先將剛剛jQuery完成的圖表直接fork,fork是幫你複製一個新的pen,修改時不會動到原本的東西。在JS的設定裡面代入Vue。

https://i.imgur.com/uadgAsW.png

由於Vue的特性是在HTML寫模板,然後利用JS帶入資料,所以在HTML裡面新增.bar,JS的部分先把height和background-color的設定複製起來,整個forEach的部分拿掉,//更新的地方暫時註解掉。

https://i.imgur.com/OWM6T9l.png

接著把Vue這個小幫手請出來幫我們抄寫跟產生DOM物件,告訴他工作的範圍後,也要指定原本的資料datas,在HTML告訴他希望他幫我們重複打.bar這個木樁10遍,同時把木樁的數值也打上去,用toFixed(1)取到小數點第1位。

HTML

#app.graph
  .bar(v-for="data in datas") {{data.toFixed(1)}}

JavaScript

var datas = [];
var elements = [];
function generateData(){
  var temp = [];
  for( var i=0; i<10; i++){
    temp.push(Math.random()*10+5);
  }
  return temp;
}

for( var i=0; i<10; i++){
  datas.push(Math.random()*10+5);
}

var vm = new Vue({
  el: "#app",
  data:{
    datas: datas
  }
})

接著來設定長條方塊的樣式,透過Vue可以動態地去修改CSS屬性。在HTML新增:style="gen_style(data)",gen_style()像是一個方法,指導我們的小幫手說style要用data這個資料去算一個屬性(顏色、高度)做呈現。那小幫手要如何知道gen_style這個方法的實際內容呢?我們需在JS新增一個Method:{}來告訴他。Method就像一本作業指導書,在裡面我們需要回傳一個JSON物件,用來說明CSS屬性的設定。因為rgb數值設定的關係,記得data需要先用語法parseInt()處理成整數唷。

HTML

#app.graph
  .bar(v-for="data in datas",
       :style="gen_style(data)") {{data.toFixed(1)}}

JS

var vm = new Vue({
  el: "#app",
  data:{
    datas: datas
  },
  methods:{
    gen_style(data){
      var d = parseInt(data);
      return {
        "height": data*20+"px",
        "background-color":"rgb("+data*10+","+data*10+","+data*10+")"
      };
    }
  }
})

登愣,神奇的事情發生了!我們明明沒有實際地對DOM物件做屬性指定的動作,他卻乖乖地自動產生對應資料的長條圖,這就是Vue好用的地方啦,就像你買了一台3D印表機,只要告訴他規則、資料,他就會自動幫你蓋好房子了。

https://i.imgur.com/swO9EQf.png

剛剛我們用jQuery寫了一拖拉庫來達到每500毫秒自動更新的效果,在Vue只需要告訴他vm.datas = datas;就可以完成囉。

//更新
setInterval(()=>{
  datas = generateData();
  vm.datas = datas;
},500);

總地來說Vue的自動化很高、很方便,但相對他的產出就必須follow你訂下的規則,沒辦法一個一個做特定調整。


框架三:D3

D3有點像是jQuery的強化版,只是這次你拿的不是一支小破錘子,而是課金後的超炫砲無敵錘子,除了可以根據規則做重複的事情以外,還可以針對個體做客製化調整。

我們再fork一次剛剛jQuery完成的圖表。在JS的設定裡面代入D3。

https://i.imgur.com/YHScuKG.png

剛剛在jQuery我們透過forEach把物件一個一個抓出來改,現在我們不需要單獨抓出來了所以先註解掉,取而代之的是批次抓取做修改,setInterval()的地方也暫時註解掉。

  1. select()選擇graph裡面全部的bar
  2. 資料來源是datas請來這邊找。
  3. 因為現在bar還不存在而datas有10筆,透過enter()來補齊物件跟資料差距的量。當DOM數量少於data的數量,或者壓根一個都沒有的時候,我們一般會使用enter()幫忙建立。
  4. append顧名思義就是添加的意思,透過append()新增div。
  5. attr()是負責命名這個div的class為bar。
d3.select(".graph").selectAll(".bar")
  .data(datas)
  .enter()
  .append("div")
  .attr("class", "bar")
https://i.imgur.com/VSS87CD.png

有了十個bar之後接下來就是我們的重頭戲──調整高度和顏色。高度根據資料datas(d)中的第幾筆資料(i)去做調整,乘上20讓他差距更大、可視性更好,記得加上px因為他是CSS的屬性。

.style("height",(d,i)=>(d*20+"px"))

這邊要介紹D3中的一個概念:定義比例scale.linear()。我們可以把它理解成地圖上常見的比例尺,例如台灣面積大約3萬多平方公里,要塞進一張 A4 大小的地圖根本是不可能的任務,這時候就必須要用到比例了。首先,命名一個函數為yscale做線性轉換,domain()代表「原始的資料範圍」,range()則代表「轉換後的資料範圍」。先前我們在CSS把grpah的height設定為400px,所以設定轉換後range為0-400。

var yscale= d3.scaleLinear().domain([5,15]).range([0,400])

由console的結果可見yscale現在是一個可做等比例放大的函數,就像多拉O夢的放大燈ㄧ樣,藉由yscale我們也不需要再自己手動幫d乘上20增加他的級距了。

https://i.imgur.com/QP45dn7.png

顏色也是用相同的概念處理,除了數值的放大轉換外,D3也可以幫你把數值轉成顏色。命名一個函數為color,設定轉換成白色到紅色的區間。由console的結果可見color會自動幫我們把5-15的數值轉成rgb的色碼。

var color = d3.scaleLinear().domain([5,15]).range(['white','red'])

https://i.imgur.com/VKSRSvz.png

接著就來完成我們的圖表啦,使用語法style()做高度和顏色的設定,美美的圖表就大功告成了。

d3.select(".graph").selectAll(".bar")
  .data(datas)
  .enter()
  .append("div")
  .attr("class", "bar")
  .style("height",(d,i)=>(yscale(d)+"px"))
  .style("background-color", (d)=>color(d))  

https://i.imgur.com/RfLadnF.png

體會到D3的強大了嗎?雖然Vue的效能最好,但D3的優點在於他可以批次地處理一些華麗的動畫效果,例如上面的圖表還可以添加以下幾種語法,至於圖表會如何地變動就讓大家體驗看看囉!

  .style("margin-bottom", (d)=>(200 - yscale(d)/2 + "px"))
  .transition().duration(500)
  .style("margin-bottom","0px")

最後再來個結尾小bonus,或許你也會有一樣的疑問。

讀者:「如果我想要有Vue的效率配上D3的華麗,到底該用哪一個呢?」

老闆:『爭什麼,摻在一起做成撒尿牛丸啊!』

Vue跟D3該如何結合呢?把剛剛用Vue完成的圖表叫回來,可以看到我們在height和background-color那做了一拖拉庫的處理,現在這些都可以交給D3來代勞。把剛剛在D3圖表用scaleLinear處理的程式碼貼過來,data的部分用yscale和color這兩個函數做計算,這樣就大功告成囉。

return {
        "height": yscale(data)+"px",
        "background-color": color(data)
      };

https://i.imgur.com/1j70XT6.png

想看更複雜的結合使用,請往這邊走👉 codepen實作範例:長條圖_Vue&D3

框架比較

以上就是這次如何用三種框架實作長條圖的講解,希望這個介紹會讓大家對於各框架的優劣勢更加了解,在未來的專案中可以無痛使用,讓長條圖替你的網頁畫龍點睛吧!我們下次再見囉~

課程推薦

老闆在Hahow好學校開了兩門課,其中,動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。期待在課程裡見到你!

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 資料視覺化的動態長條圖製作(下):Vue/ D3(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化的動態長條圖製作(上):jQuery(直播筆記) https://creativecoding.in/2021/01/06/%e8%b3%87%e6%96%99%e8%a6%96%e8%a6%ba%e5%8c%96%e7%9a%84%e5%8b%95%e6%85%8b%e9%95%b7%e6%a2%9d%e5%9c%96%e8%a3%bd%e4%bd%9c%ef%bc%88%e4%b8%8a%ef%bc%89%ef%bc%9ajquery%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86/ Wed, 06 Jan 2021 11:04:55 +0000 https://creativecoding.in/?p=369 資料視覺化是網頁上常用的媒材,在本篇文章中我們將實際操作在jQuery/ Vue/ D3這三種框架下如何繪製出好用的動態長條圖,同時分析各個框架的優勢與劣勢,讓我們繼續往下看吧! 首先讓我們對長條圖有…

這篇文章 資料視覺化的動態長條圖製作(上):jQuery(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化是網頁上常用的媒材,在本篇文章中我們將實際操作在jQuery/ Vue/ D3這三種框架下如何繪製出好用的動態長條圖,同時分析各個框架的優勢與劣勢,讓我們繼續往下看吧!

https://i.imgur.com/cEEkxZo.gif

首先讓我們對長條圖有個初步了解。長條圖多用於呈現相同性質但不同量級的資料,例如比較美國、台灣、日本這三個國家的人口數。用程式碼撰寫一個長條圖主要分為三個步驟:

  1. 資料整理,通常會用陣列表現相同性質且不斷重複的資料。
  2. 在網頁上產生相對應的物件(長條方塊)。
  3. 針對每個長條方塊設定他的屬性,例如:改變物件的高度、弧度、間距……等屬性。

我們將以Code Pen做為本次實作的平台,這是一個可以在創作的同時即時看到程式碼運作狀況的線上程式碼編輯器,只要簡單註冊就可以使用囉!

主要會用到的知識:

  1. HTML (Pug), CSS (Sass)與JavaScript/ jQuery的基礎觀念
  2. Vue框架概念
  3. D3框架概念

如果想搭配直播影片一起實作,請往這邊走👉 https://www.youtube.com/watch?v=o50XYczmoP0


框架一:jQuery

codepen實作範例:長條圖_jQuery

相信大家對jQuery都滿熟悉的,它的特性就是抓一個特定條件的東西,去修改他特定條件的屬性。

首先,在code pen上開一個新的pen,將HTML的預處理器設定成Pug、CSS的預處理器設定成Sass、jQuery的CDN掛入JS。

https://i.imgur.com/4krJiqp.png

接著來準備一組做為圖表資料的陣列。命名一個陣列為datas,運用JS的for迴圈,設定一個從1-10重複10遍的變數i,同時運用函數Math.random()每次推入一個隨機的變數,用console.log印出datas陣列內的資料。

https://i.imgur.com/hPNse2U.png

要產生圖表,需要製作一個放置長條圖、資料的容器,在HTML新增一個class為graph的div,在CSS針對graph的width, height, border進行屬性的設定增加可視性。

接著做進一步的資料處理,運用JS的forEach將陣列datas內的每一筆資料單獨取出(ES6簡化function為箭頭函式)給他一個參數index,console.log印出index, obj,即可看到陣列內單獨的資料和標號用的index。

https://i.imgur.com/aXgrclM.png

溫馨提醒:ES6中簡化function為箭頭函式()=>{}的寫法不是每個瀏覽器都吃,記得搭配webpack做轉換唷。

為了產生對應資料的長條圖,在forEach的迴圈內我們命名一個變數bar,bar是'<div class="bar"></div>'的HTML(注意單引號與雙引號的使用),用jQuery選取graph,利用語法append()在被選取的元素结尾插入bar的長條圖。

在CSS針對bar做width, border的屬性設定,在graph下屬性display: flex讓每個長條可以排排站。

https://i.imgur.com/gSX3fiW.png

接下來要替他們長不同的身高、上不同的顏色。首先在graph利用CSS flex的屬性align-items: flex-end讓長條方塊置底,在bar給每個長條margin-right: 10px

那要如何指定高度和顏色呢?我們利用jQuery選擇器的特性$(bar)把它命名為變數element,接著修改他CSS的height和background-color。由於obj為1-10的亂數,在高度呈現上的級距不明顯,所以把obj*20讓他的高度凸顯出來。

而CSS的顏色屬性是由rgb數值在0-255之間決定,如果只在1-10之間浮動的話會黑壓壓的一片,所以我們命名一個變數為color_val,利用語法parseInt()將obj轉成整數後再上15,這樣深淺鮮明、高度差異的長條圖就出現啦。

https://i.imgur.com/9KG7iRr.png

完成了靜態的長條圖,離動態更新長條圖就不遠了。由於接下來會比較複雜所以貼出JS的完整code給大家參考,步驟分為以下:

  1. 命名一個新的陣列為elements,用來儲存先前產生出的實體物件(變數element),利用elements.push(element)直接推進去。
  2. 利用語法setInterval()規定每500豪秒更新長條圖一次。
  3. 更新的動作利用函式generateData()來進行,把它想成一組亂數產生器,所以一樣命名一個陣列temp,把亂數塞進去。
  4. 接著讓datas = generateData();亂數更新一坨拉庫資料,有資料後就可以按照資料更新物件(長條方塊),因為剛剛把亂數產生包在陣列裡面了,所以記得先做初始化。運用forEach把每個物件抓出來玩一遍,根據對應的資料更新他的高度和背景顏色所以使用datas[index],第一筆資料對應第一個物件、第二筆資料對應第二個物件。

這樣我們就有一個會變化的長條圖了(撒花),但是否覺得變換瞬間有點卡卡的?只要在bar的CSS設定transition: 0.5s就可以解決囉!

var datas = [];
var elements = [];
function generateData(){
  var temp = [];
  for( var i=0; i<10; i++){
    temp.push(Math.random()*10+5);
  }
  return temp;
}

for( var i=0; i<10; i++){
  datas.push(Math.random()*10+5);
}
datas = generateData(); //function初始化
console.log(datas);

datas.forEach((obj, index)=>{
  console.log(index, obj);
  var bar = '<div class="bar"></div>';
  var color_val = parseInt(obj)*15;
  var element = $(bar);
  element.css("height", obj*20+"px")
         .css("background-color", "rgb("+color_val+","+color_val+","+color_val+")")
  $(".graph").append(element);
  elements.push(element);
});

//更新
setInterval(()=>{
  datas = generateData();
  elements.forEach((element,index)=>{
    var color_val = parseInt(datas[index])*15;
    element.css("height", datas[index]*20+"px")
         .css("background-color", "rgb("+color_val+","+color_val+","+color_val+")")
  });
},500);

最後一步是新增每個長條方塊所對應的數值,最快速的方法就是直接把數值包在長條bar裡面,然後使用CSS position的定位方法將數值定在長條方塊的下方。

把文字包在長條內var bar = '<div class="bar"><div class="text"></div></div>';

利用jQuery children選取element的小孩".text",用語法toFixed(1)取到小數點第1位。在下方setInterval的更新處重複ㄧ樣的步驟,記得把變數名稱obj修改成datas[index]

element.children(".text").text(obj.toFixed(1))
https://i.imgur.com/kE68ek9.png

接著在CSS針對bar和text的定位做設定,就可以看到長條圖表下方出現數值了。

.bar
  position: relative
  
.text
  position: absolute
  bottom: -30px

做到這裡你有沒有發現我們一直在做一件重複的事情?沒錯,就是針對實體物件的數值做修改。用jQuery寫圖表就有點像用一個簡單的錘子蓋房子,從建造到調整都需要針對每個DOM物件去做手動修改,土炮且自立自強。

下一篇文章中我們會講到另外兩個框架:Vue和D3,相較於如此「人工」的jQuery,這兩個框架比較偏自動化,就讓我們下次見囉~

課程推薦

老闆在Hahow好學校開了兩門課,其中,動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。期待在課程裡見到你!

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 資料視覺化的動態長條圖製作(上):jQuery(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>