version4ではかなり挙動が変わっているようだ
過去記事: D3.jsアプリケーション設計におけるデータとDOM操作の分離
http://qiita.com/mojaie/items/1c8ba9d70ab633938a1c
- 環境
- D3.js version 4.2.4
- Google Chrome version 52.0.2743.116
公式サイトの例
https://github.com/d3/d3-selection
var circle = svg.selectAll("circle") // 1
.data(data) // 2
.style("fill", "blue"); // 3
circle.exit().remove(); // 4
circle.enter().append("circle") // 5
.style("fill", "green") // 6
.merge(circle) // 7
.style("stroke", "black"); // 8
上記コードを順を追って説明すると、
- svgの全てのcircle要素を選択
- circleに対応するデータを更新する
- データと対応しているcircleを青で塗りつぶす
- 対応するデータが無くなったcircleを削除
- 新しく加わったデータのcircle要素を作成して選択
- 新しく作成したcircleを緑で塗りつぶす
- 新しく作成したcircleに加えて既に存在するcircleも選択
- 全てのcircleの縁を黒で書く
v4ではmergeが実装されたことで、enterした要素と既存の要素の挙動をまとめて指定できるようになっている。便利。
依然として面倒なのは、一つの親要素が複数の子要素を持っていたり、要素が入れ子になっていたりしているケース。
<ul>
<li>
<label>
<input type="checkbox" name="person" value="1"></input>
<span>John</span>
</label>
</li>
<li>以下略</li>
</ul>
データを持っているのはliだが、更新するのはその下の階層のinputとspanということになる。selectAll('li').htmlでゴリ押ししてもいいのだが...
function checkboxList(selection, data, name, key, text) {
const items = selection.selectAll('li').data(data, key);
items.exit().remove();
const entered = items.enter().append('li').append('label');
entered.append('input');
entered.append('span');
const updated = entered.merge(items.select('label'));
updated.select('input')
.attr('type', 'checkbox')
.attr('name', name)
.attr('value', key);
updated.select('span')
.text(text);
}
mergeされる2つのselectionの階層が異なる場合、mergeを呼び出す側のselectionに統一される(上記コードの場合、entered.merge(items)でも結果は変わらない、多分)が、開発者の意図した使い方ではないらしいのでmergeするselectionは揃えたほうが無難だろうか。
上記checkBoxListを呼び出す処理は以下の様な感じ。
d3.select('ul')
.call(checkboxList, [{key: 1, name: 'John'}],
'person', d => d.key, d => d.name)
.on('change', () => {
変更時の処理
});
callメソッドは実行結果に関係なく必ず呼び出し元のselectionを返すので、UIコンポーネントの生成をルーチン化するのに便利。