HogeFugaHogera

IT系の備忘録とか、他徒然なるままに

Let’s Make a Bar Chart part 1の邦訳[D3.js]

棒グラフを作ろう その1

D3.jsを勉強する為にNovember 5, 2013Mike BostocのLet’s Make a Bar Chartを邦訳したもの。訳は所々怪しいはず。part 2の翻訳はコチラ

小さななデータ、数字の配列があるとしよう:

var data = [4, 8, 15, 16, 23, 42];

棒グラフは、このようなデータに対して単純かつ知覚的に自然な^1可視化を行う方法である。この入門チュートリアルでは、D3 JavaScript libraryを使って棒グラフを作る方法をカバーしている。まず、HTMLで骨子版を作り、次にScalable Vector Graphics(SVG)でより完全なグラフを、最後に表示の間にアニメーションを付ける。

このチュートリアルはweb開発について多少の知識があることを前提としている (Webページを作り、それを自分のbrowserで見る方法、そのページにd3.jsを組み込む方法等)。始めるにあたり、このCodePen templateをフォークすると便利かもしれない。

要素の選択

平凡なJavaSctiptでは、一般的に一つずつ要素を処理する。例えば、div要素を作るには、その中身を設定し、bodyにそれを付け加える:

var div = document.createElement("div");
div.innerHTML = "Hello, world!";
document.body.appendChild(div);

このコード片はJavaScriptなので、D3をロード後、body内のscriptタグ中に置かれる必要がある。

D3(jQueryやその他ライブラリと同様)では、上記の代わりにselectionsで選ばれた要素のグループを処理する。要素を一纏めにして扱うことは、selectionsに力を与える; 大きなコードの変更なしに、一つでも、複数の要素でも操作することができる。これは小さな変化に見えるかもしれないが、ループや制御フロー等を削除すると、コードの見通しをとても良くすることが出来る。

selectionは無数の手段で作れる。ほとんどの場合、それはselectorを参照することにより作製する。selectorは、属性により望んだ要素群を特定する特別な文字列であり、名前かクラス (それぞれ"div"と".foo")に対して言われる。一つの要素に対するselectionを作るなら:

var body = d3.select("body");
var div = body.append("div");
div.html("Hello, world!");

多数の要素にも簡単に同じ処理を行える:

var section = d3.selectAll("section");
var div = section.append("div");
div.html("Hello, world!");

メソッドのチェイン

selectionのもう一つの利点は、メッゾドのチェインである: selectionメソッド(例えばselection.attr)は、現在のselectionを返す。これは、同一の要素に対して複数の処理を簡単に適用させる。メゾッドチェインを用いずにbodyの背景色と文字色をセットするには、こう書ける:

var body = d3.select("body");
body.style("color", "black");
body.style("background-color", "white");

"Body, body, body!" これとメゾッドチェーンを比較すると、それは繰り返しが除かれる点である:

d3.select("body")
    .style("color", "black")
    .style("background-color", "white");

メゾッドチェーンのインデントパターンは、現在のselectionを変更しないメソッドに対しては4スペース、selectionが変わるメソッドには2スペースをお勧めする。

注目するに、選択したbody要素に対してさえvarを必要としない (=変数を使わない)。オペレーションを適用後、そのselectionは廃棄される。メゾッドチェインの使用すると、コードを短く書ける (加えて、変数名に割く時間を減らせる)。

しかしながら、メゾッドチェインには気をつける事がある: 大抵の処理が同一のselectionを返すが、いくつかのメソッドでは新しいselectionを返す!例えば、selection.appendは新しい要素を含んだ新しいselectionを返す。この利点は、新しい要素に処理をチェインさせる点である。

d3.selectAll("section")
    .attr("class", "special")
  .append("div")
    .html("Hello, world!");

メゾッドチェインは文書階層を下ってのみ適用されるため、selectionsの参照を保持しバックアップするためにvarが使用さる。

var section = d3.selectAll("section");

section.append("div")
    .html("First!");

section.append("div")
    .html("Second.");

グラフのコーディング(手動)

ここでは、JavaScriptを用いず棒グラフを作る方法を理解しよう。現状、この平凡なデータセットの中には6つの数字しかないので、手作業で幾つかのdiv要素を書くのは苦労しないし、div要素の幅にデータの倍数を設定すれば作業完了である。

<!DOCTYPE html>
<style>

.chart div {
  font: 10px sans-serif;
  background-color: steelblue;
  text-align: right;
  padding: 3px;
  margin: 1px;
  color: white;
}

</style>
<div class="chart">
  <div style="width: 40px;">4</div>
  <div style="width: 80px;">8</div>
  <div style="width: 150px;">15</div>
  <div style="width: 160px;">16</div>
  <div style="width: 230px;">23</div>
  <div style="width: 420px;">42</div>
</div>

このグラフは、データに対して1つのdiv要素を持ち、それぞれの棒に対応する子要素のdivを持つ。子要素のdiv群は、青い背景色と白い前景色を持ち、右寄りの値ラベルを持つ棒となっている。chartクラスのdiv要素を削除すれば、この実装をもっと簡単にすることは出来る。しかし、より一般的なページではグラフの他にも別のコンテンツが加わるので、グラフにコンテナを設定する事により、グラフの位置やスタイルをページの他の部分に影響する事なく設定できる。

グラフのコーディング(自動)

当然ながらハードコーディングは大抵のデータセットで実用的でないし、このチュートリアルのポイントは、データから自動的にグラフを作製する方法を習得することである。今からD3を使って同じ構造を作るために、"chart"クラスのdiv要素のみを含む空のページを準備して始めよう。下記のスクリプトは、グラフのコンテナを選択して、そこにデータ値と対応した幅を持つ子要素のdivを加える。

d3.select(".chart")
  .selectAll("div")
    .data(data)
  .enter().append("div")
    .style("width", function(d) { return d * 10 + "px"; })
    .text(function(d) { return d; });

部分的には既知感があるだろうけど、このコードは重要かつ新たなコンセプト—data joinを導入している。簡単に説明するために、上記の簡素なコードを冗長に書き直し、どのように動くか確認する。

まず、クラスselectorを使ってグラフのコンテナを選択する。

var chart = d3.select(".chart");

次に、データを結合したいselectionを定義することにより、data joinを始める。

var bar = chart.selectAll("div");

data joinは、データの変更が起こった時に、要素を作製、更新、破棄するために使用される一般的なパターンである。奇妙に感じるかもしれないが、このアプローチの利点は、これを習得すれば、ページを操作するのにこのパターンだけを使えばいい点である。だから、流動的かつ安定したオブジェクトの静的または動的なグラフを作ろうとも、コードはほぼ同じままである。最初のselectionは、存在を望む要素の宣言と見なせる("Thinking with Joins"を見よ)。

次は、selection.dataを使って、(前もって定義済みの)データとselectionを結びつける。

var barUpdate = bar.data(data);

このデータ操作はupdate selectionを返す。enterexitが返すselectionsはupdate selectionを含まないので、要素の追加や削除が必要ないなら、enterやexitを使わない事もできる。

この時点でselectionが空であることは分かっているので^2、返ってきたupdateexit selectionsは当然空であり、存在していない要素に対応する新しいデータを意味するenter selectionを処理するだけである。enter selectionに要素を追加する事により、これら足りない要素のインスタンスを生成する。

var barEnter = barUpdate.enter().append("div");

ここで、新しい棒の幅に、対応するデータ値(d)の倍数を設定する。

barEnter.style("width", function(d) { return d * 10 + "px"; });

それら要素はdata joinによって作られたので、各棒は常にデータとバインドされている。幅のスタイル要素を求める関数から渡るデータを基にして、各棒の大きさが決まる。

最後に、各棒のテキスト要素をセットするための関数を使い、ラベルを付ける。

barEnter.text(function(d) { return d; });

テキストラベルの数字を整形する際には、可読性を上げる為の丸めやグルーピングがd3.formatによって行える。

D3のselection処理 (例えばattr,styleproperty)は、定数 (選択された全ての要素で値は同じになる)でも関数 (それぞれの要素で異なった値を算出する)でも、値として記述できる。要素に対応するデータを基にして特定の属性の値が作られる場合、それを算出する為には関数が使われる。そうでなくて、全ての要素が同じ値なら、文字列か数字で充分である。

Fitのためのスケーリング

上記コードの1つの弱点は、10というマジックナンバー#Unnamed_numerical_constants)であり、それは適切な画素幅に応じてデータ値を調整するために使われてる。この数字はデータの範囲(最小0、最大42)に依存しており、グラフの幅も指定されているが(420)、これらは当然のごとく、10という数字により暗黙下に示されている。

linear scaleを使用する事により、依存を明示的にし、マジックナンバーの削除ができる。D3のスケールとは、データの範囲(domain)を表示の範囲(range)にマッピングすることである。

var x = d3.scale.linear()
    .domain([0, d3.max(data)])
    .range([0, 420]);

D3のスケールはその他様々な表示用の値に変換することもできる(例えば、path、色空間や幾何変換)。

上記コードのx変数はオブジェクトに見えるが、与えられたdomainのデータ値からスケールした表示値を返す関数でもある。例えば、4という入力値の返り値は40であり、16なら160が返る。新しいスケールを使う為には、単純に、スケール関数が呼ばれる様にハードコーディングした乗法部と入れ替えればよい。

d3.select(".chart")
  .selectAll("div")
    .data(data)
  .enter().append("div")
    .style("width", function(d) { return x(d) + "px"; })
    .text(function(d) { return d; });

Next: Part2

チュートリアルpart1のコードは、bl.ocks.org/7322386上に置いてある。

この時点での骨子版棒グラフは単純な実装であり、まだまだ限定的である。棒グラフには値の比較しやすくるためのグリッドがあるべきだし、横棒よりも縦棒の方が良いだろう。それとも他のグラフ形式を望むなら、例えば円グラフやストリームグラフがある。より良い視覚表現には、Scalable Vector Graphicsが必要となる。SVG次のセクションで説明される。