こんにちは、伊藤です。
この記事は、TECHSCORE Advent Calendar の19日目の記事です。
D3.jsというのは、Web上でのデータ可視化の用途に使われることが多いJavaScriptライブラリで、データを元にHTMLやCSSを操作する使い勝手の良い(と個人的には思う)ライブラリです。まぁ、詳しいことは公式サイト( http://d3js.org/ )を見ていただくとして、この投稿では、そんなD3.jsのさわりをババっと紹介してみたいと思います。
データを表示する
リスト形式で表示
たとえば、以下のようなJavaScriptデータがあったとします。
1 2 3 4 5 6 7 8 9 |
var testdata = [ {"name": "A", "value": 10}, {"name": "B", "value": 20}, {"name": "C", "value": 30}, {"name": "D", "value": 50}, {"name": "E", "value": 80}, {"name": "F", "value":130}, {"name": "G", "value":210} ]; |
このデータを何らかの形でWebで表現したいとしましょう。一番手軽そうなのは、liタグのリスト形式で表示することですかね。ざっとHTMLで書くとこんな感じでしょうか。
1 2 3 4 5 6 7 8 9 |
<ul> <li>A: 10</li> <li>B: 20</li> <li>C: 30</li> <li>D: 50</li> <li>E: 80</li> <li>F: 130</li> <li>G: 210</li> </ul> |
で、このようなリストを、D3.jsで動的に生成しようとすると、以下のようになります。
1 2 3 4 5 6 7 |
d3.select("body").append("ul") .selectAll("li").data(testdata).enter() .append("li").text( function(d) { return d["name"] + ": " + d["value"]; } ); |
細かくメソッドの説明をせずともなんとなく内容がわかるのではないかと思いますが、注目すべきはdata()メソッドです。このメソッドにより、抽出してきたli要素と表現したいtestdataが結びつきます。この時点ではli要素は存在しないのですが、そこでenter()を呼ぶことでtestdataに対応するものが仮置きされ、そこへあらためてli要素を加えていきます。これにより、testdata配列と同数のli要素が生成されることになります。(このあたりの詳細は、ドキュメントのSelectionsのページを参考にしてください。)
一般的には、testdata配列の要素の数だけループしていることがより意識されるような書き方(forとか .each(...) とか)になることが多いと思いますが、D3.jsの場合はこのようなやや宣言的な書き方になります。最初はちょっととっつきにくいかもしれませんが、慣れるとなかなか素敵な仕様に思えてくるはずです、たぶん。
なお、結びついた各データにそって動的に内容を変更したい場合は、上記のコード内のtext()メソッドで行っているように、関数を渡します。この関数の第一引数は、その要素に結びついた各データとなり、第二引数が渡された順番となります。引数は省略してもかまいません。
というわけで、このようにしてリスト形式で作成されたものが以下になります。(サンプルページを直接表示)
棒グラフで表示
しかし、リスト形式というのも味気ないですね。もう少しだけ頑張って棒グラフで表示しようと思います。D3.jsでグラフや図等を表示するには(多くの場合)SVGを利用します。残念ながらInternet Explorerの8以下ではSVGを扱うことができませんが、 現在利用されている大半のウェブブラウザで表示可能です。
そのやり方ですが、これも実質的には前項のリスト形式と変わりません。まず、事前準備として描画領域となるsvg要素を生成します。
1 2 3 4 |
var barHeight = 30; var w = 500; var h = testdata.length * barHeight; var svg = d3.select("body").append("svg").attr("width", w).attr("height", h); |
生成したsvg要素に、testdataに結びついたrect要素を生成していけば棒グラフの出来上がりです。
1 2 3 4 5 |
svg.selectAll("rect").data(testdata).enter().append("rect") .attr("height", "25") .attr("width", function(d) { return d["value"]; }) .attr("x", 50) .attr("y", function(d, i) { return i * barHeight; }); |
ラベルをつけたり棒グラフの色を長さに合わせて変化させたりしてできたのが以下です(サンプルページを直接表示)。うん、多少は良い感じになりましたね!
※ところで、この例ではデータの値から画面上の表示位置を算出するのを自力でやっていますが、D3.jsではそういうことを計算してくれるスケールという仕組みが用意されています。大変便利なのですが、ここで説明すると長くなってしまうのでドキュメント「Scales」へのリンクを紹介するにとどめておきます。
アニメーションする
さくっとアニメーション
画面に表示されたグラフ等にちょっとした動きがあるだけで、見栄えが良くなったりテンションが上がったりするものですので、棒グラフが横から伸びていくという動きを追加してみたいと思います。
これも、基本的な動きをつけるのはとても簡単で、transition()を呼び出し、それのduration()メソッドでアニメーションの経過時間を設定したうえで、アニメーション後にどうなるかを設定してやればいいです。具体的には以下のような感じ。
1 2 3 |
svg.selectAll("rect").attr("width", 0); svg.selectAll("rect").transition().duration(800) .attr("width", function(d) {return d["value"]; }); |
出来上がった棒グラフは、ここをクリックして開いて見てください。横からびよ〜んと棒グラフが伸びてきたと思います。
連続的な動きをつける
単発の動きは上記のやり方が楽ですが、連続的に動き続けるようなケースではtimer()メソッドが便利です。このメソッドは、引数で渡された関数を定期的に実行してくれます。(より詳しくは、ドキュメント「Timers」を参照してください。)
一例として、小さな4つの円がひとつの大きな円の円周上をくるくる回り、かつ、その大きな円の半径が大きくなったり小さくなったりするというのを考えてみます。
やるべきことは、おおよそ以下のとおりです。
- 小さい円を4つ生成する。
- 大きい円を1つ生成する。
- 経過時間を記録する変数 t を用意する。
- timer()メソッドに渡した関数の中で、t の値にもとづいて小さい円の位置と、大きい円の半径とを計算し移動させる。
- timer()メソッドに渡した関数の中で、t の値を増やす。
そうやって出来たのがこれです。(サンプルページを表示)
核心部分となるtimer()メソッドの部分を抜き出すとこのようになっています。t によって各円の属性を変更しつつ、tを少しずつ増やしていっているのがお分かりいただけると思います。
1 2 3 4 5 6 7 8 9 |
var t = 0; d3.timer(function() { var r = Math.abs(Math.cos(t)); svg.selectAll(".smallcircle") .attr("cx", function(d) { return xScale(r * Math.cos(d + t));}) .attr("cy", function(d) { return yScale(r * Math.sin(d + t));}); svg.select(".largecircle").attr("r", xScale(r) - xScale(0)); t += 0.02; }); |
このように、timer()メソッドを使うことで連続的な動きを手軽に実現できます。利用者の想像のままに、様々な表現が可能となるでしょう。
例えば、格子状に設置した点を上下に振動させる(ただし、中心から離れるほど振幅が小さくなる)ことで、面が動いているような気がするというものを作ってみました → サンプルページはこちら。冷静に見てみると、だからどうしたという気がしなくもないですが、まぁ、これが50行程度でサクッと実現できるのも手軽だなと。
そして…
D3.jsの機能については、この記事では紹介できなかったたくさんのものがあります。特に、地図データの描画はかなり強力なツールです。公式サイトにも多様な利用例が紹介されており、眺めているだけで楽しくなってくるかもしれません。
例えば、東京及び世界の風の動きを可視化したこのサイト( http://air.nullschool.net/ )は私が感動したもののひとつです。
多少なりとも興味を持たれた方は、ぜひ、ご自身で試してみてください。