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

d3.js - 三つの小円

Mike Bostock (著)   H.Sakai (訳)

昔々、あるところに三つの小円がありました……( ※1 )。 本稿では、セレクションを使ってこの三つの小円を操作する方法を解説します。

要素の選択

selectAll オペレータは "circle" のような セレクタ文字列を受け取り、セレクションを返します。

var circle = svg.selectAll("circle");

一度セレクションが返されれば、その選択する要素に対し様々なオペレータを適用することができます。以下の例では style で色を変更したり、 attr で直径や y 座標を 変更しています。

circle.style("fill", "steelblue");
circle.attr("cy", 90);
circle.attr("r", 30);

定数の代わりに関数を使うことで属性の値を動的に計算して求めることもできます。ここでは x 座標にランダムな 値を設定してみましょう。

circle.attr("cx", function() {
  return Math.random() * w;
});

この例では、ボタンを押すたびに x 座標の値がランダムに再計算されます。Protovis とは異なり、D3 ではこの関数は内部的に 隠蔽されずに即座に実行され、それに続くコードも実行されます。したがって何度でも繰り返せ、再定義することもできるのです。

データの結合

だんだんと jQuery に似てきますが、ここでお見せしたいのはあくまで data を 使った三つの円の操作方法です。まずはデータを用意しましょう。例題として、三つの円が数字の 32、57、112 を表すものと 考えてください。data オペレータが 数字と円を結合します。

circle.data([32, 57, 112]);

D3 の data はすべて値の配列を指します。これは、セレクションが単に要素の配列に過ぎないという概念にうまく対応しています。 最初の数値(ここでは data の最初の 32 )が最初の要素(一番上に位置している最初の要素)に結合され、 以下二番目の数値が二番目の要素にと順に結合していくことに注意しましょう。

いったん結合すると、そのデータは、属性やスタイルの値を返すコールバック関数への引数として取り出すことができます。 つまり、データを視覚的にエンコードできる、言い換えればデータを視覚化することができるということです!例として データの数値とその平方根を x 座標と半径にセットしてみましょう。

circle.attr("cx", function(d) {
  return d;
});

circle.attr("r", function(d) {
  return Math.sqrt(d);
});

コールバック関数で利用できる引数がもう一つあります。セレクション内の index を表す引数です。 この index は zero-based の index で、ゼロから 始まります。この引数はオフセットを計算したり、各要素を簡単に識別するのに大変役立ちます。この引数はオプションですので、 引数で指定しなければ無視されます。次の例をご覧ください。

circle.attr("cx", function(d, i) {
  return i * 100 + 30;
});

ここではインデックス ix座標の計算にだけ使われ、要素を順番に配置しています。各要素の間隔は 100 ピクセルで、左辺から 30 ピクセルのオフセットがあります。 SVG では左上が座標の起点となります。

要素の生成

さて、ここで表示したい数値が 3個 ではなく 4 個の場合はどうすればよいでしょう?全部の数字を表示するのには円の数が 足りません。D3 は、data をエレメントに結合するとき、余剰のデータを enter セレクションに保管します( enter と exit という術語は演劇用語をあてはめたものです)。ここでは円が 3 個しかないため、4 つめの 293 という数字は enter セレクション内に留まっています。

var circle = svg.selectAll("circle")
    .data([32, 57, 112, 293]);

ここで enter セレクションを使うと残されたデータに対応して新しい円が生成されます。新しく生成された円はすでに データと結合しているため、そのデータを使った属性やスタイルの計算が可能です。

var enter = circle.enter().append("circle");

enter.attr("cy", 90);

enter.attr("cx", 160);

enter.attr("r", function(d) {
  return Math.sqrt(d);
});

話を先に進める前に、もし要素が存在しない場合にどうなるか考えてみましょう。つまりドキュメントが空の場合です。 空白のページからスタートして data に対応する新しい円を作るにはどうすればいいでしょうか?答えは、単にその空の セレクションに data を結合するだけです。次のサンプルのように、これですべての data が enter に入ってきます。

var enter = circle.enter().append("circle");

enter.attr("cy", 90);

enter.attr("cx", function(d) {
  return d;
});

enter.attr("r", function(d) {
  return Math.sqrt(d);
});

実はこれは D3 のコードではごく一般的なパターンです。 selectAll + data + enter + append の一連のオペレータの 組み合わせは、これから何度も見ることになるでしょう。一般的パターンではありますが、これはセレクションと data の 結合の特別な場合であることも心に留めておいてください。もう一つの一般的パターン(選択した既存の要素の更新)もすでに 登場しましたね。他にも考慮に値する面白いケースを紹介していきます。

コードを簡潔にするのに役立つもう一つのテクニックが メソッドチェーンです。D3 の各オペレータは現在のセレクションを返します。そのため、複数のオペレータを続けて適用することが できるのです。上の例をメソッドチェーンを使って書き直してみましょう。

svg.selectAll("circle")
    .data([32, 57, 112, 293])
  .enter().append("circle")
    .attr("cy", 90)
    .attr("cx", String)
    .attr("r", Math.sqrt);

この例では、無名関数の代わりに JavaScript 関数を埋め込むことでさらにコードを短くしています。たとえば String メソッドを 埋め込むことは、 JavaScript 標準の文字列変換を使って元の data から属性の値を求めるための簡単な方法です。同様に、 Math.sqrt を 組み込むことで元の data からその平方根を求め、r 属性(半径)にセットしています。属性値を計算するために、再利用可能な 関数を埋め込むこのテクニックは D3 では、特に scalesshapes と共に、よくつかわれています。

要素の廃棄

要素の生成とは正反対の必要性に迫られることもあります。余剰の要素を消し去りたい場合です。一つの方法は、そのノードを 選択して remove メソッドを使うことです。 しかし D3 では、exit セレクションを使って D3 自身にどの要素に退場してもらうのかを決めさせるのが一般的方法です。exit セレクションの役割は enter セレクションの逆です。 exit セレクションは対応するデータの無い全ての要素を保管します。

var circle = svg.selectAll("circle")
    .data([32, 57]);

残されたことは、退場( exit )すべき要素を取り除くことだけです。

circle.exit().remove();

enter、update、exit の各セレクションは data オペレータによって設定され、要素の削除や追加を行っても、再び selectAll を呼ばない限り変更される ことはありません。したがってそうしたセレクション(上述の circle など)を指す変数を扱っている場合は、 要素の追加や削除を行った後、再選択するのが良いでしょう。

まとめ

ここまでの話のまとめです。data と要素を結合した場合に返される結果には以下の三通りのセレクションがあります。

  1. enter - 新たな役者がステージへ登壇
  2. update - 同じ役者が演じ続ける
  3. exit - ステージから退場し去りゆく役者

デフォルトの join-by-index (インデックス順結合)を使っている場合、enter セレクション、または exit セレクションの どちらか(あるいは両方)が空となります。もし要素の個数より data が多ければ余剰の data は enter セレクションに 収まり、逆の場合は余剰のエレメントは exit セクションに収まります。しかし data オペレータにキー関数を指定すれば ( ※2 )、data と要素の結合方法を正確にコントロールすることができます。 この場合には、enter セクションと exit セレクションの両方が返ってきます。

var circle = svg.selectAll("circle")
    .data([32, 57, 293], String);

circle.enter().append("circle")
    .attr("cy", 90)
    .attr("cx", String)
    .attr("r", Math.sqrt);

circle.exit().remove();

セレクションと transition (遷移)についてもっと詳しく知りたい方は、"A Bar Chart, Part 2" をお読みください。enter と exit を使ってリアルタイムの data を表示する例を解説しています。


[訳注]
  • (*1) 本稿「三つの小円」の原題 "three little circles" は "three little pigs" (三匹の子豚)のもじり。
  • (*2) キー関数 について詳しくは API 解説ページの selection.data() の項を参照。

原文 Copyright © 2012 Mike Bostock
翻訳 Copyright © 2012 d3js.org japanize project