Edited at

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

More than 3 years have passed since last update.


講習



D3.jsの特徴


  • データに基づいて、DOM(Document Object Model)を操作する、という考え方

  • 各種チャート、グラフに必要な計算やデータ整形を賄ってくれる

  • 表示はユーザーが自分で頑張って描く→自由!

  • ウェブ標準のリソースのみを利用しているのでライブラリがなくなっても困らない

  • ブラウザ上で動く(これ大事!)

  • SVGかCanvasでいったらSVG( http://visualizing.jp/openvisconf2013-miguel/

  • 最近ヴァージョン4.0がでた


    • 各機能が小さいライブラリに分割された





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



どんなことができるのか



どんなことができるのか



どんなことができるのか



どんなことができるのか



どんなことができるのか



どんなことができるのか



どんなことができるのか



実習



今日使う環境


blockbuilder


  • http://blockbuilder.org/

  • ブラウザ上でコーディングするツール



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


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




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


  • START CODINGをクリック


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



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



ファイル構成



  • プログラムファイル


    • main.js




  • データファイル


    • japan.json...日本の地形ファイル(TopoJson形式)

    • population.tsv...人口データファイル(tsv形式)

    • capital.tsv...都道府県庁データファイル(tsv形式)




  • いじらなくていいファイル


    • index.html

    • main.css

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





操作の仕方


  • コーディング領域でファイルを修正すると、表示領域に即座に反映される

  • コーディング領域と表示領域、縦分割と横分割のどちらか選べる

  • 保存したいときは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;
})



凡例をつくる

<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作者による独自規格。


  • 40分の1のサイズに!



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


    • ジオメトリの位置を相対値に変換。

    • 境界線を二重に持たない。





内部構造


  • GeoJSONの内部構造


  • TopoJSONの内部構造

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/ )ファイルサイズが大きすぎると動作しないので、エディタアプリでの作業がいいかも。