cover

「哇!這好炫!可是我不會做」這是否是你內心裡常常有的遺憾呢?一個常見的例子便是 Voronoi Treemap — 一種類似教堂拼貼玻璃窗式的視覺呈現,利用面積表現資料大小的圖表卻仍保留了資料的相對位置;很少視覺化軟體提供這種視覺化,甚至就連 D3.js 也沒有相對應的函式。今天就讓我們一起來看看,如何利用 Voronoijs 做出 Voronoi Treemap 吧!

Voronoi Diagram 是一種特別的地理切割圖表,他運用最短距離的概念,並根據我們所提供的座標點 ( 例如捷運站 ) 將地圖切割成許多小塊,每一塊都有共同的最近點。這個算法常應用在計算並表現各個鄉鎮最近的捷運、車站或學校等概念,我們在先前的文章「D3.JS 入門系列 - Voronoi Diagram 教學」有帶著大家一步步的實作過。

然而,有時候我們在地圖上想要表現得更多;比方說,到各站點雖然距離一樣,但車資不同;或者、更甚者,我們想要用切分出的面積大小來表現各鄉鎮的人口組成等資訊,該怎麼做呢?

Treemap 是一種利用面積表現資料的視覺呈現手法,他利用階層式的方塊逐層的表現資料,面積大小更直接對應到資料大小;一般的 Treemap 使用矩形來表現資料,然而矩形有他的限制,因此便出現了不同的變化形式,例如用圓圈表現資料的 Circular Treemap 或是今天我們的主角: Voronoi Treemap。下圖即為一個 Voronoi Treemap 的範例,他搭配美國地圖來呈現美國各州的人口出生地分布,為紐約時報製作的專題「Mapping Migration in the United States」:

nytimes

當計算 Voronoi Diagram 時不以單純的距離為考量,而是以其它資訊為考量時 ( 例如所費時間、上下坡落差 ) ,我們可以把對應的數值想成是一種加權過的距離,這時的算法與結果我們便稱之為「Weighted Voronoi Diagram」,亦可稱做 Power Diagram;這時,若我們直接利用資料的大小來做加權,我們便可以把資料轉換成 Voronoi 中的面積大小,進而做到 Voronoi Treemap 的效果。

然而,這個演算法並不簡單,他牽涉到一些三維幾何與隨機演算法的運算;幸好這時我們有了 Voronoijs ,幫我們把複雜的細節處理掉了!

使用 Voronoijs 並不困難,首先引入必要的 voronoi.min.js

  <script type="text/javascript" src="http://zbryikt.github.io/voronoijs/dist/voronoi.min.js"></script>

接著,我們要準備好資料。 voronoijs 使用類似 D3.js Pack Layout 所用的資料結構,我們可以透過 d3.nest 快速做出,或者如下直接自行產出:

  var data = {
    children: [
      {children: [ {value: 100}, {value: 200}, {value: 300} ]},
      {children: [ {value: 100}, {value: 500}, {value: 900} ]}
    ]
  };

上面的資料分成了兩組,每組各有三筆資料。

接著,利用 Voronoijs 的輔助函式 Voronoi.Polygon.create 產生我們視覺化的外圍多邊形,這會產生一個橢圓,其中 800 為寬度、 400 為高度、 60 為邊的數量:

  var clip = Voronoi.Polygon.create(800, 400, 60);

這個多邊形便接著用來計算我們的 Voronoi Treemap:

  var treemap = new Voronoi.Treemap(data, clip, 800, 400);

這時我們只要呼叫 treemap.compute() ,我們的資料中便會自動被安插入「x」、「y」與「depth」等值代表了資料的中心點位置以及其在資料階層中的深度。當然光是中心座標點還不夠,我們可以另外利用 treemap.getPolygons() 取得所有多邊形的資料;多邊形的資料很單純,就是個物件陣列,每個物件都有「x」與「y」兩個屬性,用來代表多邊形的每個端點的座標值,例如:

  [{x: 100, y: 100}, {x: 200, y: 100}, {x: 150, y: 200}]

上面這個例子代表了一個簡單的三角形。實際上 getPolygons 會取得很多個這樣的多邊形,並存在一個陣列之中,因此我們可以搭配 D3.js 的資料綁定手法直接將多邊形與 SVG 的 <path> 連結,並利用其資料畫出多邊形:

  function render() {
    d3.select("svg").selectAll("path")
      .data(treemap.getPolygons()).enter().append("path")  // 資料綁定
    d3.selectAll("path").attr({
      d: function(d,i) {
        return d.map(function(it){ return "L" + it.x + " " + it.y; })
          .join(" ").replace(/^L/, "M");
      }
    });
  }

由於 Voronoi Treemap 是個逐漸收斂的計算,我們利用 setInterval 函式讓他不斷重新計算並更新畫面:

  setInterval(function() {
    treemap.compute();
    render();
  }, 100);

這樣就完成了!當然,我們會需要準備一個 SVG 元素供 D3.js 使用,也需要妥善調整多邊形的樣式;下圖為稍微整理過後的 Voronoi Treemap 範例:

這個範例的原始碼你可以在這裡找到。這個範例我們只畫出了基本的多邊形,但由於我們還有階層資料與多邊形中心點資料,我們其實可以繼續追加更多的維度進去;在 Voronoijs 文件一開頭便放了一個 Voronoi Treemap 範例,裡面除了有更多的資料外,也應用到中心點資料來額外表現泡泡在裡頭,並使用邊框粗細來區分不同階層的區域:

voronoi

若有適當的應用,就連外圈的多邊形框也可以置換成自己所選的圖案喔!

結語

這次我們簡單的介紹了如何使用 Voronoijs ,它本身操作並不複雜,畢竟原本就是設計成包裝函式庫,一定不會讓我們使用上有太大困難;然而像這樣的視覺化其底層都有一定複雜度,很多其它的視覺化也是有類似的問題,所幸有人能為我們將函式庫實作出來,讓我們可以站在開源巨人的肩膀上做出更多有趣的內容!


Written by infographics.tw

發表迴響

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