#!/usr/bin/env python # coding: utf-8 # ## Chapter9 # D3.js in Actionの9章の勉強ノートです。 # # 9章では、グラフと表を同時に表示したダッシュボックスの作成方法を説明しています。 # # ここでは、高岡市の地図と高岡市が公開しているオープンデータ(人口分布)を使って高岡市のダッシュボックスを作成します。 # # In[1]: get_ipython().run_line_magic('load_ext', 'sage') from IPython.core.display import HTML from string import Template import json import nvd3 nvd3.ipynb.initialize_javascript(use_remote=True) # ### 高岡市のオープンデータを取り込む # # In[2]: # python用のパッケージ import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns get_ipython().run_line_magic('matplotlib', 'inline') # pandasでExcelのデータを扱えるようにするために、xlsxWriter xlrdをインストールします。また、python-nvd3も合わせてインストールします。 # #
# $ sudo sage -sh
# (sage-sh) $ pip install xlsxWriter xlrd
# 
# # In[3]: # 高岡市のオープンデータから平成27年7月の人口データ(h270731.xlsx)を取り込みます # 古いデータが公開リストから削除されるため、ダウンロードしたh290531.xlsxを使用するように修正しました。 # d = pd.read_excel('http://www.city.takaoka.toyama.jp/joho/shise/opendata/documents/h290531.xlsx', header=3, index_col=1) d = pd.read_excel('data/h290531.xlsx', header=3, index_col=1) d.head() # 地区コードと地区名はセル結合していたり、頁が全てNaNになっているので、少し加工します。 # In[4]: # カラム名を付け直し、最初の5個を表示 d.columns = ['not_used', 'code', 'region', 'male', 'female', 'population', 'household'] # 最初の番号(index)が1より大きなcode, population, householdを抽出します。 d1 = d[d.index > 1][['code', 'male', 'female', 'population', 'household']] d1.head() # 地区ごとの男性、女性、世帯数がどのように分布しているか、可視化してみましょう。 # In[5]: # 男性、女性、人口、世帯数を取り出し、ペアプロットを表示 # N:地区のindexの最大数(合計やその他のデータを除くため) N = 35 d2 = d[d.index > 1][['male', 'female', 'population', 'household']] d2 = d2[d2.index <=N] sns.pairplot(d2, size=2) # ### データからスプレッドシートを作る # # In[6]: get_ipython().run_cell_magic('writefile', 'css/spreadsheet.css', 'div.table {\n position:relative;\n}\ndiv.data {\n position: absolute;\n width: 90px;\n padding: 0 5px;\n}\n\ndiv.head {\n position: absolute;\n}\n\ndiv.datarow {\n position: relative;\n width: 100%;\n border-top: 2px black solid;\n background: white;\n height: 25px;\n overflow: hidden;\n}\n\n.svgMap {\n float: left; \n background: #fcfcfc;\n}\n\n.svgChart {\n display: inline-block;\n background: #fcfcfc;\n}\n\n.spreadSheet {\n}\n') # In[7]: # データをCSVにして、D3が処理しやすいようにする d1 = d[d.index > 1][['code', 'male', 'female', 'population', 'household']] d2 = d1[d1.index <=N] # 小学校区をマージ schoolDistrictDf = pd.read_excel("data/schoolDistrict.xls") d3 = pd.merge(d2, schoolDistrictDf, on="code", how="left") # CSVで保存 d3.to_csv("data/d3.csv", index=False) d3.head() # In[8]: get_ipython().run_cell_magic('HTML', '', '\n\n\n\n\n\n
\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n') # ### スプレッドシートの表示 # スプレッドシート(表)を表示します。 # In[9]: get_ipython().run_cell_magic('javascript', '', 'd3.csv("data/d3.csv",function(error,data) { createSpreadsheet(data, "#spreadsheet")});\n \n function createSpreadsheet(incData, target) {\n \n var keyValues = d3.keys(incData[0])\n \n d3.select(target)\n .append("div")\n .attr("class", "table")\n\n d3.select(target).select("div.table")\n .append("div")\n .attr("class", "head")\n .selectAll("div.data")\n .data(keyValues)\n .enter()\n .append("div")\n .attr("class", "data")\n .html(function (d) {return d})\n .style("left", function(d,i) {return (i * 100) + "px"});\n\n d3.select(target).select("div.table")\n .selectAll("div.datarow")\n .data(incData)\n .enter()\n .append("div")\n .attr("class", "datarow")\n .style("top", function(d,i) {return "18px"});\n \n d3.select(target).selectAll("div.datarow")\n .selectAll("div.data")\n .data(function(d) {return d3.entries(d)})\n .enter()\n .append("div")\n .attr("class", "data")\n .html(function (d) {return d.value})\n .style("left", function(d,i,j) {return (i * 100) + "px"});\n }\n') # ### 小学校区の表示 # 次に小学校区を表示します。 # In[10]: get_ipython().run_cell_magic('javascript', '', 'var width = 450, height = 350;\nvar takaokaBoundingBox = {geometry: {coordinates: [[[136.904778, 36.822525], [137.072662, 36.822525], [137.072662, 36.658552], [136.904778, 36.658552], [136.904778, 36.822525]]], type: "Polygon"}, id: 999999, properties:{}, type: "Feature"};\n\nd3.select("#map").select("svg").attr("style", "width:450px;height:350px;border:1px lightgray solid;");\nd3.select("#map").select("svg").append("g").attr("id", "tiles");\nd3.select("#map").select("svg").append("g").attr("id", "vectors");\n\nvar tile = d3.geo.tile()\n .size([width, height]);\n\nvar projection = d3.geo.mercator()\n .scale((1 << 19) / 2 / Math.PI)\n .translate([width / 2, height / 2]);\n\nvar center = projection([137.025970, 36.754062]);\n\nvar path = d3.geo.path()\n .projection(projection);\nvar zoom = d3.behavior.zoom()\n .scale(projection.scale() * 2 * Math.PI)\n .translate([width - center[0], height - center[1]])\n .on("zoom", redraw);\nd3.select("#map").select("svg").call(zoom);\nprojection\n .scale(1 / 2 / Math.PI)\n .translate([0, 0]);\n\nvar geoPath = d3.geo.path().projection(projection);\n\nd3.json(\'data/A27-10_16-g_SchoolDistrict.json\', function (schoolDistnct) {\n var data = schoolDistnct.features.filter(function(d) { \n return d.properties.A27_006=="高岡市立"; \n });\n\n d3.select("#map").select("#vectors").selectAll("path.countries").data(data)\n .enter()\n .append("path")\n .attr("d", geoPath)\n .attr("class", "countries")\n .style("fill", "red")\n .style("stroke-width", 3)\n .style("stroke", "black")\n .style("fill-opacity", .25) \n \n}); \n\nredraw();\n\nfunction redraw() {\n var tiles = tile\n .scale(zoom.scale())\n .translate(zoom.translate())();\n \n var image = d3.select("#map").select("#tiles")\n .attr("transform",\n "scale(" + tiles.scale + ") translate(" + tiles.translate + ")")\n .selectAll("image")\n .data(tiles, function(d) { return d; });\n \n image.exit()\n .remove();\n\n image.enter().append("image")\n .attr("xlink:href",\n function(d) { return "http://" \n + ["a", "b", "c"][Math.random() * 3 | 0] \n + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] \n + ".png"; }) \n .attr("width", 1)\n .attr("height", 1)\n .attr("x", function(d) { return d[0]; })\n .attr("y", function(d) { return d[1]; });\n \n projection\n .scale(zoom.scale() / 2 / Math.PI)\n .translate(zoom.translate());\n\n d3.select("#map").selectAll("path")\n .attr("d", geoPath);\n}\n') # ### 円グラフの表示 # 次に各地域ごとの人口を円グラフで表示します。 # In[11]: get_ipython().run_cell_magic('javascript', '', 'var width = 350,\n height = 350,\n radius = Math.min(width, height) / 2;\nvar maxValue = 17744;\nd3.select("#chart").select("svg").attr("style", "width:350px; height:350px;border:1px lightgray solid;");\n\nvar color = d3.scale.linear()\n .interpolate(d3.interpolateHcl)\n .domain([0,maxValue]).range(["yellow", "red"]);\n\nvar arc = d3.svg.arc()\n .outerRadius(radius - 10)\n .innerRadius(0);\n\nvar labelArc = d3.svg.arc()\n .outerRadius(radius - 40)\n .innerRadius(radius - 40);\n\nvar pie = d3.layout.pie()\n .value(function(d) { d.selected = false; return d.population; });\n\nvar svg = d3.select("#chart").select("svg")\n .attr("width", width)\n .attr("height", height)\n .append("g")\n .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");\n\nd3.csv("data/d3.csv", type, function(error, data) {\n var g = svg.selectAll(".arc")\n .data(pie(data))\n .enter().append("g")\n .attr("class", "arc");\n\n g.append("path")\n .attr("d", arc)\n .style("stroke", "lightgray")\n .style("fill", function(d) { return color(d.data.population); });\n\n});\n\nfunction type(d) {\n d.population = +d.population;\n return d;\n}\n') # ### 地図にマウスイベントを追加 # 地図にマウスオーバイベントを追加して、マウスの位置の小学校区と表の該当する行を青色に変えて表示します。 # また、スプレッドシートが大きく画面では全て表示できないので、mouse-overイベントでその小学校区の情報を表示します。 # In[12]: get_ipython().run_cell_magic('javascript', '', 'd3.select("#vectors").selectAll("path.countries")\n .on("mouseover", hover)\n .on("mouseout", mouseOut);\n\nvar explode = function(x,index) {\n var offset = 10;\n var angle = (x.startAngle + x.endAngle) / 2;\n var xOff = Math.sin(angle)*offset;\n var yOff = -Math.cos(angle)*offset;\n return "translate("+xOff+","+yOff+")";\n};\n\nvar inplode = function(x,index) {\n var offset = -0;\n var angle = (x.startAngle + x.endAngle) / 2;\n var xOff = Math.sin(angle)*offset;\n var yOff = -Math.cos(angle)*offset;\n return "translate("+xOff+","+yOff+")";\n};\n\nfunction hover(hoverD) {\n d3.select("#chart").selectAll("path")\n .filter( function (d) { d.data.selected = d.data.schoolDistrict == hoverD.properties.A27_007; return d.data.selected; })\n .transition()\n .duration(500)\n .attr("transform", explode);\n d3.selectAll("div.datarow")\n .filter( function (d) { return d.schoolDistrict == hoverD.properties.A27_007})\n .style("background", "#94B8FF")\n .style("display", "block");\n d3.select("#vectors").selectAll("path.countries")\n .filter( function (d) { return d == hoverD; })\n .style("fill", "blue");\n};\n\nfunction mouseOut() {\n d3.select("#chart").selectAll("path")\n .filter( function (d) { return d.data.selected; })\n .transition()\n .duration(500)\n .attr("transform", inplode);\n d3.selectAll("div.datarow").style("display", "none");\n d3.select("#vectors").selectAll("path.countries").style("fill", "red");\n};\n') # In[ ]: