D3.jsの基本パターン
D3.jsをいじっているが、最初に期待していたのよりも難しい。
公式サイトには資料が結構多くあるのだが、あまり整理されていない感じがある。
- チュートリアル https://github.com/mbostock/d3/wiki/Tutorials
- API reference https://github.com/mbostock/d3/wiki/API-Reference
サイトに載っている実装例が大変に華麗でmotivatingなのだが、その分ちょっと複雑なソースコードが多い。
なので、以下で基本操作を最短でマスターするための道のりについての覚え書き。
データとDOM要素の対応付け
まず、データとDOM要素(主にSVG)を対応させる方法を理解する必要がある。以下のパターンがデータからDOM生成する基本である。
var dat = [{r: 10, x: 100, name: "Alice"},{r: 8, x: 150, name: "Bob"},{r: 15, x: 200, name: "Chris"}]; var sel = d3.select("body").append("svg").attr('width',300).attr('height',300); sel.selectAll("circle") .data(dat, function(d){return d.name;}) .enter() .append("circle") .attr('fill','pink') .attr('r',function(d){return d.r;}) .attr('cx', function(d){return d.x;}) .attr('cy', function(d,i){return i*50;});
第2文はjQueryみたいな感じで直感的である。selはbody内に追加されたsvg要素を示す。
attr()の第2引数には値として、定数あるいはコールバック関数を指定できる。 コールバック関数は
- 第1引数: データポイント(数値、オブジェクト、配列)の配列
- 第2引数: 配列のインデックス
を受け取る。
第3文のシーケンスはちょっとあれ?となると思う。これを理解するには、以下の記事が役に立つ。
- Thinking with Joins http://bost.ocks.org/mike/join/
selectAllというのは仮想的なプレースホルダをselの中に生成する操作である。selectとselectAllの違いはここにある。一見ちょっとややこしいが、selectAllは階層構造を潰して平たい配列を作るが、selectは階層構造を保持する、という違いがある。
- Nested Selections http://bost.ocks.org/mike/nest/
enter(), exit()という関数は、data()でデータを割り当てたあとにメソッドチェインして、DOMを更新する関数。
ここで、データには、data()の第2引数でコールバック関数を渡すことで、identity keyを設定することができる。このkeyが対応するDOM要素にも記憶されるので、データをdata()で再度割り当てた時に、DOM要素をそれに応じて更新できる。
もし割り当てた新しいデータが、選択された既存のDOM要素と一致しない場合:
- enter().append('DOM要素') を呼ぶと、足りない分のDOMが追加される。
- exit().remove() を呼ぶと、新しいデータに対応物が存在しないDOM要素が削除される。
このデータとDOM要素のマッチングの際に、上記のidentity keyが使われる。もしdata()でidentity keyを指定していなかった場合は、単に配列のインデックスが使われる。つまり、data(dat,function(d,i){return i;})としたのと同じことになる。
それを例示しているのが、以下の例
- General Update Pattern, II
- http://bl.ocks.org/mbostock/3808221
- identity keyを割り当ててデータ更新する例。
- General Update Pattern, III
- http://bl.ocks.org/mbostock/3808234
- 更新にトランジションをかけた例。
トランジション
transition()をメソッドチェインの中に挟むと、それ以降のattr()やstyle()での変更がアニメーションになる。また、
- delay()でアニメーション開始時刻を設定できる。
- duration()でアニメーションにかかる時間を設定できる
この2つとも引数として関数を指定できるので、データポイントに依存した設定が可能。以下に例を示す。
sel.transition() .delay(function(d,i){return 1000*i;}) .duration(1000) .attr('r',function(d,i){return d.r*2});
スケーリング
データの値と描画座標の間の変換を設定できる。これを有効活用すれば、自分でデータから描画座標を計算する必要はあまり無いはず。 線形、指数、対数のスケールや、座標の離散化、quantileでの座標変換を設定することもできる。 また、数値→座標だけでなく、
- 時間から座標を計算するtime scale や
- 系列データから座標を計算するordinal scale
というのもある。
- API
その他
http://bl.ocks.org/mbostock/4062085
- nest()を使ったデータをグループ化する方法が分かる。
グラフなどを書くための高レベルAPI
- d3.layout.***の関数が用意されている。