D3 transition How To

看膩了靜態圖表,又覺得互動套件效果有限嗎?D3.js 除了提供強大的視覺化輔助函式,就連製作動畫也難不倒他!那麼,就讓我們一起來看看要如何使用 D3.js 做到動態圖表的效果吧!

動畫為視覺化的元素之一,妥善運用可以做到很棒的效果。然而,即使程式都已經自行撰寫了,一般要做動畫並不是這麼容易,所幸我們還有 D3.js – D3.js 作者 Mike Bostock 從最初設計 D3.js 時就已經考慮到圖表元件的動畫彈性,而其 Functional Style 的特性更是讓這件事變得相當容易。舉例來說,若我們想用 D3.js 產生一個紅色的正方形,可以這樣寫:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  });

(若對 D3.js 不了解想要開始學習,可參考我們先前撰寫的「網頁視覺化利器 – D3.js 簡介」一文 )

由於其 Functional Style 特性,我們可以繼續在上述程式碼後面接上其它樣式的設定:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).attrs({ rx: 3, ry: 3});

在這裡,無論長寬、顏色或是圓角半徑,都會即時套用至矩形之上。上述源碼的執行結果如下:

example 1

這個串接式的寫法為什麼可以讓我們很輕易的做出動畫效果呢?

神奇的 Transition 函式

也許你會想,「我幹嘛要分兩段寫,寫成一段不是很好嗎?」的確上例來說是沒有分段的必要,不過一旦分了段、有了前後,忽然動畫的概念就變得簡單了:我只要把前面樣式設定後的狀態當成起始狀態、後面樣式設定後的狀態當成結束狀態 ,那就可以做動畫啦!

舉例來說,我們將上例稍稍做些修正:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).attrs({
    x: 20, y: 20, width: 40, height: 10, fill: "blue"
  });

現在前後兩個樣式設定都設定了不同的原點座標、長寬以及填色,就像是兩個 Keyframes 一樣;接著,在他們中間插入神奇的 Transition() 函式,噹噹!動畫就做好了:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).transition().attrs({
    x: 20, y: 20, width: 40, height: 10, fill: "blue"
  });

下圖是動畫的範例結果:

Example 2

控制時間的魔法師

當然動畫不是這樣就完了,若我想做很多組動畫怎麼辦?時間長度又要怎麼控制?transition() 函式會為我們提供一些額外的工具來控制動畫,包含動畫的延遲與長度:

  transition().duration(ms1).delay(ms2)

上例中很明顯「duration」即是控制動畫長度的函式、而「delay」則是控制動畫延遲的函式,他們都接受一個做為時間的參數,單位是千分之一秒。下例的矩形會在執行程式兩秒後,花費五秒中慢慢的由紅色變成黑色:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).transition().duration(5000).delay(2000).attrs({
    fill: "black"
  });

也許你會猜多組動畫的功能也是利用額外的函式來提供,不過其實仔細想想, Functional Style 已經為我們提供連續動畫轉場的可能性啦!我們只要在每個樣式設定之間插入 transition() ,就可以做出連續的動畫了:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).transition().attrs({
    fill: "black"
  }).transition().attrs({
    fill: "green"
  });

上例中我們可以看到兩組 transition() 搭配三個不同的樣式設定,讓矩形從紅色轉為黑色、再轉換為綠色,下圖為實際動畫的結果。串接動畫,就像是串接函式一樣直覺!

Example 3

【講個秘訣】初學者的地雷 – delay 的計算方式

在 d3.js 3.x 版中,即使你利用串接函式的方式來做動畫連發,一旦你為各個 transition 設定了 delay 值,那麼這些動畫就會「從指令執行的那一瞬間」等待你所設定的 delay 後便開始播放。雖然設計邏輯有合理,但實在是太反直覺,以致於這個設計在 4.0 中被改掉,所有的 delay 都是相對於上一個動畫的結束時間來計算了。

自製任何動畫

雖然 transition() 好用,但有些地方的確是讓人有點疑惑: d3.js 是怎麼知道「black」與「red」之間要如何做動畫的呢?難道「Lhasa」與「Palau」之間也可以做「動畫」嗎?這個動畫又會長什麼樣子?

事實上,D3.js 內部實作了一組內插函式,其中可以內差的資料種類包含了數字、顏色、包含數字的文字等等,一但知道了他的基本原理,我們就可以想像他是如何實作動畫的:

  1. 根據 Keyframe 找出合適的內插函式
  2. 在動畫中根據當前時間算出內插的值
  3. 不斷更新元素屬性

比方說,由於 SVG 中的形狀是由數字與文字的組合來描述的,我們便可以直接用 transition() 來製作變形動畫!下例利用 D3 的 d3.arc 函式產生圓餅的形狀字串,並在兩個不同的圓餅間變形:

d3.select("body").append("svg").append("path").attrs({
  fill: "red", transform: "translate(80,80)",
  d: d3.arc()({
    innerRadius: 20, outerRadius: 40,
    startAngle: 0,   endAngle: 1
  }) /* 畫出 57 度的圓餅
}).transition().duration(1000).attrs({
  d: d3.arc()({
    innerRadius: 20, outerRadius: 40,
    startAngle: 0,   endAngle: 3 /* 將動畫變形至 171 度的圓餅 */
  })
});

上述程式碼可以做出下圖的效果:

Example 4

 

雖然做得出動畫,但感覺有點怪怪的,圓餅的形狀有點被扭曲了!的確,若我們直接叫 D3.js 為我們做動畫,它便會用預設的內插函式來做,但有時我們不希望照預設的內插函式來做動畫。比方說上例的圓餅圖我們實際上會希望他透過圓餅角度的變化來做動畫,而非點的位置或大小。

這時候,我們可以利用 tween 函式來做到! tween 函式的概念很簡單:內插函式由我們自己提供:

  d3.select("body").append("svg").append("rect").attrs({
    x: 10, y: 10, width: 20, height: 20, fill: "red"
  }).transition().tween("animation", function() {
    var node = d3.select(this);
    return function(t) {
      node.attr({rx: t * 10, ry: t * 10});    
    };
  });

在這裡,紅色部份的 tween() 函式呼叫為這個內插函式命名為 animation ,並透過第二個參數傳回一個我們客製的內插函式:

  function() {
    var node = d3.select(this);
    return function(t) { /* 這裡傳回的函式即為我們客製的內插函式 */
      node.attr({rx: t * 10, ry: t * 10});
    }
  }

內插函式中的參數 t 即為內插參數,在每次動畫的過程中都會由 0 漸漸變為 1 ,而每次變化都會執行這個內插函式一次。上例中的內插函式會將矩形的角落慢慢變成半徑為 10 的圓角。

利用 tween 函式我們可以客製任何 transition() 無法產生的動畫,例如 <text> 中數字的漸變、多邊形端點數的變化、或是任意改變動畫的速率等等,相當方便!例如前面的甜甜圈動畫圖,我們便可以利用 tween 效果來改進動畫的體驗,只要將 t 參數帶入圓餅角度中即可:

d3.select("body").append("svg").append("path").attrs({
  fill: "red",
  transform: "translate(80,80)",
  d: d3.arc()({
    innerRadius: 20,
    outerRadius: 40,
    startAngle: 0,
    endAngle: 1
  })
}).transition().duration(1000).tween("animation", function() {
  var node = d3.select(this);
  return function(t) { /* 傳回我們的客製內插函式 */
    node.attrs({
      d: d3.arc()({ /* 每次內插都重新設定形狀 */
        innerRadius: 20,
        outerRadius: 40,
        startAngle: 0,
        endAngle: 1 + t * 2 /* 末端角度從 1 增加到 3 */
      })
    });
  };
});

上例的執行結果如下:

Example 5

 

各式各樣的動畫效果

雖然 tween 可以控制動畫速率,但為了一些基本的動畫效果就得自己寫內插函式,有時也是麻煩,幸好 D3.js 提供了 d3.ease 時序函式組,讓我們可以利用簡單的 ease() 函式設定來改變動畫的演進效果。

舉例來說,透過時序前進再倒退的操作,我們可以做出類似球彈跳的效果、也可以利用平方、立方的時序轉換做出加速度甚至減速的動畫效果:

  d3.select("body").append("svg").append("circle").attrs({
    cx: 50, cy: 20, r: 10, fill: "red"
  }).transition().ease(d3.easeBounce).attrs({
    cy: 100
  });

上面的程式碼片段會讓一顆紅色的圓球往下墜落並彈跳,最終圓心停留在 (50, 100) 的位置:

Example 6 - Drop ball

小結

我們在這篇文章談到了 D3.js 做動畫視覺化的基本,也許你會覺得概念很短,但事實上光是這些基本元件,就可以讓我們作出類似下圖般的多重動畫 ( 此為 D3.js 作者 Mike Bostock 所製作的動畫範例 ):

adopted from http://bl.ocks.org/mbostock/1256572

adopted from http://bl.ocks.org/mbostock/1256572

那麼,若你有 JavaScript 與 D3.js 的基礎,看完應該已經躍躍欲試了吧!不過在結束之前,再提供我們先前與讀者分享過的兩篇文章,他們都用到了 D3.js 的動畫技巧:

如果把這些文章都讀過,應該就能對動畫相當得心應手囉!那麼接下來的時間就留給讀者們,趕快打開你的編輯器來玩玩動畫吧!


Written by infographics.tw

發表迴響

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