12/01/2013

D3.jsで既存のSVG Elementを扱う

こんにちは、林です。d3.js Advent Calendar 2013の1日目です。他のAdventカレンダーに比べると参加率が低い原因は、やはり日本ではd3.jsの知名度はまだ低いからなのでしょうか?海外ではData VisualizationといえばD3.jsというぐらいメジャーなライブラリです。日本語での情報もまだ少ないと感じるので、このAdvent Calendarが少しでも日本での普及に貢献できれば幸いです。

さて、今回のテーマは「D3.jsで既存のSVG Elementを扱う」です。
一般的にD3.jsでのサンプルは四角や丸などのプリミティブな形を使ったものが多いです。実際Data Visualizationでそれで困ることはあまりないかもしれませんが、まれにIllustrator等で作成したSVGデータをD3で扱いたいという状況も発生します。いくつかのやり方があるかと思いますが、自分がとったアプローチを紹介します。

1. html内に描画領域とは別の箇所にsvgをテンプレートとして埋め込む (SVGはIllustrator等で描いてsvgで保存、その後テキストエディタ等で細かいところを修正)
<body>
<!-- SVG element template -->
<svg id="svg_templates" display="none">
  <g class="instance_small device_body">
    <rect rx="4" ry="4" width="40" height="40" class="frame"></rect>
    <g transform="translate(10,6)" class="icon">
      <rect class="instance_bg" width="20" height="26" rx="1" ry="1"></rect>
      <rect x="4" y="3" fill="#FFFFFF" width="12" height="4"></rect>
      <rect x="4" y="9" fill="#FFFFFF" width="12" height="4"></rect>
      <circle class="active" cx="6" cy="19" r="2.6"></circle>
    </g>
  </g>
</svg>
</body>
2. テンプレートからd3.selectでnodeを取得しcloneNodeで複製し、d3のenter()処理内でappendChildする
var device = svg.selectAll('g.device').data(data);
device.enter()
  .append("g")
  .attr('class','device')
  .each(function(d, i){
       this.appendChild(
           d3.select('#svg_templates > .instance_small').node().cloneNode(true)
       );
  });
3. データをelementに反映させる
テンプレートのsvgでd3のデータを反映させたい箇所は予めclassを付与しselectで参照することで、任意のelementにデータを反映させることができます。
device
  .select('.instance_bg')
  .transition()
  .duration(500)
  .delay(function(d, i) {
      return i * 400;
   })
  .style('fill', function(d){return '#' + d});
実際のサンプルは以下です。サーバアイコンはhtmlに埋め込まれたsvgからcloneし、その背景色はdata配列に従って変更されています。


このやり方がベストかどうかは分からないのですが、今のところ自分の用途にはこのやり方が使い勝手が良かったです。何か他により良い方法知ってる方は是非教えて下さい。