こんにちは、CTOの奥田です。
さて、データの可視化にはラインチャートだけでなく様々なチャートがあります。
前回はD3.jsを使ってラインチャートを描画してみました。
今回はD3.jsを使ってバーチャートを描画してみたいと思います。
Table of contents
チャートの軸を描画する
前回と同様にチャートの軸を表示します。
今回はバーの数でtick数が固定になってしまうのでフォーマットを変えることでレスポンシブにも対応できるようにします。
let format = (window.innerWidth < 768) ? d3.timeFormat("%m") : d3.timeFormat("%Y/%m");
tickFormatのコールバックで目盛りごとにフォーマットを指定できるのでスマホ時も1つ目の目盛りのみ年号を付与します。
// 目盛りの表示フォーマットを設定 .tickFormat(function (d,i) { if(i == 0){ let first =d3.timeFormat("%Y/%m"); return first(d); } return format(d); });
バーを描画する
今回はデータを2つずつ用意し、Stacked bar chartを作成します。
矩形はrectで描画します。
data()にデータを渡し、enter()を呼び出すことで描画できます。
addRects : function(){ rectGroup = svg.append("g"); rect = rectGroup.selectAll(".bar") // データを指定 .data(dataset) .enter() .append("rect") .attr('class',"bar") // バーの色を指定 .style("fill", "#4c94ff") rectGroup2 = svg.append("g"); rect2 = rectGroup2.selectAll(".bar2") // データを指定 .data(dataset) .enter() .append("rect") .attr('class',"bar2") // バーの色を指定 .style("fill", "#0061e0") }
xScale.bandwidth()でグラフの最大幅を値の数で割った値が取得できます。
そのままでもいいのですがグラフがバーで埋め尽くされてしまうのでバーに間隔をもたせます。
また、xの値はxScale(d.date)で取れるのですが、xScale.bandwidth()の左端の値なのでバーの幅の半分を足してあげることでバーの真ん中をx軸の目盛りの真ん中に表示することができます。
heightは開始位置の値からそれぞれの値を引くことで指定できます。
setRects : function(){ // バーの幅を宣言 let barWidth = xScale.bandwidth() / 2; rect.attr("y", function(d){ // y軸の位置を指定 return yScale(d.value1); }) .attr("x", function(d) { // x軸の左端からバーの幅の半分を足して指定 return xScale(d.date) + (barWidth / 2); }) .attr("width", function(d) { // バーの幅を指定 return barWidth }) .attr("height", function(d) { // 値2から値1までの高さを指定 return yScale(d.value2) - yScale(d.value1); }) rect2.attr("y", function(d){ // y軸の位置を指定 return yScale(d.value2); }) .attr("x", function(d) { // x軸の左端からバーの幅の半分を足して指定 return xScale(d.date) + (barWidth / 2); }) .attr("width", function(d) { // バーの幅を指定 return barWidth }) .attr("height", function(d) { // y軸の0から値2までの高さを指定 return yScale(0) - yScale(d.value2); }) }
アニメーションさせる
アニメーションをさせる方法はtransitionを使えば容易なのですが、バーチャートの場合ただ高さをアニメーションさせるだけでは基準がグラフの上になっているので上から下にアニメーションしてしまいます。
y軸の0からアニメーションさせるにはheightをアニメーションさせつつ、translateYを開始位置から終了位置までアニメーションさせる必要があります。
animate : function(){ let self = this; if(isAnimate){ return false; } isAnimate = true; rect .attr("height", 0) .attr("transform", function(d){ let y = (yScale(0) - yScale(d.value1)); return "translate(0,"+ y +")"; }) .transition() .delay(function(d, i) { return i * 30; }) .duration(700) .ease(d3.easeExpInOut) .attr("height", function(d) { let h = yScale(0) - yScale(d.value1); if(Math.sign(h) < 0){ h = -(h); } return h; }) .attr("transform", function(d){ return "translate(0,0)";}) rect2 .attr("height", 0) .attr("transform", function(d){ let y = (yScale(0) - yScale(d.value2)); return "translate(0,"+ y +")"; }) .transition() .delay(function(d, i) { return i * 30; }) .duration(700) .ease(d3.easeExpInOut) .attr("height", function(d) { let h = yScale(0) - yScale(d.value2); if(Math.sign(h) < 0){ h = -(h); } return h; }) .attr("transform", function(d){ return "translate(0,0)";}) .on("end",function (d,i) { if(dataset.length-1 == i){ isAnimate = false; } }) }
ツールチップを表示する
ツールチップはどちらのバーの上にマウスが乗っても上部へ出るように実装します。
dにマウスが乗っている要素のデータが入るので値を指定し、ツールチップを表示したい場所に移動させます。
mouseEvent: function(){ rect.on("mouseenter",this.handleMouseOver) .on("mouseout",this.handleMouseOut) rect2.on("mouseenter",this.handleMouseOver) .on("mouseout",this.handleMouseOut) }, handleMouseOver : function (d, i) { tooltip = contents.select(".chart--tooltip"); tooltip.html('<div>A:'+ d.value1 +'<br>B:'+ d.value2 +'</div>') .style("transform", function(){ let x = xScale(d.date) + (xScale.bandwidth() / 2); let y = (yScale(d.value1) - 60); return "translate("+ x + "px," + y +"px)"; }) .style('opacity',1); }, handleMouseOut : function () { tooltip .style('opacity',0); }
データを切り替える
今回はボタンをクリックするたびにデータがランダムな値に変わるようなサンプルを作成してみました。
getRandom : function( min, max ) { let random = Math.floor( Math.random() * (max + 1 - min) ) + min; return random; }, dataGenerate : function(){ dataset = []; for (let i = 0; i < 12; i ++){ let date = moment().month(i).startOf('month'); dataset.push({ date : timeparser(date.format('YYYY/MM/DD')), value1 : this.getRandom( 20 , 79 ), value2 : this.getRandom( 0 , 19 ) }) } }
ボタンのクリックイベントにバインドします。
button.on("click",function(){ if(isAnimate){ return false; } self.dataGenerate(); self.update(); self.animate(); });
初期表示時は0からのアニメーションですが、切替時はバーのアニメーションを初期表示時と切り替える必要があります。
data()にてデータを再度セットし、heightとyを移動することでアニメーションさせることができます。
animate:function(){ let self = this; if(isAnimate){ return false; } isAnimate = true; if(!initialized){ // 初期表示時のアニメーション処理 // 初期表示後にinitialized = true;を指定 }else{ rect .data(dataset) .transition() .duration(700) .ease(d3.easeExpInOut) .attr("height", function(d) { let h = yScale(0) - yScale(d.value1); if(Math.sign(h) < 0){ h = -(h); } return h; }) .attr("y", function(d){ return yScale(d.value1) - (yScale(0) - yScale(d.value2)); }) rect2 .data(dataset) .transition() .duration(700) .ease(d3.easeExpInOut) .attr("height", function(d) { let h = yScale(0) - yScale(d.value2); if(Math.sign(h) < 0){ h = -(h); } return h; }) .attr("y", function(d){ return yScale(d.value2); }) .on("end",function (d,i) { if(dataset.length-1 == i){ isAnimate = false; } }); } }
さいごに
いかがだったでしょうか?
今回はD3.jsを使用してStacked bar chartを描画してみました。
ラインチャートでは値の推移などを見やすく可視化できますがバーチャートは数値の差をよりわかりやすく表現できると思います。
皆様の参考になれば幸いです。