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


## 講習


- - -

## D3.jsの特徴

- https://d3js.org/

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/dd91d269-97ec-41e8-40a5-b06637137786.png)

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

- - -

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

- 地形データを自分で用意する
    - Leatlet.js（OpenStreetMap）やMapboxとの連携もできる
- ユーザーインターフェイスも自分で用意する
- 描画の自由さ
- たくさんの地図投影法が用意されている

    - ライブラリ本体に含まれる分( https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Projections.md )

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/df378635-8b83-e7f4-b949-546dd54eec82.png)

- プラグインによる拡張( https://github.com/d3/d3-geo-projection/ )


- - -

## どんなことができるのか

- ベルセロナへ乗り入れている飛行機の路線
- http://codefor.tokyo/misc/kb/Airport/barcelona.html

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/01590932-f7be-291a-3d45-a977a4f66240.png)


- - -

## どんなことができるのか

- Results - London 2012 Olympics by The New York Times
- http://london2012.nytimes.com/results

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/bd3a688c-7289-d834-d572-373bc3596877.png)



- - -

## どんなことができるのか

- EU内の人と使用言語の移動 2011年（多分）
- http://www.nzz.ch/inland-sommerserie-schweizer-karten-interaktiv/binnenwanderung-in-der-schweiz-1.18128893

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/2df3cc51-b0db-1a68-65b2-83a526f2b5db.png)

- - -

## どんなことができるのか

- モンテレーの殺人の密度マップ(Leaflet.js + D3.js) 
- http://bl.ocks.org/diegovalle/5166482

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/e6147bfe-0693-9cb2-9d7d-e6ae1e73b472.png)

- - -

## どんなことができるのか

- 移民が送金したお金。ODAよりも移民送金額が高いことがわかる。
- http://www.tageswoche.ch/de/2013_19/international/540047/

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/def49bc1-cd9a-a4ce-47ca-48d3be6989b9.png)


- - -

## どんなことができるのか

- モノクロ画像から、カラースケールにもとづいて画像を生成（着色）
- http://bl.ocks.org/mbostock/3289530
![image](https://qiita-image-store.s3.amazonaws.com/0/1417/bbb0dd91-5f00-e54a-0a4f-a87de0a32073.png)

- 応用例
- http://bl.ocks.org/syntagmatic/002ef2fb5c1dc2df5821
![image](https://qiita-image-store.s3.amazonaws.com/0/1417/2e5beab5-17ea-2285-b79f-7c6f64d5d304.png)

- - -

## どんなことができるのか

- 離島をいい感じに矩形の中に収める
- http://geoexamples.com/d3-composite-projections/
![image](https://qiita-image-store.s3.amazonaws.com/0/1417/ad586cdc-0332-66ea-25d7-febae5964aff.png)

- 日本の場合…
- http://bl.ocks.org/rveciana/72987a139a9886ed6a87
![image](https://qiita-image-store.s3.amazonaws.com/0/1417/703b5810-7f8b-3fd1-1cd3-a2c58957a1f0.png)



- - -

## 実習



- - -

## 今日使う環境

#### blockbuilder

- http://blockbuilder.org/
- ブラウザ上でコーディングするツール
![image](https://qiita-image-store.s3.amazonaws.com/0/1417/d6115dc9-95f8-02eb-5f03-c8665e6329f0.png)

- 対象ブラウザ：Chrome もしくは Firefox
- 使ってなにが嬉しいか：サーバを立てなくて済む、Gistとの連動

- - -

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

- START CODINGをクリック

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/95134df8-34ad-7c1b-399a-2c53ce937659.png)

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

- - -

## 操作の仕方（すでにあるものを利用する場合）

- 下のURLの画面を表示する
- http://blockbuilder.org/n1n9-jp/e515f3df49957efc8693b746fdaf7bc8
- folkする

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/c80c44d2-bd97-bc8f-b008-3bcbf67eb2d2.png)


- - -

## ファイル構成

- プログラムファイル
    - main.js
 
- データファイル
    - japan.json...日本の地形ファイル（TopoJson形式）
    - population.tsv...人口データファイル（tsv形式）
    - capital.tsv...都道府県庁データファイル（tsv形式）

- いじらなくていいファイル
    - index.html
    - main.css
    - README.md...メモ書き用ファイル（MarkDown形式）

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/c96c77db-4420-318a-5c9c-0b5b8d77b2b9.png)


- - -

## 操作の仕方

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

- - -

## 今からやること

- 手順を一つづつみていきましょう。
- 知ってるよという方は、自由にいじってみてください。
- いじりすぎて動かなくなってしまったら、ブラウザの開発ツールを使ってデバッグするか、ソースコードをforkし直して位置からやり直すかしてみてください。
    - Chrome DevTools（ https://developers.google.com/web/tools/chrome-devtools/ ）
    - Firefox開発ツール( https://developer.mozilla.org/ja/docs/Tools )

- - -


## 表示領域の用意

```
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を発生させる
*/
```


- - -

## 外部ファイルの読み込み

- 複数ファイルの読み込みに挑戦。
- ネットにある他のサンプルは１ファイル読み込み想定のものが多い。今回はそれとは違うやり方。


```
<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"]);
```

- わかりづらいけど、覚えるしかない！
    - domain...データのinput
    - range...表現としてのoutput

- http://visualizing.jp/normalize-visualize/

- - -

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

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

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

- TopoJSONファイルの場合、data.objects.hokkaido.geometries
[i].propertiesに属性データが収納されているので、IDとして付与する、地図を着色するなど、適宜活用する。


```
.attr("id", function(d) {
    return d.properties.nam_ja;
})
```

- - -

## 凡例をつくる

- d3 SVG Legendというライブラリがある
- http://d3-legend.susielu.com/#color-linear-custom

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/9e7ed5e8-dfc7-2c95-f96d-c073d814a54b.png)


```
<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形式）
- コマンドラインツールでブラウザ上でみえる形式へ変換する
    - ShapefileからGeoJSON...GDAL http://www.gdal.org/
    - GeoJSONからTopoJSON...topojson https://github.com/mbostock/topojson
- 地形ファイルに含まれる属性データを整形・編集したかったり、別な属性データを埋め込みたい場合はQGIS( http://www.qgis.org/ja/site/ )というアプリケーションを利用する

- - -

## 地形ファイルの在り処

- 国土数値情報（日本限定、ライセンスに注意）
    - http://nlftp.mlit.go.jp/ksj/index.html

- Natural Earth（世界全体、ライセンスフリー。ただし日本国土の国境の扱いが日本政府の方針と異なるケースがある）
    -http://www.naturalearthdata.com/

- - -

## TopoJSONとは

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

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/b156fd6e-66bf-add7-a8c7-cbf06c5f04c9.png)

- 40分の1のサイズに！

- メリットはファイルサイズ軽量化。
    - ジオメトリの位置を相対値に変換。
    - 境界線を二重に持たない。

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/c64dbca4-506b-c95a-64c0-c0d15030451f.png)



- - -

# 内部構造

- GeoJSONの内部構造

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/443f0a47-a382-54e1-39a9-f7f6c9625591.png)


- TopoJSONの内部構造

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/20ce876e-d8df-060b-8f2e-26b172674030.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/1417/24f417af-b5d6-b0fd-f33c-4a208a9ac5a6.png)


```
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ファイルの作り方

- ShapefileはいったんGeoJSONに変換し、GeoJSONからTopoJSONへ変換する
- http://visualizing.jp/shapefile-to-topojson/

- ShapefileからGeoJSONへ変換

```
ogr2ogr -f geoJSON (出力ファイル名) (入力ファイル名)
```

- GeoJSONからTopoJSONへ変換

```
topojson -o (出力ファイル名) (入力ファイル名) -p
```


- - -

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