データビジュアライゼーション(4)

この記事は1年以上前に書かれました。
内容が古くなっている可能性がありますのでご注意下さい。


今回は、ビジュアライゼーション(1)で紹介したD3.jsを使用して、動きのあるビジュアライゼーションにチャレンジしようと思います。

まずは、横浜市統計ポータルサイトで提供されている、昼夜間人口、流出入人口及び人口密度のExcelファイルから、以下のCSVファイルを作成します。

ward,nighttime,daytime
鶴見区,272178,250323
神奈川区,233429,233168
西区,94867,170450
中区,146033,243277
南区,196153,154387
港南区,221411,173691
保土ケ谷区,206634,173514
旭区,251086,197891
磯子区,163237,136711
金沢区,209274,195740
港北区,329471,309610
緑区,177631,146647
青葉区,304297,234794
都筑区,201271,193939
戸塚区,274324,238630
栄区,124866,97103
泉区,155698,121197
瀬谷区,126913,104258

横浜市の各行政区ごとの、昼夜の人口変化をビジュアライズしてみます。

HTMLファイルの完全なコードは以下のとおりです。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Yokohama Data Visualization</title>
  <script type="text/javascript" src="d3.js"></script>
  <style type="text/css">
    .axis path,
    .axis line {
      fill: none;
      stroke: black;
      shape-rendering: crispEdges;
    }
    .axis text {
      font-size: 12px;
    }
</style>
</head>
<body>
  <div></div><br />
  <script type="text/javascript">

    var dataset;
    var w = 500;
    var h = 500;
    var hour = 0;

    var div = d3.select("div");
    div.text(hour + "時");

    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);

    var scale = d3.scale.linear()
      .domain([0,380000])
      .range([0,420]);

    d3.csv("yokohama01.csv", function(data) {

      dataset = data;

      // ラベル
      svg.selectAll("text")
        .data(dataset)
        .enter()
        .append("text")
        .text(function(d) {
          return d.ward;
        })
        .attr("x", 0)
        .attr("y", function(d, i) {
          return (i * 25) + 16;
        })
        .attr("font-size", "14px");

      // 棒グラフの準備
      svg.selectAll("rect")
        .data(dataset)
        .enter()
        .append("rect");

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

      svg.append("g")
        .attr("class", "axis")
        .attr("transform", "translate(80,450)")
        .call(axis);

    });

    setInterval( function() {

      // 0時(24時)=1、12時=0、6時と18時=0.5となる係数
      fct = (Math.cos(Math.PI * (hour/12))+1) / 2;
      div.text(hour + "時");

      // グラフ描画更新
      svg.selectAll("rect")
        .data(dataset)
        .attr("x", 80)
        .attr("y", function(d, i) {
          return (i * 25);
        })
        .attr("fill", function() {
          return "rgba(" + Math.round((1-fct)*255) +",0," + Math.round(fct*255) + ",1.0)";
        })
        .attr("height", "20px")
        .attr("width", function(d) {
          var pop = d.daytime - (fct * (d.daytime - d.nighttime));
          return scale(pop) + "px";
        });

      hour ++;
      if(hour == 24) {
        hour = 0
      }

    }, 1000);

  </script>
</body>
</html>

まず、headタグの中でD3.jsを読み込み、bodyの先頭で時刻を表示するためのdiv要素を用意しています。
JavaScriptでは、まず

var div = d3.select("div");
div.text(hour + "時");

var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var scale = d3.scale.linear()
  .domain([0,380000])
  .range([0,420]);

divに時刻の初期値を表示し、幅と高さが500ピクセルのSVG(Scalable Vector Graphics)要素を作成し、スケールを定義しています。スケールは、0から38万までの人口の値を、0から420までのピクセル数に変換するためのものです。

次に、

d3.csv("yokohama01.csv", function(data) {

先ほどのCSVファイルを読み込んで処理します。function(data) { 以降が処理の内容です。
まず、

dataset = data;

読み込んだデータを後で利用できるよう、グローバル変数に代入しておきます。

次に、行政区名のラベルを表示します。

svg.selectAll("text")
  .data(dataset)
  .enter()
  .append("text")
  .text(function(d) {
    return d.ward;
  })
  .attr("x", 0)
  .attr("y", function(d, i) {
    return (i * 25) + 16;
  })
  .attr("font-size", "14px");

この時点ではまだ存在しないtext要素とデータセットをバインドし、enter()でデータに対応するプレースホルダを取得し、append(“text”)でtext要素を追加します。
CSVファイルのward列の値をtext要素のテキストとします。
ラベルのx座標は0固定、y座標は25ずつ増加させます。

次に、棒グラフの棒を表すrect要素の準備をします。

svg.selectAll("rect")
  .data(dataset)
  .enter()
  .append("rect");

この時点ではまだ存在しないrect要素とデータセットをバインドし、enter()でデータに対応するプレースホルダを取得し、append(“rect”)でrect要素を追加します。棒グラフは後で描画します。
さらに、軸と目盛を追加します。

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

svg.append("g")
  .attr("class", "axis")
  .attr("transform", "translate(80,450)")
  .call(axis);

作成してあるスケールを用いて軸を作り、svg要素の[80, 450]の位置に表示します。
以上で、CSVファイル読み込みのコールバック処理は終わりです。

動きのあるビジュアライゼーションのために、JavaScriptのsetIntervalを使用し、

setInterval( function() { }, 1000);

1秒毎に描画を行い、24秒で24時間分の変化を表すようにします。とは言っても、元となるデータは「昼間人口」と「夜間人口」であり、1時間毎の人口データはありません。そこで、深夜0時の人口を「夜間人口」、12時の人口を「昼間人口」とし、その間はサインカーブを描いて変化するという仮定で、毎時の推定人口を計算します。

fct = (Math.cos(Math.PI * (hour/12))+1) / 2;

が、そのための係数の計算です。0時には1、12時には0、6時と18時には0.5になります。
0時や12時の前後では係数の値の変化は少なく、6時や18時の前後では変化が大きくなります。

svg.selectAll("rect")
  .data(dataset)
  .attr("x", 80)
  .attr("y", function(d, i) {
    return (i * 25);
  })
  .attr("fill", function() {
    return "rgba(" + Math.round((1-fct)*255) +",0," + Math.round(fct*255) + ",1.0)";
  })
  .attr("height", "20px")
  .attr("width", function(d) {
    var pop = d.daytime - (fct * (d.daytime - d.nighttime));
    return scale(pop) + "px";
  });

棒グラフを表すrect要素のx座標は80固定、y座標は25ずつ増加させます。
棒の色は、深夜0時には青(rgba(0,0,255,1.0))、12時には赤(rgba(255,0,0,1.0))、その間では青と赤が混じりあうようにします。
棒の長さは、係数を用いて求めた推定人口からスケールを用いてピクセル数に変換します。
最後に、24時間でループするよう、

hour++;
if(hour == 24) {
  hour = 0
}

時刻のカウントアップとリセットをします。

0時、6時、12時の表示は以下のようになります。

実際の動きは、こちらのHTMLファイルで見て下さい。

動きがあると、西区と中区だけは他の区と逆に動く(夜間人口より昼間人口が多い)こと、神奈川区は昼夜でほとんど変化がないことが良く分かります。

データビジュアライゼーション 123456