iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 26
1
Modern Web

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

第 26 天:DataVis:JavaScript 速成班

第 26 天:DataVis:JavaScript 速成班

  終於來到我們主題的最後一站,資料視覺化(data visualization)了。什麼是資料視覺化呢?就是將我們的資料以圖表的方式讓相關的統計數字呈現出來。為什麼要做資料視覺化呢?先來看看經過了一次又一次的練習與實作,跨過了一個又一個的領域,到現在為止我們手上有了些什麼。第一個我們掌握了 Python 這個語言的基本操作,第二個我們透過 Python 教 LINE 聊天機器人說話哈啦,讓他變得幽默風趣又貼心。第三點,我們學了 SQL 語法,在網路上建立了資料庫,並瞭解如何新增、提取、更新、刪除資料庫裡的紀錄。第四點,我們學了 Flask,架構出了一個網站,供我們隨時可以用更方便且清楚的平台去瀏覽我們資料庫裡的紀錄。然後就是今天了。一切看起來這麼完美,直到我發現我的草泥馬訓練紀錄越疊越多,多到在瀏覽器中顯示每一筆資料變得龐大又沒有意義。如果我想知道'約翰'總共進行了多久的'嚼食訓練'?如果我想知道即將參加大賽的'法蘭克'9/16之後的訓練量是否過多?如果我想知道我對'威廉'安排的各種訓練課程是否平均?以我們目前的方式,單單調閱資料還是很難讓我能夠正確判斷上述問題的。這時候就需要用到資料視覺化的手段了,也就是將一筆一筆的資料,按照我們的需求,畫出相對應的圖表呈現在瀏覽器當中。
  聽起來是不是很棒呢?是不是想馬上就來實作看看呢?當然好,不過在那之前,我們得些「略為」了解一下另一種程式語言:JavaScript。
  怎麼這麼殘酷,學了 Python、學了 SQL、現在居然還想要凹我去學 JavaScript。原因是,不同語言有不同語言擅長的領域,Python相當擅長於資料處理分析、人工智慧神經網路開發,SQL 負責處理資料庫相關的操作,而 JavaScript 則幫我們在瀏覽器上作運算。當我們把資料用 Python 傳送到使用者端的瀏覽器,也就是所謂的前端,之後,剩下的所有工作,就交由 JavaScript 來處理。因此熟悉 JavaScript 能夠幫助我們在網頁開發與設計上更上一層樓。
  接著說個好消息:因為我們已經對 Python 有一定程度的熟悉了,再學習另一種程式語言時,可以用觸類旁通的方式,讓我們能夠很快的掌握一些基本的要素。因此,今天的內容就是,為了要做好資料視覺化,讓我們一起來上一堂 JavaScript 速成班。
  話先說再前面,所謂的速成班聽起來都不太可靠。我這堂當然也是。之所以能夠速成,其中有一個關鍵是我們已經學過 Python,可以用對照比較的方式,讓大家很快進入 JavaScript 的世界。另一個關鍵則是,我只介紹一些和接下來實作資料視覺化相關的基本概念和操作,所以可以在有限的篇幅說明完畢。其實我是相當推薦好好再學個 JavaScript 的。這次鐵人賽也有不少相關的主題➀➁,另外,想要再從基礎開始,有系統的了解 JavaScript 的人,可以下載這本教科書 Eloquent JavaScript➂。不要懷疑,免會員、免註冊,免費下載!
  還記得我們是怎麼學 Python 的嗎?現在我們就這麼來學 JavaScript。

JavaScript:集成開發環境

  第一個問題:我們可以在哪裡編輯並且試跑 JavaScript 的程式碼呢?上面說過,將資料送到使用者端的瀏覽器,接下來就是由 JavaScript 來作運算了。因此每個瀏覽器都可以是 JavaScript 的集成開發環境!隨意開啟 Chrome、FireFox、或是 Edge,接著按下 "Ctrl" + "Shift" + "i",就可以看到一個開發人員工具,接著選 "Console",這就是我們的 JavaScript 集成開發環境囉!

https://ithelp.ithome.com.tw/upload/images/20191004/20120178BPuCXVgpMq.png
圖一、用 Chrome 瀏覽器來編輯 JavaScript

https://ithelp.ithome.com.tw/upload/images/20191004/20120178MtnSzdz5na.png
圖二、用 Edge 瀏覽器來編輯 JavaScript

  寫入 JavaScript 程式碼之後,"Enter" 就可以輸入指令看到結果。"Shift" + "Enter" 則是換行繼續編輯。

JavaScript:資料型態與基本操作

  在 JavaScript 當中,基本的資料型態分三種:數值(Number)、字串(String)、布林(Boolean)。先來講講第一種,數值。

數值(Number)資料

  不像 Python 還分成整數跟浮點數,JavaScript 的數值直接霸氣的將這兩種都囊括了進來。數值的基本運算如下表:

表一、數值(Number)的運算方法

運算 運算符號
加法 +
減法 -
乘法 *
除法 /
餘數 %

  和 Python 相比,少了一個商數的計算方式,怎麼辦呢?我們可以用Math.floor(),小於等於這個數值的最大整數,來計算我們的商數:

> 8 / 3
< 2.6666666666666665
> Math.floor( 8 / 3 )
< 2
> Math.ceil( 8 / 3 )
< 3

  觸類旁通,Math.ceil()則傳回大於等於這個數值的最小整數。
  另外,也可以用Number()的方式,將其他類型的資料轉換為數值資料:

> Number("2.5")
< 2.5
> Number(true)
< 1

字串(string)資料

  第二種,字串資料,和 Python 相同,用單引號或是雙引號作為字串資料的開始跟結束。也有換行符號(\n)、tab符號(\n)。還記得 Python 的 F 字串(f-string)嗎?在 JavaScript 中,想要做到 F 字串的效果很簡單:

In [1]: word = "是這樣"
        f"Python 中的 F 字串{word}!"
Out[1]: " Python 中的 F 字串是這樣!"
> let word = "是這樣"
  `JavaScript 中的則${word}!`
< "JavaScript 中的則是這樣!"

  用` `包住字串,並在需要放入變數的地方用${}。相同的,可以用String()來將其他類型的資料換為字串資料。

布林(boolean)資料

  跟 Python 一樣,JavaScript 的布林資料只有兩種:truefalse。與布林資料相關的幾種邏輯運算子如表二所示:

表二、邏輯運算子

運算 運算符號
等於 ==
不等於 !=
大於 >
小於 <
&&
||
全等於 ===
不全等於 !==

  在 JavaScript 裡比較麻煩的是,它給了我們兩種等於。因為 JavaScript 允許不同類型的資料擺在一起比較,當用==將不同類型的資料擺在一起時,JavaScript 會(自做?)聰明的轉換資料類型:

> 1 == "1.0"
< true
> 1 == true
< true

  如果你不希望發生這種事,就請用===

> 1 === "1.0"
< false
> 1 === true
< false

  Python 當中的print() 在 JavaScript 當中是 console.log()

> let wordAgain = "是這樣"
  console.log(`JavaScript 中的則${wordAgain}!`)
  JavaScript 中的則是這樣!
< undefined

  在 JavaScript 當中查詢資料的類型,請用typeof

> typeof("123");
< "string"
> typeof(123);
< "number"

  接下來介紹一個 JavaScript 當中有趣的三元運算子?,用法如下:

> let 如果正確 = true
  如果正確 ? console.log("喊 1"): console.log("否則喊 2")
  喊 1
< undefined

  三元運算子?會先判斷條件是否正確(true),若是,則執行第一段程式碼,若否,則執行:之後的第二段程式碼。

JavaScript:資料結構與基本操作

  在 Python 當中,貼標籤非常簡單:

In [2]: 標籤 = 123
        標籤
Out[2]: 123

  然而在 JavaScript 當中,我們要先宣告標籤的類型varletconst,接下來才可以開始貼標籤,同一個標籤只需要宣告一次,也只能能宣告一次。

> let 標籤 = 123;
  標籤;
< 123
> 標籤 = 456;
  標籤;
< 456
> let 標籤 = 789;
  Uncaught SyntaxError: Identifier '標籤' has already been declared
    at <anonymous>:1:1

  用varlet做成的標籤就像便利貼,隨時可以撕下來重新貼在另外的資料上。用const做成的標籤則不行,一旦宣告完畢,貼上了資料,就必須跟隨到底。

> const 貼好貼滿 = 123
  貼好貼滿
< 123
> 貼好貼滿 = 456
  Uncaught TypeError: Assignment to constant variable.
    at <anonymous>:1:6

  在 JavaScript 當中,有兩種資料結構:序列(Array)跟物件(Object)。序列就像是 Python 當中的清單(List),而物件則像是 Python 當中的字典(Dict)。

> let anArray = [1, 3, "5", true];
  anArray;
< [1, 3, "5", true]
> anArray.length
< 4

  要在序列中加入/移出物件,就用push()pop()

> anArray.push("new item");
  anArray;
< [1, 3, "5", true, "new item"]
> anArray.pop();
  "new item"
> anArray;
< [1, 3, "5", true]

  將多個序列合併在一起則用concat()

> let anotherArray = [4, 7, "9"];
  let theOtherArray = ["再來啊"];
  let overallArray = anArray.concat(anotherArray, theOtherArray);
  overallArray
< [1, 3, "5", true, 4, 7, "9", "再來啊"]

  從序列中選取切片:

> overallArray.slice(2, 5);
< ["5", true, 4]

  有沒有發現到 JavaScript 當中序列的編號方式跟 Python 是一樣的,也就是從 0 開始。查詢一下序列中某一個資料的編號:

> overallArray[1];
< 3
> overallArray.indexOf(7);
< 5

  在 JavaScript 中,第二種資料結構叫物件(Object)。大家其實可以想成 Python 中的字典。

> let anObject = {keyOne: "valueOne", keyTwo: "valueTwo"};
  anObject;
< {keyOne: "valueOne", keyTwo: "valueTwo"}

  想當然爾,要調閱物件中,某一個鑰匙其相對應的資料:

> anObject.keyOne;
< "valueOne"
> anObject["keyOne"];
< "valueOne"

  也能夠查詢物件中的所有鑰匙或是所有資料:

> Object.keys(anObject);
< ["keyOne", "keyTwo"]
> Object.values(anObject);
< ["valueOne", "valueTwo"]
> typeof(Object.keys(anObject));
< "object"

  利用Object.keys()可以得到一個包含了anObject物件當中所有鑰匙的序列。而用Object.values()則會得到包含了物件當中所有資料的序列(這邊會回傳"object",因為事實上,序列可以看做物件的其中一個特例,鑰匙是編號,而資料則是序列中的項目)。

  擴充一個物件裡面的鑰匙:資料配對:

> Object.assign(anObject, {keyThree: "valueThree", keyMOre: "valueMore"});
< {keyOne: "valueOne", keyTwo: "valueTwo", keyThree: "valueThree", keyMOre: "valueMore"}

  將{keyThree: "valueThree", keyMOre: "valueMore"}成功擴充至anObject裡面囉!

JavaScript:常用句型

  在 Python 中,有if的判斷句,有whilefor迴圈,在 JavaScript 當中,也有相對應的句型,讓我們來看看該怎麼寫:

if判斷句

> let 狀況為真 = true
  if (狀況為真) {console.log("執行這段程式碼囉!");}
  執行這段程式碼囉!
< undefined

  在 Python 當中,我們是藉由縮排來區分程式碼的段落。而在 JavaScript 當中,則需要用(){}將程式碼的段落包裝起來,相反的,JavaScript 並不強求縮排(雖然這樣說,但習慣上為了可讀性還是會縮一下)。用剛才的if為例,我們需要用()將判斷句包裝起來,而用{}將符合判斷時該執行的程式碼包裝起來。但是,為了讓程式碼看起來更乾淨,或是讓辛辛苦苦工作的碼農們可以輕鬆點,當程式碼只有簡簡單單一行的時候,{}的包裝可以省略。

> if (狀況為真) console.log("只有一行,可以不用加{}!");
  只有一行,可以不用加{}!
< undefined

  當然,也可以做到更複雜的ifelse

> let 狀況一 = false; let 狀況二 = true;
  if (狀況一) {
    console.log("狀況一為真")
  } else if(狀況二) {
    console.log("狀況二為真")
  } else {
    console.log("以上狀況都不為真")
  }
  狀況二為真
< undefined

while迴圈

  除了須加上(){}之外,其他寫起來真的跟 Python 很像呢:

> let numberForWhile = 0;
  while(numberForWhile < 10) { 
    console.log(numberForWhile);
    numberForWhile += 2; 
  }
  0
  2
  4
  6
  8
< 10

  JavaScript 還有另外一種形式的while迴圈,寫作do {執行的內容} while (判斷),執行的效果基本上跟上面看到的while (判斷) {執行的內容}迴圈相同,唯一的差別在do {執行的內容} while (判斷)一定會將要執行的內容先執行一次,接著才進入判斷,若判斷成立,則繼續執行下一個迴圈,若判斷不成立,則結束迴圈。

> do { console.log("無論如何先印一個"); } while ( false )
  無論如何先印一個
< undefined

for迴圈

  還記得在 Python 當中,我們這麼寫for迴圈:

for i in range(6):
    print(i)

  在 JavaScript 當中,則是:

> for ( let i = 0; i < 6; i += 1 ) { console.log(i); }

  或者也可以:

> for ( let i = 0; i < 6; i ++ ) { console.log(i); }

  用i++代表i += 1
  另一種寫法:

> for ( let i of "POPCORN" ) { console.log(i); }
  P
  O
  P
  C
  O
  R
  N
< undefined

  想要在程式碼裡加上註解的話,JavaScript 用//

> console.log("comment") // 我是註解
  Comment
< undefined

  或是用/*作為註解的開頭,而*/作為註解的結尾:

> /*
  這是一段
  簡單的
  小註解
  */

JavaScript:函數物件

  看到這裡,有沒有對 JavaScript 的寫法越來越有概念了呢?這篇文章要講的最後一種則是 JavaScript 當中的函數。還記得函數是什麼嗎?我們將一個或一組參數送進函數,函數接受參數並執行程式碼,最後回傳結果。在 JavaScript 中,函數是這樣寫的:

> let aFunction = function ( 參數 ) { 執行程式碼; return 結果; }

  首先用function來宣告我們要開始定義一個函數。接著用()承接函數的參數,並將函數要執行的程式碼放入{}當中,最後,用return來返回執行結果。

> let multiplyTwoNumbers = function ( paraOne, paraTwo ) { let result = paraOne * paraTwo; return result; }
  multiplyTwoNumbers(4, 5)
< 20

  我們也可以這樣定義函數:

> function anotherFunction ( paraOne, paraTwo ){ let result = paraOne * paraTwo; return result; }

  把函數的名字放在定義函數的關鍵字function(參數)中間。除此之外,還有第三種寫法:

> let theOtherFunction = ( paraOne, paraTwo ) => { let result = paraOne * paraTwo; return result; }

  這種方式叫做箭號函數(Arrow Function),用箭號(=>)暗指一個函數,箭號前是參數,箭號後則是函數的執行內容。最後,因為我們的函數其實相當簡單,一行程式碼便可結束,因此我們的函數還可以再簡化成:

> let simpleFunction = ( paraOne, paraTwo ) => paraOne * paraTwo;

  箭號前是函數的參數,箭號後則直接放入我們需要的回傳值。
  此外,當我們想要放入多個數量不定的參數時,可以這樣做:

> function multiplyNumbers ( ...paras ) {
    let result = 1;
    for (let para of paras) {
      result *= para; 
    } 
    return result;
  }
> multiplyNumbers(4, 5, 6, 7);
< 840

  既然我們對 JavaScript 的函數有一些初步的認識了,那麼就打鐵趁熱再介紹些 JavaScript 內建常用的序列(Array)函數吧:

  • forEach():對序列中的每個項目執行同一種操作。
> let arrayExample = [2, 5, 8];
  arrayExample.forEach(i => console.log(i));
  2
  5
  8

  再舉一例:

> let alpacaInfo = [{"吉姆": [100, 78]},  //草泥馬的名字: [身高(cm), 體重(kg)] 
{"威廉": [102, 82]}, 
{"彼得": [93, 71]}, 
{"法蘭克": [98, 80]}];
  alpacaInfo.forEach(x => {console.log(`${Object.keys(x)}的 BMI: ${Object.values(x)[0][1]*10000/Object.values(x)[0][0]/Object.values(x)[0][0]}`)})
  吉姆的 BMI: 78
  威廉的 BMI: 78.8158400615148
  彼得的 BMI: 82.0904150768875
  法蘭克的 BMI: 83.29862557267805

  在forEach當中放入一個函數,這個函數會對序列當中每一個項目執行相同的程式碼:

> anArray.forEach( (anArray當中的項目) => { 函數的程式碼; } );
  • map():對序列中的每個項目執行程式碼後,回傳新的序列
> let alpacaHeightInMeters = alpacaHeight.map( x => x / 100 )
  alpacaHeightInMeters
< [1, 1.02, 0.93, 0.98]
  • filter():回傳一個序列中符合判斷的項目
      查查看身高高於等於 100 公分的草泥馬有哪些?
> alpacaInfo.filter(x => Object.values(x)[0][0] >= 100)
< [{吉姆: [100, 78]}, {威廉: [102, 82]}]
  • some():檢查一個序列中是否有項目符合指定的判斷?
      這幾隻草泥馬當中,是否有任何一隻的身高超過 100 公分的草泥馬?
> alpacaInfo.some(x => Object.values(x)[0][0] > 100)
< true
  • every():檢查一個序列中,是否每一個項目都符合指定的判斷?
      那是否所有的草泥馬身高都高於 100 公分呢?
> alpacaInfo.every(x => Object.values(x)[0][0] > 100)
< false
  • reduce():將序列中的項目進行累積性的運算,並傳回最後的結果
      這個函數比較不容易說清楚,我們直接來看程式碼:
> alpacaInfo.map(x=>Object.values(x)[0][1]).reduce((totalWeight, x) => totalWeight + x, 0)
< 311

  上面那段程式碼,我們首先用alpacaInfo.map(x=>Object.values(x)[0][1])做出一份只有紀錄了草泥馬體重的序列,接著在這個序列上用.reduce((totalWeight, x) => totalWeight + x, 0)把這個序列當中所的有項目加總起來,得到 4 隻草泥馬的體重總和。
  我們讓totalWeightreduce()當中扮演累積的結果,而x則是代表序列中的項目。reduce()在執行的時候,會先初始化一個totalWeight。這個totalWeight一開始的值,我們指定為0。接著掃過序列中的所有項目,並在掃過項目的同時,執行totalWeight + x這個程式碼,掃過序列中的所有項目之後,我們就得到體重的總和totalWeight
  看個不那麼複雜的例子:

> ["吉姆", "威廉", "彼得", "法蘭克"].reduce((totalChar, x) => totalChar + x.length, 0)
< 9

  上面那個例子,我們用reduce算出序列中每個項目字串長度的總和。totalChar是累積的結果,x代表每一個項目,我們將totalChar初始化為0,接著掃過序列中的所有項目,並在每掃過一個項目時執行totalChar + x.length
  想要看更多reduce()的例子,或是更詳細的瞭解reduce()運作的方式,可以到這個網站➃來參考看看。

  好了,JavaScript 的速成班就到這結束了,若對 JavaScript 想更了解的朋友,記得把 Eloquent JavaScript➂ 翻來看一看,或是查看 FireFox 提供的線上資源➄。我們就介紹到這裡,下課囉!

參考資料

好 Js 不學嗎 !?
RE:從零開始的學習 JS 生活
➂ Eloquent JavaScript 官方
➃ 開發者開發資源網站 reduce() 在線說明
➄ 開發者開發資源官方網站

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


上一篇
第 25 天:Flask:登入系統 Flask-Login
下一篇
第 27 天:DataVis:C3 Donut
系列文
從LINE BOT到資料視覺化:賴田捕手30

尚未有邦友留言

立即登入留言