D3.jsでグラフを描いてみる

http://www.alexrothenberg.com/2014/01/06/learning-d3-by-building-a-chart.js.html

1 comment | 0 points | by WazanovaNews 3年以上前


Jshiike 3年以上前 | ▲upvoteする | link

Alex Rothenbergが、GitHubグラフでおなじみのD3.jsの解説をしています。

今日は、D3.jsを使って、このようなグラフを描いてみる。D3.jsのポイントとしては、

  • データをDOM elementとバインディングする。
  • チャート、地図、配列など多くのAPIがある。
  • チャートなどをビジュアル化するにはSVGが適している。

Binding data to the DOM

D3とはData-Driven Documentを意味し、データをDOM elementに同期する機能を提供してくれる。では早速見てみよう。

<script src="http://d3js.org/d3.v3.min.js"><script>

d3.jsスクリプトをインクルードすると、elementを追加するのに必要なトップレベルのd3オブジェクトができる。SVGはHTML5のタグで、ページの形状を決めることができます。CSSでスタイルが決まり、一般的なhtmlタグのようにJavaScriptで操作できるが、テキストというよりは形としてデザインされているので、様々な描画に使うことができる。

var svg = d3.select('body').append('svg')
             .attr('height', '200')
             .attr('width', '500');
var g = svg.append('g');

jQueryっぽく見えるが記述法は若干異なる。

  • Line 1: 新しいsvg elementをbody内に追加し、そのelementを示すd3オブジェクトを返す。
  • Line 2-3: D3はjQueryのように全てをうまくつなぐので、svg elementにheightwidth属性をセットできる。
  • Line 4: svgにg (groupのこと) elementを追加する。

この段階では、svg elementとg elementはビジュアル化の機能をもたないので、ユーザの見た目には特に変化はない。しかし、DOMを確認するとタグがあることがわかる。

<body>
   <svg height="200" width="500">
     <g></g>
   </svg>
 </body>

次にデータを追加する。ボストンの月別平均降雪量を示したtsv (tab-separated values)ファイルである。

Month  Average
Oct    0.1
Nov    0.6
Dec    12
Jan    14.9
Feb    11.8
Mar    7
Apr    0.3

d3 tsv APIを使って、データをパースして、DOMの各行にelementを挿入する。コードを確認する前に、二つのコンセプトを紹介しておきたい。svg内にrect elementをつくっていく。名前から想像がつくように、ブラウザはそのelementを長方形として描画する。また、D3のメインのメカニズムは、データをDOMに結合することである。基本的にデータセットはDOMのelementと結合され、d3はデータの行と同じ数のDOM elementがあることを担保できる。Mike Bostockのブログでこのたりの詳細が説明されている。[概念図]

コード上では実際にどうなっているのか。

 1   // This is unchanged
 2   var svg = d3.select('body').append('svg')
 3               .attr('height', '200')
 4               .attr('width', '500');
 5   var g = svg.append('g');
 6 
 7   var parseRow = function(row) {
 8     return {
 9       month: row.Month,
10       average: parseFloat(row.Average)
11     }
12   };
13 
14   d3.tsv('data/snow.tsv', parseRow, function(data) {
15     var rect = g.selectAll('.bar').data(data)
16                .enter().append('rect')
17     rect.attr('class', 'bar')
18         .attr('width', 10)
19         .attr('height', 100)
20         .attr('x', function(d) { return d.month.charCodeAt(0)})
21   })
  • Line 14: snow.tsvを読込み、パースする。そして二つのコールバックを用意する。まず各行をフォーマットして、そしてデータを処理してDOMに追加する。
  • Line 7-12: averageカラムをstringからnumberに変換することで、各行をフォーマットします。
  • Line 15: データとbarクラス(ページを読込んだときには空だが、後で役にたちます。)をelementにバインドする。
  • Line 16: DOM elementへの紐付けがないデータ(それがenter()の意味。)がrect elementをつくる。
  • Line 17-20: rectの位置属性をセットする。この場合の属性は、rect elementに紐づいたデータアイテムとともに呼び出されるcontantもしくはfunctionになる。

ここまでくるとバーチャートが表示されるが、しかしデータはまだ重複したままである。(マウスオーバーするとわかります。)

各バーの間にスペースを設けるには、x属性のロジックを.attr('x', function(d) { return d.month.charCodeAt(0) * 5 })に変える。まだ見た目はおかしいが、このようなグラフになる。

次に、各月データのバーの間にスペースを入れる替わりに、位置設定を助けてくれるd3 chart APIをいくつか紹介する。

Scaling with the D3 Scale API

データに基づいて、xとy方向それぞれにスケールさせる必要がある。バーの横幅は何ヶ月分を表示させるかにより、バーの高さは最大降雪量の月の数字次第で可変する。幸いD3にはscales APIがある。月のサイズはオリジナルのスケールを利用し、降雪量にはリニアに変化するスケールを適用する。各recにwidth, height, x & yをセットする際、tsvのコールバックを指定したスケール方式に変更する。

 1   d3.tsv('data/snow.tsv', parseRow, function(data) {
 2     var months = data.map(function(d) { return d.month })
 3     var x = d3.scale.ordinal()
 4             .rangeRoundBands([0, 400], .1)
 5             .domain(months)
 6 
 7     var averages = data.map(function(d) { return d.average })
 8     var y = d3.scale.linear()
 9             .range([150, 0])
10             .domain([0, d3.max(averages)])
11 
12     var rect = g.selectAll('.bar').data(data).enter().append('rect')
13     rect.attr('class', 'bar')
14         .attr('width', x.rangeBand())
15         .attr('height', function(d) { return 150 - y(d.average)})
16         .attr('x', function(d) { return x(d.month) })
17         .attr('y', function(d) { return y(d.average) })
18   })

どのようにx方向にスケールさせているかコードを確認してみよう。

  • Line 2: データから月を抽出する。
  • Line 3: d3にx方向には通常の値を設定するように指示する。(数えて並べ替えることはできるが、この時点では月の名前を認識するわけではない。)
  • Line 4: バーを横に引き伸ばし、各バーの間に一定の間隔を空ける。
  • Line 5: 計算結果を月データに適用する。

次にバーごとに正しいxの値と幅をセットする。

  • Line 14: 上記の計算をもとに各バーの幅をセットする。
  • Line 16: xをもとにどこにバーを配置するのかを指示する。

Line 6-9とLine 15 & 17で同じロジックがy方向にも適用されていることがわかる。唯一違うのは、平均降雪量というのはnumberなので、リニアスケールを採用しているところ。ラベルが表示されていないことを除いては、このように見た目がぐっと良くなった。

Adding Axis labels

d3 API for axesで、グラフの軸線を操作することができる。

 1 var g = svg.append('g')
 2   .attr("transform", "translate(40, 0)");
 3 
 4 g.append('g')
 5    .attr('class', 'x axis')
 6    .attr('transform', 'translate(0, 150)');
 7 g.append('g')
 8    .attr('class', 'y axis');
 9 
10 d3.tsv('data/snow.tsv', parseRow, function(data) {
11   // everything that was here before ... then
12 
13   var xAxis = d3.svg.axis()
14                 .scale(x)
15                 .orient('bottom')
16   d3.select('.x.axis').call(xAxis)
17 
18   var yAxis = d3.svg.axis()
19                 .scale(y)
20                 .orient('left')
21                 .tickFormat(d3.format('.0%'));
22   d3.select('.y.axis').call(yAxis)
23 })
  • Line 2: y方向に余裕を持たせるために、ggroupを右に移動させる。
  • Line 4-8: 二つの軸に対して、それぞれg elementを追加する。
  • Line 13-16: バーと同じスケールを利用してx軸を決め、D3に対してDOM elementに適用するように指示する。
  • Line 18-23: 同じようにy軸を決める。

このように、x軸に月の名称、y軸に降雪量(インチ)に表示されるようになった。次はもっと動的にしてみよう。

Loading different data sets

降雨量や気温など他の気象データも取り込めるようにしてみよう。

 1 <select onchange="loadData()" id="metric">
2   <option >snow</option>
3   <option >precipitation</option>
4   <option >temperature</option>
5 </select>

シンプルなセレクトボックスを用意して、JavaScriptを変更してみる。

 1 var loadData = function() {
 2   var metric = document.getElementById('metric').selectedOptions[0].text;
 3   var dataFile = 'data/' + metric + '.tsv'
 4   d3.tsv(data, parseRow, function(data) {
 5     // .. unchanged code ...
 6 
 7     var rect = g.selectAll('.bar').data(data);
 8     rect.enter().append('rect');
 9     rect.exit().remove();
10 
11     // .. more unchanged code ...
12   })
13 }
14 
15 loadData()
  • Line 1: d3.tsvの呼び出しを、新しいleadData functionでラップする。
  • Line 2-4: どのデータを読込むか指定する。(もう、'data/snow.tsv'をハードコードはしない。)
  • Line 7-9: d3がどのようにデータをDOM elementとバインドするかを思いだしてほしい。DOM elementよりデータの行が多い場合、enter()を使っていた。今回は、データ行よりもDOM elementが多いケースとなり、その場合は、exit()を使い、elementを削除する。グラフではこのように気候データの切り替えができるようになる。

Animating with the d3 transitions API

降順、月順などで並べ替えるのにはd3 transition APIを使う。

まずはUIエレメントを追加。

<input type=checkbox>Sort</input>

次にロジックを追加する。

 1 d3.tsv(dataFile, parseRow, function(data) {
 2   // .. all the unchanged code ... then
 3 
 4   d3.select('input').on('change', function() {
 5     var sortByAverage = function(a, b) { return b.average - a.average; };
 6     var sortByMonth = function(a, b) { return d3.ascending(monthIndex(a.month), monthIndex(b.month)); };
 7     var sortedMonths = data.sort(this.checked ? sortByAverage : sortByMonth)
 8                        .map(function(d) { return d.month; })
 9     x.domain(sortedMonths)
10 
11     var transition = svg.transition().duration(750);
12     var delay = function(d, i) { return i * 50; };
13 
14     transition.selectAll(".bar")
15         .delay(delay)
16         .attr("x", function(d) { return x(d.month); });
17 
18     transition.select(".x.axis")
19         .call(xAxis)
20       .selectAll("g")
21         .delay(delay);
22   })
23 
24 })
  • Line 4: 変更したイベントにバインドされることで、ソートするチェックボックスが変更になれば呼び出されるようになる。
  • Line 5-9: 月を並べ直し、xドメインを更新する。
  • Line 11-12: d3.transition APIを使い、750msの実行時間を設定することで、elementが一気に描画されないようにする。
  • Line 14-16: 全てのバーを描画する。(.barクラスをもっている。)
  • Line 18-21: x軸のラベルを表示する。

これで最終形のチャートができあがる。D3はAPIのドキュメントが充実していて、たくさんの事例がギャラリーにあるので是非チェックしてみてください。


[2013] ワザノバTop100アクセスランキング


#d3.js #コーディング #ビジュアル化

Back