内容が古くなっている可能性がありますのでご注意下さい。
前回から少し間が空いてしまいましたが、引き続きD3.jsを使用して横浜市の統計データをビジュアライズします。
今回の題材は、横浜市の緑被率です。
まずは、横浜市統計ポータルサイトで提供されている、行政区別緑被率のExcelファイルから、以下のCSVファイルを作成します。
ward,1975,1982,1987,1992,1997,2001,2004,2009 鶴見区,20.9,18,17,15.5,15.3,14.8,14.7,13.7 神奈川区,27.4,26.2,25.9,24.3,23,24.1,23.5,22.6 西区,11.7,11.6,11.2,10.9,11.4,12.3,13.1,11.2 中区,19.6,16.6,17.1,15.8,15.2,14.8,15.2,14.3 南区,34.4,23.9,20.4,17.8,17.2,15.6,16,15.4 港南区,31.9,28.4,24.8,23.3,21.3,22.4,23,22.9 保土ケ谷区,40.2,36.9,35.3,33.8,32.5,32.5,32.2,31.1 旭区,43.9,42,40.3,38.3,36.1,37.8,37.1,36 磯子区,39.2,33.6,29.6,28.2,27.7,26.4,27.8,27.6 金沢区,50.2,38.8,37.4,33.2,33.7,31.5,31.8,31.8 港北区,49.6,42.6,34.2,35.3,31.8,28.2,27.8,26.5 緑区,58.2,50.9,41.5,52.2,50.2,44.6,44.3,42.8 青葉区,58.2,50.9,41.5,38.7,37.8,34.5,34,31.4 都筑区,49.6,42.6,34.2,34.7,38.1,38.1,36.1,33.6 戸塚区,50.9,47.7,45,42.2,40.4,38.5,39,37.8 栄区,44,47.4,43.3,41.6,40.7,41.7,42.1,41.8 泉区,61.8,52.6,50.7,45.9,44.3,41.9,41.1,39 瀬谷区,45.8,42.9,40.3,38.4,35.8,36.6,35.9,35.1
行と列を入れ替え、年度を元号から西暦に変更しました。また、1987年までは青葉区と都筑区の値が入っていませんので、青葉区には緑区の値を、都筑区には港北区の値を入れました。
次に、D3.jsで横浜市の地図を描くためのGeoJSONファイルを作成します。元となるSHAPE形式のファイルは、国土交通省の行政区域データのページで入手します。
ダウンロードしたN03-130401_14_GML.zipファイルを解凍し、N03-13_14_130401.shpファイルをogr2ogrコマンドでGeoJSON形式に変換します。
ogr2ogr -f GeoJSON -where N03_003=\"横浜市\" yokohama.json N03-13_14_130401.shp
元となるSHAPE形式のファイルには、横浜市以外も含まれているため、「-where N03_003=\”横浜市\”」を付けて横浜市のみ抽出しています。
ogr2ogr2コマンドについては、本連載の第1回を参照して下さい。
以上でデータは揃いました。
HTMLファイルの完全なリストは以下のとおりです。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Yokohama Data Visualization</title> <link rel="stylesheet" href="bootstrap.min.css"> <script type="text/javascript" src="d3.js"></script> </head> <body style="padding: 10px"> <div class="btn-group"></div><br /> <svg width="550px" height="550px"></svg> <script type="text/javascript"> var jsonData; var legend = [70,60,50,40,30,20,10]; var svg = d3.select("svg"); svg.selectAll("rect") .data(legend) .enter() .append("rect") .attr("x", "500px") .attr("y", function(d,i) { return ((15*i)+50) + "px"; }) .attr("width", "50px") .attr("height", "15px") .attr("fill", "limegreen") .attr("opacity", function(d, i) { return d/100 + 0.2; }); svg.selectAll("text") .data(legend) .enter() .append("text") .attr("x", "510px") .attr("y", function(d,i) { return ((15 * i)+63) + "px"; }) .text(function(d) { return d + "%"; }); var projection = d3.geo.equirectangular() .center([139.73, 35.48]) .scale([90000]); var path = d3.geo.path().projection(projection); d3.csv("yokohama02.csv", function(data) { var years = []; for(var year in data[0]) { if(year != "ward") { years.push(year); } } var div = d3.select("div"); var button = div.selectAll("button") .data(years) .enter() .append("button") .attr("class", "btn btn-default") .attr("onClick", function(d) { return "onClick=changeYear(" + d + ")"; }) .text( function(d) { return d; }); }); d3.csv("yokohama02.csv", function(data) { d3.json("yokohama.json", function(json) { for(var i=0; i<data.length; i++) { for(var j=0; j<json.features.length; j++) { if( data[i].ward == json.features[j].properties.N03_004 ) { greens = data[i]; for(var year in greens) { if(year != "ward") { json.features[j].properties[year] = greens[year]; } } } } } svg.selectAll("path") .data(json.features) .enter() .append("path") .attr("d", path) .style("fill", "limegreen") .style("stroke", "gray") .style("stroke-width", "0.5px"); jsonData = json; }); }); function changeYear(y) { svg.selectAll("path") .data(jsonData.features) .style("opacity", function(feat) { return feat.properties[y]/100 + 0.2; }); } </script> </body> </html>
以下、主なところを解説します。まず、headでは
<link rel="stylesheet" href="bootstrap.min.css"> <script type="text/javascript" src="d3.js"></script>
BootstrapのCSSと、D3.jsのスクリプトを読み込んでおきます。
bodyの先頭では
<div class="btn-group"></div><br /> <svg width="550px" height="550px"></svg>
ボタンを表示するためのdivと、D3で描画するためのsvg要素をあらかじめ用意しておきます。
スクリプトでは、まずrect要素とtext要素で凡例を描きます。
var legend = [70,60,50,40,30,20,10]; var svg = d3.select("svg"); svg.selectAll("rect") .data(legend) .enter() .append("rect") .attr("x", "500px") .attr("y", function(d,i) { return ((15*i)+50) + "px"; }) .attr("width", "50px") .attr("height", "15px") .attr("fill", "limegreen") .attr("opacity", function(d, i) { return d/100 + 0.2; }); svg.selectAll("text") .data(legend) .enter() .append("text") .attr("x", "510px") .attr("y", function(d,i) { return ((15 * i)+63) + "px"; }) .text(function(d) { return d + "%"; });
配列legendとデータバインドし、rect要素を作成して緑色(LimeGreen)で塗りつぶします。緑被率の値を利用して不透明度(opacity)を指定します。opacityは0で完全に透明、1で完全に不透明です。色が薄すぎないよう、+0.2のゲタを履かせています。
さらに、rect要素に重なるようにtext要素を追加し、legendの値に%を付けて表示します。
次に、GeoJSONのデータを使用して地図を描くための準備として、
var projection = d3.geo.equirectangular() .center([139.73, 35.48]) .scale([90000]); var path = d3.geo.path().projection(projection);
投影法(projection)とパスジェネレータ(path)を定義します。
前準備の最後として、緑被率を表示する年度を選択するためのボタンを作成します。
d3.csv("yokohama02.csv", function(data) { var years = []; for(var year in data[0]) { if(year != "ward") { years.push(year); } } var div = d3.select("div"); var button = div.selectAll("button") .data(years) .enter() .append("button") .attr("class", "btn btn-default") .attr("onClick", function(d) { return "onClick=changeYear(" + d + ")"; }) .text( function(d) { return d; }); });
はじめにCSVファイルを読み込み、for…inループで反復してプロパティ名を取得し、
{ward:"鶴見区",1975:"20.9",1982:"18",1987:"17",1992:"15.5",1997:"15.3",2001:"14.8",2004:"14.7",2009:"13.7"}
というオブジェクトから、
[1975,1982,1987,1992,1997,2001,2004,2009]
という年度の配列が得られます。
button要素と年度の配列をバインドしてボタンを作成します。ボタンのonClickでは、年度を引数としてchangeYear()関数を実行するようにします。
さて、いよいよ横浜市の地図の描画です。CSVファイルとGeoJSONのファイルを読み込み、
d3.csv("yokohama02.csv", function(data) { d3.json("yokohama.json", function(json) { for(var i=0; i<data.length; i++) { for(var j=0; j<json.features.length; j++) { if( data[i].ward == json.features[j].properties.N03_004 ) { greens = data[i]; for(var year in greens) { if(year != "ward") { json.features[j].properties[year] = greens[year]; } } } } }
JSONのデータに緑被率のデータを追加します。
svg.selectAll("path") .data(json.features) .enter() .append("path") .attr("d", path) .style("fill", "limegreen") .style("stroke", "gray") .style("stroke-width", "0.5px"); jsonData = json;
path要素を追加して地図を描画し、緑被率を追加したJSONデータをグローバル変数に格納しておきます。この時点では不透明度を設定していないため、均一な緑色で塗りつぶされています。
最後に、ボタンのonClinkイベントのハンドラです。
function changeYear(y) { svg.selectAll("path") .data(jsonData.features) .style("opacity", function(feat) { return feat.properties[y]/100 + 0.2; }); }
すでに作成してあるpath要素と、グローバル変数に格納しておいたJSONデータをバインドし、引数で渡された年度の緑被率を不透明度に設定します。ここでも、+0.2のゲタを履かせています。
実行結果は以下のようになります。
2009年を選択した例です。緑区と栄区に比較的緑が多く残されていることが分かります。
こちらのHTMLファイルでは、緑被率を表示する年度をボタンで選択することができます。