この記事でできること
- 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ページに統合。フィルタで絞り込みも可能。
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は投稿コンテンツ内の&&を&&に変換して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で &&が && になっていないか確認
確認チェックリスト:
- コンソールにSyntaxErrorが出ていないか(
&&変換の検出) - レーダーチャートのポリゴンが描画されているか
- スマホでドットをタップすると下部パネルが開くか
- PCでラベルが表示され、スマホでは非表示になっているか
- フィルタ変更で全チャートが連動して更新されるか
つまずきポイントまとめ
-
D3.jsがロードされない:
<script src>→ 動的ロードに変更。typeof d3で確認 -
&&でSyntaxError: WordPressが&&に変換。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.lengthとangleSliceが自動で再計算される設計になっている。データ追加・削除だけで済む。ただし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月時点の情報
