iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

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

Day8-D3 資料整理的API們:Array、Time Formats、Number Formats、Random

  • 分享至 

  • xImage
  •  

整理資料的API們:Array、Time Formats、Number Formats、Random

上一篇介紹完怎麼取得不同格式的資料後,接著要來看的是:怎麼把資料整理或篩選出我們想要的內容~

D3 Array

由於大多數的情況下,使用 d3 的 API 時都必須帶入陣列格式的資料,因此 d3 Array 這個分類也提供最多能協助我們整理資料的API ,先來看看 array 分類中包含的子項目:

  • Statistics 統計數據
    用來運算基本的數據,常用的 API 有: d3.min、d3.max、d3.extent、d3.sum

  • Search 尋找
    用來搜尋陣列給指定的DOM元素使用,常用的 API 有:d3.ascending、d3.descending

  • Transformations 改變結構
    用來改變陣列並生成一個新的陣列,常用的 API 有:d3.merge、d3.range

  • Iterables 迭代
    常用的 API 有: d3.every、d3.some、d3.map、d3.filter、d3.sort

  • Sets 數組
    比較多組資料集的交集/差集狀態,並根據使用的 API 返還一個物件

  • Histograms 直條圖
    把離散的資訊變成連續、不重疊的整數,來產生可以繪製直條圖的數據資料

  • Interning
    使用陣列的key或value

D3 Array 包含非常多API,涵蓋幾乎所有陣列相關的資料處理,有興趣的人可以自行上官方文件查找,我這邊只挑幾個最常用到的來介紹:

  • d3.min
  • d3.max
  • d3.extent
  • d3.sum
  • d3.every
  • d3.some
  • d3.filter
  • d3.sort

d3.mind3.max ⇒ 取最小值/最大值

這兩個方法跟 JS 的 Math.min、Math.max 基本上是一樣的,都是在一個陣列中找出最小或最大值,但有一個細微的差異是,d3.min/max 會忽略掉 undefined, null 或 NaN 的值。我們直接看到以下範例:

const data = [7, 5, 1, 13, 55, 2, 64, null];
const min = d3.min(data);
const max = d3.max(data);
console.log(min, max) // 1, 64

要特別注意的是,這兩個方法只適合用在比較數字。如果陣列中的內容是字串的話,根據官網解說 (特別感謝邦友 @smallstars10 協助解釋),D3.js 會使用 "natural order" 而非 "numeric order"來排序,也就是說字串會先被 轉換成 UTF-16 code unit (介於 0~65535 的整數),接著再根據這個數值去比大小

const dataString = ['狗', '貓', '羊', '豬']
const min = d3.min(dataString )
const max = d3.max(dataString );
console.log(min, max) // '狗' '貓'

最終出來的結果不一定會符合希望能達到的排序結果。

d3.extent ⇒ 同時返回最大與最小值

這是超級方便的一個方法,能同時把資料中的最小值與最大值挑出來,並返還成一個陣列。這個方法通常在設定 scale 那邊會用到,等之後到了 scale 的章節會再講解~

const data = [7, 5, 1, 13, 55, 2, 64, null];
const extent = d3.extent(data); // [1, 64]

d3.sum ⇒ 把資料加總

這個方法會把傳入的資料陣列都加總起來,並返還加總的數值;如果資料陣列中沒有可以加總的數值的話 (例如全部都是字串),就會返還0。

// 數字可以加總
const data = [7, 5, 1, 13, 55, 2, 64, null];
const sum = d3.sum(data)
console.log(sum); // 147

// 字串無法加總
const dataString = ['狗', '貓', '羊', '豬']
const sumString = d3.sum(dataString)
console.log(sumString);  // 0

d3.every ⇒ 遍歷資料陣列,確認陣列內的值是否全都符合條件

每次遇到新的API時,我都會先去查官方文件的資料,確認是否有必須要帶入的參數。我們這次一樣看到官方文件的 API 設定,文件上寫著 # d3.some(iterable, test) ,只要參數沒有用 [ ] 括起來,就代表是必須填入的參數,只要沒填就會報錯
https://ithelp.ithome.com.tw/upload/images/20210920/20134930QZD4GtPFqE.jpg

這個方法跟 JS 的 array.every 很相似,一樣需要帶入兩個參數:d3.every ( 資料, 方法 ),帶入的這個方法會遍歷帶入的資料陣列,如果陣列內的每個數都符合設定的條件,就會回傳true;如果其中任何一個數不符合條件,則會回傳false

// 全部的數字都非空值
const data = [7, 5, 1, 13, 55, 2, 64];
const every = d3.every(data, d=>d)
console.log(every);  // true 

// 全部的數字都大於20
const data = [7, 5, 1, 13, 55, 2, 64];
const every = d3.every(data, d => d > 20)
console.log(every);  // false

d3.some ⇒ 遍歷資料陣列,確認陣列內的任一資料是否符合條件

跟 d3.every 一樣是帶入兩個參數:d3.some( 資料, 方法 ),但用法剛好相反;帶入的這個方法會遍歷資料陣列,如果陣列內的任一資料符合設定的條件,就會回傳true;如果全部都不符合,才會回傳false

// 其中任意一個數字大於20
const data = [7, 5, 1, 13, 55, 2, 64];
const some = d3.some(data, d => d > 10)
console.log('some',some); // true

d3.filter ⇒ 遍歷資料陣列,返還陣列內所有符合條件的資料

這個方法跟 array.filter 很相似,一樣是帶入兩個參數:d3.filter( 資料, 方法 ),在帶入的方法內設定條件,並設定只返還所有符合條件的資料。

const data = [7, 5, 1, 13, 55, 2, 64];
const filter = d3.filter(data, d=>d>10)
console.log('filter',filter); // [13, 55, 64]

d3.sort ⇒ 按照條件排序資料陣列

這個方法是用來整理並排序陣列中的資料,它一樣要帶入兩個參數:d3.sort( 資料, 方法 ),比較特別的是,如果參數沒有帶入方法的話,d3.sort就會自動使用 d3.ascending 當作它的參數,並按照 d3.ascending 的方法排序陣列 (由小排到大)

const data = [7, 5, 1, 13, 55, 2, 64, null];
const sort= d3.sort(data)
console.log('sort',sort); // [1, 2, 5, 7, 13, 55, 64, null]

陣列最常用的方法就介紹到這邊~接著我們來看看另外一個類型吧!


D3 Time Formats

除了整理陣列資料之外,有時候我們的圖表會需要帶上一些日期、時間等數據資料,這時候就需要 轉換日期或時間的API 啦~ 我個人認為時間或日期的轉換實在頗複雜,還好 D3 有一些內建好的API 可以使用。
https://ithelp.ithome.com.tw/upload/images/20210920/20134930xHTGqMflbM.jpg

一開始看到 Time Format 的官方文件時,會發現大部分的API 都寫著:alias for _____ on the default local. 這是因為D3 v3 以前的版本是用 locale 這個 API 在處理日期、時間、語言的轉換,而 v4 之後的版本將它重新改名變成 Time Formats 系列,但它們的方法都是一樣的。

這邊一樣介紹幾個比較常用到的API,有其他需求的人可以直接上 Time Formats 官方文件 去找自己想使用的方法。我自己比較常用的API 是:

  • d3.timeParse
  • d3.timeFormate

d3.timeParse ⇒ 將日期等資訊轉換成 D3 看得懂的數值

這個方法是 D3 用來處理時間格式的 API。只要是跟時間、日期相關的圖表,都要用這個方法先把資料轉換成D3能讀懂、能夠去計算的數值,之後才能去建構圖表,通常都是用在設定圖表的 scale( ) 或 domain( )。

d3.timeParse( ) 本身是一個方法,我們呼叫它時要帶入特定的參數 (符合要處理的資料格式);之後 d3.timeParse( ) 會回傳另一個方法,我們再接著使用這個回傳的方法去計算要帶入的資料,實際運作如下:

const timeData = '2021-09-07'
const timeParse = d3.timeParse('%Y-%m-%d')
timeParse(timeData) // Tue Sep 07 2021 00:00:00 GMT+0800 (台北標準時間)

要注意的是,這邊帶入的參數格式一定要符合資料的格式哦,如果資料格式是 "2021/09/07",參數也要改成 d3.timeParse ('%Y/%m/%d'),否則你就會得到 null 的結果。

至於可以帶入那些參數呢?我常用的基本上有 (按照常用順序排列):

參數 格式
%Y 西元年
%y 西元年最末的兩位數
%m 一年的某一個月 ( 01 到 12 )
%d 一月的某一天 (1 到 31)
%j 一年的某一天 ( 001 到 366 )
%B 月份
%b 月份的縮寫
%A 星期幾
%a 星期幾的縮寫

其他的就比較沒那麼常用,如果有需要或是想看詳細版的人,可以上官方文件 locale.format 去查找~

d3.timeFormat ⇒ 將D3的日期時間數值轉換成我們看得懂的文字

d3.timeFormat( ) 的寫法跟 d3.timeParse( ) 是一樣的,只是作用剛好相反。d3.timeFormat( ) 是把 D3能處理的數值,轉成我們看得懂的文字,通常都用在 axis( ) 跟 ticks( ) 畫軸線、標示刻度時使用。

這個 API 通常是在畫軸線、標示刻度時使用,寫法跟 d3.timeParse( ) 稍微不同的地方是 d3.timeFormat( ) 可以自己設定轉換後日期的顯示格式,所以就不用按照原始資料的日期格式來進行參數配置。直接看例子

// Time Formats 轉換成 D3 看得懂的數值
const timeData = '2021-09-07'
const timeParse = d3.timeParse('%Y-%m-%d')
const parsedData = timeParse(timeData)
console.log(parsedData);  // 得到D3看得懂的數值

// Time Formate 轉換成人類看得懂的數值
const timeFormat = d3.timeFormat('%Y/%m/%d') // 轉換後想變成用 "/" 分隔
console.log(timeFormat(parsedData)) // 2021/09/07

有關 Time Formats 的方法目前先瞭解這樣就夠了,之後的「軸線與刻度 Axis & ticks 」篇章會有實際的應用~


D3 Number Formats

除了 Time Formats 之外,還有另外一個分類的 Number Formats 專門在處理數字格式的轉換。其實數字並不需要經過特殊處理就能直接使用在d3.scale 的 API 中,但如果希望呈現特定的數字格式,就可以用 D3 Time Formats 提供的 API 快速處理,方便許多~

Time Formats 的 API 包含以下幾種:
https://ithelp.ithome.com.tw/upload/images/20210920/20134930Y9wtCM5FOl.jpg

我自己偶爾會用到的就是 d3.format,而且通常也是在處理 Axis 軸線時使用。

d3.format ⇒ 轉換數字格式

d3 number format 系列跟 d3 Time Format 很相似,在v3版以前都是歸類在local 下方的,也因此如果你查官方文件的話,會被指引到 local.format 的頁面。

d3.format( ) 一樣是要帶入參數,參數填入欲設定的數字格式,接著呼叫此API後會回傳一個方法,透過這個方法就能將資料轉成我們想要的格式

// Number Formate
const originNumber = 10
const numberFormat = d3.format('b') // 參數 b 是指二進位制
console.log(numberFormat(originNumber)); // 1010

至於哪些參數分別代表什麼樣的格式呢? 這邊列出幾個最常用的,如果想看詳細參數設定,可以參考這邊

參數 格式 範例
d 返回這個數字的字串格式,忽略任何非整數值 d3.format('d')(12.35)
g 指定位數 (參數前需要填入幾位數) d3.format('2g')(120)
f 指定小數點後的位數( 參數前需要先填上幾位數) d3.format('.2f')(3.14159)

D3 Random

最後再講另一類偶爾會用到的API: D3 Random Numbers 。D3 Random Numbers 這類的方法主要是快速產生一些亂數以供我們使用。
https://ithelp.ithome.com.tw/upload/images/20210920/20134930glpwHspQsx.jpg

D3 一樣提供了超級多亂數相關的API,以下介紹比較常用到的:

d3.randomInt ⇒ 產生一個隨機整數

我們一樣先來看看官方文件對這個 API 的解釋
https://ithelp.ithome.com.tw/upload/images/20210920/20134930JoPqgyUCC9.jpg

文件上說明,d3.randomInt 這個 API 會回傳一個方法,我們可以使用這個方法在指定的範圍生出一個隨機整數。至於怎麼設定亂數的範圍呢?這個 API 可以帶入兩個參數,這兩個參數分別代表著範圍的最小值與最大值,舉例來說,如果我們想要一個介於50跟100之間的隨機整數:

//使用 API 設定範圍
d3.randomInt(50,100)

// 使用方法生成亂數
d3.randomInt(50,100)() //76

這樣就能成功得到一個隨機整數了,是不是非常簡單呢?

好啦!整理資料的 API 就講到這邊~常用到的我都已經搜集在這篇文了,如果有沒提到但你又需要使用的 API,建議直接去看官方文件哦~會省下很多東查查西查查的時間。如果還不太知道這些 API 要用在哪邊也沒關係,等到之後實作的篇章時,就更清楚該如何去使用~今天就先講到這邊啦,我們明天見!


Github Page 圖表與 Github 程式碼

最後附上本章的程式碼與圖表 GithubGithub Page,今天的頁面一樣要開啟devTool 來看console 內容哦,需要的人請自行取用~


上一篇
Day7-D3 不同格式檔資料匯入的API
下一篇
Day9-D3繪圖:繪製形狀的Helper Functions
系列文
三十天成為D3.js v7 好手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
smallstars10
iT邦新手 5 級 ‧ 2022-08-01 16:12:41

d3.mind3.max 對字串的比較並不是隨機的
字串會先被轉換成 UTF-16 code unit,是介於0~65535的整數
然後才比較大小
const dataString = ['狗', '貓', '羊', '豬']
dataString.forEach(d => console.log(d.charCodeAt()))
// 29399, 35987, 32650, 35948
charCodeAt的MDN

金金 iT邦新手 1 級 ‧ 2022-08-01 16:59:25 檢舉

原來如此!非常感謝你的解釋~想問一下,我看官網上的文件是說"elements are compared using natural order rather than numeric order.",但並沒有特別註明 natural order 是使用 UTF-8/UTF-16,請問這部分是從哪裡得知的呢? 再次感謝你協助釐清!我再來修正一下我的文章~

我是看Array.prototype.sort()去猜他可能是用Unicode的方式去比較

以下是根據我測試的結果做結論,沒有參考文件
我不確定d3.maxd3.min 在比大小用了哪些演算法
可以確定的是string不是只有使用Unicode
還會用到Lexicographic order
也就是從第一個字開始比,一樣的話再比第二個,以下範例就可以看出來
const dataString = ['貓', '貓狗貓', '狗', '羊']
dataString.forEach(d => console.log(d.charCodeAt()))
// 35987, 35987, 29399, 32650
const min = d3.min(dataString)
const max = d3.max(dataString)
console.log(min, max) // '狗' '貓狗貓'
同樣都是35987,卻選了'貓狗貓'當max

另一個英文字串的例子
const strList = ['Driven', 'Data', 'Documents']
strArr.forEach(d => console.log(d.charCodeAt())) // 都是68
console.log(d3.min(strList)) // 'Data'

但是針對number就不會用到Unicode
const numberArray = [1002, 30, 4, 21, 101]
const min = d3.min(numberArray)
const max = d3.max(numberArray)
console.log(min, max) // 4, 1002

如果是字串化的數字還是會用Unicode、Lexicographic order
const stringifyNumberList = ['1032', '30', '4', '21', '101']
const min = d3.min(stringifyNumberList)
const max = d3.max(stringifyNumberList)
console.log(min, max) // 101, 4

不過我覺得實務上很少用string在比大小的,通常都是用number
以上如果有錯誤的部份,請其他大神幫忙修正,感謝

金金 iT邦新手 1 級 ‧ 2022-08-02 14:34:54 檢舉

感謝解釋!

我要留言

立即登入留言