第223期 / May 5, 2016

分享到臉書!分享到維特!分享到噗浪!分享到Google+!分享到微博!轉寄友人友善列印

使用Data-Driven Document自製視覺化工具介紹

作者/洪坤德

[發表日期:2016/3/4]

Introduction

近年來大數據盛行,無論是政府、企業甚至個人都開始回頭來檢視自己所擁有的資料,但是既有的資料眾多為了讓使用者快速的解讀資料的內容,視覺化就扮演相當重要的角色。網頁是一個最容易達到跨平台呈現且兼具互動功能的視覺化介面,相關的JavaScript類別庫相當的多,如D3.js、chart.js、highchart.js...等。 D3.js是 Data-Driven Document的縮寫,一種可將數據視覺化的JavaScript 類別庫,提供網頁將數據以圖像的方式呈現;實作雖較為複雜,但是彈性極大,足以客製化呈現所有客戶端的需求,本文將以實例的方式介紹D3.js的功能,包含Bar chart、Circle、Data Binding…相關的範例請上github下載( https://github.com/kunde520/D3Sample )。

Data

圖一是資料的截圖,本次文章使用到的資料是虛擬資料,內容主要是從2015年1月1日到2015年8月17日,不同標籤內容(Big Data、Hadoop、Cloudera、Hortonworks、MapR、BigInsights、Pivotal)於六都的正負向評價,欄位中P結尾代表正向、M結尾代表負向,共1603筆。


《圖一》各標籤於六都評價


Use Bar Chart

Bar Chart是最常用的圖表格式,如圖二,第一筆資料為2015年1月1日Hadoop標籤於六都的正負向文章的數量,分別是76、8、71、98、11、10、49、74、97、55、98、26;單用數值無法立即理解彼此正負向文章的數量關係,最簡單的就是利用Bar Chart來呈現,圖三為使用D3.js繪製的結果。


《圖二》2015年1月1日Hadoop標籤於六都的正負向文章的數量



《圖三》使用D3.js繪製的結果


要達成上述的圖形其實很簡單,原始碼分Html、Css Style以及JavaScript呈現如圖四、圖五以及圖六,其中Html原始碼當中必須引用jQery.js跟d3.js,而JavaScript為本次說明的重點,將在依Selector、Scale、Data Binding分述亦可參考github網站下載之範例一。


《圖四》HTML原始碼



《圖五》CSS原始碼



《圖六》JavaScript原始碼


Selector

D3.js提供了兩種選擇元素的方式:select、selectAll。如同一般jQuery所提供的Selector,這兩種選擇元素的方式分別可以選取單一及多筆。如上圖六所示,本次文章就用到了兩種選擇方式,



以及



其中前者是幫助我們把Html中的選取後放置於變數chart中,供後續使用;但後者在Html的原始碼中應該是不存在,所以如果單獨使用會是一個空的集合。這邊我們可以先解釋JavaScript裡面的第一段



這裡選擇了Class是chart的元素,並且指定其高度與寬度的大小。

Scale

在本次的例子中,若將數值即視為該Bar Chart的像素高度,其畫面將如圖七所示。


《圖七》使用數值當作畫面的像素


在有限的範圍裏面(本文章用的是960*500),根據不同的資料來源應該要能夠動態調整數值在畫面中的比例,使其呈現結果最佳,試想像如果當中有數值超過500,是不是畫面就不能呈現了。因此D3才會提供Scale的API,如表一。


《表一》Scale API-Reference
資料來源:https://github.com/mbostock/d3/wiki/API-Reference




從上方的原始碼中可以發現,本次使用的數值被裝在名為values的容器當中,而d3.scale就是本次的重點,我們產生了一個線性的比例尺y,並且指定range必須介於[0, height]也就是[0,500],domain則是[0, d3.max(values)]也就是[0,98],其中range所指的是輸出的範圍,也就是藉由比例尺y所產生出的結果必須介在0到500之間;而domain所指的是輸入的範圍,因為本次數列最大值只有到98,因此輸入必須介在0到98之間。
使用d3.max(values)是為了取出values中最大值,使這張圖表能根據數值的內容作彈性的變化,其他功能可參考D3所提供的Working with Arrays API-Reference,如表二,用法就如同一般副程式,將數值帶入即可得到對應的結果,如.attr("height", function (d) { return y(d); })中的y(d)。


《表二》Working with Arrays API-Reference
資料來源:https://github.com/mbostock/d3/wiki/API-Reference


Data Binding

接下來是D3的重頭戲,Data Binding的部分,D3雖然實作起來比較繁雜,但其彈性就可以相當的大;首先chart.selectAll("g")代表就是選擇所有標籤為g的元素,不過正常來說會找不到,因為一開始根本沒有這樣的內容,接下來就是利用.data(values)將資料傳入D3.js內,此時會回傳一個與資料大大小相同的Array,.enter()則是讓每筆資料在Array內以Object的方式存在,其值則為該Object的data屬性,如圖八所示。




《圖八》不同階段所產生出的結果


接下來就是繪圖了,由於變數bar所指向的是產生在畫面上的12個繪圖元素,根據這12個元素我們分別要產生Bar Chart跟文字,如下方程式所示,唯一要說明的只有給予attr數值時的差別,這邊允許四種方式:直接給值、匿名程式(無參數、一個參數以及兩個參數),若要將data傳入匿名程式當中就必須要採用一個參數的方式,也就是.attr("height", function (d) { return y(d); })。若同時需要data以及index則使用.attr("class", function (d, i) {if ((i % 2) == 1) return "odd";}),此時d代表data、i代表Index,這是限制。



Use Circle

使用Circle的面積來代表資料量的大小亦是常見的作法,呈現的結果如圖九。Html、Css以及JavaScript原始碼分別呈現於圖十、十一及十二,由圖例中可以發現,其實差距不大,只有將原本產生rect標籤改成circle而已,參考github網站下載之範例二。


《圖九》Circle呈現



《圖十》Html原始碼



《圖十一》Css原始碼



《圖十二》JavaScript原始碼


Add Axis

在使用上述文章中,我們利用了Bar Chart 及Circle來呈現數值的狀態,不過如果該數值並沒有被呈現在畫面上,因此我們必須要改變一下呈現方式,加入X軸作為標籤說明、Y軸作為數值的對應,並且把原本的說明文字改成數值,呈現結果如圖十三。


《圖十三》加入了X、軸的圖形


參考github網站下載之範例三,為了加入X軸及Y軸,我們必須要利用到剛剛說明過的Scale,程式碼如圖十四。


《圖十四》部分程式碼


Scale的部分就不多做說明了,Axis的部分使用到了d3內建的API,呼叫axis()產生一個新的axis產生器,允許調整innerTickSize、orient、outerTickSize、scale、tickFormat、tickPadding、tickSize、ticks以及tickValues,這個範例在x軸使用了orient將其調整於畫面的下方,而y軸則特別指定了ticks將間格分為十格。為了將x軸與y軸繪製於畫面上,必須使用在append物件後呼叫該axis產生器。

Use More Data

上述的範例都是將資料完全寫死在程式碼之中,但這樣的作法其實是完全沒有彈性的,當資料量一多就會是個災難,D3.js提供多種檔案的讀取API,如表三。


《表三》Loading External Resources API-Reference
資料來源:https://github.com/mbostock/d3/wiki/API-Reference


本次使用的範例是讀取csv檔,如下方程式碼所示,第一種是單純讀csv檔,第二種同時會將資料傳入匿名程式之中,第三種包含了錯誤的處理,允許使用這針對讀檔錯誤時做相對應的處理,第四種是加入了逐一訪問的函式,由於D3會將每筆資料裝在一個物件裡面,若要針對物件進行額外的處置,就必須要使用到這種方式。



由於資料筆數相當多,所以必須先做整理,我們預期先從資料當中取出2015年1月1日所有標籤正向及負向評價的數量進行呈現,結果如圖十五。


《圖十五》2015年1月1日各標籤正負向評價


這邊除了用到上述讀取csv的api外,另外使用了客製化的物件來協助圖表的呈現,首先先利用Date將資料進行分組,關於linq.js的使用技巧這邊就不贅述,程式碼如下,藉由這樣的方式我們會得到一個Size為229的Array,我們取出日期為2015年1月1日的群組來使用。



每一筆資料的如圖十六所示,不過從property裡面會發現,多了negative及positive兩個原本不再資料欄位的內容,這是因為這邊針對該物件進行額外的處置,如下方程式碼所示,我們撰寫了一個副程式名字叫做type,在程式內針對所有既有property進行加總,將正向及負向的評價加總起來,剩下的就是針對資料進行繪圖了,這邊就不多做說明,可以參考github網站下載之範例四。


《圖十六》分組後的物件樣貌




Use Transaction and Event

最後一個要介紹的東西是Transaction,由於上面的範例只使用到了一天的資料,如果希望能夠隨著使用者的選擇而變換不同的日期,才能使得這個圖表兼具互動性跟完整性;圖表隨著資料的變化如果少了中間的過場,就會變得相當奇怪。範例五希望透過滑鼠在日期文字上左右滑動後能夠切換不同的日期,因此必須要加上滑鼠移動的事件來完成這件事情,參考程式碼如圖十七,我們先新增了日期的文字並且在讀取檔案的副程式內,宣告了一個滑鼠位移的區間(名為box),在該區間上方覆蓋了一個透明的長條圖,並且在長條圖上綁定mourseover的事件(名為enableInteraction),其中內含的yearScale變數是為了要讓滑鼠左右移動的x軸座標轉換成固定範圍的數字,接著才能藉由displayDate將其轉換成日期。


《圖十七》程式碼截圖


接下來比較重要的是Transaction的部分,D3.js因為有資料綁定的技術,所以只需要更新資料就可以轉化圖表的樣貌,我們將更新資料的副程式放在dispalyDate裡面,當日期更新的時候,同時從原本的result當中取得正確的資料集,然後呼叫updateDate,這裡以更新y軸及正面評價說明,如圖十八。


《圖十八》更新資料


首先必須先更新y軸Scale所能輸入的範圍跟資料集符合,也就是說y軸的刻度必須要跟著不同日期而變動,也就是下列的程式碼。



接下來就是選取正面評價的既有元素(class是bar的長條圖以及class是whiteText的文字)並且給予其新的dataset,原始碼如下。



在物件都選取及調整完畢之後,只需要在給予新的屬性之前加上transition()即可產生相對應的動畫,此時duration(100)代表轉場的時間長度為100毫秒,原始碼如下;值得注意的事情是,這邊不需要利用enter(),enter只有在必須要增加筆數的時候才需要使用。



不過還沒完,由於我們更新了新的圖樣,但並沒有把既有的圖形移除,因此還必須要使用到exit(),原始碼如下,詳細的狀況可以參考github網站下載之範例五。



Conclusion

D3.js比較屬於資料呈現範疇,可能無法拿來當作資料探索的工具,不過在展現資料圖形化上,他絕對是相當強大的工具,雖然在實作上比較複雜,不過也因此具備了相當大的彈性,同時D3.js亦可與地理圖資(如OpenStreetMap)結合。目前在實務上已經有相當多的案例使用了D3.js做為資料呈現的工具,包含政府的預算書、國家財富與健康的關係…等,相信藉由本篇文章的介紹,可以很容易修改範例達成自己想要的效果。

參考資料

Data-Driven Documents。
Data-Driven Documents Wiki。
The Wealth & Health of Nations。
D3.js - Getting Started。
資料視覺化。
Kunde’s GitHub。