読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

D3.jsによるデータドリブンなアニメーション

JavaScript

今回のエンジニアブログを担当する藤岡です。
宜しくお願い致します。

前回に引き続き、D3.jsを用いたデータの可視化を行っていきたいと思います。

今回は、D3.jsの機能を用いて、棒グラフにデータドリブンなアニメーションをさせてみます。
アニメーション機能が搭載されているグラフを見ると、楽しいですよね。


フロー

1. グラフを用意
2. 目盛りを追加
3. アニメーション

1. グラフを用意

ではまず、前回のようにサンプルデータセットを元にデータバインディングを行い、svg描画領域にグラフを作成します。

今回は、前回使用したグラフを少し拡張したものを使用します。

<!DOCTYPE html>  
<html>  
  <head>  
    <meta charset="utf-8">  
    <title>D3.js Animation</title>  
    <style>  
      svg {  
        border: 1px solid black;  
      }  
      #barGraph rect {  
        stroke : rgb(0, 0, 130);  
        stroke-width : 1px;  
        fill : rgb(0, 0, 200);  
      }  
    </style>  
    <script charset="utf-8" type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>  
  </head>  
  <body>  
    <h1>Sample Graph</h1>  
    <div id="barGraph"></div>  
    <script src="index.js"></script>  
  </body>  
</html>  
var svg_w = 550; // 幅  
var svg_h = 300; // 高さ  
  
var dataSet = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];   
  
// x軸のスケールを設定  
// 横棒のスケールマッピングと軸の追加に使用する  
var xScale = d3.scale.linear()  
               .domain([0, d3.max(dataSet)])  
               .range([0, svg_w]);  
  
var svg = d3.select("#barGraph") // barGraph要素を監視  
            .append("svg")   // svg領域を追加  
            .attr({  
              width: svg_w,  // 幅を設定  
              height: svg_h, // 高さを設定  
            });  
  
// データに基づいて描画する  
svg.selectAll("rect")  
   .data(dataSet)  
   .enter()  
   .append("rect")  
   .attr("x", 0)  
   .attr("y", function(d, i) {  
     return i * 25;  
   })  
   .attr("height", "20px")  
   .attr("width", function(d, i) {  
     return xScale(d);  
   });  

前回同様にサンプルデータセットをバインディングし、
スケールによるマッピングを行い、数値を可視化しています。

graph01

実行すると、棒線が表示されます。
目盛りも無い状態なので、これではグラフであるとは言えません。

2. 目盛りを追加

そもそもグラフとして成り立たせるためには目盛りが必須です。
D3.jsでは、目盛りを設定する機能もバッチリ含まれています。

D3.jsで目盛りを表示するには、domainとrangeの設定が大切になってきます。
下記のソースコードを追加することで目盛りが追加されます。

.axis text {  
    font-family: sans-serif;  
    font-size: 15px;  
}  
.axis path,  
.axis line {  
    fill: none;  
    stroke: black;  
}  

目盛りのスタイルを設定しています。

var xScale = d3.scale.linear()              // リニアスケールを設定  
               .domain([0, d3.max(dataSet)])      // 元のデータ範囲  
               .range([0, svg_w]);            // マッピングするサイズ  

目盛りを生成する際にもD3.jsのスケール機能を使用します。
ドメインとレンジを設定し、目盛りの範囲を指定しています。

var xAxis = d3.svg.axis()  
              .scale(xScale)  
              .orient("bottom");  

d3.svg.axis()が目盛りを処理するメソッドです。
上記のスケールを使用し、orient()によって目盛りの表示場所をbottomに指定しています。

目盛りの設定が完了したので、後は表示してあげるだけです。

svg.append("g")  
   .attr("class", "axis")  
   .attr("transform", "translate(0," + ((dataSet.length + 1) * 20 + 50) + ")")  
   .call(xAxis);  

目盛りを描画するにはsvg領域にg要素を追加します。
g要素を使うことで、g要素に内包されている複数の要素を1グループとして扱うことが出来るようになります。

transform属性で、translateによる並進を指定しています。今回は棒線の下に表示されるよう位置を調節しています。
call()に設定した目盛りを渡してやることで、目盛りが描画されます。

graph02

3. アニメーション

しかし、このままでは味気が全く無く、データドリブンであるとは言えません。

ということで、
・ボタンがクリックされたらデータを更新する
・データの更新時にアニメーションさせる
これら二つの機能を実装したいと思います。

◆ボタンクリック時のデータ更新

ボタンを追加するために、button要素を追加します。

<button id="update-btn">Update</button>  

D3.jsではイベント処理を用意することが可能です。
要素に対して、on()メソッドを使ってイベント処理を設定します。

// ボタンクリック時の処理  
d3.select("#update-btn").on("click", function() {  
  var newDataSet = new Array(dataSet.length);  
  for (var i = 0; i < newDataSet.length; i++) {  
    newDataSet[i] = Math.floor(Math.random() * d3.max(dataSet));  
  }  
  svg.selectAll("rect")  
     .data(newDataSet)  
     .attr("width", function(d, i) {  
       return xScale(d);  
     });  
});  

上記の処理は、update-btn要素がクリックされた時、ランダムに値を設定し直しています。
再設定されたデータセットを各要素にバインディングしてやることで、データの更新を行っています。

◆データ更新時にアニメーションさせる

アニメーションの実装ですが、実は凄く簡単です。
データを更新する前に、要素にtransition()メソッドを追加してあげるだけで実装することが出来ます。

棒線のwidthを設定していた箇所を以下のように変更します。

// 初期rect描画  
svg.selectAll("rect")  
   .data(dataSet)  
   .enter()  
   .append("rect")  
   .attr("x", padding)  
   .attr("y", function(d, i) {  
     return i * 25 + 15;  
   })  
   .attr("height", "20px")  
   .attr("width", "0px")  
   .transition()  
   .delay(function(d, i) {  
     return i * 100;  
   })  
   .duration(500)  
   .attr("width", function(d, i) {  
     return xScale(d);  
   });  
// ボタンクリック時の処理  
d3.select("#update-btn").on("click", function() {  
  var dataArray = new Array(dataSet.length);  
  for (var i = 0; i < dataArray.length; i++) {  
    dataArray[i] = Math.floor(Math.random() * d3.max(dataSet));  
  }  
    
  // rect要素のwidthを再設定  
  svg.selectAll("rect")  
     .data(dataArray)  
     .attr("width", "0px")  
     .transition()  
     .delay(function(d, i) {  
       return i * 100;  
     })  
     .duration(500)  
     .attr("width", function(d, i) {  
       return xScale(d);  
     });  
});  

・transition()
transition()メソッド以後に、メソッドチェーンで指定された属性値に時間をかけて変化させる処理を行います。
属性値の初期値と最終値を設定してやるだけで、その間のアニメーションはD3.jsが勝手に補完してくれます。

・delay()
delay()メソッドは、アニメーションに待ち時間を持たせることが可能です。
delayメソッドは関数をパラメータとして渡してやることで、データセットのデータと順番を取得してくれます。

.delay(function(d, i) {  
    return i * 100;  
})  

上記の例では、各データごと、アニメーション開始にディレイを与えています。

・duration()
duration()メソッドは、アニメーション開始から終了までの時間を指定することが出来ます。
パラメータにはミリ秒単位で時間を指定します。

こちらの動画をクリックすることで、データの更新とアニメーションが確認出来ると思います。
sample_graph_animation


最終的なコードはこちらになります。
表示した段階で目盛り、棒線が欠けてしまっていたので、余白を設定し位置を微調整しています。

<!DOCTYPE html>  
<html>  
  <head>  
    <meta charset="utf-8">  
    <title>D3.js Animation</title>  
    <style>  
      svg {  
        border: 1px solid black;  
      }  
      #barGraph rect {  
        stroke : rgb(0, 0, 130);  
        stroke-width : 2px;  
        fill : rgb(0, 0, 230);  
      }  
      .axis text {  
        font-family: sans-serif;  
        font-size: 15px;  
      }  
      .axis path,  
      .axis line {  
        fill: none;  
        stroke: black;  
      }  
    </style>  
    <script charset="utf-8" type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>  
  </head>  
  <body>  
    <h1>Sample Graph</h1>  
    <button id="update-btn">Update</button>  
    <div id="barGraph"></div>  
    <script src="index.js"></script>  
  </body>  
</html>  
var svg_w = 550; // 幅  
var svg_h = 300; // 高さ  
  
var padding = 20;  
var dataSet = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];   
var xScale = d3.scale.linear()  
               .domain([0, d3.max(dataSet)])  
               .range([0, svg_w - padding * 2]);  
  
var xAxis = d3.svg.axis()  
              .scale(xScale)  
              .orient("bottom");  
  
var svg = d3.select("#barGraph") // barGraph要素を監視  
            .append("svg")  // svg領域を追加  
            .attr({  
              width: svg_w,  // 幅を設定  
              height: svg_h, // 高さを設定  
            });  
  
// 目盛りを表示する  
svg.append("g")  
   .attr("class", "axis")  
   .attr("transform", "translate(" + padding + "," + ((dataSet.length + 1) * 20 + 50) + ")")  
   .call(xAxis)  
  
// データに基づいて描画する  
svg.selectAll("rect")  
   .data(dataSet)  
   .enter()  
   .append("rect")  
   .attr("x", padding)  
   .attr("y", function(d, i) {  
     return i * 25 + 15;  
   })  
   .attr("height", "20px")  
   .attr("width", "0px")  
   .transition()  
   .delay(function(d, i) {  
     return i * 100;  
   })  
   .duration(500)  
   .attr("width", function(d, i) {  
     return xScale(d);  
   });  
  
// ボタンクリック時の処理  
d3.select("#update-btn").on("click", function() {  
  var newDataSet = new Array(dataSet.length);  
  for (var i = 0; i < newDataSet.length; i++) {  
    newDataSet[i] = Math.floor(Math.random() * d3.max(dataSet));  
  }  
  svg.selectAll("rect")  
     .data(newDataSet)  
     .attr("width", "0px")  
     .transition()  
     .delay(function(d, i) {  
       return i * 100;  
     })  
     .duration(500)  
     .attr("width", function(d, i) {  
       return xScale(d);  
     });  
});  

いかがでしたでしょうか。
アニメーション機能を実装することで、見ていて楽しい&見やすいグラフを作り上げることが可能だと感じました。