0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

D3.jsで投資額チャート+地域カードUIを作ってみた—WordPressで動かすまでに詰まった話" tags

0
Posted at

ai-datacenter-map.jpg

日本の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)が &&&#038;&#038; に変換する。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: automin-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の実際の位置を取得してオフセットを補正する。


引用・出典

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?