Chart Morphing Cover

常見的圖表工具提供的往往是繪製特定圖表的功能,你有看過可以在類型間變化的圖表嗎?這次我們為了展現 D3JS 與  SVG 強大的一面,特地實作一個變形圖表,讓大家了解客製化圖表比起圖表工具能有多大的變化空間。

D3JS 提供了資料-物件模型、視覺化輔助函式庫、同時也包含了一個動畫模組 - d3.transition ;這個模組不僅可以將單一數值 ( 例如半徑、高度等 ) 動態化,連一整個形狀 ( Path 元素的 d 屬性 ) 也可以協助你做動畫處理。

由於圓形 ( 或是任何包含圓弧的形狀 ) 也可以利用 Path 處理,只要適當的對應其參數,不難想像要如何從長條圖變形成圓餅/甜甜圈圖或者泡泡圖。那麼接下來就讓我們來實作看看長條圖與圓餅圖之間的變形圖表吧!

準備材料

  • HTML 一份,包含一個 SVG 元素,並且已經引入 D3JS 函式庫
  • 測試資料少許
  • JS 檔一個,驅動圖表繪製

HTML 的內容摘要如下:

  <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
  <svg width="100%" height="400px" viewBox="0 0 800 400" preserveAspectRatio="xMidYMid">
  </svg>
  <script type="text/javascript" src="index.js"></script>

測試資料直接放在 JS 檔中,隨意挑選了九個國家的2014生育率:

var data = [
  ["Iceland",14.1],
  ["Egypt",30.4],
  ["Syria",27.6],
  ["Malaysia",17.5],
  ["Japan",8.3],
  ["USA",12.7],
  ["Taiwan",8.5],
  ["India",21.8],
  ["Germany",8.1]
];

JS 檔 ( index.js ) 的內容我們則逐項加入。

長條圖實作

長條圖可說是圖表製作中最經典也最簡單的例子了。我們需要的就只是將資料一個一個對應到 SVG 元素;最快的方式是使用  RECT 元素,不過今天為了要做變形圖表,我們使用 PATH 元素:

path = d3.select(“svg").selectAll(“path").data(data).enter().append(“path");

與資料一對一的 PATH 元素建立好之後,接著要設定元素的參數:

path.attr("d", function(d,i) {
  var x1 = 100, x2 = 100 + d[1] * 22;
  var y1 = i * 40, y2 = i * 40 + 30;
  return (
    "M" + x1 + " " + y1 +
    "L" + x2 + " " + y1 +
    "L" + x2 + " " + y2 +
    "L" + x1 + " " + y2 + "Z"
  );
});

因為光是這樣長條的顏色很單調,我們利用 d3.scale.category20 來隨意給他上點顏色:

var color = d3.scale.category20();
path.attr("fill", function(d,i) { return color(d[0]); });

製作出來的結果如下:

Morph - Bar chart

圓餅圖製作

有了長條圖的程式碼,要製作圓餅圖相對就容易多了,問題在於圓餅對應的 Path 路徑如何計算?D3JS 很貼心的提供了產生圓餅的工具函式 d3.svg.arc ,我們可以直接利用他來繪製。不過再那之前,我們對資料做了點修改,在每項資料後面追加一個累積的生育率數值,可以用程式快速的加入:

var sum = 0;
for(var i = 0; i < data.length ; i++ ) {
  data[i].push(sum);
  sum += data[i][1];
}

這裡的 sum 變數代表所有國家的生育率總和,並利用在 path 路徑的計算中:

var arc = d3.svg.arc().innerRadius(180).outerRadius(200);
path.attr({
  transform: "translate(400,200)",
  d: function(d,i) {
    return arc.startAngle(6.28 * d[2] / sum).endAngle(6.28 * (d[2] + d[1]) / sum)();
  }
});

於是你可以看到圓餅圖的繪製結果如下 ( 其實是甜甜圈圖 ):

Morph - Donut Chart

變形圖表

使圖表變形相當的容易,插入 d3.transition 即可。當我們繪製好長條圖之後,想要變形成圓餅圖時,在設定圓餅圖屬性前呼叫 d3.transition:

d3.transition().duration(1000).attr({
  /* 圓餅圖的參數設定(略) */
});

但是產生的動畫效果不如預期,我們會看到一堆東西爆炸然後忽然出現圓餅圖。問題出在哪?

特製的圓餅生成函式

d3.transition 的實作方式是在屬性中對應數值然後逐項內插,以變形來說,對應的參數是 “d" ;但我們繪製長條圖跟繪製圓餅時使用的 “d" 格式並不完全相同,導致內差的數值出現了問題。我們隨意挑選一個圓餅圖的 “d" 參數來一窺究竟 ( 為了方便閱讀,我做了小幅度的修改與排版 ):

  M-190.6,-60.5
  A 200,200 0 0,1 -67.5,-188.2
  L-54.0,-150.5
  A 160,160 0 0,0 -152.4,-48.4
  Z

指令依序為 M、A、L、A、Z -這與我們繪製長條圖時使用指令 M、L、L、L、Z 不同。我們只要能利用 M、A、L、A、Z 這樣的序列畫出長條,那一切就完成了。 ( 我相信你很可能對 SVG Path 的 “d" 參數不太熟悉,那也沒關係,因為這邊主要是在示範如何達成變化效果,大概了解到 “d" 是透過一個個指令告訴瀏覽器要如何畫到下一個座標這樣就行了。若想要深入了解 Path 的使用方式,請參考這個網頁,有詳細的規格 )

然而,因為 A 指令的設計並不適合做漸變, M、A、L、A、Z 這樣的序列動畫效果並不好,所以我們必須改寫 Arc 生成函式以達到更好的效果。比方說,利用長條指令序列 M、L、L、L、Z 來模擬圓餅圖效果的話可以獲得一個類似這樣的結果:

動畫效果很不賴,不過這個圓餅看起來很硬。為了讓圓餅看起來很正常,我們改用 M、S、L、S、Z 的序列,並且重新製作 Arc 與 Bar 生成函式,最終做出來的結果如下,點擊圖中的「Change」按鈕來看變形效果:

由於這個範例目的純粹在於示意,所以 Arc 生成函式只做個大概,並沒有弄得非常準確;同時 M、S、L、S、Z 的序列使用了貝茲曲線,但是貝茲曲線無法完美畫出圓型,所以其實還有相當的改進空間。若你對程式碼有興趣,可以參考該程式碼的 github 頁面

結語

由於使用 SVG 的Path 元素控制形狀,再加上 D3JS transition 的效果,可以做到很多樣版圖表工具辦不到的事情。事實上,不光是 D3JS , 像是 SVG Morpheus 這樣的 Javascript Library 也可以拿來做類似的效果;甚至 SVG 本身其實也提供了動畫的功能 ( SVG SMIL ) 。

不過客製圖表當然也不是不會有問題,首當其衝的就是技術難度吧,要能做到像這樣的圖表轉換,不僅需要對 Javascript 跟相關 Library 有所了解, SVG / Dom 的操作也必須有相當經驗,甚至基本的數學 ( 三角函數等等 ) 都要有能力使用;這對理工背景的學生來說可能沒太大問題,但對要做報表的業務助理、視覺化新聞的新聞記者來說可能就有點超過了。

另一個問題則是在相容性上,至今瀏覽器之間對不同網頁技術的支援度仍然有些差異,更別提過去版本的瀏覽器很可能不支援特定的技術。比方說 IE 就不支援 SMIL ,舊版本 IE 也不支援 SVG ,在舊版 IE 尚有一定使用率之前,這些都是必須要克服的問題。

市面上許多 freemium 的圖表工具大多以足夠的相容性為前提製作,所以使用這些工具之時除了不太需要深入的技術、也不太需要煩惱跨瀏覽器的支援。不同的選擇各有優劣,我想大家應該可以在不同的情況下自行判斷該怎樣製作圖表比較妥當囉。


Written by infographics.tw

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *