本文是針對 d3.js 的入門介紹 #2
我在上一篇入門文章中介紹了 D3 的選擇器和資料繫結的基本方法。本篇仍然將接著介紹其它的基本功能。
比例調整
D3 提供了一個相當強大而方便的比例調整功能, 它讓你的圖型輸出(output range)依資料範疇(domain)而調整。舉個簡單的例子:
var data = [50, 10, 100, 40, 80, 20];
var maxWidth =
document.getElementsByClassName('chart')[0].offsetWidth - 10;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([10, maxWidth]);
如果你使用 d3 V7 的話, 請把程式中的 d3.scale.linear() 改作 d3.scaleLinear()。
我們將把這幾筆資料依序畫出為 bar chart。在這個例子中, 資料(data)的最大值是 100, 而輸出物件的最大寬度被設定為 maxWidth, 這是某個 DOM 元素的最大寬度略減 10px。使用以上的程式, 我們定義 domain 介於 0 和 data 的最大值之間, range 則介於 10 和 maxWidth 之間。如此一來, 當我們在輸出圖形時, 它的最小寬度至少有 10px。如果你把 range 的最小值設定為 0, 那麼圖形的最小寬度就會是 0。在這個例子裡, 我們可以看出 data 的最大值是 100。假設 maxWidth 是 500px, 那麼輸出圖形的最大值會剛好等於 500px, 各個值都會畫出等比例長度的長條圖。範例程式的原始碼請看這個 jsFiddle。其輸出圖形如下:
這裡的 x 實際上是個 function, 所以它接受像 x(5) 這樣的寫法。把你傳入一個數值之後, 它會根據定義的 range 跟 domain, 傳回一個符合範圍區間的數值。
不過, 在這個輸出畫面中, 有幾個並不精確的地方。首先, 由於我們把 range 的最小值設定為 10px, 所以即使 data 裡有某個值是 0, 它也會有 10px 的寬度, 而不是 0px。其次, 如果你仔細研究那個 jsFiddle 的話, 你會發現我把 padding 設定為 3px。這些設定都會影響到輸出圖形的精確度。所以如果在你的程式中精確度比美觀度重要的話, 以上兩點是你必須好好考慮的。
如果你把這個 jsFiddle 裡的各個值調整一下, 例如把某個值設定為很大的數字, 輸出圖形將會改變:
換句話說, 不管資料範疇如何變動, 輸出圖形都能夠畫得出來, 不致於跑到畫面之外。這就是 D3 比例調整功能的主要目的。
值得注意的是, 由於 jsFiddle 內建支援的 D3 版本是 3.X 版本, 所以本文中使用 3.X 的語法。在 4.X 版本中, 已不再使用像 scale.linear() 函式, 而是改成 scaleLinear() 函式; 讀者請自行轉換此類語法。
繪製 SVG
D3 最重要的功能之一,就是可以方便地協助開發者繪製 SVG。雖然我們也可以用原有的方法畫 SVG 圖形,但 D3 提供了很多配套工具;包括前面提到的轉場功能和比例調整功能。在這裡我先示範一個相對簡單的做法。
var data = [
{x:50,y:10}, {x:10,y:110}, {x:60,y:90}, {x:110,y:110}, {x:70,y:10} ];
var svg = d3.select('.chart').append('svg');
var line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
svg.append('path').attr({
'd': line(data),
'y': 0,
'stroke': '#e80',
'stroke-width': '12px',
'fill': '#fd5'
});
在這個範例中,我們使用 d3.svg.line() 函式來繪製線條,然後在 path 標簽屬性裡呼叫 line() 函式把線條真正畫出來。
如果你對 SVG 的基本指令(例如 stroke, fill 等)覺得陌生的話,你不妨參考 MDN 上的說明。
不過,若要把 D3 提供的功能發揮到最大,我們要把上面已經介紹過的所有功能都加上去:
var data = [
{x:50,y:10}, {x:10,y:110}, {x:60,y:90}, {x:110,y:110}, {x:70,y:10} ];
var maxWidth =
document.getElementsByClassName('chart')[0].offsetWidth - 20;
var x = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return Math.max(d.x, d.y); }) ])
.range([10, maxWidth]);
var svg = d3.select('.chart').append('svg');
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return x(d.y); });
svg.append('path').attr({
'd': line(data),
'y': 0,
'stroke': '#e80',
'stroke-width': '12px',
'fill': '#fd5'
});
這麼一來,原來我們繪製的線條就會因容器的大小而縮放了。範例程式的原始碼請看這個 jsFiddle。
畫出來的圖形如下:
另一個值得注意的是,D3 還提供了一個 interpolate() 函式,可以讓 line() 函式做出各式各樣的變化。例如,我們可以在上例中加入一個 interpolate('linear-closed') 函式, 讓畫出來的線條自動封閉(把頭尾連接起來):
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return x(d.y); })
.interpolate('linear-closed');
原來的圖形會變成如下:
如果你把 'linear-closed' 改成 'basis-closed',那麼圖形會變成:
interpolate() 函式提供很多其它選項,如果你有興趣的話,可以參考這裡。修改後的程式請看這個 jsFiddle。
轉場
還記得我們在上一篇介紹中提到的「轉場」效果嗎? 如果圖形是使用 SVG 所繪製的,還有轉場效果可用嗎? 答案是肯定的, 而且語法完全一樣。
舉上個程式為例,我們原本使用 attr() 函式來繪出第一個圖形(根據一組資料, data1)。現在,我們可以給予第二組資料,同樣使用 attr() 函式來繪出第二個圖形, 然後在中間插入 transition() 函式:
svg.append('path')
.attr({
'd': line(data1),
'y': 0,
'stroke': '#e80',
'stroke-width': '12px',
'fill': '#fd5'
})
.transition()
.duration(800).delay(300)
.attr({
'd': line(data2),
'y': 0,
'stroke': '#e80',
'stroke-width': '12px',
'fill': '#fd5'
});
如此,這兩個圖形之間就會產生過場效果了。程式請看這個 jsFiddle。