この記事でできること
- D3.js v7をWordPress(SWELL)に
<script src>なしで組み込む方法を知る - 米国1,091億ドル vs 日本9億ドルのような桁違いデータを対数スケールで可視化する
- 年度切り替えボタンでD3.jsのSVGチャートを動的に更新する
- KPIカードに
requestAnimationFrameでカウントアップアニメを実装する -
&&演算子がWordPressで壊れる問題と回避方法を理解する
完成形はAI Impact Report(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 -->ブロック(カスタムHTML) - 外部ライブラリ: D3.jsのみ
- ビルドツール: 不使用(静的HTML)
完成形
日本のAIに関する7指標(市場規模・導入率・投資額・特許・人材・スタートアップ・論文)を1画面に統合したダッシュボード。KPIカードのカウントアップ、セクションナビ(スクロールスパイ)、年度切り替えドーナツチャートが実装されている。
Step 1: D3.jsをWordPressに動的ロードする
Chart.jsを使っていたが、SWELLテーマのJSと競合して<canvas>が描画されない問題が5回の修正でも解決しなかった。D3.jsに変えて即解決した。
ただしD3.jsも<script src>形式では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;
}
var 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("dashboard-root").textContent =
"グラフの読み込みに失敗しました。ページを再読み込みしてください。";
};
document.head.appendChild(s);
}
document.addEventListener("DOMContentLoaded", loadD3ThenInit);
typeof d3チェックで他ページでの二重ロードを防ぎ、onerrorでCDN失敗時のフォールバックを表示する。
Step 2: &&演算子をWordPressで安全に使う
WordPressは投稿コンテンツ内の&&を&&にHTML変換する。これでJS全体がSyntaxErrorになる。
NG(WordPressが壊す):
var highlighted = data.filter(d => d.rank <= 3 && d.highlight);
OK(ネストifで&&を回避):
var highlighted = data.filter(function(d) {
if (d.rank > 3) return false;
return d.highlight ? true : false;
});
この問題は発見が難しい。コンソールに Unexpected token '&' や Unexpected token 'amp' が出たら&&の変換を疑う。
Step 3: 対数スケールで桁違いデータを可視化する
米国1,091億ドルと日本9億ドルを同じY軸に表示すると、日本のバーが視覚的に消える。
通常スケール(NG):
米国 |████████████████████████████████████| 1091
中国 |████ | 93
英国 |██ | 45
日本 | | 9 ← 見えない
対数スケール(OK):
// D3.js v7の対数スケール
var yScale = d3.scaleLog()
.domain([1, d3.max(data, function(d) { return d.amount; }) * 1.2])
.range([height, 0]);
// 手動で見やすいtickを指定
var yAxis = d3.axisLeft(yScale)
.tickValues([1, 10, 100, 1000])
.tickFormat(function(d) { return d + "億ドル"; });
対数スケールのデメリット(「直感的に差が読めない」)はツールチップで補完:
// スマホは下部固定パネル、PCはツールチップ
function buildTooltipText(d) {
var ratio = Math.round(1091 / d.amount);
return d.name + ": " + d.amount + "億ドル(米国の約" + ratio + "分の1)";
}
Step 4: D3.jsのSVGチャートを年度切り替えで動的更新する
年度ボタンでドーナツチャートを切り替える実装。D3.jsにはchart.destroy()がないため、svg.selectAll("*").remove()でクリアしてから再描画する:
var talentData = {
2025: { shortage: 88000, total: 200000 },
2030: { shortage: 124000, total: 300000 },
2040: { shortage: 3260000, total: 5000000 }
};
function renderTalentChart(year) {
var svgContainer = d3.select("#talent-chart-svg");
// 既存コンテンツを全消去(これがないと二重描画になる)
svgContainer.selectAll("*").remove();
var data = talentData[year];
var pieData = [
{ label: "充足", value: data.total - data.shortage, color: "#2d9e6b" },
{ label: "不足", value: data.shortage, color: "#ef4444" }
];
var width = 200;
var height = 200;
var radius = Math.min(width, height) / 2 - 8;
var svg = svgContainer
.attr("viewBox", "0 0 " + width + " " + height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var pie = d3.pie().value(function(d) { return d.value; });
var arc = d3.arc().innerRadius(radius * 0.5).outerRadius(radius);
svg.selectAll(".slice")
.data(pie(pieData))
.enter()
.append("path")
.attr("d", arc)
.attr("fill", function(d) { return d.data.color; });
// 中央テキスト
svg.append("text")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("fill", "#ef4444")
.attr("font-size", "13px")
.text((data.shortage / 10000).toFixed(1) + "万人不足");
}
// 年度ボタンのイベント
document.querySelectorAll(".year-btn").forEach(function(btn) {
btn.addEventListener("click", function() {
document.querySelectorAll(".year-btn").forEach(function(b) {
b.classList.remove("active");
});
this.classList.add("active");
renderTalentChart(parseInt(this.dataset.year));
});
});
selectAll("*").remove()を呼ばないと古い描画が残り、二重描画になる。
Step 5: KPIカードのカウントアップアニメーション
function animateCountUp(el, targetValue, duration) {
duration = duration || 1200;
var startTime = performance.now();
var isFloat = (targetValue % 1 !== 0);
function update(now) {
var elapsed = now - startTime;
var progress = elapsed / duration;
if (progress > 1) progress = 1;
// easeOutCubic: 最初は速く最後はゆっくり止まる
var eased = 1 - Math.pow(1 - progress, 3);
var current = targetValue * eased;
if (isFloat) {
el.textContent = current.toFixed(1);
} else {
el.textContent = Math.floor(current).toLocaleString();
}
if (progress < 1) requestAnimationFrame(update);
}
requestAnimationFrame(update);
}
// 使い方
var el = document.getElementById("kpi-market-value");
animateCountUp(el, 13412, 1200); // 0 → 13,412 を1.2秒でカウントアップ
setIntervalの代わりにrequestAnimationFrameを使うことで、フレームレートに同期した滑らかな動きになる。
Step 6: 動作確認
// コンソールで確認
console.log("D3 version:", d3.version); // "7.x.x" → D3が正常にロードされている
// データが正しく参照されているか
console.log("KPI data:", aiImpactData.kpiSummary.length); // 4 → 4件
// チャートが描画されているか(SVGの子要素数確認)
console.log("Investment bars:", document.querySelectorAll("#investment-chart-svg .bar").length); // 4
確認ポイント:
- KPIカードの数値が0からカウントアップするか
- 「年度を切り替える」ボタンでドーナツが更新されるか
- 投資額チャートのY軸が対数スケールか(ラベルが1, 10, 100, 1000と等間隔)
- スマホでバー横の数値ラベルが非表示になるか
つまずきポイントまとめ
-
D3.jsがロードされない:
<script src>はSWELLで無視される。動的ロードに変更。typeof d3で確認 -
&&でSyntaxError(&): WordPressの自動変換。三項演算子・ネストifで回避 -
二重描画:
svg.selectAll("*").remove()を呼ぶのを忘れた。動的更新前に必ず呼ぶ -
日本のバーが見えない: 通常スケールではゼロに見える。
scaleLog()に変更 -
バブルが画面外にはみ出る: 半径は
Math.sqrt(amount) * scaleFactorでスケーリング -
KPIアニメがカクカク:
setInterval→requestAnimationFrame+easeOutCubicに変更
まとめ
- D3.js v7のWordPress組み込みは動的ブートストラップ必須
-
&&演算子はWordPressで必ず壊れる。使わない - D3.jsの動的更新は
svg.selectAll("*").remove()でSVGをクリアしてから再描画 - 桁違いデータには
scaleLog()を使い、ツールチップで相対比を補足する - KPIアニメには
requestAnimationFrame+ easeOutCubicの組み合わせが最適
完成したツール(AI Japan Index - AI Impact Report)
FAQ(手順・エラー対処視点)
Q1. Unexpected token '&' エラーが出た場合の対処法は?
WordPressが&&を&&に変換している。コードをview-source:で確認して&が含まれていたら、ソースの&&を全て三項演算子またはネストifに書き直す。スクリプト全体のエラーになるため、1箇所でも残っていると全てのチャートが動かない。
Q2. D3.jsの動的ロードが失敗した時に確認すべきことは?
ブラウザのネットワークタブでd3.min.jsのリクエストを確認する。404または403が出ている場合はCDN URLの変更を試す(unpkg.com/d3@7またはcdnjs.cloudflare.com)。コンテンツブロッカーが原因のこともある。
Q3. スマホで数値ラベルを非表示にするコードパターンは?
window.innerWidth <= 767でモバイル判定し、ラベル描画をifで囲む。d3.selectAll(".bar-label").style("display", isMobile ? "none" : "block")でCSSでの切り替えも可能だが、D3.jsでSVGを生成する際は最初から描画しない方がSVGのDOMが軽量になる。
Q4. 対数スケールで0の値が含まれるデータはどう扱いますか?
scaleLog()は0や負の値を扱えない。0が含まれる場合は0.1や0.5に置き換えるか、d3.scaleSymlog()(対数に近い対称スケール)を使う選択肢がある。ツールチップで「データなし」と表示する方が誠実な場合も多い。
データ出典: IDC Japan(2025年5月)、総務省 令和7年版情報通信白書(2025年7月)、Stanford AI Index 2025(2025年4月)、経産省 IT人材需給調査(2019年4月)/ 2026年3月時点の情報
