在交互式可視化中,有一個詞叫reactive,指的是以可視化的方式來響應用戶的行爲,幫助用戶進行可視化並理解其結果。這個頗有用。那如何來實現這種交互呢?經過動畫。html
若是處理得當,動畫能夠展示出不錯的可視化交互數據...react
是怎樣的呢?git
比較下面兩個圖表:github
哪個更能讓用戶注意到柱狀圖中最後的那一個柱子呢?編程
[說明:以上兩個圖使用了相同的model。點擊按鈕能夠開始一個動畫。若是圖中內容爲空,點擊按鈕能夠顯示圖表。]瀏覽器
這就是說,動畫也確定會破壞你的可視化。這裏有三個廣泛問題。app
看這個例子。svg
動畫過程當中線通過了一個空白狀態,從而使咱們很難去跟蹤原始狀態和最終狀態之間的變化。找出變化的惟一方法是將注意力集中到一個點上,而後記住它的原始位置,但這樣作效率過低。函數
咱們已經看到動畫在數據可視化中的做用了。如今咱們來作吧!咱們使用d3,它提供了多種數據動畫的方式,使用它實現動畫效果會相對容易一些。動畫
若是你知道如何在d3中繪圖,你就知道如何實現動畫。(若是你還不知道,Alignedleft有一個精彩的教程集,能夠幫助你如何開始,d3站點也列出了不少,包括一些我提供的。)出於某種緣由,動畫在d3中被稱之爲transitions。動畫在技術上被定義爲,對象的一個或多個特徵在通過一段時間以後,從一個值過渡(transitions)到了另外一個值。
那麼咱們所說的特徵指的是什麼呢?它基本上表明瞭任何能夠用數字表示的東西。
不出所料,當你隨着時間來平滑地更新item的位置時,它就移動了。在svg中,對大多數形狀而言位置是肯定的,例如咱們這裏的藍色矩形,它的位置由屬性x和y的值來肯定,對應於形狀的左上角。對於圓形,使用cx和cy或者中心點的座標來肯定位置。對於路徑,例如咱們的紅色三角形,實際上經過"d"屬性指定了全部點的位置。
一樣,當你改變大小時,對象會增大(或收縮)。你可使用width(寬)和height(高)來肯定矩形的大小,或者使用r(半徑)來肯定圓形的大小。
顏色也是一個數字屬性,從一種顏色過分到另外一種顏色也是可能的(這個也頗有用)。在svg中,顏色是由fill或stroke定義的樣式屬性。
與顏色同樣,改變透明度也頗有用。當opacity被設置爲0時,對象是徹底透明的。因此要實現對象的淡入淡出效果,須要對opacity屬性進行transition操做。
如今咱們已經看到transitions能夠用來些幹什麼了,讓咱們來看看如何用d3來編寫代碼。
咱們回到第一個例子。咱們儘可能簡單一點。
在d3中要建立一個像這樣的方塊,咱們能夠這樣寫:
var mySquare=svg.append("rect") .attr("x",60) .attr("y",60) .attr("width",60) .attr("height",60);
4個屬性,很簡單。
若是你想讓它移動到右邊,只須要更新屬性x的值。像這樣:
mySquare
.transition()
.attr("x",320);
就是這麼簡單:使用transition方法,而後指定你想要改變的值,就像建立一個新對象同樣。經過這種方式,咱們能夠很容易地實現上面任何一個例子的效果。
mySquare .transition() .attr("width",120); // 將會變大 mySquare .style("fill","white") // 若是fill的值一開始是空白,而後再指定樣式,那麼動畫將從黑色開始 .transition() .style("fill","blue"); mySquare .transition() .style("opacity",0);
咱們的例子並不是如此。Transitions發生在event以後,即當用戶點擊按鈕時纔開始動畫。事實上,transitions一般會與事件和交互關聯在一塊兒。不過這並不複雜。咱們能夠這樣寫:
button.on("click", function() { mySquare.transition().attr("x",320); })
如今,咱們的動畫僅當按鈕被點擊時纔開始。很明顯,因爲transition是在一個函數內部,因此咱們能夠經過編程來決定方塊走向哪裏。不過這個例子咱們仍是讓它簡單一點。
到目前爲止,咱們已經看到了如何在d3中實現一些簡單的動畫,甚至進行一些交互。正如咱們所看到的,動畫的實現方式和建立元素同樣簡單。好消息是,d3中的transitions很是靈活,同時也能夠經過不少的技巧來進行自定義,而不用編寫很複雜的代碼。咱們更多須要知道的是該如何來作。
在使用transition()方法以後,咱們能夠指定一個duration和delay的值。Duration是transition將要持續的毫秒數,而delay是動畫在執行以前須要等待的毫秒數。寫法:
mySquare.transition() .attr("x",320) .duration(1000) // 持續時間爲1秒 .delay(100) // 延遲0.1秒執行
默認的duration是250ms,沒有delay。
我發現250ms的duration時間有點過短了。大多數時候,咱們都但願動畫要明顯,我本身常常將duration的值增長到500或1000.除非有特殊緣由,動畫的持續時間不該太長。若是你使用它們來可視化數據,你確定不但願動畫要花好幾秒纔將數據都顯示出來。觀察下面這兩個例子(點擊按鈕開始)。
第二個是否是會讓人抓狂?你很難相信它純粹浪費了你25秒的時間。
緩動是一個技術名稱,它其實是一個將時間轉換爲屬性值變化的函數。在前面的例子中,你可能已經注意到了,一開始的時候值變化得很慢,而後加快,而後又變慢。是的,這代表你可使用不一樣的函數來得到不一樣的結果。我這裏的例子只給出了3個緩動函數,而事實上還有不少的緩動函數。你能夠本身編寫緩動函數,這個不在本文的討論範圍。
寫法相似於這樣:
mysquare.transition() .attr("x",320) .ease("elastic")
(順便說一下,修改屬性和指定動畫方式的順序沒有任何影響,你能夠先使用.ease,後使用.attr)
對於路徑對象,經過transition來更新每個點的位置,你能夠有效地改變路徑的形狀。這對於線圖或任何一個路徑圖來講特別有意思。
像這樣,若是你繪製的值發生了變化,你能夠輕易地發現這些變化。相反,若是你只是清除並重繪,那麼就很難發現數據的變化。在這幾個例子中,路徑的屬性"d"的值被修改了(因此它們與最簡單的例子本質上是不一樣的)。
有時(事實上常常會有),你但願在一個transition完成以後立刻啓動另外一個transition。然而下面這種方式不起做用:
mysquare.transition() .attr("x",320); mysquare.transition() .attr("y",200);
你可能會認爲方塊會向右移動,而後再向下移動。但事實並不是如此,它會開始一個向右的移動,而後緊接着啓動第二個transition使其向下移動。因爲這兩個transition的duration相同而且都沒有delay,所以第二個transition的效果更明顯。
若是第二個transition有一個delay,而且比第一個transition的duration要小,那麼第一個transition會持續一段時間直到第二個transition的delay時間到期。而後,第二個transition會接管第一個transition開始執行。然而這並非你想要的,由於第一個transition完成到什麼程度徹底取決於用戶的機器和瀏覽器等,而這些都是不可預知的(看下面一段的解釋)。
那麼給第二個transition一個精確的delay使其可以恰好對應上第一個transition的duration如何呢?這一般是能夠的,可是delay和duration的值並不是十分精準。啓動一個transition須要必定的時間(在個人機器上大約須要15毫秒,但可能會有變化),所以咱們很難經過這種方式將兩個transitions無縫地鏈接起來。
在更加複雜一點的程序中,有時候好幾個事件會嘗試對同一個對象觸發transition。在這種狀況下,第一個過程會被啓動而後運行,直到另外一個transition開始。第二個transition會取代第一個transition。這意味着在第一個transition中被改變的屬性值將會保留到第二個transition開始,這個值處於開始值和目標值之間。
若是你想要確保全部的transitions都能將其屬性更新爲要達到的值,那麼你可能須要在第一個transition的後續transitions中從新指定屬性值,就像這樣:
mysquare.transition() .attr("x",320); mysquare.transition() .delay(250) .attr("y",200) .attr("x",320); // 即便第一個transition沒有執行完,這裏也會將x更新爲320。
這裏還有一個方法能肯定將兩個transitions鏈接起來。使用下面這種寫法,一個事件能夠精準地在一個transition結束時開始。這個事件能夠是另外一個transition(上面的例子就是這樣)。
mysquare .transition() ... .each("end", function() { ... });
這裏,由.each("end")引入的最後一行的回調函數中的內容會正好在transition結束時被觸發。
那要怎麼作呢?這裏有3種常見的場景。
(順便說一句,這裏的例子和前面的例子沒有什麼不一樣,僅僅只是爲了方便查看)。一種狀況是在同一個item上啓動另外一個transition。這裏,方塊會向右移動,而後向下移動。
這裏是實現的代碼:
mysquare .transition() .attr("x",320) .each("end",function() { // 正如上面所說的,這是一個新的transition對象 d3.select(this). // 這裏咱們還能夠有另外一個.each("end") transition() .attr("y",180); });
另外一種狀況是在transition執行完後刪除對象。這個頗有用,特別是在建立大量臨時對象時。一個有趣的組合動畫是,當你將透明度一直減到0,使對象不可見,若是你再也不須要它了,那麼你就可使用remove()方法來刪除它。
mysquare .transition() .attr("x",320) .each("end",function() { d3.select(this). // 如上所述,這裏咱們將對象刪除了 remove(); });
最後,咱們能夠建立一個新對象。咱們能夠經過這種方式添加一個特效。下面是一個例子:
這裏,在transition結束時,建立了一個圓形,隨後在圓形上開始了一個transition,並將透明度減小爲0,而後刪除該圓形。
這是最後一個帶有多個效果合併的例子。
信不信由你,咱們只是接觸到了d3動畫一些很是基礎的東西。
還有兩個transition的用處咱們沒有講到,由於稍微有點複雜,因此這裏我只是簡單提一下。
到目前爲止,咱們看到的一直都是基於一個特定對象的屬性的transition。例如咱們將這個方塊的x屬性的值變到200。
但有時候咱們須要根據一個變量值的變化來更新可視化圖形中的許多部分。
咱們能夠經過.tween和.interpolate方法來實現。全部的方法在d3的文檔中都有說明。
還有一個是d3 timer的使用,它容許重複調用一個函數,也能夠用來建立動畫。
我一直但願能夠經過相對簡單的代碼和技術來實現你想要的東西,特別是對於鏈接多個transitions,以及在恰當的時刻添加和刪除對象。要建立更強大的效果,還有很長的路要走!