0
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】公式ソース別に整理した日本AIトレンドデータをWordPressダッシュボードに組み込んでみた

0
Posted at

ai-trend-dashboard.jpg

この記事でできること

  • JUAS・IDC Japan・経産省等の複数公式ソースから「定義の異なるデータ」を1つのダッシュボードで安全に扱う設計を知る
  • D3.js v7でレーダーチャートを実装し、スマホでタップ→下部固定パネルを表示する
  • WordPressでの&&演算子問題と動的D3.jsロードの実装パターンを理解する
  • データオブジェクトにscopeフィールドを持たせて定義不一致の誤表示を防ぐ

完成形はAIトレンドダッシュボード(ai-japan-index.com)で確認できる。


環境・前提

  • D3.js: v7.9.0(動的ロード)
  • ブラウザ: Chrome 133 / Safari 18 / Firefox 135 で動作確認
  • WordPress: 6.7.x + SWELLテーマ 2.11.x
  • デプロイ形式: <!-- wp:html --> ブロック
  • 外部ライブラリ: D3.jsのみ
  • Python: 3.12(データ検証スクリプトに使用)

完成形

AIバズワード10選のレーダーチャート、企業AI導入率の棒グラフ、市場規模の折れ線グラフ、政府予算推移、AI人材需給、スタートアップの6チャートを1ページに統合。フィルタで絞り込みも可能。

動作確認はこちら(AI Japan Index - AIトレンドダッシュボード)


Step 1: データソースの定義不一致を防ぐ設計

「企業AI導入率」は調査によって数値が異なる。同一チャートに混在させると誤読を招く。

# データ検証スクリプト(scripts/validate_data_definitions.py)
# 同じ指標名で定義が異なるデータを検出する

adoption_rates = {
    "JUAS_言語系生成AI_導入済み": {
        "value": 33.9,
        "scope": "言語系生成AI・導入済みのみ",
        "target": "東証上場企業等4500社(有効回答957社)",
        "date":  "2025年9-10月調査"
    },
    "JUAS_広義_含準備中": {
        "value": 53.4,
        "scope": "試験導入・準備中含む広義定義",
        "target": "同上"
    },
    "総務省_企業生成AI業務利用率": {
        "value": 55.2,
        "scope": "企業の生成AI業務利用(広義)",
        "target": "国内企業(中小含む)",
        "date": "2025年7月公表"
    }
}

# scope が異なるデータを同一チャートに使う場合は警告
def check_scope_consistency(data_keys, rate_dict):
    scopes = [rate_dict[k]["scope"] for k in data_keys]
    if len(set(scopes)) > 1:
        print("WARNING: 異なる定義のデータが混在しています")
        for k, s in zip(data_keys, scopes):
            print("  " + k + ": " + s)

JavaScriptのdata.jsにも同様のscopeフィールドを持たせる:

// data.js: 企業導入率データ
const adoptionData = [
  {
    label:  "導入済み(狭義)",
    value:  33.9,
    scope:  "言語系生成AI・導入済みのみ",
    source: "JUAS「企業IT動向調査2026」速報",
    url:    "https://juas.or.jp/news/6082/",
    note:   "東証上場企業等が対象。中小企業は含まれにくい"
  },
  {
    label:  "導入済み(大企業)",
    value:  85.1,
    scope:  "売上1兆円以上の大企業",
    source: "JUAS「企業IT動向調査2026」速報(同調査)",
    url:    "https://juas.or.jp/news/6082/",
    note:   "同一調査内の内訳数値"
  }
];

Step 2: D3.jsのWordPress動的ロード

// NG: SWELLが無視する
// <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>

// OK: 動的ロード
(function() {
  function boot() {
    if (typeof d3 !== "undefined") {
      initDashboard();
      return;
    }
    var s = document.createElement("script");
    s.src = "https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js";
    s.onload  = function() { initDashboard(); };
    s.onerror = function() {
      var el = document.getElementById("dashboard-error");
      if (el) el.textContent = "グラフの読み込みに失敗しました。ページを再読み込みしてください。";
    };
    document.head.appendChild(s);
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", boot);
  } else {
    boot();
  }
}());

Step 3: &&演算子をWordPressで安全に扱う

WordPressは投稿コンテンツ内の&&&#038;&#038;に変換してJSをSyntaxErrorにする。

NG(WordPressが壊す):

var filtered = data.filter(d => d.category === cat && d.trend === trend);

OK(ネストifで完全回避):

var filtered = data.filter(function(d) {
  if (cat) {
    if (d.category !== cat) return false;
  }
  if (trend) {
    if (d.trend !== trend) return false;
  }
  return true;
});

この変換はWordPressのコンテンツフィルタが原因であり、<!-- wp:html -->ブロック内でも発生する。コード全体に&&が1つでも残ると、スクリプト全体がSyntaxErrorになって全チャートが動かない。


Step 4: レーダーチャートの実装(スマホ対応込み)

function renderBuzzwordRadar(data) {
  var isMobile = window.innerWidth <= 767;
  var size     = isMobile ? 260 : 380;

  // 既存SVGをクリア(再描画対応)
  d3.select("#radar-svg").selectAll("*").remove();

  var svg = d3.select("#radar-svg")
    .attr("viewBox", "0 0 " + size + " " + size)
    .append("g")
    .attr("transform", "translate(" + (size / 2) + "," + (size / 2) + ")");

  var n           = data.length;
  var radius      = (size / 2) - 30;
  var rScale      = d3.scaleLinear().domain([0, 100]).range([0, radius]);
  var angleSlice  = (Math.PI * 2) / n;

  // グリッド(同心円)
  [20, 40, 60, 80, 100].forEach(function(level) {
    svg.append("circle")
      .attr("r", rScale(level))
      .attr("fill", "none")
      .attr("stroke", "#2a2a4a");
  });

  // 軸(PC: キーワードラベル表示 / スマホ: 非表示)
  data.forEach(function(d, i) {
    var angle = angleSlice * i - Math.PI / 2;
    svg.append("line")
      .attr("x2", rScale(100) * Math.cos(angle))
      .attr("y2", rScale(100) * Math.sin(angle))
      .attr("stroke", "#2a2a4a");

    // PCのみラベル表示
    if (!isMobile) {
      svg.append("text")
        .attr("x", rScale(118) * Math.cos(angle))
        .attr("y", rScale(118) * Math.sin(angle))
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "middle")
        .attr("font-size", "9px")
        .attr("fill", "#a7a9be")
        .text(d.keyword);
    }
  });

  // スコアポリゴン
  var lineGen = d3.lineRadial()
    .radius(function(d) { return rScale(d.score); })
    .angle(function(_, i) { return i * angleSlice; })
    .curve(d3.curveLinearClosed);

  svg.append("path")
    .datum(data)
    .attr("d", lineGen)
    .attr("fill", "rgba(255,137,6,0.18)")
    .attr("stroke", "#ff8906")
    .attr("stroke-width", 2);

  // スマホ: タップエリア(透明な円)→下部固定パネル
  if (isMobile) {
    svg.selectAll(".tap-area")
      .data(data)
      .enter()
      .append("circle")
      .attr("class", "tap-area")
      .attr("cx", function(d, i) {
        return rScale(d.score) * Math.cos(angleSlice * i - Math.PI / 2);
      })
      .attr("cy", function(d, i) {
        return rScale(d.score) * Math.sin(angleSlice * i - Math.PI / 2);
      })
      .attr("r", 12)
      .attr("fill", "transparent")
      .on("touchstart", function(event, d) {
        event.preventDefault();
        event.stopPropagation();  // 必須: バブリングでパネルが即閉じるのを防ぐ
        showMobilePanel(d.keyword + "(スコア" + d.score + "): " + d.description);
      });
  }
}

Step 5: 動作確認

// 確認1: D3.jsがロードされているか
console.log("D3:", typeof d3 !== "undefined" ? d3.version : "NOT LOADED");

// 確認2: データが正しく読み込まれているか
console.log("buzzword count:", buzzwordData.length);  // 10

// 確認3: SVGが描画されているか
var svgChildren = document.querySelectorAll("#radar-svg *").length;
console.log("SVG children:", svgChildren);  // 0 より大きければOK

// 確認4: &&が変換されていないか(WordPressで一番よく発生するバグ)
// ブラウザのview-sourceで &&が &#038;&#038; になっていないか確認

確認チェックリスト:

  1. コンソールにSyntaxErrorが出ていないか(&&変換の検出)
  2. レーダーチャートのポリゴンが描画されているか
  3. スマホでドットをタップすると下部パネルが開くか
  4. PCでラベルが表示され、スマホでは非表示になっているか
  5. フィルタ変更で全チャートが連動して更新されるか

つまずきポイントまとめ

  • D3.jsがロードされない: <script src> → 動的ロードに変更。typeof d3で確認
  • &&でSyntaxError: WordPressが&#038;&#038;に変換。view-source:で確認してネストifに変換
  • 政府予算チャートで2026年に急跳ね上がり: 異なる定義(内閣府集計 vs 経産省AI+半導体合算)の混在が原因。scopeフィールドで管理し同一系列に混ぜない
  • レーダーチャートのポリゴンが開いている: d3.curveLinearClosedを指定していなかった。最後の点が始点に戻らない
  • スマホでタップして「開いて即閉じ」: touchstartのstopPropagation()漏れ。パネル外タップ判定のdocumentリスナーにバブリングして即閉じた
  • レーダーチャートのラベルがSVG外にはみ出る: rScale(118)で余白を設けているがスマホ幅では超過する。if (!isMobile)で非表示にした

まとめ

  • 複数ソースのデータ統合ではscopeフィールドによる定義管理が必須
  • D3.js v7のWordPress組み込みは動的ブートストラップのみ。<script src>は使わない
  • &&演算子はWordPressで必ず破壊される。コード全体をネストifかつ||のみで書く
  • レーダーチャートのポリゴンはd3.curveLinearClosedで閉じる
  • スマホのtouchstartにはe.stopPropagation()を必ずセットで書く

完成したツール(AI Japan Index - AIトレンドダッシュボード)


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

Q1. レーダーチャートの頂点数(キーワード数)を変更するには?
buzzwordData配列の要素数が変わるとn = data.lengthangleSliceが自動で再計算される設計になっている。データ追加・削除だけで済む。ただし10件以上になるとラベルが重なるため、if (!isMobile)の基準を変更するか、キーワードを短縮する必要がある。

Q2. 政府予算のデータを追加するときに気をつけることは?
内閣府集計の全省庁AI予算(2018〜2025年)と経産省のAI+半導体合算(2026年〜)は定義が異なるため、同じseries配列に入れてはいけない。scopeフィールドを必ず付与し、validateDataConsistency()で検出できる状態にしておく。チャートに注記(「※定義が異なる」)を付けることも重要。

Q3. バズワードスコアを毎週更新する仕組みはどう作りますか?
pytrends(Google Trends非公式Python API)で検索ボリュームの相対変化を取得し、前週比でscoreフィールドを更新するスクリプトを組める。ただし非公式APIのため変更リスクがある。lastVerifiedフィールドを更新日付で管理し、7日以上古い場合に「データが古い可能性」と表示する実装が現実的。

Q4. SVGレーダーチャートのスマホ表示でタップ精度を上げるには?
透明なcircleのタップエリア半径を12pxに設定しているが、指の太さを考慮すると20〜24pxが最適とされる(Apple HIG推奨は44pt)。レーダーチャートの場合、スコアに比例した点の位置にタップエリアを置くと、スコアが低い点は中心付近に集中して重なるため、最低半径をMath.max(12, rScale(d.score) * 0.15)等で確保する設計が有効。


データ出典: JUAS「企業IT動向調査2026」速報(2026年2月)、IDC Japan(2025年5月)、総務省「令和7年版情報通信白書」(2025年7月)、経産省「IT人材需給に関する調査」(2019年4月)、内閣府AI戦略会議資料 / 2026年3月時点の情報

0
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
0
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?