スコット・マレイ
コード・アーティスト

Tutorials > D3 > 棒グラフの作成

棒グラフの作成

最終更新日 2012年12月30日(原文)  2013年04月13日(翻訳 / h.sakai

本章では、これまでに学んだ知識を総動員し、D3 を使ってシンプルな棒グラフを作ります。

最初に 8 章の div 要素を使った棒グラフの復習をします。次に SVG 要素を使ったグラフに書き換え、柔軟な視覚表現ができるようにします。最後にラベルを加えて数値を一目でわかるようにします。

元のグラフ

8章の棒グラフに若干データを追加したのがこのサンプルです。

var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
                          11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];

d3.select("body").selectAll("div")
    .data(dataset)
    .enter()
    .append("div")
    .attr("class", "bar")
    .style("height", function(d) {
        var barHeight = d * 5;
        return barHeight + "px";
    });

Bar chart with divs

すぐにはイメージがつかめないかもしれませんが、div 要素を使ったこのシンプルなグラフをベースに、どんどん改良を加えて行きます。

新しいグラフ

まず最初に、SVG のサイズを決める必要があります。

// 幅(Width)と高さ( height)
var w = 500;
var h = 100;

(もちろん wh の代わりに、svgWidth でも svgHeight でも好きな変数名を使ってかまいません。自分にとって一番わかりやすい変数名を使いましょう。JavaScript には効率性に病的に執着する、という文化があります。そのため一文字の変数名や、極度にスペースを切り詰めた書き方がよく見られます。読みやすいとは言えませんが、そのプログラマにとっては効率の良い書き方なのです。)

次に D3 に空の SVG 要素を作らせ、DOM に追加します。

// SVG 要素の生成
var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);

このコードは新しい <svg>body の終了タグ </body> の直前に挿入し、幅 500 ピクセル、高さ 100 ピクセルを設定します。またここで、式の結果を新しく宣言した変数 svg に記憶しています。後でこの要素が必要になった時、わざわざ d3.select("svg")SVG を選択しなおさなくても、svg を参照するだけですむようにするためです。

その次に、div 要素を生成する部分を rect シェイプの生成に書き換え、それをsvg に追加します。

svg.selectAll("rect")
   .data(dataset)
   .enter()
   .append("rect")
   .attr("x", 0)
   .attr("y", 0)
   .attr("width", 20)
   .attr("height", 100);

一行目の selectAllメソッドは svg 内のすべての rect を選択します。もちろんこの時点では rect 要素はまだ存在していませんので、このメソッドは空セレクションを返します。(何度も繰り返しますが、ここは D3 の一番わかりにくいところです。頑張ってついて来てください。D3 では何かの要素で処理を行う場合、たとえその時点では空であっても必ず最初にその要素を選択しなければなりません。)

二行目の data(dataset) メソッドは、まずデータセットを調べ、三行目の enter() メソッドをデータセットの値の個数の数だけ、つまり 20 回呼び出します。呼び出された enter() は、データッセットのデータのうち、対応する rect が存在しないデータ -- ここではすべてのデータ -- ごとにプレイスホルダセレクションを返します。

四行目の append("rect") は、20 個それぞれのプレイスホルダーごとに DOM へ rect 要素を挿入します。10 章「 SVG の基本」で学んだように、rect には必ず xywidthheight の値を設定しなければなりません。これらの属性の設定に attr() メソッドを用いています(五~八行目)。

ここまでの出来はどうでしょうか

One lonely bar

全然駄目?ごもっとも。棒グラフの棒はすべて生成されています(デモページ を WEB インスペクタで開いて DOM をチェックしてみてください)。しかしどの棒にも同じ xywidthheight の値を設定したため、棒がすべて重なってしまったのです。これでは何の視覚化にもなっていません。

まずはこの重なり合っている状態を何とかしましょう。x の値が常にゼロになっているところを、i の値、つまりデータセット中の値の位置を示すインデックスを使って動的に設定します。次の式で最初の棒の x 座標は 0、次が 21、その次が 42…、となります。

.attr("x", function(d, i) {
    return i * 21;  // 幅 20、間隔が 1 の棒
})

書き換えたサンプルページはこちらです

Twenty bars

これで棒は分離できました。しかしまだ柔軟性には欠けています。これではデータセットが増え、棒の数が増えると、 SVG 領域の右側にはみ出してしまいます。棒の幅は 20 ピクセル、間隔が 1 ピクセルですから、500 ピクセルの SVG 領域に入る棒の数は 23 本までです。24 本目の棒は、下図のように切れてしまいます。

Twenty-four bars

高さ、幅、X、Y などの数値や座標をコードで柔軟に、動的に計算するようにするのは良い習慣です。ここではデータ数の増減に応じて、適切な比率で視覚化するようにしてみましょう。

プログラミング一般になりたつ話ですが、座標計算一つとっても無数の計算方法があります。今回は簡単な方法で行きましょう。まず、それぞれ棒の x 座標を計算している行を修正します。

.attr("x", function(d, i) {
    return i * (w / dataset.length);
})

x の値が SVG の幅( w )とデータセットの値の個数( dataset.length)に直接結びつけられた点に注意してください。データの個数を 20 個にしてみましょう。どうなるでしょうか?はい、ご覧のとおり、20本の棒が等間隔に並びました。

Twenty evenly spaced bars

5 つに減らしてみましょう。

Five evenly spaced bars

サンプル画面はこちらです。

次に棒の横幅 width です。データ数が多ければ狭く、少なければ広くなるよう、自動的に調整される必要があります。SVG のサイズ宣言部にもう一つ変数を追加しましょう。

// 幅(Width)と高さ( height)
   var w = 500;
   var h = 100;
   var barPadding = 1;  // <-- パディング(棒の間の間隔)を追加!

この変数は棒の width を設定する関数内で使います。棒の幅には定数 20 ではなく、SVG の幅をデータの個数で割り、パディング(barPadding)の値を引いた数字がセットされます。

.attr("width", w / dataset.length - barPadding)

Twenty evenly spaced bars with dynamic widths

OK です!(サンプル)。データの個数が 20 個の場合も 5 個の場合も、棒の横幅、X 座標ともまく調節されています。

Five evenly spaced bars with dynamic widths

100 本入れても大丈夫!

One hundred evenly spaced bars with dynamic widths

最後に、データの値をそれぞれの棒の高さにエンコード(変換 - ここではデータの値を高さに変換すること)します。これは簡単そうです。棒の height を設定する関数内で、データの値 d を返せばよさそうですね。やってみましょう。

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

Dynamic heights

なんだかのっぺりしています。もう少し縮尺を上げましょうか。

.attr("height", function(d) {
    return d * 4;  // <-- 4 倍にしました!
});

Dynamic heights

あれれ?うまくいきませんね。棒は下から上へと延びてほしいところです。実はこれは D3 のせいではなく、SVG の仕様のせいなのです。

10章「 SVG の基本」で、rect を描くとき、xy の値は、その左上からの位置を示す、と習いました。つまり rect の座標の原点は左上ということです。 左下が原点であれば本当に楽なのですが、SVG の仕様はそうなっていません。率直に言ってこの点に関して SVG の仕様は、私たちの感覚に反するものです。

さて棒が「上から下へと延びていく」という仕様だとすれば、その「上」( y の値)は、SVG 領域の上辺からどの位置にあるべきでしょうか?そうですね。y の値は、SVG の高さからデータの値を引いた数値とするのが良さそうです。

.attr("y", function(d) {
    return h - d;  // SVG の高さからデータの値を引く
})

棒の「底辺」を SVG 領域の底辺に合わせるには、rect の高さをデータの値そのものにします。

.attr("height", function(d) {
   return d;  // データの値そのもの
});

Growing down from above

再び縮尺を上げましょう。y と height の両方の dd * 4 に変更します(なお、縮尺の変更方法については、15章の「スケール」でもっとスマートな方法を学習します)。

Growing bigger from above

これで棒グラフは、下から上へと延びるようになりました(サンプル)。

配色

色をつけるのは簡単です。attr() メソッドで fill 属性を設定するだけです。

.attr("fill", "teal");

Teal bars

青緑色の棒グラフになりました(サンプル)。 さて、このグラフは青緑の一色だけですが、視覚化においてはデータの特性の一部をシェイプの色の違いで表現したい場合がよくあります。つまり、データを色にエンコードしたい場合です。これを今回の棒グラフに適用すると、二重エンコード、つまり同じデータを高さと色という二通りの視覚的プロパティにエンコードすることになります。

データを使って色を指定するのは簡単です。"teal" の替りに独自関数を使うだけです。ここでも d を参照します。

.attr("fill", function(d) {
    return "rgb(0, 0, " + (d * 10) + ")";
});

Data-driven blue bars

サンプルはこちらです。それほど効果的な視覚化ではありませんが、ここではデータから色への変換方法を理解するようにしてください。関数内で、10 倍した drgb() 色関数の青の値( 3 番目のパラメータ)に設定しています。その結果、d の値が大きくなるほど棒は(より高く)より青くなります。値が小さくなるほど棒は(より低く)青は弱く(より黒っぽく)なります。

ラベル

視覚化の効果は大きいですが、元データをテキストとして表示しておきたいこともあります。その場合は値のラベルを用います。D3 でラベルを作成するのは極めて容易です。

10 章「 SVG の基本」を思い出しましょう。SVG 要素には text 要素も追加できました。ここでは次のようなコードになります。

svg.selectAll("text")
   .data(dataset)
   .enter()
   .append("text")

もうこの構文には慣れましたでしょうか?前のコードの rect 要素を text に置き換えているだけですね。最初に selectAll() で目的の要素を選択し、data() でデータを持ち込み、enter() で新しい要素を登場させる(ただしこの時点ではプレースホルダのみ)。最後に append() で新しい text 要素を DOM に追加します。

text 要素の中にデータの値を挿入するために、text メソッドを追加します。

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

さらに attr メソッドを二つ追加して x 属性と y 属性を設定し、 text の位置を決めます。ちょっと手を抜いて、棒の x/y の設定で使ったコードをコピペしてみましょう。

   .attr("x", function(d, i) {
       return i * (w / dataset.length);
   })
   .attr("y", function(d) {
       return h - (d * 4);
   });

Baby value labels!

ラベルが表示されました!しかし一部、数字の上が切れていますね。棒の内側に移動することにしましょう。xy の値を少し調整します。

   .attr("x", function(d, i) {
       return i * (w / dataset.length) + 5;  // +5
   })
   .attr("y", function(d) {
       return h - (d * 4) + 15;              // +15
   });

In-bar value labels

位置は改善されたかわりに文字が読みづらくなりました。手直しが必要です。

   .attr("font-family", "sans-serif")
   .attr("font-size", "11px")
   .attr("fill", "white");

Really nice value labels

小躍りしたくなるようなコードが出来上がりました!組版パラノイアでもなければこれで OK サインを出すところです。しかし私同様、棒上のラベル位置の微妙なズレが我慢できないという人もいるはずです。これはワンタッチで直せます。x 属性を指定する行の前で、SVG の text-anchor 属性を使い、水平方向の中央に配置します。

    .attr("text-anchor", "middle")

次に、x の位置を、棒半分だけ右にずらします。

    .attr("x", function(d, i) {
        return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
    })

最後に縦位置を 1 ピクセル上げれば完璧です。

    .attr("y", function(d) {
       return h - (d * 4) + 14;  // 15 を 14 に
    })

Centered labels

ついに完成です! 棒グラフは完成しましたが、ぼうっとしてないで、すぐに次の章に進みましょう。

次章は散布図の作成

インタラクティブ・データ・ヴィジュアライゼーション このチュートリアルの書籍版の翻訳がオライリー・ジャパンより発売されました。タイトルは『インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化』です(画像をクリックするとアマゾンに飛びます)。

このチュートリアルを大幅に拡充し、3倍近い内容となっています。JavaScriptを中心に基礎編をさらに詳しく解説し(書籍版第3章)、応用編としてモーション、イベント、レイアウト、地図の作成法、データのエクスポート(PDFやSVG等)の章が追加されています(同9章~13章)。アマゾンのページで目次を見ることができます。

本チュートリアルがわかりにくいと感じられた方、あるいは本チュートリアルを終え、さらに応用力を身につけたいと思われた方のどちらにもお勧めの内容となっています。

翻訳はコンピュータ・プログラミング関連書籍を多数翻訳されている長尾高弘氏です。