はじめに
「都道府県ごとの件数」を地図に色で塗る、いわゆる**コロプレス図(choropleth map)**を作る機会は多いのですが、いざ実データでやると 1か所だけ値が突出していて、残りが全部同じ色に潰れる という壁にぶつかります。
この記事では、全国のエンジニア求人 約14.3万件を都道府県別に集計したデータを題材に、D3.js + TopoJSON で日本地図のヒートマップを作ります。そのうえで「一極集中したデータを、対数カラースケールでちゃんと読める地図にする」ところまでをコード付きで解説します。
題材データは求人検索エンジン「Jobmore」の都道府県別ページの掲載件数を集計したものです(2026年6月時点)。
完成イメージ
- 47都道府県を「エンジニア求人の件数」で色分け
- マウスオーバーで件数をツールチップ表示
- 線形スケール版と対数スケール版を比較
題材データ:都道府県別エンジニア求人数
まず集計結果から。47都道府県の合計は 142,789件。上位・下位はこうなっています。
| 順位 | 都道府県 | 件数 | 全体比 |
|---|---|---|---|
| 1 | 東京都 | 55,033 | 38.5% |
| 2 | 神奈川県 | 12,148 | 8.5% |
| 3 | 大阪府 | 11,982 | 8.4% |
| 4 | 愛知県 | 8,733 | 6.1% |
| 5 | 埼玉県 | 6,223 | 4.4% |
| … | … | … | … |
| 45 | 長崎県 | 169 | 0.1% |
| 46 | 佐賀県 | 106 | 0.1% |
| 47 | 高知県 | 53 | 0.04% |
ここで効いてくるのが「分布の形」です。
- 平均:3,038件/県
- 中央値:974件(岐阜県)
- 最大/最小:約1,038倍(東京 55,033 ÷ 高知 53)
平均が中央値の3倍以上ある、典型的な**右裾の長い分布(right-skewed)**です。東京都だけで全国の約4割を占めるため、平均値が東京に強く引っ張られています。
このデータをそのまま「件数 → 色の濃さ」に線形で割り当てると、東京だけが濃く塗られ、残り46都道府県はほぼ同じ薄さになってしまいます。後半でこれを対数スケールで解決します。
データはコード内で使うので、JSオブジェクトにしておきます。
// 都道府県名 → エンジニア求人数
const jobData = {
"北海道": 2902, "青森県": 525, "岩手県": 702, "宮城県": 2731, "秋田県": 486,
"山形県": 614, "福島県": 976, "茨城県": 1542, "栃木県": 1336, "群馬県": 1306,
"埼玉県": 6223, "千葉県": 4938, "東京都": 55033, "神奈川県": 12148, "新潟県": 1228,
"富山県": 786, "石川県": 1039, "福井県": 494, "山梨県": 804, "長野県": 1391,
"岐阜県": 974, "静岡県": 2224, "愛知県": 8733, "三重県": 1087, "滋賀県": 958,
"京都府": 2770, "大阪府": 11982, "兵庫県": 3379, "奈良県": 743, "和歌山県": 513,
"鳥取県": 356, "島根県": 425, "岡山県": 1034, "広島県": 2103, "山口県": 709,
"徳島県": 405, "香川県": 795, "愛媛県": 261, "高知県": 53, "福岡県": 3500,
"佐賀県": 106, "長崎県": 169, "熊本県": 601, "大分県": 189, "宮崎県": 175,
"鹿児島県": 312, "沖縄県": 1029
};
使うもの
-
D3.js v7(
d3-geo,d3-scale,d3-scale-chromaticを含むフルバンドル) -
TopoJSON:日本地図のジオメトリ。ここでは dataofjapan/land の
japan.topojsonを使います(47都道府県のポリゴンが入っている軽量データ) - topojson-client:TopoJSON → GeoJSON 変換用
CDNで読み込む最小構成にします。
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3"></script>
<svg id="map" width="600" height="600"></svg>
<div id="tooltip" style="position:absolute;pointer-events:none;opacity:0;
background:#333;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px;"></div>
ステップ1:地図データを読み込んでプロパティを確認する
TopoJSON は中身のプロパティ名がデータソースによって違います。最初に必ず1つfeatureを console.log して、都道府県名がどのキーに入っているか確認するのがコツです。
const topo = await d3.json("https://raw.githubusercontent.com/dataofjapan/land/master/japan.topojson");
// topo.objects のキー名を確認(このデータでは "japan")
console.log(Object.keys(topo.objects)); // ["japan"]
const geo = topojson.feature(topo, topo.objects.japan);
// 1件目のプロパティを確認 → 都道府県名がどのキーかを特定する
console.log(geo.features[0].properties);
// → { id: 1, nam: "Hokkai Do", nam_ja: "北海道", ... }
このデータでは日本語名が properties.nam_ja に入っています。以降はこれをキーに jobData と突き合わせます。
プロパティ名が違うデータを使う場合(
name、N03_001など)も、このconsole.logで確認して読み替えてください。
ステップ2:投影法とパスを設定する
日本地図には geoMercator(メルカトル)で十分です。fitSize を使うと、SVGのサイズにジオメトリを自動でフィットしてくれるので、緯度経度の手計算が不要になります。
const width = 600, height = 600;
const svg = d3.select("#map");
const projection = d3.geoMercator().fitSize([width, height], geo);
const path = d3.geoPath(projection);
ステップ3:まずは「線形スケール」で塗ってみる(失敗例)
ここが本題です。素直に scaleSequential を線形で作ると、どうなるか見てみます。
const maxJobs = d3.max(Object.values(jobData)); // 55033(東京)
const colorLinear = d3.scaleSequential()
.domain([0, maxJobs])
.interpolator(d3.interpolateBlues);
svg.selectAll("path")
.data(geo.features)
.join("path")
.attr("d", path)
.attr("fill", d => colorLinear(jobData[d.properties.nam_ja] ?? 0))
.attr("stroke", "#fff")
.attr("stroke-width", 0.5);
結果:東京だけが濃紺で、神奈川(12,148件)ですら薄い水色、それ以外の40県以上はほぼ真っ白で見分けがつきません。
理由は単純で、東京の 55,033 に対して中央値が 974。スケール上では中央値の県は 974 / 55033 ≒ 0.018、つまり色の濃さ2%弱にしかマッピングされません。これでは「東京が多い」以外の情報がゼロです。
ステップ4:対数カラースケールで描き直す(解決)
こういう右裾の長いデータは、**対数スケール(scaleSequentialLog)**を使うと各県の差が見えるようになります。対数は0を扱えないので domain の下限は最小値(か1)にします。
const values = Object.values(jobData);
const colorLog = d3.scaleSequentialLog()
.domain([d3.min(values), d3.max(values)]) // [53, 55033]
.interpolator(d3.interpolateYlOrRd);
svg.selectAll("path")
.data(geo.features)
.join("path")
.attr("d", path)
.attr("fill", d => {
const v = jobData[d.properties.nam_ja];
return v ? colorLog(v) : "#eee"; // データなしはグレー
})
.attr("stroke", "#fff")
.attr("stroke-width", 0.5);
これで、東京(最濃)→ 大阪・愛知・神奈川(濃いめ)→ 地方中核県(中間)→ 過疎県(淡色)と、全国のグラデーションが読める地図になります。
対数にすると「件数が10倍違うと色が1段階濃くなる」という見え方になるので、55,033 / 53 ≒ 1,000倍 ≒ 3桁 の差が、3段階ぶんの色差としてきれいに表現されます。
ステップ5:ツールチップを足す
最後に件数を表示するツールチップを付けて仕上げます。
const tooltip = d3.select("#tooltip");
svg.selectAll("path")
.on("mousemove", (event, d) => {
const name = d.properties.nam_ja;
const v = jobData[name];
tooltip
.style("opacity", 1)
.style("left", (event.pageX + 12) + "px")
.style("top", (event.pageY + 12) + "px")
.html(`<b>${name}</b><br>${v ? v.toLocaleString() : "-"} 件`);
})
.on("mouseleave", () => tooltip.style("opacity", 0));
これで「色で全体傾向、ホバーで正確な件数」が両立した地図の完成です。
データから分かること
可視化すると、数字の羅列では見えなかった構造が一目で分かります。
- 東京一極集中:東京都だけで全国の 38.5%。上位5都府県(東京・神奈川・大阪・愛知・埼玉)で 65.9%、首都圏1都3県だけで 54.9% を占めます。
- 第2極は関西・中京:大阪・愛知が1万件前後で続き、三大都市圏で大半が説明できます。
- 地方中核都市の存在感:一方で、福岡(九州)・宮城(東北)・北海道・広島(中国)など、各地方ブロックの中核県は数千件規模を確保しています。たとえば九州のIT集積が進む福岡では、福岡県のエンジニア求人が3,500件にのぼり、地方でも都市単位では一定の母数があることが分かります。
「平均3,038件」だけ見ると地方にもそれなりに求人がありそうに見えますが、中央値は974件。平均は東京に引っ張られた値であり、可視化と合わせて見ると実態(都市圏集中)が正しく掴めます。集計や平均値の扱いでも、分布の形を意識することの大切さが分かる例だと思います。
まとめ
- D3.js + TopoJSON で日本地図のコロプレス図は数十行で作れる
- TopoJSONは最初にプロパティを
console.logで確認してから結合キーを決める -
一極集中したデータは線形カラースケールだと潰れる。
scaleSequentialLog(対数)を使うと各地域の差が読めるようになる - 地図にする前に「平均と中央値の乖離」を見ておくと、スケール選択の判断がつけやすい
件数系のデータを地図にする機会があれば、ぜひカラースケールの線形/対数を切り替えて比べてみてください。