D3 資料視覺化 - 初探 D3.js

過去在網頁上描繪各種圖形或是統計圖表不是一件容易的事,對於一些簡單的圖形(例如長條圖)僅能透過 DOM 進行實作,如果要進一步的視覺效果則必須依賴其它的外掛程式,例如 Flash。

HTML5 導入了額外的 Canvas, SVG 元等素支援繪圖功能,當你需要將數字資料視覺化時,可以透過 這些新的元素進行實作,Canvas, SVG 是相當基礎的標籤,對於高度複雜且具美觀要求的圖形描繪,依然須進一步撰寫大量的 JavaScript 完成實踐。

SVG 是一種向量圖形,為了避免撰寫大量處理圖形的 JavaScript 程式碼,我們可以利用D3.js來處理 SVG 之類的向量圖形內容,將以下這一行嵌入網頁的 head 標籤中即可開始使用 D3。

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

D3透過簡潔的鏈結語法操控 DOM 內容,快速載入資料,繫結至特定的元素物件,進而產生所需的視覺效果或是各種對應圖表。

d3.select("body").style("background-color", "blue");

例如這一行 D3 鏈結程式碼將網頁背景設定成藍色(blue)。

想要瞭解 D3在網頁上呈現的視覺效果,可以瀏覽 https://github.com/mbostock/d3/wiki/Gallery 這個範例集散網頁。


右方側邊欄中,有 API中文手冊連結,想閱讀 API 請自行前往參考。

操作 DOM

D3 是一套JavaScript 函式庫,以資料為導向進行文件內容的維護處理,當你需要將一群資料轉化成網頁文件內容,可以考慮透過D3進行實作降低JavaScript的複雜度。
D3 採用了大量的鏈結語法,結合回呼函式進行資料處理並根據回傳的資料進一步控制網頁內容,因此在使用D3之前,必須對JavaScript語法與DOM有足夠的認識與理解。
運用D3 有兩個部份首先必須理解-DOM維護與資料處理。

D3 可以輕易操控網頁內容,考慮以下的配置:

<div>
    <p>響應式網頁設計精要</p>
    <p class="d3p">HTML5完美風暴III</p>
    <p class="d3p">HTML5從零開始:前端開發完全入門</p>
    <p class="d3p">D3 實務精要</p>
</div>

假設要調整其中p元素的字型,利用傳統的DOM API 所需的程式碼如下:

var ps = document.getElementsByClassName('d3p');
for (var key in ps) {
ps[key].style.setProperty("font-size", "1.6em", null);
}

調用getElementsByClassName(),利用類別名稱將要調整字型的p元素找出來,然後透過迴圈逐一設定其style 樣式。



在輸出結果當中,由於第一個P元素並不屬於d3p類別,因此維持原來的字型大小,後三個P元素則放大至1.6em。現在來看D3 的寫法:

d3.selectAll('.d3p').style('font-size', '1.6em');

這是一段典型的 D3 鏈結語法,selectAll() 表示取出選擇器為 .d3p 的元素,然後逐一將其 style 設定為 1.6em 的字型大小。如你所見,操控DOM在D3非常的簡單,特別的是你可以進一步擴充鏈結語法字串以對付更複雜的結構,避免冗長的迴圈語法。

d3.selectAll() 可以找出所有符合條件的元素,如果你只是要找到第一個,只要調用d3.select() 再將條件參數傳入即可,例如修改如下:

d3.select('.d3p').style('font-size', '1.6em');

這一行程式碼將只會放大第二行文字,也就是符合d3p類別名稱的第一個元素。另外再看一個例子,假設要將網頁中所有p元素的內容設定成某個特別的字串,只需以下的語法:

d3.selectAll('p').text('Hello D3 !');

其中的text 表示要將指定的文字「Hello D3 !」嵌入selectAll找到的所有P元素當中,如此一來上述網頁會呈現四行的Hello D3 !。
除了鏈結語法,D3強大的另外一個地方在於其接受動態屬性,我們可以在網頁執行的過程中,透過預先寫好的函式動態載入所要處理的即時資料內容,這對於需要程式化控制的動態內容處理相當有用。考慮以下另外一個網頁配置:

<div style="font-size:2em">
今天星期:<span id="day"></span>
</div>

其中的內容必須透過程式取得今天所代表的日子,並根據不同的日子變換顏色,因此我們透過動態屬性支援的函式語法進行實作。

var color = ['red', 'orange', 'yellow','green', 'blue', 'cyan', 'purple', ];

d3.select('#day').style('color', function () {            
            return color[(new Date()).getDay()];
}).text(function () {
            return (new Date()).getDay();
});

將要測試的顏色名稱儲存在陣列中方便讀取,接下來的鏈結語法首先取得透過ID名稱day取得 SPAN元素,設定其style樣式,因為樣式名稱必須根據取得的日期進行顏色的設定,因此除了第一個參數指定為color ,第二個參數則是一個function。

function () {            
return color[(new Date()).getDay()];
}

在這個函式中,取得當日所代表的數字,將其當作索引值傳入color陣列,取出對應的顏色值,這個值透過 return回傳,成為color 樣式值。接下來的text() 原理相同。

如你所見,D3 的鏈結語法搭配動態屬性設計,可以讓我們透過精簡的程式碼,處理複雜的DOM維護作業,這對於網頁資料的視覺化處理相當的好用。

資料圖形化

D3著眼於資料處理,除了優異的DOM操作功能之外,最核心的關鍵功能便是資料綁定,一旦瞭解如何設計,你將能夠輕易透過D3將文字資料轉換成圖表進行呈現。

透過JavaScript 處理資料並不容易,D3是為了簡化資料處理作業而開發的一套函式庫,特別擅長執行資料的視覺化呈現,無論陣列、JSON或是CSV資料,搭配簡單的DOM元素或是SVG,均能有效的進行內容的整合與圖形轉換運算。
考慮以下的陣列資料:

var numbers = [6, 8, 10, 2, 4, 1, 5, 9];

當我們要將這個陣列內的資料取出顯示在網頁上,必須透過迴圈進行實作。

<div id="area">
</div>

於網頁上配置一個div元素以顯示陣列內容,並且設定其id屬性以方便存取。

for (var i = 0; i < numbers.length; i++) {
var p = document.createElement('p');
p.textContent = i.toString();
document.getElementById('area').appendChild(p);
}

for迴圈依序取出陣列中的所有元素,設定給新建立的p元素,最後將其嵌入div中。當資料的內容愈複雜,所需的JavaScript會愈冗長且愈難以處理,而同樣的功能,利用D3來簡化所需的程式碼如下:  

d3.select('#area').
selectAll('p').
data(numbers).enter().
append('p').
text(function (d) {
        return d.toString()
});

data() 為此段程式碼的關鍵,將要處理的資料物件傳入-這裏是numbers,它會解析這個資料物件,並且如同迴圈邏輯,根據其中的資料筆數,逐一取出每一筆資料作處理,enter()表示針對data() 所取得的資料建立新的節點,到目前為止是資料萃取的部份,接下來則是建立節點的內容,由於傳入的資料物件numbers 有8筆資料,因此會有8個p節點會被建立,然後根據text()的參數建立其文字內容。

 資料(numbers)是逐次動態取出的, text() 參數透過回呼函式即時讀取,請注意function(d)中的d表示每一次取出的單筆資料,這個觀念非常重要,後續進一步討論其它更複雜的格式資料處理時,會應用到這裏的原理。

6
8
10
2
4
1
5
9

上面演示的程式碼中,無論JavaScript或是D3語法,都會得到以下相同的結果,其中配置了8個p元素,並且以陣列中的資料為其文字內容呈現,不過D3在撰寫上要容易許多,這對於複雜的資料處理甚為有用,D3在這一部份提供了強大的功能,我們持續深入其中的細節。 

鏈結語法萃取的資料,可直接於回呼函式中運用於圖表的繪製邏輯,簡單的圖表可以透過DOM描繪,更複雜的圖形則必須進一步依賴SVG技術。現在進一步將上述的資料轉換成為圖表,首先建立一個樣式類別bar:

.bar{
background-color:skyblue;
 color:#fff;
    text-align:right;
    padding:4px 1em;
}

這一段設計提供長條圖所需的背景、文字以及邊界樣式內容,接下來再根據資料指定寬度並呈現資料即可,調整程式內容如下:

d3.select('#area').
selectAll('p').
data(numbers).enter().
append('p').
attr('class', 'bar').
text(function (d) {
return (d.toString());
}).
    style('width' ,function (d) {
               return (d * 100)+ 'px' ;
});

以網底標示的部份設定每一個建立的p元素其類別為bar ,套用上述的bar樣式,最後再根據資料設定寬度(width)樣式,將每一筆回傳的資料乘上適當的倍數並加上長度單位px,如此一來可以得到一個簡單的圖表。



處理 SVG 

Svg提供強大的繪圖功能,對於商業應用的圖表繪製,透過svg進行實作可以得到比較好的效果,D3在這方面內建出色的支援,考慮以下的配置:

<body>
    <div id="s">
    </div>
    <script>
        d3.select('#s').append('svg')
            .attr('width', 600).attr('height', 360)
            .append('rect')
            .attr('x', 10).attr('y', 10)
            .attr('width', 300).attr('height', 180)
            .attr('fill', 'dimgray');
    </script>
</body>

首先append('svg') 將一個svg 元素那入指定的 div 元素中,接下來的 append('rect') 則於 svg 區域描繪一個矩形,然後依序設定矩形的相關屬性,最後我們可以在網頁上得到以下的結果:


網頁上呈現一個灰色方塊的 svg 元素,這是最簡單的svg 繪圖實作,由於 svg 本身就是標籤,因此到目前為止所討論的D3語法同樣適用svg圖形繪製。現在針對一組陣列資料範例,將原本透過div實作的長條圖,以 svg 再描繪一次。

<body>
    <div id="s">
    </div>
    <script>
        var numbers = [677, 86, 500, 235, 488, 14, 556, 92];
        d3.select('#s').append('svg')
            .attr('width', 600).attr('height', 360).selectAll('rect')
            .data(numbers).enter()
            .append('rect')
            .attr('x', 10).attr('y', function (d,i) { return i * 30; })
            .attr('width',function (d) { return d + 60 }   ).attr('height', 20 )
            .attr('fill', 'royalblue');
    </script>
</body>

由於在D3 資料視覺化 - 使用D3維護DOMD3 資料視覺化 - 資料綁定與圖表描繪這兩篇文章中已經說明了相關的語法,這裏不再討論,此段script 會得到以下的結果:



從這裏讀者可以發現,D3語法簡化了建構 svg 內容的過程,而實際的圖形描繪,依然需要足夠的svg知識才能處理,所以在更深入D3之前,務必先對svg有足夠的理解。

導入互動事件

D3允許你為描繪好的圖表建立互動性,關鍵作法是在資料萃取過程中,同時完成事件註冊設定,你可以指定任何想要互動的事件名稱,先不考慮SVG與資料,以下來看如何為特定元素註冊事件。
d3.select("body").append('div')
    .style({"width": "100px",
    "height": "150px",
    "background-color": "yellow",
    "margin": "10px"
    }).on('click', function () {alert('Hellow D3 !');});

這段程式碼在畫面上呈現一個黃色方塊,最後一行引用 on() 並指定第一個參數為 click ,表示註冊 click 事件,第二個函式物件參數表示當使用者點擊此方塊即時回應其中的訊息。



相同的原理,可以直接套用於由資料動態產生的元素中,考慮以下的陣列:

var datainfo = [120, 480, 226, 568, 230, 186, 45, 256, 390];

利用這個陣列產生由div元素構成的水平長條圖,所需的程式碼如下:

d3.select("body").selectAll('div')
    .data(datainfo).enter()
    .append('div').style({
    "background-color":'blue' , 
    "width": function (d) { return d + 'px'; },
    "height": '24px'  
    }).on('click', function (d) { alert('資料值:'+d); });

如前述說明,於最後引用 on() 註冊click事件,並且於回呼函式中回傳此筆資料所屬的內容資料。



現在點擊任一長條,則會顯示此長條所展示的資料說明訊息。

D3 完全融合 JavaScript 語法, 因此只要熟悉 JavaScript ,遇用原生 JavaScript 語法很快便能上手建構需要的資料圖表。


沒有留言: