最近名前を聞くことが多くなってきたD3.jsを試してみています。
まだ使い始めたばかりですが、D3.jsの設計の素晴らしさに感動しています。
データを与えればグラフが表示されるというような単純なものではないので、敷居が高く感じていましたが、設計を知るほどによく出来ていて驚きます。
D3.jsとは何か?
そもそも、D3.jsとはなんでしょう?
D3.jsは、データをブラウザで可視化するためのライブラリです。
単なるグラフライブラリではなく、もっと抽象的な「可視化」を扱うのが特徴です。
抽象的なレイヤーを扱うので、ライブラリと言っても機能より設計が重要で、D3.jsが支持されている理由はその設計の素晴らしさにあります。実際に描画しながら、その設計の違いについて考えていきます。
企業の時価総額と営業利益の関係をプロット
例題として、企業の時価総額を横軸に、営業利益を縦軸にとった散布図を描いてみます。
var dataset=[ {name:'CyberAgent', value:226936E6, profit:10318E6}, {name:'GREE', value:241511E6, profit:48615E6}, {name:'DeNA', value:248987E6, profit:76840E6}, {name:'mixi', value:93187E6, profit:2574E6}, {name:'OPT', value:23190E6, profit:1300E6}, {name:'GungHo', value:599045E6, profit:91228E6}, {name:'COLOPL', value:305376E6, profit:5744E6} ]; |
数字は正確でないかもしれないです。選択されている企業はたまたま僕のブックマークに入っていた企業です。
普通に考えたグラフライブラリの設計
普通のグラフライブラリは、描画処理が全てのデータを管理しながら、それぞれを順に描画していきます。
とりあえず、jQueryでsvg要素を配置しておきます。width変数は描画領域の横幅、height変数は描画領域の縦幅です。
var svg = $(document.createElementNS('http://www.w3.org/2000/svg', 'svg')).attr({ 'width' : width, 'height' : height }); $('body').append(svg); |
これで、svg要素がbody要素に追加されます。
<body> <svg width="1000" height="800"></svg> </body> |
この中にデータをプロットしていくために、dataset変数をループしながら、circle要素を配置していきます。
この時、横軸に時価総額のvalue変数をとっていて、縦軸に営業利益のprofit変数をとっています。maxValueとmaxProfitは、事前に計算したそれぞれの最大値です。データの値がどんな範囲になっても画面におさまるようにするために使っています。
for (var i in dataset) { var circle = $(document.createElementNS('http://www.w3.org/2000/svg', 'circle')); circle.attr('cx', width * dataset[i].value / maxValue); circle.attr('cy', height * (1 - dataset[i].profit / maxProfit)); circle.attr('r', 70); svg.append(circle); } |
for文の中で、次のようなcircle要素を作っています。
<circle cx="378.8296371724996" cy="709.5190073223133" r="70"/> |
そして、それをひとつずつsvg要素の中に追加しています。
<svg width="1000" height="800"> <circle cx="378.8296371724996" cy="709.5190073223133" r="70"/> <circle cx="403.16002971396136" cy="373.68351821809097" r="70"/> ...(略)... </svg> |
ブラウザで実行してみると確かにプロットされます。
これは誰でも考える普通の描画方法です。
D3.jsの素晴らしい描画
今度はD3.jsの描画の方法を紹介します。
まずは、D3.jsでもsvg要素を配置することから始めます。D3.jsにもjQueryと同じような構文でDOMを生成する機能があります。
var svg = d3.select('body').append('svg').attr({ width : width, height : height }); |
これでbody要素内にsvg要素が作られるところは同じです。
<body> <svg width="1000" height="800"></svg> </body> |
次にcircle要素をsvg要素に追加しますが、この時はまだcircleの位置などは指定しません。
その代わりに、data関数を使って、要素にデータをバインドしておきます。このバインドが、D3.jsの設計の凄いところです!
svg.selectAll('circle').data(dataset).enter().append('circle'); |
この結果、DOMの構成は次のようになります。
<svg width="1000" height="800"> <circle/> <circle/> ...(略)... </svg> |
circleに位置や半径が指定されていないので、この時点ではまだ画面には何も表示されません。
どうやって位置を調整するかというと、画面に配置したcircle要素を選択して、属性の変更をしていきます。
svg.selectAll('circle').attr('cx', function(d) { return xScale(d.value); }); |
先ほど要素にバインドしたデータが匿名関数の引数から渡されます。データを元に、どこに表示すれば良いかを計算して、返り値とします。
xScale関数は、データの範囲から横軸の位置を調整するために事前に作った関数です。こういう軸のスケール調整の機能もD3.jsにはあります。
縦軸方向の位置もデータに合わせて移動させておきます。
svg.selectAll('circle').attr('cy', function(d) { return yScale(d.profit); }); |
最後に半径も設定しておきます。
svg.selectAll('circle').attr('r', 70); |
これで、描画が完了して、先ほどjQueryで作ったのと同じようなDOM構成が出来上がります。
<svg width="1000" height="800"> <circle cx="378.8296371724996" cy="709.5190073223133" r="70"/> <circle cx="403.16002971396136" cy="373.68351821809097" r="70"/> ...(略)... </svg> |
こちらもブラウザで見ると、確かにプロットされています。
D3.jsは、for文をひとつも書かずに全てのデータを描画していることと、匿名関数で可視化の表現をしているのがポイントです。
ふたつの設計の違いとは?
D3.jsの設計の違いは、for文を書かなくて良いというのもあるんですが、もっと大きな違いがあります。データを要素自身が管理するようになっていることです。
普通のグラフライブラリだと、全てのデータを描画する人が管理していて、1つずつ取り出しながらどこに描画するかを決めていきます。
このとき、要素自身は「自分がどんなデータを表現しようとしているか」を知らないのがポイントです。要素は言われるがままに配置されているだけです。
D3.jsは要素自身がデータを保持している。
D3.jsでは、まず要素にデータを紐付けてしまいます。だから、要素自身が「自分がどんなデータを表現しようとしているか」を知っています。
この時点では、要素は原点などに固まっているんですが、要素がそれぞれ自分自身のデータを保持しているので心配はいりません。
要素は自分のデータを知っているので、そのデータを元にどこに配置されるべきかを問いかけてきます。問いかけはJavaScriptの匿名関数でおこないます。
これによって、プログラマは、全てのデータを一元的に管理する必要がなくなります。全てのデータは要素が持っているからです。
グラフを描画するプログラムは、データがどこに行くべきかを指示するだけで良いんです。
複雑なグラフも超シンプルに記述できる。
そういうわけで、データの管理は要素に任せてしまうことで、プログラマは「データがどう表現されるべきか」という問題に集中できます。だから、複雑な可視化も簡単にできるようになります。
最後に、先ほどの「時価総額」と「営業利益」に加えて、「売上」と「前日からの値上がり率」を加えて4次元的なグラフを描いてみます。4次元のプロットはできないので、売上は円の大きさで表現し、値上がり率は円の色で表現します。
このような複雑な要求も、実質1行のプログラムで書けます。
svg.selectAll('circle').attr({ 'cx' : function(d) { return xScale(d.value); }, 'cy' : function(d) { return yScale(d.profit); }, 'r' : function(d) { return rScale(Math.sqrt(d.revenue)); }, 'fill' : function(d) { return 'rgba(' + Math.round(128 + colorScale(d.difference)) + ', ' + Math.round(128 - colorScale(d.difference)) + ', 0, 0.6)'; } }); |
わずかにこれだけで、円の大きさや、色まで含めて、グラフが描画できます。
見栄えのために軸とかラベルは追加してますが、こういう表現が簡単にできてしまうのはD3.jsの凄さです。
おわりに
設計だけではなくて、座標変換などの機能も充実しています。プログラマは表現に必要なパーツを組み合わせるだけで、簡単に可視化ができるのもありがたいです。
最後に勉強に使った本を紹介しておきます。
エンジニアのための データ可視化[実践]入門 ~D3.jsによるWebの可視化
インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化
まだ勉強しはじめたばかりなので、理解が違うところもあるかもしれませんが、この素晴らしいライブラリをもっと深く知りたいと思いました!