【D3.js】おさらい1回目 棒グラフ
昨年末、ブログレイアウトのリニューアルのために去年書いた記事を見直していたのですが、よくよく考えてみると棒グラフとか折れ線グラフとかD3を使った基本的なグラフの作成方法について記事を書いたことが無かったな、ということに気付いたので改めてD3についておさらいしていくことに決めました。
ライブラリを読み込む
まず基本ですが、D3ライブラリを読み込みます。
1 |
<script src="http://d3js.org/d3.v3.min.js"></script> |
データセットを用意する
今回使用するデータセットは以下。このデータを棒グラフとして表示します。
1 2 3 4 5 6 7 |
var dataSet = [ {name:"a", val1: 1000, val2: 500 }, {name:"a", val1: 2000, val2: 400 }, {name:"a", val1: 3000, val2: 300 }, {name:"a", val1: 4000, val2: 200 }, {name:"a", val1: 5000, val2: 100 } ]; |
SVGを使って棒グラフ(横)を作成する
svg要素を設置する
まずは、body要素のなかにsvg要素を設置します。
このsvgタグが今回グラフが表示されるステージとなります。
高さ200px、幅300pxに指定しています。
1 2 3 4 5 6 7 8 9 |
var svgW = 300; //ステージの幅 var svgH = 200; //ステージの高さ var svg = d3.select('body') .append('svg') //svg要素をbody要素の中に追加 .attr({ width: svgW, height: svgH, }); |
値を正規化するscale関数を作成する
データの値を正規化するscale関数を作成します。
今回作成したscale関数は「ゼロ〜データ(val1)の最大値」間の値を、「ゼロ〜ステージの高さ」までの値に変換します。
1 2 3 |
var scale = d3.scale.linear() .domain([0, d3.max(dataSet, function(d){ return d.val1 })]) //正規化される値の範囲を指定(0〜val1の最大値) .range([0, svgH]); //出力値の範囲を指定(0〜ステージの高さ) |
データセットを束縛しrect要素を追加します
svg要素に対して架空のrect要素を選択し(この時点ではまだドキュメントにrect要素は存在しない)、データセットを束縛して足りないrect要素を追加します。
attrメソッドを使って各rect要素に属性値を設定、y属性(縦位置)はデータセットのインデックスを元に指定し、width属性(幅)はデータセットのval1の値を正規化して適用しています。
1 2 3 4 5 6 7 8 9 10 11 |
var barchart = svg.selectAll('rect') //rect要素を選択 .data(dataSet) //データセットを束縛 .enter() //データセットの数に対して選択された要素がいくつ足りないかチェック .append('rect') //足りない分のrect要素を追加 .attr({ x: 0, y: function(d, i){ return i * 30 }, //データのインデックス数をy属性に反映 width: function(d){ return scale(d.val1) }, //データの値(val1)を正規化してwidth属性に反映 height: 20, fill: "blue" }); |
セレクションメソッドやenterメソッドの動作についてもっと詳しく知りたい方は以下を参照してください。
D3 – セレクションの仕組み
・実行結果
ここまでのコードを実行した結果が以下です。
横向きのグラフが表示されます。
次は、このグラフを縦に表示されるように修正します。
SVGを使って棒グラフ(縦)を作成する
縦表示に変更する
y属性に指定していたコールバックとx属性の値を入れ替えます。
また、width属性に指定していたコールバックとheight属性の値を入れ替えます。
この変更によって、各rect要素に設定される値が、x属性(横位置)はデータセットのインデックスを元に指定され、height属性(高さ)はデータセットのval1の値を正規化した値が適用されるようになります。
1 2 3 4 5 6 7 8 9 10 11 |
var barchart = svg.selectAll('rect') .data(dataSet) .enter() .append('rect') .attr({ x: function(d, i){ return i * 30 }, // ↓ 入れ替えました y: 0, // ↑ width: 20, // ↓ 入れ替えました height: function(d){ return scale(d.val1)} , // ↑ fill: "blue" }); |
・実行結果
コードを修正して実行した結果が以下となります。
グラフが上から下へ向かって表示されるようになりました。
後ほどグラフの向きを反転(下から上へ向かって表示)させますが、その前に次は目盛り軸を作成します。
目盛りを表示する
マージンを設定する
今回は目盛りをグラフの左側に表示します。
このままでは、目盛り要素を表示するスペースが無いのでまずマージンを設定します。
1 |
var xMargin = 50; |
マージンがグラフに反映されるようにコードを修正します。
1 2 3 4 5 6 7 8 9 10 11 |
var barchart = svg.selectAll('rect') .data(dataSet) .enter() .append('rect') .attr({ x: function(d, i){ return i * 30 + xMargin }, //マージンをグラフに反映 y: 0, width: 20, height: function(d){ return scale(d.val1)} , fill: "blue" }); |
目盛りを作成する
目盛りを作成するには、まずd3.svg.axisメソッドを使ってaxisオブジェクト(yAxisCall)を生成します。
その後、svg要素にg要素(グループ要素)を一つ追加し、callメソッドに作成したyAxisCallオブジェクトを渡します。
1 2 3 4 5 6 7 8 9 10 |
var yAxisCall = d3.svg.axis() .scale(scale) //スケールを適用する .orient('left') //目盛りの表示位置を左側に指定 var yAxis = svg.append('g') .attr({ "class": "axis", //classを指定しておく "transform": "translate(" + [xMargin, 0] + ")" }) .call(yAxisCall); //yAxisCallオブジェクトを適用 |
・実行結果
修正したコードを実行した結果が以下となります。
目盛りをデザインする
表示されている目盛りをスタイルシートを使用して見た目を変更します。
前回、attrメソッドを使って指定したクラス名を元にpaht要素とline要素にスタイルを適用します。
1 2 3 4 5 6 7 |
.axis path, .axis line{ fill:none; stroke:black; } .axis text { fill:black; } |
・実行結果
上記のスタイルを適用した結果が以下となります。
次はいよいよ、グラフの上下を反転させます。
反転する
スケールを反転させる
先ずはscale関数のrange(出力範囲)を反転します。
この修正によって大きい値のデータほどscale関数を適用すると、小さい数値に正規化されて出力されます。
1 2 3 |
var scale = d3.scale.linear() .domain([0, d3.max(dataSet, function(d){ return d.val1 })]) .range([svgH, 0]); |
・実行結果
yAxisCallオブジェクトは出力する目盛りの値を読み込んでいるscale関数を元に生成しているので、scale関数を修正したことによって出力される目盛りも反転され下に行くほど少ない値が表示されるようになります。
グラフの高さを反転させる
上記の実行結果をよく見るとグラフの値が実際のデータの値と反対になっていることに気付くと思います。
そこで、attrメソッドで適用しているrect要素のheight属性を「正規化したデータの値」から「ステージの高さ-正規化したデータの値」に変更します。
1 2 3 4 5 6 7 8 9 10 11 |
var barchart = svg.selectAll('rect') .data(dataSet) .enter() .append('rect') .attr({ x: function(d, i){ return i * 30 + xMargin }, y: 0, width: 20, height: function(d){ return svgH - scale(d.val1)} , //(ステージの高さ-正規化したデータの値)を反映 fill: "blue" }); |
・実行結果
修正したコードを実行した結果が以下です。
グラフの表示位置(縦)を反転させる
最後の仕上げとして、各rect要素のy属性を調整しグラフを下方向に揃えます。
1 2 3 4 5 6 7 8 9 10 11 |
var barchart = svg.selectAll('rect') .data(dataSet) .enter() .append('rect') .attr({ x: function(d, i){ return i * 30 + xMargin }, y: function(d){ return scale(d.val1)}, //縦位置を下方向に揃える width: 20, height: function(d){ return svgH - scale(d.val1)} , fill: "blue" }); |
・実行結果
これでひとまず棒グラフ(縦)の完成です
動的にグラフを変化させる
ついでなので、イベント実行時にグラフを動的に変化するようにしてみます。
今回は、body要素上でクリックされたら、表示するグラフをval1からval2のグラフへと変化させてみます。
イベントを設置する
まずは、body要素にイベントを設置しアラートが正しく実行されるかテストします。
1 2 3 |
d3.select('body').on('click', function(){ alert('test'); }); |
正常に動作することが確認できたらalertは削除してください。
スケールを更新する
正規化する対象をデータセットのval1からval2へと変更するため、スケールの適用範囲を「ゼロからval2の最大値」へと更新します。
1 2 3 |
d3.select('body').on('click', function(){ scale.domain([0, d3.max(dataSet, function(d){ return d.val2 })]); //正規化される値の範囲を指定(0〜val2の最大値) }); |
目盛りを更新する
さらに、目盛りの表記をval1に対するものからval2に合わせたものへ変更するのために、xAxisオブジェクトに再度yAxisCalオブジェクトを渡して更新します。
1 2 3 4 |
d3.select('body').on('click', function(){ scale.domain([0, d3.max(dataSet, function(d){ return d.val2 })]); yAxis.call(yAxisCall); }); |
棒グラフを更新する
最後に各rect要素のy属性値・height属性を更新します。
1 2 3 4 5 6 7 8 |
d3.select('body').on('click', function(){ scale.domain([0, d3.max(dataSet, function(d){ return d.val2 })]); yAxis.call(yAxisCall); barchart.attr({ y: function(d){ return scale(d.val2)}, height: function(d){ return svgH - scale(d.val2)} , }); }); |
・実行結果
クリックイベント発生後の実行結果が以下です。
グラフの内容がデータセットのval2の値を元にしたものに変更され、目盛りもそれに合わせた値に変更されています。
アニメーションさせる
val1グラフからval2グラフへの変更の際にアニメーションされるようにコードを修正します。
transitionメソッドを間に挟むだけなので簡単です。
1 2 3 4 5 6 7 8 9 10 |
d3.select('body').on('click', function(){ scale.domain([0, d3.max(dataSet, function(d){ return d.val2 })]); yAxis.transition().call(yAxisCall); //目盛り表示更新時にアニメーションさせる barchart .transition() //グラフ表示更新時にアニメーションさせる .attr({ y: function(d){ return scale(d.val2)}, height: function(d){ return svgH - scale(d.val2)} , }); }); |
以上です。
次回は、折れ線グラフをおさらいします。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
var dataSet = [ {name:"a", val1: 1000, val2: 500 }, {name:"a", val1: 2000, val2: 400 }, {name:"a", val1: 3000, val2: 300 }, {name:"a", val1: 4000, val2: 200 }, {name:"a", val1: 5000, val2: 100 } ]; var svgW = 300; //ステージの幅 var svgH = 200; //ステージの高さ var xMargin = 50; //マージン var svg = d3.select('body') .append('svg') //svg要素をbody要素の中に追加 .attr({ width: svgW, height: svgH, }); var scale = d3.scale.linear() .domain([0, d3.max(dataSet, function(d){ return d.val1 })]) //正規化される値の範囲を指定(0〜val1の最大値) .range([svgH, 0]); //出力値の範囲を指定(ステージの高さ〜0) var barchart = svg.selectAll('rect') .data(dataSet) .enter() .append('rect') .attr({ x: function(d, i){ return i * 30 + xMargin }, //マージンをグラフに反映 y: function(d){ return scale(d.val1)}, //縦位置を下方向に揃える width: 20, height: function(d){ return svgH - scale(d.val1)} , //(ステージの高さ-正規化したデータの値)を反映 fill: "blue" }); var yAxisCall = d3.svg.axis() .scale(scale) //スケールを適用する .orient('left') //目盛りの表示位置を左側に指定 var yAxis = svg.append('g') .attr({ "class": "axis", //classを指定しておく "transform": "translate(" + [xMargin, 0] + ")" }) .call(yAxisCall); //yAxisCallオブジェクトを適用 d3.select('body').on('click', function(){ scale.domain([0, d3.max(dataSet, function(d){ return d.val2 })]); yAxis.transition().call(yAxisCall); //目盛り表示更新時にアニメーションさせる barchart .transition() //グラフ表示更新時にアニメーションさせる .attr({ y: function(d){ return scale(d.val2)}, height: function(d){ return svgH - scale(d.val2)} , }); }); |