日本のAIデータセンター建設18プロジェクトを可視化するツールを作った。
横棒グラフ4本(投資額推移・事業者別投資額・電力容量ランキング・稼働タイムライン)と、地域別に展開するカードUIを組み合わせた構成だ。
完成までに3回詰まった。どれも「先に知っていれば」で済む話なので、残しておく。
完成したもの: AIデータセンター建設トラッカー
この記事でできること
- D3.jsでWordPressに動く横棒グラフを作れる
- 日本語ラベルがスマホで切れない実装方法がわかる
- WordPressで外部ライブラリを読み込む方法がわかる
- 実際に動作するツールはAIデータセンター建設トラッカーで確認できる
環境・前提
- ブラウザ: Chrome / Firefox(EdgeもSafariも同様)
- D3.js: v7
- WordPress: SWELLテーマ(他テーマでもほぼ同様)
- HTMLファイルに直接JavaScriptを書く方法を使う(ビルドツール不要)
- D3.jsはCDNから動的ロードで読み込む(後述)
完成形
地域別カード(タップで展開するテーブル)と4本のD3.jsチャートを組み合わせた構成。
- 地域カードをタップすると、その地域のプロジェクト一覧(事業者・電力容量・投資額・稼働時期)が表示される
- 横棒グラフで投資額・電力容量・稼働タイムラインを表示
- スマホでも動く(下部固定パネルで詳細表示)
手順
Step 1: D3.jsをWordPressで読み込む
最初に詰まったのがここだ。
普通は <script src="https://cdn.jsdelivr.net/npm/d3@7"> と書けば読み込める。ローカルのHTMLファイルならこれで動く。
WordPressのカスタムHTMLブロックに貼ったら動かなかった。コンソールにエラーも出ない。D3.jsが読み込まれていない。
NG例(WordPressでは無視される):
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
SWELLはカスタムHTMLブロック内の外部scriptタグを黙って無視する。エラーも出ないから気づきにくい。
OK例(動的ロードで読み込む):
function loadD3(callback) {
if (typeof d3 !== 'undefined') {
callback();
return;
}
var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/d3@7';
s.onload = callback;
document.head.appendChild(s);
}
loadD3(function() {
// D3.jsが使える
initAllCharts();
});
typeof d3 !== 'undefined' のチェックで、同じページに複数チャートがあっても二重ロードを防げる。
つまずきポイント: d3 is not defined というエラーが出たら、まずD3.jsが読み込まれているか確認する。<script src> を書いていると確認してしまいがちだが、WordPressでは動的ロードが必要。
Step 2: 横棒グラフを描く
D3.jsの横棒グラフの基本はこれだけ。
var margin = { top: 20, right: 30, bottom: 40, left: 20 };
var innerW = width - margin.left - margin.right;
var innerH = height - margin.top - margin.bottom;
var xScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.value; })])
.range([0, innerW]);
var yScale = d3.scaleBand()
.domain(data.map(function(d, i) { return i; }))
.range([0, innerH])
.padding(0.3);
var g = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
g.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('y', function(d, i) { return yScale(i); })
.attr('width', function(d) { return xScale(d.value); })
.attr('height', yScale.bandwidth())
.attr('fill', '#5C97CB');
ポイント: scaleBand の .domain() に渡す値と、バー描画で使う yScale(i) のインデックスを一致させる。ここがズレるとバーが正しい位置に描画されない。
Step 3: 日本語ラベルをHTMLで描く
「ソフトバンク / IDCフロンティア」のような長い日本語ラベルをSVGの <text> で描くと、スマホで必ず切れる。SVGのテキストはCSSの word-wrap が効かないから。
解決策はラベルだけHTMLのdivで描く方法だ。
// バーの描画はSVGのまま(上記のコード)
// ラベルはHTMLで描く
var container = document.getElementById('chart-container');
container.style.position = 'relative';
data.forEach(function(d, i) {
var label = document.createElement('div');
label.textContent = d.name;
label.style.position = 'absolute';
label.style.top = (margin.top + yScale(i) + yScale.bandwidth() / 2) + 'px';
label.style.left = '0px';
label.style.fontSize = '12px';
label.style.color = '#a7a9be';
label.style.wordBreak = 'keep-all';
container.appendChild(label);
});
HTMLのdivなら word-break: keep-all でどんな画面幅でも文字が収まる。
ポイント: バーのY座標(SVGの translate 分も含む)とラベルのTOP値を揃えないとズレる。margin.top + yScale(i) の計算を確認する。
つまずきポイント: SVGで長い日本語ラベルを描こうとして「マージンを広げれば解決する」と思いがちだが、10文字超は無理。素直にHTMLに分離する方が早い。
Step 4: && を使わない条件分岐を書く
フィルター機能の実装で2回目の詰まりが発生した。
// NGパターン(WordPressがエラーになる)
if (selectedRegion !== 'all' && selectedStatus !== 'all') {
showFiltered();
}
WordPressのフィルター(wpautop)が && を && に変換する。JavaScriptの構文エラーになり、スクリプト全体が停止する。
コンソールには「SyntaxError: Unexpected token '&'」と出るが、ソースを見ると && に見える(ブラウザが表示を元に戻すから)。原因の特定に時間がかかった。
OK例(ネストifで代替):
if (selectedRegion !== 'all') {
if (selectedStatus !== 'all') {
showFiltered();
} else {
showByRegion(selectedRegion);
}
}
少し冗長になるが確実に動く。コードを書く段階から && を使わないと決めておくと後で困らない。
学び: WordPressはJavaScriptの中身まで書き換える。ローカルで動いたコードがWordPressで動かないときは && を疑う。
Step 5: スマホのタップイベントを設定する
PCのhoverで動くツールチップはスマホで動かない。タップしたときに詳細を出すには touchstart イベントを使う。
bar
.on('mouseover', function(e, d) {
// PC: ツールチップ
showTooltip(e.pageX, e.pageY, d);
})
.on('touchstart', function(e, d) {
e.preventDefault();
e.stopPropagation(); // これを忘れると即閉じになる
showMobilePanel(d);
});
スマホの詳細パネルは画面下部に固定して表示する:
.mobile-panel {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #1a1a2e;
border-top: 2px solid #5C97CB;
padding: 16px;
z-index: 10000;
max-height: 40vh;
overflow-y: auto;
}
つまずきポイント: e.stopPropagation() を忘れると、タップイベントがdocumentまで伝わって「パネル外タップ」と判定される。パネルが開いた瞬間に閉じる現象が起きる。e.preventDefault() と e.stopPropagation() は必ずセットで書く。
Step 6: ガントチャートで稼働タイムラインを描く
「いつ頃どのDCが稼働するか」を横向きのバーで表現した。
// 年を横軸にしたscaleBand
var xTimeline = d3.scaleBand()
.domain(['2024', '2025', '2026', '2027', '2028'])
.range([0, innerW])
.padding(0.05);
// ステータスで色分け
var statusColor = {
'稼働済み': '#4ade80',
'建設中': '#5C97CB',
'計画中': '#a7a9be'
};
g.selectAll('.gantt-bar')
.data(data)
.enter()
.append('rect')
.attr('x', function(d) { return xTimeline(d.year); })
.attr('width', xTimeline.bandwidth())
.attr('fill', function(d) { return statusColor[d.status]; });
ポイント: xTimeline(d.year) の d.year は文字列でないとドメインに一致しない。'2026' と 2026 は別物として扱われる。ドメインの型とデータの型を統一する。
つまずきポイント: スマホではガントチャートが横スクロールになる。コンテナに overflow-x: auto を設定し、SVGには min-width: 480px を設定する。
つまずきポイントまとめ
-
<script src>がWordPressで無視される:document.createElement('script')で動的ロードする -
日本語ラベルがSVGでスマホに収まらない: ラベルだけHTMLのdivで描く。
word-break: keep-allを使う -
scaleBandのドメインとデータの型が合わない: 文字列と数値を混在させない。ドメインに合わせて統一する -
&&がWordPressで構文エラーになる: ネストifか三項演算子で代替する -
stopPropagation()忘れでパネルが即閉じになる:touchstartには必ずセットで書く -
ガントチャートがスマホで見切れる:
overflow-x: autoとmin-widthで横スクロール対応する
まとめ
- WordPressで
<script src>は動かない。動的ロードを前提にする - 日本語ラベルが10文字を超えたら、SVGではなくHTMLのdivで描く
-
scaleBandのドメインとデータの値・型を揃えないとバーが描画されない -
&&はWordPressが壊す。書く前から意識する - スマホのタッチイベントは
touchstart+stopPropagation()のセット
どれも「ローカルで動いたのにWordPressで動かない」という現象で気づく。先に知っておけば2〜3時間は節約できると思う。
完成したツール: AIデータセンター建設トラッカー
関連ツール一覧: AI Japan Index
FAQ
Q. D3.js v6以前との違いはありますか?
イベントハンドラの書き方が変わった。v7では関数の第1引数にイベントオブジェクトが直接渡される(function(e, d) { e.preventDefault(); ... })。v6以前は d3.event でアクセスしていたので、古いサンプルをそのまま使うと d3.event が undefined になる。
Q. SWELLテーマ以外でも <script src> の問題は起きますか?
&& の変換はWordPress本体のフィルター(wpautop)なので、テーマに関係なく起きる。<script src> の無視はSWELL固有だが、他テーマでも同様の報告がある。動的ロードにしておけばどのテーマでも安全。
Q. HTMLラベルとSVGバーの位置がズレる場合の対処法は?
チャートコンテナのパディングやボーダーが計算に含まれていない場合が多い。コンテナに box-sizing: border-box を設定し、getBoundingClientRect() でSVGの実際の位置を取得してオフセットを補正する。
引用・出典
- D3.js v7 公式: https://d3js.org/
- WordPress wpautop: https://developer.wordpress.org/reference/functions/wpautop/
- SWELLテーマ: https://swell-theme.com/
- AIデータセンター建設トラッカー: https://ai-japan-index.com/ai-datacenter-map/
