visualize-combo cover

常用 D3.js 繪圖,但都只能在瀏覽器裡面看嗎?前端動態的結果需要即時截圖,卻不知道怎麼做比較好?這次我們帶大家看看怎麼利用 D3.js 與 NodeJS 自動的做出截圖 PNG 、 向量 SVG 提供給後端及 Adobe Illustrator 使用。

D3js 是相當強大的視覺化函式庫,前陣子我們在粉絲團上分享的一個網站中甚至整理了 2490 個 D3.js 範例,光是一天參考一個來練習,都要花上將近七年才能完整做過一輪。不過, D3JS 「資料驅動文件」的本質使得他不容易在瀏覽器以外的環境執行,進而限制了 D3.js 使它只能在網頁上呈現其強大的效果。

跳脫瀏覽器

很多時候我們會希望將視覺化的結果紀錄下來,比方說之前文章介紹過的「Open Graph 與資料視覺化」中,利用視覺化結果產生網頁預覽;又或者說產生的圖檔有時會想進一步利用影像編輯軟體修改。這時,D3.js 與 SVG 的夥伴關係讓這件事變成了可能 - 如果我們能提供一個環境讓 D3.js 執行、又找到能轉換 SVG 格式的套件呢?

NodeJS

visualize-combo nodejs

為了讓 JavaScript 可以不透過瀏覽器執行,我們需要一個提供本地端 API 的直譯器。NodeJS 為目前最主流的 JavaScript 直譯器,它包含了一組相當便利的 API 外,也附有一個非常豐富的套件集 NPM;關於 NodeJS 的基本安裝與使用我們在先前的文章「資料爬蟲實戰-使用 NodeJS」中有提及,還不瞭解的朋友可以稍微看一下該文的前半部。

事實上 D3.js 本身也在 NPM 裡面,所以我們可以直接使用下列指令安裝 D3.js:

  npm install d3

這也代表了我們可以在 NodeJS 程式碼中直接使用 require 來引入 D3js ,感謝 D3.js 作者 Mike Bostock 讓我們省了一些工。

JSDOM

能執行 D3js 之後,還需要解決 D3js  「資料驅動文件」的問題。我們需要提供一個文件模型給 D3js ,最好是與瀏覽器中的文件模型相容、也就是符合 DOM Level 1 ~ 3 的文件模型。 NPM 套件 jsdom 雖然沒有完全實作不同等級的 DOM Spec ,但經過測試,要運作 D3js 基本上是沒有太大問題的。

那麼我們同樣的可以用 npm 來安裝 jsdom :

  npm install jsdom

D3.js + JSDOM

要使用 jsdom ,我們首先要提供一份文件給他,接著 jsdom 就會建立類似於瀏覽器中 window 變數的物件給我們。使用並不難,提供兩個參數給 jsdom.env 即可:

  var d3 = require("d3");
  var jsdom = require("jsdom");
  jsom.env({
    html: "<body><div></div></body>",
    done: (error, window) {
       console.log("我們拿到 window 了!");
    }
  });

在 done 回呼函式中我們拿到了 window ,便可以用其中的 API 來取得文件中的子節點,如 <div>:

  var root = window.document.querySelector("div");

接下來就跟在瀏覽器裡一樣的做法了!

  svg = d3.select(root).append("svg").attr({width:"800",height:"600", ... });
  svg.selectAll("path").data(...) ...

我們這裡利用亂數隨意製作一個泡泡圖範例:

   /* 隨機大小的 100 個圈圈 */
   var data = {children: d3.range(0,100,1)
     .map(function() { return {value:Math.random()};})};
   /* 使用 Pack Layout */
   var nodes = d3.layout.pack().size([800,400]).sort(null).nodes(data)
     .filter(function(it){return it.depth==1;});
   var color = d3.scale.category20();
   /* 繪出 */
   svg.selectAll("circle").data(nodes).enter().append("circle")
     .attr({
       cx: function(it) { return it.x; },
       cy: function(it) { return it.y; },
       r: function(it) { return it.r; },
       fill: function(it) { return color(it.value); }
     });
   }]);

此段程式碼若在網頁上會畫出下圖般的結果:

visualize-combo: bubble chart

產生圖檔

我們現在可以不透過瀏覽器執行 D3.js 來畫圖了,但執行結果要怎麼儲存呢? 存成 SVG 檔相當容易,但存成 PNG 檔則需要點小技巧,描述如下。

SVG

完成視覺化以後,我們的成果都存在 <SVG> 標籤裡面了,這時我們可以利用 innerHTML 屬性將他取出,再利用 NodeJS 的 API 寫入檔案:

  svg_string = root.innerHTML;
  fs.writeFileSync("output.svg", svg_string);

產生的 output.svg 檔案即可利用 Adobe Illustrator 開啟並修改,比方說把部份的圓圈畫上墨水邊:

open in illustrator

用 Illustrator 開啟 SVG 檔

 

PNG

jsdom 沒辦法直接將 DOM Tree 轉換成圖檔,因為這牽涉到網頁繪製。大家可能會猜想是否有模擬整個瀏覽器畫面的套件?的確像 PhantomJS 這樣的套件可以做到這件事,但我們其實只要描繪 SVG 的套件即可。剛好 NPM 裡有個套件「canvg」,可以幫我們把 SVG 畫到 Canvas 上,然後 jsdom 剛好能讓我們用 Canvas 的 API -例如 toDataURL - 來產生圖檔!

由於 toDataURL 輸出的是 base64 格式,最後一個步驟便是將 base64 轉回 binary ,然後就完成了:

  var canvg = require("canvg");
  var cvs = window.document.querySelector("body").appendChild(window.document.createElement("canvas"));
  canvg(cvs, svg_string);
  img_base64 = cvs.toDataURL().replace(/^[^,]+,/, "");
  fs.writeFileSync("output.png", new Buffer(img_base64, "base64"));

結語

我們看到了如何利用 NodeJS 來操作 D3.js 產生 SVG 與 PNG ,接下來將 PNG 銜接到網站 Open Graph 與利用 Illustrator 編修 SVG 就不是問題囉。事實上, Adobe Illustrator / Photoshop 是可以用 JavaScript 控制的,這為我們開啟了另一種可能性 - 將我們的整合環境從 Web 、 Server 到 Illustrator / Photoshop 。

提到程式控制就不得不提一個最近出來的工具: Datylon 這個套件利用  D3.js 與 Highchart 實作了數種圖表,讓你可以直接在 Adobe Illustrator 中從資料建利各種圖表。我們前些日子試玩了一下,Datylon 似乎還有些問題待克服,但仍然讓人相當的期待,未來有機會會再幫大家介紹一下 Datylon 。

程式化控制各種工具互相銜接是不是相當有趣?開放程式介面與標準讓很多事情成為可能,也產生了無限多種的可能應用,透過網頁端 D3.js 如何連動 Adobe Illustrator ,就連小編也躍躍欲試!大家別忘了常回來關注我們,說不定過不久我們就會分享 Illustrator 端的有趣應用喔!


Written by infographics.tw

發表迴響

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