iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 29
0
Modern Web

從LINE BOT到資料視覺化:賴田捕手系列 第 29

第 29 天:DataVis:C3 Stanford

第 29 天:DataVis:C3 Stanford

  經過昨天跟前天的研究,大家是不是對 C3 的操作越來越了解了呢?看到自己手邊的數據,透過 JavaScript 以圖表的型式呈現在網頁上,是不是很振奮呢?我們利用 C3 畫出了甜甜圈圖和曲線圖,而圓餅圖(Pie Chart)和柱狀圖(Bar Chart)的繪製方式又和甜甜圈圖、曲線圖如出一轍,可以說幾個基本類型的圖表我們都能用 C3 幫我們畫出來了。今天要來試著實作的,則是一種用顏色來表明資料的圖表,熱度圖(Heat)➀。
  熱度圖常用來與地圖結合,清楚表明某項統計數字與地理位置的關係。或是與時間週期結合,讓我們可以很快地看出某項統計數字與時間以及時間週期的關係。想想看我們的草泥馬訓練紀錄,大家應該心中一片雪亮,這篇文章要來實作的,就是草泥馬的訓練量與時間關係的熱度圖。
  常見與時間日期有關的熱度圖可能會是這個樣子➁,用一年當中的週數當作 x 軸,用星期幾當作 y 軸,而用顏色表示某項統計數字的大小。

https://ithelp.ithome.com.tw/upload/images/20191007/20120178RungplzmQB.png
圖一、Cal-heatmap 所提供的熱度圖。

  C3 雖然沒有提供這種型式的圖表,但 C3 給了我們另外一種也是在二維空間中用顏色表達出第三維度資料的圖表:史丹佛圖(Starford Chart)。雖然樣式跟常見的熱度圖不太一樣,但是史丹佛圖也可以表達出熱度圖所能傳達的資料,因此把史丹佛圖作為熱度圖來用應該是沒問題的。

日期時間

  要畫這種熱度圖,我們之前從 Python 送過去的資料只包含有日期,可能還是不夠。所以首先需要從日期裡推算出這是一年當中的第幾週、星期幾。幸好 Python 裡面的 datetime 函數可以輕鬆幫我們辦到這件事。

def get_unique(table):
    unique_alpaca_name = sorted({i[1] for i in table})
    unique_training = sorted({i[2] for i in table})
    unique_date = sorted({i[4] for i in table})
    unique_isocalendar = sorted({datetime.datetime.strptime(i[4], '%Y-%m-%d').isocalendar() for i in table})
    
    return unique_alpaca_name, unique_training, unique_date, unique_isocalendar

  我們稍微更改一下之前的get_unique()這個函數。

  • 第五行:unique_isocalendar = sorted({datetime.datetime.strptime(i[4], '%Y-%m-%d').isocalendar() for i in table})
      還記得我們送進來的變數table是包含了每一筆資料的一個清單(List)物件,而一筆資料就是一個不可變更清單(tuple),當中的項目如下所示:
(record_no, alpaca_name, training, duration, date)

  我們利用i[4]拿出不可變更清單中的第五個項目date,用strptime()將字串轉換為date物件。接著再用isocalendar()date物件轉換成一個不可變更清單,裡面分別為(ISO year, ISO week number, ISO weekday)。這樣就拿到我們需要的資訊了。

@app.route("/stanford_chart")
@login_required
def stanford_chart():
    table = web_select_overall()
    table = total_seconds(table)
    uniques = get_unique(table)
    return render_template("stanford_chart.html", table=table, uniques=uniques)

  最後再用render_template將需要的資料送到網頁"stanford_chart.html"去。

C3 史丹佛圖的資料格式

  老招了,說到這裡,大家應該也知道我們要來做什麼吧?沒錯,首先就是要搞清楚要畫一張 C3 史丹佛圖,我們需要提供怎麼樣的資料格式。翻到官網來看一看➂,哇沒搞錯吧,這一串設定資料真是有夠長的。我們先簡化一下,大概的設定如下:

var chart = c3.generate({
    data: {
        x: 'HPE',
        epochs: 'Epochs',
        columns: [['HPE', 2.5, 2.5],
                  ['HPL', 24.5, 24],
                  ['Epochs', 1, 1]],
        type: 'stanford',
    },
    legend: { hide: true },
    point: { focus: { expand: { r: 5 } }, r: 2 },
    axis: {
        x: {
            show: true,
            label: { text: 'HPE (m)', position: 'outer-center' },
            min: 2,
            max: 3,
            tick: { values: d3.range(0, 65, 10) },
            padding: { top: 0, bottom: 0, left: 0, right: 0 },
        },
        y: {
            show: true,
            label: { text: 'HPL (m)', position: 'outer-middle' },
            min: 10,
            max: 30,
            tick: { values: d3.range(0, 65, 10) },
            padding: { top: 5, bottom: 0, left: 0, right: 0 },
        }
    },
    stanford: {
        scaleMin: 1,
        scaleMax: 10000,
        scaleFormat: 'pow10',
        padding: { top: 15, right: 0, bottom: 0, left: 0 }
    }
});

  比較重要的是data裡面的資料,其他的參數大家可以自己玩玩看。我們把data拉出來單獨看一看:

data: {
        x: 'HPE',
        epochs: 'Epochs',
        columns: [['HPE', 2.5, 2.5],
                  ['HPL', 24.5, 24],
                  ['Epochs', 1, 1]],
        type: 'stanford',
}

  裡頭又包含了四個參數:xepochscolumnstype。就像我們昨天在畫曲線圖一樣,x指的是要當作 x 軸的資料。第二個參數epochs也是這個概念,就是設定要用來當作epochs資料的地方。不過什麼叫做epochs資料呢?在史丹佛圖裡,其實就是我們要用顏色來表示數據大小的資料。第三個參數columns,我們應該都已經認識了,就是要放入資料的位置。要放入怎麼樣的資料呢?C3 要求我們要在columns放入一個序列,並在該序列中再放入三個序列,分別當作 x 軸、y 軸、和要用顏色來呈現的資料。最後一行則是宣布我們要畫stanford史丹佛圖。
  剩下的參數大家可以自己在 codepen 上調整看看。裡面包括有調整 x 軸最小值最大值的參數、調整 y 軸最小值最大值的參數、調整epochs點在二維座標軸上標示的大小等等。
  都調整好了以後,我們就可以來畫個史丹佛圖了。

// 將資料換成JavaScript 能夠操作的資料
const table = {{ table|tojson }};
const uniqueAlpacaName = {{ uniques[0]|tojson }};
const uniqueTraining = {{ uniques[1]|tojson }};
const uniqueDate = {{ uniques[2]|tojson }};
const uniqueIsocalendar = {{ uniques[3]|tojson }};

// 先準備好要放入 C3 columns 的資料
function showOverall() {
  let uniqueOverall = [['x'].concat(uniqueIsocalendar.map(x=>x[1])), 
                       ['isoweekday'].concat(uniqueIsocalendar.map(x=>x[2])),
                       ['Epochs'].concat(uniqueDate.map(x=>1))];

  table.forEach(function (x) {
    let tempDate = uniqueDate.indexOf(x[4])+1;

    uniqueOverall[2][tempDate] += x[3]/60;
  })
  return uniqueOverall;
}

// 請 C3 幫我們畫圖
function c3Generate (bindto, columns) {
  return c3.generate({
    bindto: bindto,
    data: { x: 'x', epochs: 'Epochs', columns: columns, type: 'stanford'},
    legend: { hide: true },
    point: { focus: { expand: { r: 10 } }, r: 5 },
    axis: {
        x: {
          show: true, 
          label: { text: 'isoWeekNumber', position: 'outer-center' },
          min: -1,
          max: 53,
          tick: { values: d3.range(0, 50, 5) },
          padding: { top: 0, bottom: 0, left: 0, right: 0 },
        },
        y: {
          show: true,
          label: { text: 'isoWeekDay', position: 'outer-middle' },
          min: 0,
          max: 8,
          tick: { values: d3.range(0, 8, 1) },
          padding: { top: 5, bottom: 0, left: 0, right: 0 },
        }
    },
    stanford: {
        scaleMin: Math.min(...columns[2].slice(1)),
        scaleMax: Math.max(...columns[2].slice(1)),
        scaleFormat: 'pow10',
        padding: { top: 15, right: 0, bottom: 0, left: 0 }
    }
  });
}


var alpacaNameChart = c3Generate('#alpacaNameStanford', showOverall())

var trainingChart = c3Generate('#trainingStanford', showOverall())

  做到這裡,我們的第一張史丹佛圖應該就畫好了。

https://ithelp.ithome.com.tw/upload/images/20191007/201201780m9Y3g2Z26.png
圖二、輕鬆做出史丹佛圖

  接著來複習一下昨天學到過的 HTML 5 事件處理器(EventHandler)。
  事實上是,我目前的訓練資料還不算太多,4 個星期的訓練資料擺在寬寬的 52 週的空間中,顯得有點孤單。我們能不能來做一個顯示範圍的調整器,讓我們可以改變史丹佛圖的顯示資料的範圍呢?

HTML 5 事件處理器

  這邊我要用的範圍選擇方式是 Bootstrap 的當中的元素costum-range

<div class="slidecontainer">
<input type="range" min="0" max="100" value="20" class="custom-range" id="alcapaNameRange">
</div>

https://ithelp.ithome.com.tw/upload/images/20191007/20120178gXPDcishPu.png
圖三、四個custom-range

  如此,便可以做出一個簡單的custom-range了。
custom-range當中有 4 個與範圍相關的參數可以設定。min設定最小值,max設定最大值、value設定初始值,第 4 個是step,預設為"1"

  有了可以調整的範圍的custom-range,下一步就是要利用 JavaScript 從custom-range當中讀出使用者設定的值,並以此為依據重新畫圖。
  先來更改我們的畫圖函數:

// 新的 C3 畫圖函數, 多了一個參數 xMin
function c3Generate (bindto, columns, xMin) {
  return c3.generate({
    bindto: bindto,
    data: { x: 'x', epochs: 'Epochs', columns: columns, type: 'stanford', onmouseover: function (d) {console.log(d)} },
    legend: { hide: true },
    point: { focus: { expand: { r: 10 } }, r: 5 },
    axis: {
        x: {
          show: true, 
          label: { text: 'isoWeekNumber', position: 'outer-center' },
          min: xMin,  // 利用 xMin 調整 x 軸的最小值
          max: 53,
          tick: { values: d3.range(0, 50, 5) },
          padding: { top: 0, bottom: 0, left: 0, right: 0 },
        },
        y: {
          show: true,
          label: { text: 'isoWeekDay', position: 'outer-middle' },
          min: 0,
          max: 8,
          tick: { values: d3.range(0, 8, 1) },
          padding: { top: 5, bottom: 0, left: 0, right: 0 },
        }
    },
    stanford: {
        scaleMin: Math.min(...columns[2].slice(1)),
        scaleMax: Math.max(...columns[2].slice(1)),
        scaleFormat: 'pow10',
        padding: { top: 15, right: 0, bottom: 0, left: 0 }
    }
  });
}

  下一步就是要動用到我們 HTML 5 事件處理器的地方了。還記得事件處理器怎麼運作的嗎?

document.getElementById('custom-range 的 id').onchange = function ( e ) { console.log(e.target.value) }

  先用document.getElementById('custom-range 的 id')找出我們想要放上事件處理器的 HTML 5 元素,在這邊就是要找出我們的<input type="range" class="custom-range" id="custom-range 的 id">。接著放上onchange事件處理器,只要這個元素一發生onchange更改數值的事件,處理器便會運作,執行後面的函數。以上面的例子,就是利用console.log(e.target.value)將更改後的數值列印到 console 上面。
  了解運作模式之後,我們就來試著寫寫看利用custom-range更改史丹佛圖 x 軸最小值的函數吧:

document.getElementById('alpacaNameRange').onchange = function (e) {
  c3Generate('#alpacaNameStanford', showOverall(), e.target.value);
};

https://ithelp.ithome.com.tw/upload/images/20191007/201201783INtOpdO8P.png
圖四、親愛的,我們把 x 軸最小值變大了

  另外,如果想要更改 Bootstrap 提供的custom-range的一些外觀設定,如顏色、大小等等,會稍微麻煩一點,有興趣的可以參考 W3School 上面的教學➄。

  今天的內容就到這裡了,到目前為止,在資料視覺化這個主題上,我們利用 D3 的衍生套件 C3,幫我們畫出了甜甜圈圖、曲線圖、史丹佛圖,還學會了用 JavaScript 做資料處理,並且利用 HTML 5 事件處理器,抓取使用者瀏覽網站時的動作,快速且即時的回饋所需的內容。我根據今天討論的主題將實作出來的內容放到 "phoebe-takescareof-alpaca.herokuapp.com" 上了,有興趣的人可以過去玩玩。另外,本篇文章的相關程式碼我也已經放到 Github 上了,若覺得我上面的說明不夠清楚的,也可以過去參考,當然也相當歡迎大家直接在下面留言,我會盡可能的回覆的,謝謝大家。

參考資料

➀ 熱度圖 wiki
➁ Cal-heatmap 官方網站
➂ C3 史丹佛圖介紹
➃ Bootstrap custom-range 說明文件
➄ W3School Range Slider 說明文件

註:對於此系列文有興趣的讀者,歡迎參考由此系列文擴編成書的 LINE Bot by Python,以及最新的系列文《賴田捕手:追加篇》
第 31 天 初始化 LINE BOT on Heroku
第 32 天 快速回覆 QuickReply 介紹
第 33 天 妥善運用 Heroku APP 暫存空間
第 34 天 妥善運用 LINE Notify 免費推播
第 35 天 製造 Deploy to Heroku 按鈕


上一篇
第 28 天:DataVis:C3 Spline
下一篇
第 30 天:DataVis:Packaged
系列文
從LINE BOT到資料視覺化:賴田捕手30

尚未有邦友留言

立即登入留言