この記事でできること
- D3.js v7で対数スケール(
scaleLog)を使ったバーチャートを実装できる - 値域差が100倍以上あるデータの可視化設計パターンを理解できる
- WordPress(SWELL)でD3.jsを正しく動かすための3つの必須対応を把握できる
- スマホ対応(Y軸ラベル非表示+タッチパネル)の実装パターンを習得できる
実物の動作確認は日本AI投資マップ(AI Japan Index)でできる。
環境・前提
- D3.js: v7.9(CDN経由)
- JavaScript: Vanilla JS(ES2020)
- デプロイ先: WordPress 6.x(テーマ: SWELL)
- 動作確認ブラウザ: Chrome 120+、Safari 17+、Firefox 121+
- モバイル: iOS Safari、Android Chrome(767px以下でスマホ判定)
完成形
最小520億円(Sakana AI)から最大6兆円(ソフトバンクグループ)まで、115倍の値域差があるデータを1画面に収めた横棒グラフ。4段階のTier(規模帯)で色分けし、スマホではバータップで下部パネルに詳細を表示する。
手順
Step 1: WordPress向けD3.js読み込み(動的インジェクション)
WordPressのコンテンツ内に<script src="CDN">を書いても、SWELLテーマのフィルターが無視する。代わりにdocument.createElement('script')で動的にロードする。
// WordPress/SWELL対応のD3.js読み込みパターン
function loadD3AndInit() {
if (typeof d3 !== 'undefined') {
// すでにロード済みなら即実行
initAll();
return;
}
const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
s.onload = function() { initAll(); };
document.head.appendChild(s);
}
document.addEventListener('DOMContentLoaded', loadD3AndInit);
つまずきポイント1: <script src="https://cdn.jsdelivr.net/npm/d3@7/..."></script> を直接書くとWordPressが無視する。必ず動的インジェクションを使う。
Step 2: データ設計——commitment_typeフィールドを必ず入れる
「AI投資額」は企業ごとに定義が異なる。この違いをデータ構造に埋め込んでおかないと、後でチャートに注記を入れるときに対処できない。
const companies = [
{
id: "softbank-group",
name: "ソフトバンクグループ",
tier: 1,
commitment_jpy_bn: 60000, // 億円
commitment_type: "MA", // MA/CAPEX/REVENUE/FUNDING のいずれか
period: "2025年〜",
description: "OpenAI追加投資 最大400億ドル(約6兆円)",
source: "SBG公式プレスリリース 2025/4/1",
lastVerified: "2026-03-29"
},
{
id: "hitachi",
name: "日立製作所",
tier: 2,
commitment_jpy_bn: 3000,
commitment_type: "CAPEX",
period: "2024年〜",
description: "生成AI関連投資。Lumadaプラットフォーム中核",
source: "日立公式発表 2024/6",
lastVerified: "2026-03-29"
},
{
id: "ntt",
name: "NTTグループ",
tier: 2,
commitment_jpy_bn: 1500,
commitment_type: "REVENUE",
period: "2025年度",
description: "AI受注額見通し。tsuzumi 2を軸に拡大",
source: "NTT IR資料 2025",
lastVerified: "2026-03-29"
}
// 以下同様に17社追加
];
const commitmentTypeLabel = {
MA: "出資・M&A",
CAPEX: "設備・R&D投資",
REVENUE: "受注額(売上)",
FUNDING: "調達額"
};
Step 3: 対数スケールの設定
const margin = { top: 20, right: 40, bottom: 60, left: isMobile() ? 15 : 200 };
const innerWidth = totalWidth - margin.left - margin.right;
const innerHeight = totalHeight - margin.top - margin.bottom;
// 対数スケール(底は10がデフォルト)
const xScale = d3.scaleLog()
.domain([
100, // 最小表示値(0は対数で表現不可)
d3.max(companies, d => d.commitment_jpy_bn) * 1.3
])
.range([0, innerWidth])
.nice();
// Y軸(企業名のバンドスケール)
const yScale = d3.scaleBand()
.domain(companies.map(d => d.name))
.range([0, innerHeight])
.padding(0.25);
NG例と対処:
// NG: 0値をscaleLogに渡す(-Infinityになり描画エラー)
const data = [{ name: "X社", value: 0 }, ...];
xScale(0); // → エラー
// OK: 0値(非開示)は別処理
const chartData = companies.filter(d => d.commitment_jpy_bn > 0);
// 非開示企業はテーブル形式で別セクションに表示
Step 4: バーとラベルの描画
// バー描画
g.selectAll('.bar')
.data(companies)
.enter()
.append('rect')
.attr('class', d => 'bar tier-' + d.tier)
.attr('x', 0)
.attr('y', d => yScale(d.name))
.attr('width', d => xScale(d.commitment_jpy_bn))
.attr('height', yScale.bandwidth())
.attr('fill', d => tierColors[d.tier]);
// PC: バー右端に数値ラベル表示(スマホは非表示)
if (!isMobile()) {
g.selectAll('.bar-label')
.data(companies)
.enter()
.append('text')
.attr('class', 'bar-label')
.attr('x', d => xScale(d.commitment_jpy_bn) + 6)
.attr('y', d => yScale(d.name) + yScale.bandwidth() / 2 + 5)
.text(d => formatAmount(d.commitment_jpy_bn));
}
// 数値フォーマット(億円→兆円変換)
function formatAmount(bn) {
if (bn >= 10000) return (bn / 10000).toFixed(1) + '兆円';
if (bn >= 1000) return (bn / 1000).toFixed(1) + '千億円';
return bn + '億円';
}
つまずきポイント2: スマホで数値ラベルをバー内に入れると他の要素と重なる。if (!isMobile())でPC専用にすること。
Step 5: X軸(対数スケールの目盛り設定)
対数スケールのデフォルト目盛りは2, 5, 10, 20, 50...と細かく出すぎる。任意の目盛り値を指定する。
const xAxis = d3.axisBottom(xScale)
.tickValues([100, 500, 1000, 5000, 10000, 50000])
.tickFormat(d => {
if (d >= 10000) return (d / 10000) + '兆円';
return (d / 100) + '千億円';
});
g.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0, ${innerHeight})`)
.call(xAxis);
// スマホ: X軸ラベルを3本に間引く(日本語は4本以上で重なる)
if (isMobile()) {
const allTicks = g.selectAll('.x-axis .tick');
const tickCount = allTicks.size();
allTicks.each(function(_, i) {
const show = [0, Math.floor((tickCount - 1) / 2), tickCount - 1];
if (!show.includes(i)) d3.select(this).style('display', 'none');
});
}
Step 6: スマホ用タッチパネルの実装
スマホではY軸ラベルを非表示にする代わりに、バータップで詳細パネルを表示する。
<!-- HTML側: 下部固定パネル -->
<div id="aji-s3-panel" style="
display: none;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #1a1a2e;
border-top: 2px solid #ff8906;
max-height: 40vh;
overflow-y: auto;
z-index: 10000;
padding: 16px;
">
<button id="aji-s3-panel-close">×</button>
<div id="aji-s3-panel-content"></div>
</div>
// タッチイベント(e.stopPropagation()必須)
bars.on('touchstart', function(e, d) {
e.preventDefault();
e.stopPropagation(); // これがないとパネルが開いて即閉じる
showPanel(d);
});
// パネル外タップで閉じる
document.addEventListener('touchstart', function(e) {
const panel = document.getElementById('aji-s3-panel');
if (panel.style.display === 'block') {
if (!panel.contains(e.target)) {
panel.style.display = 'none';
}
}
});
つまずきポイント3: e.stopPropagation()を省略すると、タップイベントがdocumentまで伝播し「パネル外タップ」と判定されてパネルが即座に閉じる。e.preventDefault()とe.stopPropagation()はセットで書く。
Step 7: &&演算子の代替(WordPress必須対応)
WordPressのコンテンツフィルターが&&を&&に変換し、JSが構文エラーになる。
// NG(WordPressがエンティティ変換してエラー)
if (isValid && data.length > 0) {
render();
}
// OK(三項演算子)
if (isValid ? data.length > 0 : false) {
render();
}
// OK(ネストif)
if (isValid) {
if (data.length > 0) {
render();
}
}
つまずきポイント4: このバグはブラウザのデベロッパーツールでHTMLソースを確認すると&&という文字列が見えるため、発見できる。WordPressのHTMLエディタ上では&&と表示されていても、保存時に変換される。
つまずきポイントまとめ
-
<script src="CDN">が動かない: WordPressコンテンツ内では無視される。document.createElement('script')で動的ロードする -
対数スケールに0値を渡すとエラー:
log10(0) = -Infinity。0値データは別処理(非表示または別セクション) -
&&がエラーになる: WordPressが&&に変換する。三項演算子またはネストifで代替 -
スマホでパネルが即閉じる:
touchstartハンドラにe.stopPropagation()がない。必ずセットで書く -
対数スケールの目盛りが細かすぎる: デフォルトの
ticks()は過剰。tickValues([])で任意指定する
まとめ
-
d3.scaleLog()で100倍超の値域差も1画面に収まる横棒グラフを実装できた - WordPress向けには3点(動的ロード・
&&禁止・スマホパネル)の対応が必須 - 定義が異なるデータを同一チャートに並べる場合は、
commitment_type等のフィールドで「何の数字か」を明示する設計が重要 - 対数スケールの直感性低下は、Tier分類によるカラーコーディングで補完できる
関連ツールはAI Japan Indexで公開中。D3.jsによる他の可視化実装も参考になる。
