1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【D3.js v7】SVG日本地図にデータをバインディングしてクリッカブルな政策マップを作った

1
Posted at

ai-policy-tracker.jpg

この記事でできること

  • SVGパスにD3.jsでデータをバインドして色分け・クリックイベントを実装する
  • WordPress(SWELL)上でD3.js v7を<script src>なしで安全にロードする
  • フィルタ変更時に地図・テーブル・チャートを同時更新するデータフローを組む
  • スマホのtouchstartで「開いて即閉じ」バグを防ぐ方法を知る

完成形は日本自治体AI政策データベース(ai-japan-index.com)で動作確認できる。


環境・前提

  • D3.js: v7.9.0(CDN動的ロード)
  • ブラウザ: Chrome 133 / Safari 18 / Firefox 135 で動作確認
  • WordPress: 6.7.x + SWELLテーマ 2.11.x
  • デプロイ形式: <!-- wp:html --> ブロック(インラインHTML)
  • 外部ライブラリ: D3.jsのみ(他不使用)
  • Node.js/ビルドツール: 不使用(静的HTMLのみ)

完成形

47都道府県のクリッカブルSVGマップ、政策比較テーブル、D3.jsチャート3種を組み合わせたインタラクティブツール。都道府県をクリックすると政策詳細が表示され、地域/分野/ステータス/年のフィルタで絞り込みができる。

AI政策トラッカーの動作確認はこちら


Step 1: D3.jsをWordPressで安全にロードする

WordPressのコンテンツ内に <script src="CDN"> を書いても、SWELLテーマが無視してロードされない。動的ロードが必須。

NG(SWELLで動作しない):

<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>

OK(動的ロード):

function loadD3ThenInit() {
  // 既にロード済みの場合はスキップ
  if (typeof d3 !== "undefined") {
    initAll();
    return;
  }
  const s = document.createElement("script");
  s.src = "https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js";
  s.onload = function() { initAll(); };
  s.onerror = function() {
    document.getElementById("map-root").textContent =
      "グラフの読み込みに失敗しました。ページを再読み込みしてください。";
  };
  document.head.appendChild(s);
}
document.addEventListener("DOMContentLoaded", loadD3ThenInit);

typeof d3チェックで二重ロードを防ぎ、onerrorでフォールバックメッセージを表示する。この2点が実務上重要。


Step 2: SVGパスにデータをバインドして色分けする

都道府県パスにはJIS X 0401の2桁コード(01=北海道、13=東京都など)をdata-code属性で付与しておく。

<!-- japan-map.svg の各パス -->
<path class="prefecture-path" data-code="13" data-name="東京都" d="M..."/>
<path class="prefecture-path" data-code="27" data-name="大阪府" d="M..."/>

D3.jsでパス全体を一括更新:

const statusColorMap = {
  deployed: "#2d9e6b",  // 導入済み
  pilot:    "#f0a500",  // 実証中
  planned:  "#6b7fd4",  // 計画中
  unknown:  "#c8c8c8"   // 不明
};

function renderMap(filteredData) {
  // prefectureCodeをキーにしたMapで高速ルックアップ
  const statusByCode = new Map(
    filteredData.map(d => [d.prefectureCode, d.aiAdoptionStatus])
  );

  d3.selectAll(".prefecture-path")
    .attr("fill", function() {
      const code = this.getAttribute("data-code");
      return statusColorMap[statusByCode.get(code)] || statusColorMap.unknown;
    })
    .on("click", function(event) {
      event.stopPropagation();  // バブリング防止
      const code = this.getAttribute("data-code");
      showPrefectureDetail(code);
    });
}

d3.selectAllで47パスを一括処理するのがポイント。forEachループよりもD3のバインディングの方がDOM操作がバッチ化されて効率的。


Step 3: フィルタ→3ビュー同期更新

フィルタはAND条件で1関数に集約し、地図・テーブル・チャートを同時更新する。

重要: &&演算子はWordPressが&#038;&#038;に変換してJSを壊す。

NG(WordPressで動作しない):

// これはWordPressが &#038;&#038; に変換してSyntaxErrorになる
return regionOk && categoryOk && statusOk;

OK(三項演算子またはネストifで回避):

function applyFilters() {
  const filtered = policyData.localPolicies.filter(function(policy) {
    const regionOk   = !activeFilters.region
      ? true
      : getRegion(policy.prefectureCode) === activeFilters.region;
    const categoryOk = !activeFilters.category
      ? true
      : policy.category === activeFilters.category;
    const statusOk   = !activeFilters.status
      ? true
      : policy.aiAdoptionStatus === activeFilters.status;

    // ネストifでAND条件を表現(&&を使わない)
    if (!regionOk) return false;
    if (!categoryOk) return false;
    if (!statusOk) return false;
    return true;
  });

  // 3ビューを同時更新
  renderMap(filtered);
  renderTable(filtered);
  renderCharts(filtered);
}

フィルタボタンのクリック:

document.querySelectorAll(".filter-btn").forEach(function(btn) {
  btn.addEventListener("click", function() {
    const key = this.dataset.filterKey;
    const val = this.dataset.filterValue;
    // トグル(同じ値を再クリックで解除)
    activeFilters[key] = activeFilters[key] === val ? null : val;
    applyFilters();
  });
});

Step 4: スマホ向け下部固定パネル

スマホではツールチップではなく画面下部固定パネルで詳細を表示する。マウス追従型はスマホで画面外にはみ出す。

function showPrefectureDetail(code) {
  const prefecture = getPrefectureData(code);
  if (!prefecture) return;

  // スマホ判定(767px以下)
  const isMobile = window.innerWidth <= 767;

  if (isMobile) {
    // 下部固定パネルに表示
    const panel = document.getElementById("mobile-detail-panel");
    panel.innerHTML = buildDetailHTML(prefecture);
    panel.style.display = "block";
    // パネル外タップで閉じる
    document.addEventListener("touchstart", closePanelOnOutsideTap, { once: true });
  } else {
    // PCはツールチップ
    showTooltip(prefecture);
  }
}

function closePanelOnOutsideTap(e) {
  const panel = document.getElementById("mobile-detail-panel");
  if (!panel.contains(e.target)) {
    panel.style.display = "none";
  }
}

パネルのCSS:

#mobile-detail-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  background: #1a1a2e;
  border-top: 2px solid #ff8906;
  max-height: 40vh;
  overflow-y: auto;
  z-index: 10000;
  padding: 16px;
  display: none;
}

Step 5: 動作確認

ブラウザの開発者ツールで以下を確認する:

  1. コンソールエラーがないか: D3.jsロード失敗・&#038;変換によるSyntaxErrorが最多
  2. 地図が色分けされるか: 「導入済み(緑)」が表示される都道府県をクリックして詳細が出るか
  3. フィルタが機能するか: 「ステータス: 実証中」フィルタで地図・テーブルが連動して変わるか
  4. スマホ表示: Chrome DevToolsのモバイルエミュレータで767px以下のレイアウトを確認
// デバッグ用: D3.jsが正しくロードされているか確認
console.log("D3 version:", d3.version);  // "7.x.x" が出れば正常

つまずきポイントまとめ

  • D3.jsがロードされない: <script src> → 動的ロードに変更。typeof d3チェックで確認
  • &&演算子でSyntaxError: WordPressが&#038;&#038;に変換する。三項演算子かネストifで回避
  • スマホでパネルが「開いて即閉じ」: touchstartのstopPropagation()漏れ。パスのタッチハンドラに必ずセットで書く
  • SVGのインライン<style>がページ全体に漏れる: DOMParserでSVGを取得後、<style>要素を削除してから挿入
  • フィルタ後に地図色が残る: renderMap内で全パスをいったんグレーにリセットしてから再描画する
  • chart.destroyが必要な場面: D3.jsはsvg.selectAll("*").remove()でクリアするか、svgごと再生成する

まとめ

  • D3.js v7のWordPressロードは動的ブートストラップ必須(<script src>不可)
  • &&演算子はWordPressで必ず壊れる。三項演算子(? :)かネストifに置き換える
  • スマホのtouchstartにはe.stopPropagation()を必ずセットで書く
  • フィルタは1関数に集約し、地図・テーブル・チャートを同時更新する設計が保守しやすい
  • SVG外部ファイルを動的挿入する際はインラインstyleの漏れに注意

完成したツールはこちら(AI Japan Index - 日本自治体AI政策データベース)


FAQ(手順・エラー対処視点)

Q1. SVGパスのdata-code属性はどこから取得すればよいですか?
JIS X 0401の都道府県コード(01〜47)を使う。GitHubで公開されているフリーのjapan-svgファイルはdata-codeid属性を持つものが多い。持っていない場合は、<path>要素に手動で追加するか、Pythonスクリプトで一括付与できる(xml.etree.ElementTree使用)。

Q2. フィルタを追加するにはdata.jsのどこを変更しますか?
フィルタキーはpolicyData.localPoliciesの各オブジェクトのプロパティ名に対応している。新しいフィルタ(例: 「予算規模」)を追加する場合は、各エントリにbudgetRange: "large"等のフィールドを追加し、applyFilters()内にbudgetOkの判定を追加するだけで済む。

Q3. 「グラフの読み込みに失敗しました」と表示されたときの対処法は?
CDN(cdn.jsdelivr.net)へのアクセスがブロックされている可能性がある。ネットワークタブでd3.min.jsのリクエストを確認する。企業プロキシ環境ではunpkg.comcdnjs.cloudflare.comのURLに変更すると解決する場合がある。

Q4. 地図の都道府県が全てグレーのまま色が変わらない場合はどうしますか?
SVGパスにdata-code属性がない、またはpolicyData.localPoliciesprefectureCodeと一致していない可能性がある。console.log(statusByCode)で変換後のMapを確認し、d3.selectAll(".prefecture-path").size()で選択されたパス数を確認する。0が返る場合はクラス名のタイポを疑う。


データ出典: 総務省 自治体AI・RPA実証実験・導入状況等調査(2024年12月31日現在)/ 2026年3月時点の情報

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?