Work1: コロプレス図について
はじめに
皆さんはどんなチャートが好きですか?
僕はコロプレス図が一番好きです
というわけで作ってみたのがこちら
/works/japan-choropleth/
この記事ではどんな風に作ったか設計面を解説したいと思います
技術スタック
描画: D3.js(d3-geo / d3-scale / d3-scale-chromatic)
地図の描画はD3.jsを使いました
使っているモジュールは3つです
- d3-geo: メルカトル投影(
geoMercator)と SVG パス生成(geoPath) - d3-scale: 値から色への変換スケール(
scaleSqrt,scaleLinear) - d3-scale-chromatic: 配色(
interpolateYlGnBuなど 8 種)
地図というだけならleafletやMapLibreが定番ですが、今回はズームやパンをしないのでデザインも整えやすいD3にしています。google maps的な地図を見せる場合には地図タイルのハンドリングやズームやパン機能が必要になるのでleafletやMapLibreを選ぶと思いますが今回は動かさないですからD3.js一択だと思います
D3.jsはデータを展開していくような書き方になるのも好きですし、コード量自体はちょっと増えますがコードの構造はシンプル 、フロントエンドでの扱いやすさが優れていると思うんですよね。よく習熟難度が高いなんて言われるようですが、フロントエンドを普通に書けるならむしろ他のライブラリより習熟は早いと思います
地理データ: TopoJSON + topojson-client
今回のコロプレス図は県の範囲をsvg pathでポリゴン描画していますが、この座標情報は自前で持ってくる必要があります。これはd3-geo, leaflet, MapLibreのどれでも同じです
地図を扱う時にややこしいのが緯度経度が距離とそのまま対応しないことですが、地図系ライブラリにGeoJSONとして放り込んでやればメルカトル図法で適切に変換して使ってくれます。ただし、GeoJSONは文字列で書いていくので結構なデータ容量になってしまいますから、容量を小さくするためにTopoJSONで転送し、topojson-clientでGeoJSONに変換してd3-geoに渡す、という処理にしています
TopoJSONの仕様を読んでもらうと分かるんですがかなりの涙ぐましいデータ節約をやっていて、量子化しつつ前の点との差で書くことで文字数を減らしています。そこまでするぐらいなら普通にバイナリにした方が容量が小さくなるんじゃないかと思って計算してみたらfloat64でやる方が容量は大きくなっちゃうんですよね。計算時間としても現代のPCなら一瞬だし、JavaScriptに渡すときには文字列にしないといけないので結局TopoJSONが一番いい選択になるようです
今回データ元は定番のdataofjapan/landを利用しています。国土地理院の地理情報をデータ化した無料で利用できるリポジトリで、営利利用の場合は著作権者への利用報告義務があるようですが、非営利利用なら出典明記するだけで無料で使えます。リポジトリにTopoJSONも置いてあるのでこれをそのまま利用しました。ブラウザ側では topojson-client の feature() 関数で TopoJSON を GeoJSON に変換し、d3-geo に渡しています。変換コストはほぼゼロで、初回レンダリングへの影響もありません。昔、国土地理院のデータをそのままGeoJSONにしたことがあるんですが、こちらのリポジトリのデータよりはるかに大きな容量になってしまったのを思い出します。測量データとしては非常に精密なものがあるのですが、コロプレス図には過剰品質なので適当にはしょったデータが必要になるわけです
UI フレームワーク: React + Astro Islands
このサイトはSSGブログがメインなのでAstroで作成していますが、コロプレス図コンポーネントはCSRでそこそこリッチな書き方になるので、Reactで実装してIslands Architectureで埋め込む形で実装しています。AstroというとSSGというイメージですが、Islandで入れればReactもVueも普通に組み込めるのが嬉しいところです
<ChoroplethMap client:visible />
このコンポーネントはD3や統計JSONデータによりバンドルサイズが大きい為に表示遅延が発生しますが、アイランドにしておくとAstroがコンポーネント部分だけハイドレーションしてくれるので、画面全体が表示遅延するのを防いでくれます。ついでにclient:visible ディレクティブにより、地図がビューポートに入るまで JavaScript のロード・ハイドレーションが遅延するようにしていますが、現状だと最初からコンポーネントが画面内にあるのであまり関係ないですが、これを付けたせいで遅くなることもないし一応付けてあります
Reactを選んだ理由はReactが好きだからですが、今回で言うとホバー時のランキングの同期やテーマ切替のような状態管理を宣言的に書けるのが選定理由として挙げられると思います
アーキテクチャ: ビルド前 ETL + 静的 JSON
データパイプラインの全体像
e-Stat API ──┐
├─→ Python ETL ─→ 統合 JSON ─→ Astro ビルド ─→ 静的サイト
内閣府 Excel ─┘ (etl_estat.py) (choropleth-themes.json)
データはe-Stat APIとウェブ上にある内閣府ExcelをPython ETLで処理しています
手動で事前に取得してJSONをリポジトリに書き出しておいて、Astroビルドする際にViteでインラインバンドルしています
配信時にCDNが効いて最速で届く筈です
なぜETLしているか
ブラウザから直接e-Stat APIを叩く方式でも書けますが、以下の理由で採用しませんでした
- API キーが露出する: e-Stat API には appId が必要で、クライアントサイドから叩くと公開されてしまう
- レスポンス形態のばらつき: 各統計調査でコード体系・単位・欠損の扱いが異なり、ブラウザ側で正規化すると処理が重い
- 応答パフォーマンス: 多数の統計値を取得するのでAPIコールがたくさん必要で、それをユーザーごとに実行するのはちょっとイマイチ
- 可用性: 外部APIの障害がサイト表示に直結しない
ETLはPythonでpandasとopenpyxlを使っています
機能的にも手軽さとしても一番良いと思います
統合 JSON の構造
ETLの出力は src/data/choropleth-themes.json 1ファイル28KBにまとめています
以下のような書式です
{
"prefecture_names": {"01": "北海道", "02": "青森", ...},
"themes": [
{
"key": "gdp",
"label": "県内総生産(GDP)",
"colorScheme": "GnBu",
"variants": [
{
"mode": "total",
"modeLabel": "県内総額",
"metric": "県内総生産(名目)",
"unit": "億円",
"valueFormat": "oku",
"scale": "sqrt",
"year": 2022,
"source": "内閣府「県民経済計算」...",
"source_url": "https://...",
"note": "GDPの都道府県版(生産側)。",
"values": {"01": {"v": 208892}, "02": {"v": 44391}, ...}
},
...
]
},
...
]
}
GDPなどのテーマ別に統計値がぶら下がる構造で、scaleはコロプレス図の色分けスケール用、source, source_urlをつけてデータ出典表示に使う為です
このJSONをフロントエンド側で TypeScript の型定義と合わせてimportして、テーマ切替やランキング表示に使っています
分母設計(total / percapita / perworker)
県内総生産や製造品出荷額のような総額は、人口が多い県が大きくなるのは当然なので、県民1人あたり、就業者1人あたりで割った値も表示できるようにしています。出典がそれぞれ少し違ったりするので論文に書くような数字にはなっていないですが、カジュアルに眺めて楽しむためのものという事で許してください
それで、やってもらうと分かると思いますが1人あたりにすると色の分布が全然違ってきます。製造業を例にとると総額では愛知県が圧倒的に濃いですが、就業者1人あたりに切り替えると大分県や山口県が上位に来るんですよね。何故かっていうと大分、山口には石油化学コンビナートや製鉄工場などの大規模設備を少人数で動かす設備があって、しかも他の産業が少ないので、県内就業者1人あたりにすると大きくなるわけです。大阪、兵庫、広島も大規模設備産業がありますが、中小製造業やサービス業も規模が大きいので、ここでやっているようなざっくり就業人口比でみると薄まってしまう
一方で、静岡、三重も人口あたりにすると色が濃くなり、東海一円に自動車などの工業集積がされていることも分かったりします
色スケールの使い分け
指標ごとに色スケールを変えています
| スケール | 対象 | 理由 |
|---|---|---|
| 平方根(sqrt) | 製造品出荷額、農業産出額、県内総生産(総額) | 分布の偏りが極端(最大と最小で数十〜百倍)。線形だと最大県だけが濃く、他が全部薄くなる |
| 線形(linear) | 最低賃金、失業率、1 人あたり指標 | 範囲が狭く、値の差を素直に見せられる |
平方根スケールは値の大きい側を圧縮して中位県の差も見えるようにするためのものです。対数(log)スケールも選択肢ですが、0の処理で困るのと圧縮が強すぎてイマイチなので平方根にしています
データソース
統計データの取得元
8テーマのデータは、大きく3系統の公的統計から取得しています
1. e-Stat API
e-Stat は日本の政府統計ポータルで、REST API でデータを取得できます。
| テーマ | 統計調査 | 年次 |
|---|---|---|
| 製造業(出荷額) | 経済センサス - 活動調査 | 2021 |
| 農業(産出額) | 生産農業所得統計 | 2020 |
| サービス業(売上) | 経済センサス - 活動調査(産業横断的集計) | 2021 |
| 最低賃金 | 社会・人口統計体系 | 2024 |
| 完全失業率 | 社会・人口統計体系(国勢調査ベース) | 2020 |
| 従業者 1 人あたり指標 | 社会・人口統計体系 | 各年 |
e-Stat API は無料で使えますが、appId(API キー)の登録が必要です。ETL スクリプトでは環境変数 ESTAT_APP_ID から読み込み、生成 JSON や公開バンドルには含めていません
2. 内閣府「県民経済計算」Excel
県内総生産(GDP の都道府県版)、県民所得、雇用者報酬(給与所得)は、内閣府が公開している Excel ファイルから取得しています
| テーマ | Excel ファイル |
|---|---|
| 県内総生産 | soukatu1.xlsx(生産側・名目) |
| 県民所得 | soukatu5.xlsx / soukatu7.xlsx(1 人あたり) |
| 雇用者報酬 | soukatu6.xlsx |
| 総人口 | soukatu9.xlsx |
| 就業者数 | soukatu10.xlsx |
これらは e-Stat API では提供されていない(または粒度が異なる)ため、直接 Excel をダウンロードして pandas で解析しています。Excel のフォーマットは年度によって微妙に異なるため、ETL スクリプトでは列位置をハードコードせず、年度の値を検索して動的に特定しています
3. 地理データ
都道府県の境界 TopoJSON は dataofjapan/land から取得しています。GitHubで公開されている日本の都道府県境界データとしては最も広く使われているもので、ライセンスも明確です
JIS コードによる統合
データソースが異なると、地域の識別コードも異なります
- e-Stat の一部: 5 桁の地域コード(先頭 2 桁が都道府県)
- 内閣府 Excel: 行番号が都道府県順だが明示的なコードなし
- TopoJSON: 元データの独自 id
ETL スクリプトでは、すべてを JIS 都道府県コード(01〜47) に正規化しています。TopoJSON 側も取得時に id を JIS コードに書き換えているため、フロント側では単純なキーマッチでデータと地図を結合できます。
年次の不一致について
各テーマのデータ年次は 2020〜2024 年とばらつきがあります。これは統計調査の実施サイクルが異なるためです(経済センサスは 5 年周期、県民経済計算は毎年、国勢調査は 5 年周期)。テーマ間の年次を揃えるために古いデータに合わせるよりも、各テーマで利用可能な最新データを使う方針にしています。年次は各バリアントの year フィールドに保持し、UI 上でも出典と合わせて表示しています。
まとめ
| 層 | 選定 | 判断理由 |
|---|---|---|
| 描画 | D3.js(d3-geo / scale / chromatic) | 色スケール・投影の細かい制御 |
| 地理データ | TopoJSON + topojson-client | サイズ圧縮、JIS コード正規化 |
| UI | React + Astro Islands(client:visible) | 状態管理の宣言性 + 遅延ハイドレーション |
| データ取得 | Python ETL(pandas / openpyxl) | API + Excel の統合処理 |
| データ形式 | 統合 JSON(テーマ → バリアント → 県別値) | ブラウザ側の処理を最小化 |
| データソース | e-Stat API + 内閣府 Excel + dataofjapan TopoJSON | 公的統計、API キー非公開 |
設計の軸は「ビルド前にデータの取得・加工を完結させ、ブラウザには描画だけをやらせる」ことです。公的統計のコード体系の違いや Excel フォーマットのばらつきを ETL で吸収し、フロント側には綺麗な JSON だけを渡す。この分離により、新しい指標の追加は JSON の構造に 1 テーマ分を足すだけで済みます。
お仕事募集しています
案件相談はX DM (@studio_haneya) まで