エンジニア

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

投稿日:2014年11月4日 更新日:

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

前回に引き続き、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);
     });
});

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

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-

© WonderPlanet Inc.