概要 事例集 ドキュメント ソースコード D3 wiki 日本語 Home 日本語化プロジェクトについて

データ・ドリブン・ドキュメント

D3: Data Driven Document

D3.js はデータに基づいてドキュメントを操作するための JavaScript ライブラリです。D3 はHTML や SVG、 CSSを使ってデータに命を吹き込みます。D3は WEB 標準に重点を置いており、強力な視覚化コンポーネントとデータドリブン (データ駆動型)DOM 操作手法の組み合わせにより、特定のフレームワークに縛られることなく、モダンブラウザの性能をフルに 引き出すことができます。

最新版のダウンロードはこちらから:

下記の SCRIPTタグを html に貼り付けるだけでも最新版を利用できます。
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>

Github では全コードと事例集ダウンロードできます。

#初めに

D3は任意のデータをドキュメント・オブジェクト・モデル( DOM )と結合させ、データ駆動によるドキュメントの 変更を可能にします。たとえば、数値配列から HTML テーブルを生成したり、同じデータからインタラクティブな SVG 棒グラフを 生成し、それをスムーズに、対話的に変化させることができます。

D3 の目標は、あらゆる機能をそなえた画一的なフレームワークとなることではありません。D3が提供するのは、問題の核心部分への ソリューションです。すなわち、データに基づいてドキュメントを効率的に操作する手段です。これにより、特殊な記法を 用いることなく、高度な柔軟性を保ちながら、CSS3 や HTML5、SVG といった WEB 標準の持つ能力をフルに発揮させることが 可能になるのです。

#セレクションとは

W3C の DOM API を使ってドキュメントを変更する作業はとても手間のかかるものです。 メソッド名は冗長で、そこに採用されている命令文的アプローチでは、繰り返し処理や状態の設定処理をいちいち書く必要があります。 例として、パラグラフ( p )要素のテキスト色を変更する処理を書いてみましょう。

var paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
   var paragraph = paragraphs.item(i);
   paragraph.style.setProperty("color", "white", null);
}

一方、D3 が採用するのは宣言文的アプローチです。セレクションと呼ばれる任意のノードセットを操作します。 例えば上の例の繰り返し処理が D3 では次のように書けます。ここでは selectAll() がセレクションを返しています。

d3.select("#TEXT1").selectAll("p").style("color", "fuchsia");

必要があれば個々のノードを操作することも従来通り行えます。
d3.select("#TEXT1").style("background-color", "white");

TEXT1

「ふん、それから」

「仕方がないから、床を出て障子をあけて椽側へ出て、渋柿の甘干しを一つ取って食いました」

「うまかったかい」と主人は小供みたような事を聞く。

「うまいですよ、あの辺の柿は。とうてい東京などじゃあの味はわかりませんね」

「柿はいいがそれから、どうしたい」と今度は東風君がきく。

セレクタは W3C Selectors API によって定義されており、モダンブラウザは これをネイティブにサポートしています。旧ブラウザに対する後方互換も Sizzle を使うことにより可能です。上の例では、各ノードをタグ名によって選択しました( それぞれ "p""body")。 要素は様々な記述方法で選択可能です。たとえば包含関係や属性値、クラスや ID を指定して選択できます。

D3 はノードを変化させるためのメソッドを多数備えています。属性やスタイルを設定するメソッド、イベントリスナーを登録するメソッド、 他のノードを加えたり削除したりソートするメソッド、HTML や テキストの内容を変更するメソッドなどです。これら標準のメソッドだけで 大部分の用途をまかなえます。また、下位 DOM に直接アクセスすることも従来と変わりなく行えます。なぜなら、D3 の selection は、 単にノードの配列に過ぎないからです。

#動的プロパティ

jQueryPrototype に馴染のある方は、 ここまでで D3 とそうした他の DOM フレームワークとの類似性に気が付いたことでしょう。D3 ではさらに、スタイルや属性値その他の プロパティを、単なる定数値ではなく、データを返す関数として指定することができます。この機能は、見た目の単純さに反して、 驚くほど強力です。たとえば d3.geo.path 関数は、地理座標を SVG パスデータとして渡します。

また D3 は、エリアグラフ、線グラフ、円グラフのような 図形プリミティブ等、多くの再利用可能な組み込み関数や関数ファクトリを備えています。

例えば次のコードは、パラグラフごとの色をランダムに設定します。

d3.select("#TEXT2").selectAll("p").style("color", function() {
   return "hsl(" + Math.random() * 360 + ",100%,50%)";
});

次のコードは奇数番目・偶数番目ごとに別の濃度を設定します。

d3.selectAll("p").style("color", function(d, i) {
   return i % 2 ? "#008" : "#00f";
});

TEXT2

「それから床を出て障子を開けて、椽側へ出て甘干しの柿を一つ食って……」

「また柿を食ったのかい。どうもいつまで行っても柿ばかり食ってて際限がないね」

「私もじれったくてね」

「君より聞いてる方がよっぽどじれったいぜ」

「先生はどうも性急だから、話がしにくくって困ります」

「聞く方も少しは困るよ」と東風君も暗に不平を洩らした。

多くの場合、プロパティの計算には結合した data メソッドが用いられます。data は値の配列を返し、配列の値が順番にセレクション 関数の一つ目の引数( d )に渡されます。デフォルトの「 join-by-index (インデックス順結合)」では、data 配列の 最初の要素がセレクション関数の最初のノードに渡され、以下2番目の要素が2番目のノードに、と渡されていきます。次の例では、数値の配列と パラグラフ要素を結合し、配列の数値を使ってフォントサイズを動的に設定しています。

d3.select("#TEXT3").selectAll("p")
     .data([ 11, 13, 16, 20, 26, 32])
     .style("font-size", function(d) { return d + "px"; });

一度 data とドキュメントを結合すれば、次からは data 演算子を省略できます。D3 が結合済みの data を参照するからです。 これにより、data を再結合することなく動的なプロパティの再計算が可能となります。このボタンは上の例を実行した後でないと 効きません。

d3.selectAll("p")
   .style("font-size", function(d) { return ( 43 - d ) + "px"; });

TEXT3

「南郷街道をついに二丁来て、鷹台町から市内に這入って、古城町を通って、仙石町を曲って、喰代町を横に見て、通町を一丁目、 二丁目、三丁目と順に通り越して、それから尾張町、名古屋町、鯱鉾町、蒲鉾町……」

「そんなにいろいろな町を通らなくてもいい。要するにヴァイオリンを買ったのか、買わないのか」と主人がじれったそうに聞く。

「楽器のある店は金善即ち金子善兵衛方ですから、まだなかなかです」

「なかなかでもいいから早く買うがいい」

「かしこまりました。それで金善方へ来て見ると、店にはランプがかんかんともって……」

「またかんかんか、君のかんかんは一度や二度で済まないんだから難渋するよ」と今度は迷亭が予防線を張った。

#Enter と Exit

D3 の enterexit セレクションを用いることで、追加されたデータに対応した新しいノードを生成したり、 不要になったノードを削除することができます。

data をセレクションと結合すると、data 配列の各要素は対応するノードとペアを作ります。このとき enterセレクションを 追加しておけば、data の要素数よりノードの個数が少ない場合は、余剰のデータ要素が enter セレクションを形成し、以下の例のように append メソッドによりそのインスタンスを生成することができます。

ここで text() が作用するのはあくまで enter() セレクションだけ、つまり data() の余剰要素によって追加されたノードだけが 対象である点に注意してください。元から存在するノードには text() は作用していません。

d3.select("#LIST ol").selectAll("li")
     .data([ 0, 10, 20, 30, 50, 99 ])
     .enter().append("li")
     .text(function(d) { return d + ' x ' + d + ' = ' + ( d * d ); });

LIST
  1. 1 x 1 = 1
  2. 2 x 2 = 4
  3. 3 x 3 = 9

ノードを更新するときに対象となるのはデフォルトでは data 演算子の返すセレクションです。したがって enter や exit セレクションを指定し忘れると、対応するデータの存在する要素のみが自動的に選択されます。一般的なプログラミングパターンでは、 初期セレクションの処理は三つのパートに分かれます。既存のノードの更新処理、 enter によるノードの追加処理、exit による ノードの削除処理です。

// Update…
var p = d3.select("body").selectAll("p")
     .data([ 0, 10, 20, 30, 50, 99 ])
     .text(String);

// Enter…
p.enter().append("p")
     .text(String);

// Exit…
p.exit().remove();

この三つのケースを個別に処理することで、どの操作がどのノードに作用するのかを正確に指定できるようになり、 パフォーマンスが向上し、transition (遷移)のコントロールがより容易になります。たとえば棒グラフを変化させる場合、 enter セレクションで棒の長さを初期値に設定し、transition で徐々に終値へと変化させ、最後に exit を使います。

D3 はデータに基づいてドキュメントを変換します。これは要素の生成と廃棄の両者を含みます。D3 はユーザの操作に応じて ドキュメントを変更したり、アニメーションさせたり、さらには外部からの非同期通知を受け取ることもできます。サーバ側で 初期生成されたドキュメントを、D3 経由で、クライアント側で更新するというハイブリッドな手法すら実現可能です。

#表現方法ではなく変換方法

D3 は新たなグラフィックの表現方法ではありません。ProcessingRaphaelProtovis などとは 異なり、使われる要素は HTML や SVG、CSS などの WEB標準に直接由来しています。たとえば D3 で作った SVG 要素に、外部 スタイルシートのスタイルを適用することができます。さらにフィルター効果や点線ストローク、クリッピングを重ねることもできます。 ある日ブラウザベンダーが新機能を追加したとしても、D3 のアップデートを待つ必要はなく、即座に対応できるのです。 もし将来 D3 を見限って他のツールキット移行したとしても、それまでに身につけた WEB 標準に関する知識は決して無駄にはなりません!

D3 の最も優れている点は、ブラウザに標準で組み込まれている検査ツールで簡単にデバグできることです。D3 で操作しているノードは ブラウザがネイティブに解釈可能な要素そのものだからです。

#Transition とは

D3は変換( transformation )に重点を置いており、その自然な発展としてアニメ化 transition(遷移)があります。transition は、 スタイルや属性の初期値と終値を指定し、その値の間を補完して徐々に変化させていく仕組みです。ツイーンのコントロールは、 "elastic" や "cubic-in-out"、"linear"などのイージング関数によってコントロールできます。ツイーン( between の tween から) は初期値と終値の間の数字をどのように変化させるか(アニメさせるか)を定義します。D3 の値補完は、数値や文字と結合した 数値(フォントサイズ、パスデータ等々)などのプリミティブ、および複合値の両方に対応しています。さらに、補完レジストリを 拡張して、複雑なプロパティやデータ構造を扱うことも可能です。

次の例は、ページ背景を溶暗させていきます。

d3.select("#TEXT4").transition()
     .style("background-color", "black");

TEXT4

吾輩は死ぬ。死んでこの太平を得る。太平は死ななければ得られぬ。
南無阿弥陀仏南無阿弥陀仏。ありがたいありがたい。

次の例では、タイミングをずらせながら図形地図の中の円のサイズを変更していきます。

d3.selectAll("circle").transition()
     .duration(750)
     .delay(function(d, i) { return i * 10; })
     .attr("r", function(d) { return Math.sqrt(d * scale); });

D3 は実際に必要な属性だけを更新することによってオーバーヘッドを低減し、複雑なグラフィックや高いフレームレートを 実現しています。さらに D3 は、イベントを介することにより複雑な transition を連続して行うこともできます。また、 従来通りの CSS3 transition を使うこともできます。なぜなら D3 の機能は、ブラウザの機能を置き換えているのではなく、 その機能を使いやすく提供しているだけだからです。


D3 を勉強したいという方にお薦めのリソース・チュートリアル


Fork me on GitHub