LoginSignup
78
73

More than 5 years have passed since last update.

D3.jsで地図のデータビジュアライズに挑戦!

Last updated at Posted at 2016-07-08
1 / 37

講習


D3.jsの特徴

image

  • データに基づいて、DOM(Document Object Model)を操作する、という考え方
  • 各種チャート、グラフに必要な計算やデータ整形を賄ってくれる
  • 表示はユーザーが自分で頑張って描く→自由!
  • ウェブ標準のリソースのみを利用しているのでライブラリがなくなっても困らない
  • ブラウザ上で動く(これ大事!)
  • SVGかCanvasでいったらSVG( http://visualizing.jp/openvisconf2013-miguel/
  • 最近ヴァージョン4.0がでた
    • 各機能が小さいライブラリに分割された

CartoDBやMapboxなどのサービスとの違い

image


どんなことができるのか

image


どんなことができるのか

image


どんなことができるのか

image


どんなことができるのか

image


どんなことができるのか

image


どんなことができるのか


どんなことができるのか


実習


今日使う環境

blockbuilder

  • http://blockbuilder.org/
  • ブラウザ上でコーディングするツール
    image

  • 対象ブラウザ:Chrome もしくは Firefox

  • 使ってなにが嬉しいか:サーバを立てなくて済む、Gistとの連動


操作の仕方(新しく作る場合)

  • START CODINGをクリック

image

  • 次の画面で、右上にある「Log In」ボタンを押し、ログインする(GitHubのアカウントが必要です)。

操作の仕方(すでにあるものを利用する場合)

image


ファイル構成

  • プログラムファイル

    • main.js
  • データファイル

    • japan.json...日本の地形ファイル(TopoJson形式)
    • population.tsv...人口データファイル(tsv形式)
    • capital.tsv...都道府県庁データファイル(tsv形式)
  • いじらなくていいファイル

    • index.html
    • main.css
    • README.md...メモ書き用ファイル(MarkDown形式)

image


操作の仕方

  • コーディング領域でファイルを修正すると、表示領域に即座に反映される
  • コーディング領域と表示領域、縦分割と横分割のどちらか選べる
  • 保存したいときはSaveをクリック
  • ファイルを追加したいときは右上の十字をクリック

今からやること


表示領域の用意

var svgContainer = d3.select("#main").append("svg")
            .attr("width", width).attr("height", height);

var mapContainer = svgContainer.append("g").attr("class", "japan");
var dataContainer = svgContainer.append("g").attr("class", "japan");
var legendContainer = svgContainer.append("g").attr("id", "legendBlock")
                      .attr('transform', 'translate(' + 800 + "," + 60 + ')')
                      .attr("width", 300).attr("height", 60);


/*
#main...HTMLに書いてあるid="main"(のdiv)を指定
.append("svg")...新しくsvgを発生させる
*/

外部ファイルの読み込み

  • 複数ファイルの読み込みに挑戦。
  • ネットにある他のサンプルは1ファイル読み込み想定のものが多い。今回はそれとは違うやり方。
<script src="http://d3js.org/queue.v1.min.js"></script>
<script>
queue()
    .defer(d3.json, "japan.json")
    .defer(d3.tsv,  "population.tsv")
    .defer(d3.tsv,  "capital.tsv")
    .await(mainFunc);

function mainFunc(_error, _topojson, _population, _capital) {
    /*
    括弧内はひとつめはエラー発生時エラーメッセージを受け取るためのもの
    ふたつめ以降は defer() としてリストした順に、データを受け取れる
    データは読み込み時に、オブジェクトの配列に変換される
    外部ファイルのデータを受け取ったあとのコードをここに書く
    */
}
</script>

地図の表示のさせ方(投影法)の指定

/* 投影法の指定 */

//日本全体の場合
var projection = d3.geo.mercator() 
          .scale(1200)
          .center([140.467551, 37.750299]);

//北海道のみの場合
var projection = d3.geo.mercator() 
          .scale(4000)
          .center([143.635254, 43.704119]);

/* 地形データをSVGに変換するときに呼び出す */
var path = d3.geo.path().projection(projection);


/*
d3.geo.mercator()...メルカトル法を指定
.center([140.467551, 37.750299])...中心の座標を経度緯度の順で指定する
*/


地図の表示のさせ方(地図の描画)

var japanmap = svgContainer.append("g");

japanmap.selectAll("path")
        .data(topojson.feature(_topojson, _topojson.objects.japan).features)
        .enter()
        .append("path")
        .attr("id", function(d) { 
          return d.properties.nam_ja;
        })
        .attr("d", path)
        .style("stroke", "#333")
        .style("stroke-width", "0.2px")
        .style("fill", "#FFF");

データと表現の連動

var rScale = d3.scale.linear()
    .domain([0, 14000]).range(["#FFF", "#00F"]);

統計データとの地形データ連携

  • 統計データ自体に緯度経度が含まれているなら、コーディングの中で、地形データとの連携は考えなくてよい。
  • 統計データ自体に緯度経度が含まれていない場合、共通のデータ項目を用いて、統計データとの地形データを連携させる必要がある。

地形データに含まれるデータの利用

  • TopoJSONファイルの場合、data.objects.hokkaido.geometries [i].propertiesに属性データが収納されているので、IDとして付与する、地図を着色するなど、適宜活用する。
.attr("id", function(d) {
    return d.properties.nam_ja;
})

凡例をつくる

image

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.10.0/d3-legend.js"></script>

var rScale = d3.scale.linear().domain([0, 14000]).range(["#FFF", "#00F"]);

var legendObj = d3.legend.color()
  .shapeWidth(15).shapeHeight(15)
  .labelFormat(d3.format(".0f"))
  .cells([0, 2000, 4000, 6000, 8000, 10000, 12000, 14000])
  .orient('vertical')
  .scale(rScale);

legendContainer.call(legendObj);

/*
rScale...インプットとアウトプットの組み合わせ(スケール)を指定
shapeWidth/shapeHeight...四角の大きさ(横幅、縦幅)
cells...凡例として表示したいデータ値
orient...向き(horizontal or vertical)
*/


ユーザーインターフェイスをつくる(今回はプルダウン)

/* 変数を準備 */
var menu;
var indexYear = -1;
var yearArray = new Array();

/* プルダウン初期化 */
function initMenu() {

    menu = d3.select("#menuBlock select").on("change", changeYear);

    menu.selectAll("option")
        .data(yearArray)
        .enter().append("option")
        .attr("value", function(d, i) { return i; })
        .text(function(d) { return d+"年"; });
}


ユーザーインターフェイスと地図表現の連動(japan.jsonとpopulation.tsv)

/* プルダウン変更時の挙動 */
function changeYear() {

      /* プルダウンで選んだのが何番目か */
      if (indexYear != -1) {
          indexYear = parseInt( menu.property("value") );
      } else {
          indexYear = 0;
      };

      /* プルダウンで番目の年のみのデータを抽出 */
      selectedPopulation =  _population.filter( function(d) { return d.date == yearArray[indexYear] });

      /* 地図を更新 */
      renderMap();
}


function renderMap() {

      /* 一部略 */

      //プルダウンの変更がある度に実行
      mapContainer.selectAll("path")
              .style("fill", function(d, i) {
                  //変化させる前の色
                  return rScale( parseInt(_prev[i]) )
              })
              .transition()
              .duration(2000)
              .style("fill", function(d, i) {
                  //変化させた後の色
                  var _value = selectedPopulation[0][ d.properties.nam_ja ];
                  _prev[i] = _value;
                  return rScale( parseInt(_value) )
              });
};



データの描画(capital.tsv)

function renderCircle() {

    dataContainer.selectAll("circle")
        .data(_capital)
        .enter()
        .append('circle')
        .attr('class', "pref")
        .attr('id', function(d){
            return d.nam_ja;
        })
        .attr('cx', function(d){
          console.log(d);
          return projection([d.lon, d.lat])[0];
        })
        .attr('cy', function(d){
          return projection([d.lon, d.lat])[1];
        })
        .attr('r', 2)
        .style('fill', function(d){
            return "#000";
        });
}

カスタマイズしてみよう!

  • 地形や丸の色を変えてみよう
  • 丸を画像に変えてみよう
  • データと連動する色を変えてみよう
  • 違うデータファイルを読み込んでみよう
  • (違う地図投影法を試してみよう)

丸を画像に変える

  • blockbuilderに画像をアップロードすることはできないので、使用したい画像をどこかのサーバにあげてからURLを.attr("xlink:href")に指定します。

  • function renderCircle() の中身を下記のソースコードへ丸っと差し替える。

dataContainer.selectAll(".chara")
    .data(_capital)
    .enter()
    .append('image')
    .attr('class', "chara")
    .attr('id', function(d){
        return d.nam_ja;
    })
    .attr("xlink:href", "aprilfool_piert_face.png")
    .attr('x', function(d){
      console.log(d);
      return projection([d.lon, d.lat])[0];
    })
    .attr('y', function(d){
      return projection([d.lon, d.lat])[1];
    })
    .attr("width", 20)
    .attr("height", 16);

補習


地図の用意の仕方

  • どこかから地形ファイルを手に入れる(たいていShapefile形式)
  • コマンドラインツールでブラウザ上でみえる形式へ変換する
  • 地形ファイルに含まれる属性データを整形・編集したかったり、別な属性データを埋め込みたい場合はQGIS( http://www.qgis.org/ja/site/ )というアプリケーションを利用する

地形ファイルの在り処


TopoJSONとは

  • GeoJSON( http://geojson.org/ )は、JSONを基にした、地理空間データ交換フォーマットです。オープンな標準規格。
  • TopoJSONはD3.js作者による独自規格。

image

  • 40分の1のサイズに!

  • メリットはファイルサイズ軽量化。

    • ジオメトリの位置を相対値に変換。
    • 境界線を二重に持たない。

image


内部構造

  • GeoJSONの内部構造

image

  • TopoJSONの内部構造

image

image

topojson.feature(topology, object).features

/*
topology...読み込んだtopojsonファイルのデータ全体を指定(今回の例では、_topojson)
object...読み込んだ地形データからGeometryCollectionへのパスを指定(今回の例では、_topojson.objects.hokkaido。最後のhokkaidoにあたる部分は、読み込むtopojsonファイルごとに違うと思ったほうがいい)

d3.jsでは、配列になっているGeometryCollectionを、自動的に順番に読み込むので、それを描画する。

_topojson.objects.hokkaido.geometries
[i].propertiesに、属性データが収納されているので、IDとして付与する、地図を着色するなど、適宜活用する。
*/

TopoJSONファイルの作り方

ogr2ogr -f geoJSON (出力ファイル名) (入力ファイル名)
  • GeoJSONからTopoJSONへ変換
topojson -o (出力ファイル名) (入力ファイル名) -p

便利ツール

  • ブラウザ上でGeoJSONを編集 http://geojson.io/
  • JavaScriptソースコードをきれいに整形...beauitfy
  • JavaScriptソースコードをミニマムに整形... uglify ブラウザ上で動くツールもあるが( http://jscompress.com/ http://jsbeautifier.org/ )ファイルサイズが大きすぎると動作しないので、エディタアプリでの作業がいいかも。
78
73
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
78
73