AI電力消費のデータをWordPressで可視化するツールを作った。エリアチャート、ダンベルチャート、スロープチャート、ワッフルチャートの4種類を1ページに収めた。
完成物: AI電力消費トラッカー
「ダンベルチャートって何?」「ワッフルチャートってどう実装する?」という疑問から調べ始めたが、D3.jsでこれらを作る日本語の記事がほとんどなくて困った。この記事はその記録。
この記事でできること
- D3.js v7でダンベルチャートを実装できる
- D3.js v7でワッフルチャート(100マスグリッド)を実装できる
- WordPressでD3.jsを動かすための定型パターンがわかる
- スマホ対応のタップパネルの基本形がわかる
- 実際に動作するツールはAI電力消費トラッカーで確認できる
環境・前提
- ブラウザ: Chrome最新版で動作確認
- D3.js: v7(CDNから動的ロード)
- CMS: WordPress(SWELLテーマ)
- 言語: Vanilla JS(フレームワークなし)
- 追加依存: なし(D3.js以外は不要)
完成形
4つのチャートが縦に並ぶ1カラムレイアウト。スマホではY軸ラベルを非表示にして、データポイントのタップで下部に詳細パネルが出る。
実物はこちら: AI電力消費トラッカー
手順
Step 1: WordPressでD3.jsを読み込む(定型パターン)
SWELLテーマのカスタムHTMLブロック内に <script src="CDN"> と書いても無視される。これを知らずに30分溶かした。
NG例(SWELLでは動かない):
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script>
// d3 is not defined になる
d3.select('body');
</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() {
initAllCharts();
});
typeof d3 のチェックが重要。同じページに複数のチャートブロックがある場合、最初のブロックがロードしたD3.jsを2番目以降のブロックが使い回せる。
つまずきポイント: s.onload の前に描画コードを書いてしまうと、D3.jsが読み込まれる前に実行されてエラーになる。必ずcallback内で描画を呼ぶ。
Step 2: && を使わない(WordPress最大の罠)
D3.jsを動的ロードして動いた。次の罠は && だった。
WordPressのコンテンツフィルターが && を && にHTMLエンティティ変換する。JavaScriptの構文エラーになってスクリプト全体が止まる。
NG例(WordPressが壊す):
if (a > 0 && b > 0) {
doSomething();
}
OK例(三項演算子 or ネストif):
// 三項演算子バージョン
var result = (a > 0) ? ((b > 0) ? doSomething() : null) : null;
// ネストifバージョン(読みやすい)
if (a > 0) {
if (b > 0) {
doSomething();
}
}
つまずきポイント: エラーメッセージは「構文エラー」としか出ないので、どこで壊れているかわからない。&& が犯人だと気づくまで1時間かかった。
Step 3: ダンベルチャートの実装
ダンベルチャートは「旧推定値 → 最新推定値」という2点間の変化を見せるのに使う。左端と右端に円を置いて、線で繋ぐだけ。
const data = [
{ name: 'Google検索', oldVal: 0.30, newVal: 0.04 },
{ name: 'GPT-3.5→4o', oldVal: 2.9, newVal: 0.30 },
{ name: 'GPT-5', oldVal: null, newVal: 18.9 }
];
// 対数スケール(0.04〜18.9をまとめて表示するため)
const x = d3.scaleLog()
.domain([0.03, 25])
.range([0, innerW]);
// 横軸(バンド)
const y = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, innerH])
.padding(0.4);
const row = g.selectAll('.dumbbell-row')
.data(data)
.enter()
.append('g')
.attr('transform', d => 'translate(0,' + y(d.name) + ')');
// 線(旧値がある場合のみ)
row.filter(d => d.oldVal !== null)
.append('line')
.attr('x1', d => x(d.oldVal))
.attr('x2', d => x(d.newVal))
.attr('y1', y.bandwidth() / 2)
.attr('y2', y.bandwidth() / 2)
.attr('stroke', '#555')
.attr('stroke-width', 2);
// 旧値(グレー円)
row.filter(d => d.oldVal !== null)
.append('circle')
.attr('cx', d => x(d.oldVal))
.attr('cy', y.bandwidth() / 2)
.attr('r', 6)
.attr('fill', 'var(--aji-accent-sub4)');
// 新値(メインカラー円)
row.append('circle')
.attr('cx', d => x(d.newVal))
.attr('cy', y.bandwidth() / 2)
.attr('r', 6)
.attr('fill', 'var(--aji-accent)');
ポイント: 対数スケールの場合、ドメインの下限を0にできない(log(0)が-Infinity になる)。domain([0.03, 25]) のように最小値を正の数に設定する。
Step 4: ワッフルチャートの実装
ワッフルチャートは100個の正方形のグリッドで割合を表す。「東京圏52%なら52マスを塗る」というシンプルな仕組みだ。
const waffle = {
tokyo: 52,
kansai: 31,
hokkaido: 14,
other: 3
};
const cellSize = isMobile() ? 22 : 28;
const gap = 3;
const cols = 10;
// 100マスを順番に塗る
// 0〜51: 東京圏, 52〜82: 関西圏, 83〜96: 北海道, 97〜99: その他
const colorMap = (i) => {
if (i < waffle.tokyo) return 'var(--aji-accent)';
if (i < waffle.tokyo + waffle.kansai) return 'var(--aji-accent-sub2)';
if (i < waffle.tokyo + waffle.kansai + waffle.hokkaido) return 'var(--aji-accent-sub3)';
return 'var(--aji-accent-sub5)';
};
const svgW = cols * (cellSize + gap) - gap;
const svgH = cols * (cellSize + gap) - gap;
const svg = d3.select('#waffle-chart')
.append('svg')
.attr('width', svgW)
.attr('height', svgH);
d3.range(100).forEach(function(i) {
svg.append('rect')
.attr('x', (i % cols) * (cellSize + gap))
.attr('y', Math.floor(i / cols) * (cellSize + gap))
.attr('width', cellSize)
.attr('height', cellSize)
.attr('fill', colorMap(i))
.attr('rx', 2);
});
ポイント: 100マスの配列を d3.range(100) で生成してforEachで処理すると、シンプルに書ける。Math.floor(i / cols) で行番号、i % cols で列番号を計算する。
Step 5: スマホ対応——タップで下部固定パネル
スマホではY軸のラベルが日本語10文字以上あって必ず見切れた。解決策は「Y軸ラベルを非表示にして、タップで詳細を出す」。
// スマホでY軸ラベルを非表示
function isMobile() {
return window.innerWidth <= 767;
}
if (!isMobile()) {
// PCのみY軸ラベルを描画
g.call(yAxis);
}
// 下部固定パネルの表示
function showMobilePanel(text) {
var panel = document.querySelector('.aji-apt-panel');
var content = document.querySelector('.aji-apt-panel-text');
content.textContent = text;
panel.style.display = 'block';
}
// タッチイベント(e.stopPropagation()必須)
element.addEventListener('touchstart', function(e) {
e.preventDefault();
e.stopPropagation(); // これを忘れるとパネルが開いて即閉じる
showMobilePanel('東京圏: 15施設(52%)');
});
// パネル外タップで閉じる
document.addEventListener('touchstart', function() {
document.querySelector('.aji-apt-panel').style.display = 'none';
});
つまずきポイント: e.stopPropagation() を書き忘れると、タッチイベントがdocumentまで伝播して「パネル外タップ」と判定され、パネルが開いた瞬間に閉じる。絶対忘れないようにする。
Step 6: CSSをスコープに閉じ込める
グローバルCSSを書いてしまって、WordPressの記事本文全体のデザインが崩れた。ラッパークラスで全セレクタを囲む。
/* NG: WordPress全体に影響する */
* { box-sizing: border-box; }
h3 { color: #e0e0e0; font-size: 16px; }
/* OK: .aji-apt-wrap スコープ内のみ */
.aji-apt-wrap * { box-sizing: border-box; }
.aji-apt-wrap h3 { color: #e0e0e0; font-size: 16px; }
つまずきポイント: * のグローバルCSSは全要素に影響するので特に注意。ツールのラッパーdivに専用クラスを付けて、全セレクタにそのクラスをプレフィックスとして付ける習慣を作ると安全。
つまずきポイントまとめ
-
<script src>が効かない: SWELLのカスタムHTMLブロックは外部scriptを無視する。動的ロードで解決 -
&&で構文エラー: WordPressが&&にエンティティ変換する。三項演算子かネストifを使う - Y軸ラベルが見切れる: 日本語10文字超はスマホで必ず見切れる。Y軸ラベル非表示+タップパネルで対応
-
タップパネルが開いて即閉じる:
e.stopPropagation()の省略が原因。touchstartと同時に書く - 対数スケールのドメイン: 下限に0を設定するとエラー。最小値は正の数(例: 0.03)にする
- グローバルCSS: ラッパークラスなしでCSSを書くとWordPressのレイアウトが崩れる。全セレクタにプレフィックスを付ける
まとめ
- ダンベルチャートは「2点間の変化」を見せるのに棒グラフより直感的
- ワッフルチャートは
d3.range(100).forEachでシンプルに実装できる - WordPressでD3.jsを使うには「動的ロード・
&&回避・CSSスコープ」の3点セット - スマホのタッチイベントは
e.stopPropagation()を忘れずに
実際に動かして確認できる。
AI電力消費トラッカー
引用・出典
- IEA「Energy and AI」2025年4月10日
- Wood Mackenzie「Japan Data Centers Power Demand」2025年
- 電力中央研究所「SERC Research Report」2025年版
- Epoch AI「How Much Energy Does ChatGPT Use?」2025年2月
- Deloitte TMT Predictions 2026「Compute Power and AI」2026年
- 日経クロステック「国内大手20社のデータセンター建設計画を調査」2025年12月
