1
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?

【AI規制・法律タイムライン 2026】WordPressでD3.jsの年表チャートを作ろうとして3回詰まった話

1
Posted at

ai-regulation-timeline.jpg

この記事でできること

  • WordPressでD3.jsのインタラクティブなチャートを動かす方法がわかる
  • &&演算子がWordPressで使えない理由と回避策がわかる
  • D3のタイムスケール(時系列)の基本的な使い方がわかる
  • スマホ横スクロール年表の実装パターンがわかる
  • 実際に動作するツールはAI規制・法律タイムライン 2026で確認できる

環境・前提

  • ブラウザ: Chrome/Firefox/Safari(モダンブラウザ)
  • D3.js: v7.9.0(CDN経由で動的ロード)
  • CMS: WordPress 6.x + SWELLテーマ
  • 言語: Vanilla JavaScript(ES2020相当)
  • ビルドツール: なし(全コードをWordPress投稿コンテンツに直接埋め込む形式)

完成形

5か国(日本・EU・米国・中国・韓国)と国際機関のAI規制イベント42件を横スクロールの年表チャートで表示する。各マーカーをクリック/タップすると、そのイベントの概要と出典が表示される。

https://gozen-ai.com/tools/ai-regulation-timeline/


手順

Step 1: D3.jsをWordPressで読み込む

WordPressの投稿コンテンツ内に <script src="cdn.url"> と書いても動かない。SWELLテーマ(というかWordPressのコンテンツフィルター)が外部スクリプトタグを無視するからだ。

NG例(WordPressでは動かない):

<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js"></script>
<script>
  // d3 is not defined になる
  const svg = d3.select("#chart");
</script>

OK例(動的ロードで回避):

function loadD3AndInit() {
  // 既にロード済みならスキップ
  if (typeof d3 !== 'undefined') {
    initAll();
    return;
  }
  // document.createElementで動的ロード
  const script = document.createElement('script');
  script.src = 'https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js';
  script.onload = function() { initAll(); };
  document.head.appendChild(script);
}

document.addEventListener('DOMContentLoaded', loadD3AndInit);

document.createElement('script') でscriptタグを作り、document.head.appendChild で動的にheadに追加する。これならSWELLのフィルターをすり抜けられる。

つまずきポイント: DOMContentLoaded の前に loadD3AndInit() を呼ぶと、まだ描画対象のdivが存在しないため d3.select("#chart")null を返す。必ずDOM構築後に実行する。


Step 2: &&演算子を使わない

WordPressのコンテンツフィルターが &&&#038;&#038; というHTMLエンティティに自動変換する。これがJavaScriptとして実行されると &#038;&#038; は文法エラーになり、スクリプト全体が止まる。

NG例(WordPressが変換してJSエラーになる):

if (a && b) {
  doSomething();
}

OK例(三項演算子でネスト):

// 三項演算子で書く
const result = a ? (b ? doSomething() : null) : null;

// またはネストifで書く
if (a) {
  if (b) {
    doSomething();
  }
}

つまずきポイント: &&|| に混同しないこと。|| は変換されない。また &&= の複合代入演算子も変換対象になるので注意。


Step 3: CSSをツール内にスコープする

WordPressにはもともとSWELLテーマのCSSが大量に読み込まれている。グローバルセレクタ(body {}h2 {} 等)を書くとSWELLのスタイルを上書きしてサイト全体のレイアウトが崩れる。

NG例(SWELLを破壊する):

* { box-sizing: border-box; }
body { background: #0f0e17; }
h2 { margin: 0; color: white; }

OK例(ラッパーでスコープ):

.aji-art-wrap * { box-sizing: border-box; }
.aji-art-wrap h2 { margin: 0; color: #e0e0e0; }

ツール全体を <div class="aji-art-wrap"> で囲み、全CSSセレクタに .aji-art-wrap を先頭に付ける。

ポイント: クラス名は他のツールと衝突しないよう aji-{ツールID}- プレフィックスを使うのが実務上の慣習になっている。


Step 4: D3のタイムスケールを設定する

時系列の横軸を作る。D3の scaleTime()Date オブジェクトを受け取る。

// 日付文字列を Date オブジェクトにパースする
const parseDate = d3.timeParse("%Y-%m");

// NG: new Date("2024-08") はUTC解釈でタイムゾーンズレが起きることがある
// const date = new Date("2024-08");

// OK: d3.timeParse で明示的にパース
const date = parseDate("2024-08"); // Mon Aug 01 2024 00:00:00 (ローカルタイム)

// スケールの設定
const xScale = d3.scaleTime()
  .domain([parseDate("2016-01"), parseDate("2028-12")])
  .range([0, innerWidth]);

つまずきポイント: new Date("2024-08") は環境によって UTC として解釈されるため、日本(UTC+9)では 2024年7月31日 09:00:00 になる。表示は問題ないように見えても、マーカーの位置が微妙にズレる。d3.timeParse を使えばローカルタイムで確実にパースされる。


Step 5: スマホ横スクロールに対応する

年表の横軸が2016〜2028の12年分なので、スマホ画面(375px)には収まらない。横スクロールで対応する。

/* スクロールコンテナ */
.aji-art-timeline-outer {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch; /* iOS慣性スクロール */
  width: 100%;
}

/* SVGの最小幅を保証 */
.aji-art-timeline-svg-wrap {
  min-width: 640px;
}

スマホでは年ラベルが多すぎると重なる。最初・中間・最後の3点だけ表示する。

const isMobile = window.innerWidth <= 767;
const years = d3.range(2016, 2029); // [2016, 2017, ..., 2028]

const tickValues = isMobile
  ? [years[0], years[Math.floor((years.length - 1) / 2)], years[years.length - 1]]
  : years;

const xAxis = d3.axisBottom(xScale)
  .tickValues(tickValues.map(y => new Date(y, 0)));

つまずきポイント: 横スクロールコンテナのスタイルを親要素に当てること。SVG自体に overflow を設定しても横スクロールは効かない。


Step 6: タッチイベントでパネルを開閉する

スマホではhoverが使えないので、マーカーをタップすると画面下部に詳細パネルが出る仕組みにした。

marker.on('touchstart', function(e) {
  e.preventDefault();
  e.stopPropagation(); // ← これが超重要
  showPanel(event);
});

// パネルの外をタップしたら閉じる
document.addEventListener('touchstart', function() {
  hidePanel();
});

つまずきポイント: e.stopPropagation() を書かないと、マーカーのタッチイベントが document まで伝播して hidePanel() が即座に呼ばれる。パネルが開いた瞬間に閉じるという謎バグになる。e.preventDefault()e.stopPropagation() は必ずセットで書く。


つまずきポイントまとめ

  • && がJSエラーになる: WordPressのコンテンツフィルターが &#038;&#038; に変換する。三項演算子かネストifで回避
  • CDN script タグが動かない: SWELL/WordPressはコンテンツ内の <script src> を無視する。document.createElement で動的ロードに切り替える
  • タイムゾーンズレ: new Date("2024-08") はUTC解釈になる。d3.timeParse("%Y-%m") を使う
  • タッチしたらパネルが即閉じる: e.stopPropagation() の書き忘れ。e.preventDefault() とセットで必ず書く
  • グローバルCSS: body {}h2 {} を書くとSWELLのテーマが壊れる。ラッパークラスでスコープする

まとめ

  • WordPressでD3.jsを動かすには「CDN動的ロード」「&&回避」「CSSスコープ」の3点が必須
  • D3の時系列データは d3.timeParse() で明示的にパースする
  • 横スクロール年表はコンテナに overflow-x: auto を設定し、SVGに min-width を指定する
  • タッチイベントの e.stopPropagation() は省略しない
  • スマホの年ラベル間引きは「最初・中間・最後の3点」パターンが実用的

実装したツールはこちら → AI規制・法律タイムライン 2026

AI Japan Indexの全ツール一覧 → https://gozen-ai.com/

1
1
1

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
1
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?