PixiJS Cover

「好不容易學了 D3.js ,可是做出來的作品都好慢…」結合資料與文件模型的 D3.js 可說已是當代視覺化必備技能,可是與 SVG 的搭配使他有著先天上的效能限制,在繪製大量元素時更是力不從心。沒關係,聽說過 WebGL 嗎?這次我們為大家介紹另一個 JavaScript 函式庫 — PIXI.js ,讓你搭上 WebGL 的肩膀,做出更高效率的視覺化!

如果你有玩視覺化,那麼你一定有聽過 D3.js 。 D3.js 可說是近年最火紅的視覺化函式庫,網路上太多的視覺化作品是基於 D3.js 開發出來的。然而, D3.js 的設計理念 — 資料與文件模型的連結 — 使他原生就必須建構在網頁顯示與排版的架構上,從而大大的拖慢了他顯示的效能。

下圖是紐約時報「Can You Live on the Minimum Wage?」專題報導,利用四處飄動的綠色小方塊表示一美元。這張圖裡至少有 16000 個方塊。

minwage

當我們使用各別的 SVG 來繪製圖形時,重覆的資料更新、屬性分析與繪製會讓整個動畫更新的過程額外花上非常多的時間,這時候 WebGL 就派上用場了。屬於 HTML5 的技術之一,WebGL 是一組類似 OpenGL 的 JavaScript,搭配硬體繪圖加速的力量做後盾,提供 3D 繪圖相關的函式給網頁前端程式設計師使用。

只是 WebGL 並不是這麼容易入門,對一般網頁程式設計師來說可能還是有些門檻; 況且我們只需要做 2D 的視覺化,並不需要用到複雜的空間計算、光線調控跟視角設定。很幸運地,有個人叫做 Mat Groves ,他也覺得 WebGL 實在是有夠複雜,「我們需要一個夠快又簡單的函式庫才對」( 小編腦補 ) 於是他便利用 WebGL 開發了 PIXI.js 這個跨平台的 2D 繪圖函式庫。

PIXI.js

基本的 PIXI 繪圖包含了兩個部份:

  • 幫我們把東西畫出來的 Renderer
  • 紀錄我們要畫的東西的 Container

首先我們要在網頁源碼中引入 PIXI 函式:

  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/3.0.8/pixi.min.js"></script>

接著,建立繪圖的兩大元件,並將元件連結至網頁中:

  var renderer = new PIXI.WebGLRenderer(900,400);
  var stage = new PIXI.Container();
  document.body.appendChild(renderer.view); // 連結至網頁

這樣便可以準備開始繪圖了。想要畫什麼呢?來畫個矩形吧!使用 PIXI.Graphics 物件幫我們畫:

  var graphics = new PIXI.Graphics();
  stage.addChild(graphics);  // 要將 Graphics 物件加到 Stage 中
  graphics.beginFill(0xff0000); // 設定我們要畫的顏色
  graphics.drawRect(100,100,700,200);

最後利用 renderer 把 stage 畫出來:

  renderer.render(stage);

執行的結果如下:

render rect

當然我們不會只想畫一個這樣的矩形就算了,讓我們來畫一萬個矩形吧:

  for(var i=0;i<10000;i++) {
     graphics.drawRect(Math.random()*900,Math.random()*400,3,3); // 隨機決定位置
  }

匯製出來的結果如下:

10000 rectangles

除了矩形以外,我們也可以畫其它的形狀,例如圓形;只要將 drawRect 改為 drawCircle 即可:

  for(var i=0;i<10000;i++) {
     graphics.drawCircle(Math.random()*900,Math.random()*900,3); // 隨機決定位置
  }

於是我們現在有了一萬個圓圈:

10000 Circles

進一步優化

到目前為止,繪製大量圖形的執行速度仍是差強人意,為了更快速的繪圖, PIXI 提供了特別的 Container — ParticleContainer ,他只保留少數如平移、旋轉等圖形的動畫,讓執行速度大大的改善。首先建立 ParticleContainer 並將其加到 stage 中:

  container = new PIXI.ParticleContainer();
  stage.addChild(container);

ParticleContainer 只能繪製 Sprite 物件而無法繪製 Graphics 物件,所以我們必須先利用 Graphics 產生圓形圖樣材質 ( texture ):

  var circle = new PIXI.Graphics();
  circle.beginFill(0xff0000);
  circle.drawCircle(3,3,3);
  var texture = circle.generateTexture(3*3, PIXI.ScaleModes.DEFAULT);

然後再利用 texture 製作一萬個 Sprite ,加到 ParticleContainer 之中:

  for(var i=0;i<10000;i++) {
    var sprite = new PIXI.Sprite(texture);
    sprite.x = Math.random() * 900;
    sprite.y = Math.random() * 400;
    container.addChild(sprite);
  }

最後,我們利用 RequestAnimationFrame 來做移動的動畫,細節不再此贅述。下圖為結果截圖 ( 截取約 0.6 秒 ) ,可以看到動畫相當的順暢:

animation of circles

所有的圓往右下角移動 ( 截取約 0.6 秒 )

與 D3.js 比較

我們接著同時利用 D3.js 與 Pixi 製作一萬個點掉落的動畫。下圖為結果截圖,為時一秒的動畫可以很明顯看出兩者速度的差異:

d3js drop animation

D3js

Pixi Drop Animation

Pixi

 


結語

實作互動式視覺化很難不去碰觸到使用體驗與大量資料的問題。 D3.js 雖然方便,但其基於 SVG 與文件模型的特性使得我們在其上要深入做出更複雜的視覺化是件相當具有挑戰性的事。這時像 Pixi 這樣的輔助函式庫便可以派上用場。

事實上,也有不少人想嘗試著將 D3.js 利用 WebGL 加速 ( 例如「Converting a D3 Visualization to WebGL」這篇文章中所提及的 ) ,亦有利用模擬文件模型的方式來介接 Canvas 與 D3.js 的嘗試 ( 例如:「Working with D3.js and Canvas: When and How」)。

若不使用 D3.js 的文件模型機制, D3.js 仍提供了像是 Force Layout 、 Scale 與 Geometry 等相當方便的輔助函式,因此這篇文章並不是在提倡丟掉你的 D3.js ,而是去思考如何搭配各式各樣的技術讓我們的視覺化作品可以達到盡善盡美。畢竟,重點不是在背後的技術,而是前面的成果,不是嗎? 🙂


Written by infographics.tw

發表迴響

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