この記事でできること
- D3.js v7でレーダーチャート(蜘蛛の巣グラフ)を作れる
- WordPressにD3.jsチャートを埋め込むときの「ありがちな罠」を回避できる
- 8問のステップフォームをVanilla JSで実装できる
- 実際に動作するツールは AIリスキリング適性診断 2026 で確認できる
環境・前提
- ブラウザ: モダンブラウザ(Chrome/Firefox/Safari の最新版)
- ライブラリ: D3.js v7(CDNから動的ロード)
- CMS: WordPress(SWELLテーマ)—
<!-- wp:html -->ブロック内に全コード - 言語: Vanilla JS(ES2020)+ HTML + CSS
- 追加依存関係: なし(ビルドプロセスなし)
完成形
8問に答えると、こんなものが表示される。
- 円弧型ゲージ(0〜100点のスコア)
- 6軸のレーダーチャート(AIリテラシー・デジタルスキル・ビジネス・コミュ・専門性・学習意欲)
- 診断タイプ(5種類)
- 優先リスキリングTOP3 + おすすめ学習リソースのリンク
手順
Step 1: D3.jsをWordPressに読み込む
NG例(WordPressでは動かない):
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
WordPressのSWELLテーマは、投稿コンテンツ内の<script src="...">タグを無視する。外部スクリプトは読み込まれない。
OK例(動的ロードパターン):
function loadD3(callback) {
if (typeof d3 !== 'undefined') {
callback();
return;
}
var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
s.onload = callback;
document.head.appendChild(s);
}
loadD3(function() {
initDiagnostic(); // D3が読み込まれてから初期化
});
typeof d3でロード済みかチェック → 未ロードならcreateElement('script')で動的に読み込む → onloadで初期化関数を呼ぶ。このパターンだとWordPressでもD3.jsが使える。
つまずきポイント: onloadの前にinitDiagnostic()を呼んでしまい、「d3 is not defined」エラーが出た。非同期処理の順番の問題。
Step 2: &&演算子を使わない
NG例(WordPressがJSを壊す):
if (answers.q4 === 'daily' && answers.q1 === 'engineer') {
score += 2;
}
WordPressのコンテンツフィルターが&&を&&(HTMLエンティティ)に変換する。これで構文エラーになり、スクリプト全体が止まる。
OK例(ネストifに変換する):
if (answers.q4 === 'daily') {
if (answers.q1 === 'engineer') {
score += 2;
}
}
||も同様の問題が起きる場合がある。三項演算子a ? b : cか、ネストifに書き直すのが安全。
つまずきポイント: 最初は「なぜ動かないの?」とずっとD3.jsを疑っていた。実際の原因は&&だったので、DevToolsのConsoleでエラーメッセージを確認するのが最初のステップ。
Step 3: ステップフォームの状態を正しく持つ
8問を1問ずつ表示するステップフォームを作るとき、「戻る」ボタンで前の回答を保持するには状態管理が必要だ。
NG例(ステップ番号だけ管理):
let currentStep = 0;
function goBack() {
currentStep--;
renderStep(currentStep); // 回答が消える
}
OK例(回答も一緒に持つ):
var state = {
step: 0,
answers: {}
};
function selectAnswer(questionKey, value) {
state.answers[questionKey] = value; // 回答を保存
}
function goBack() {
state.step--;
renderStep(state.step, state.answers); // 保存済み回答を渡す
}
function renderStep(stepNum, savedAnswers) {
// savedAnswersに回答があればラジオボタンをchecked状態で描画
var saved = savedAnswers['q' + (stepNum + 1)];
if (saved) {
var radio = document.querySelector('input[value="' + saved + '"]');
if (radio) radio.checked = true;
}
}
ポイント: 状態をvar state = { step, answers }という1つのオブジェクトにまとめておくと、どこからでも参照しやすい。
Step 4: レーダーチャートを描く
D3.jsでレーダーチャートを描くには、「各軸の角度と値から座標を計算してpolygonで結ぶ」のが基本だ。
var axes = ['aiLiteracy', 'digital', 'business', 'communication', 'expertise', 'adaptability'];
var maxValue = 20; // 各軸の最大値
var cx = innerW / 2;
var cy = innerH / 2;
var innerR = Math.min(cx, cy) - 20;
var angleStep = (2 * Math.PI) / axes.length;
// 各軸の先端座標を計算
function getCoord(axisIndex, value) {
var angle = axisIndex * angleStep - Math.PI / 2; // 12時スタート
var r = (value / maxValue) * innerR;
return {
x: cx + r * Math.cos(angle),
y: cy + r * Math.sin(angle)
};
}
// スコアのポリゴン(塗り面)を描画
var points = axes.map(function(key, i) {
var coord = getCoord(i, scores[key]);
return coord.x + ',' + coord.y;
}).join(' ');
svg.append('polygon')
.attr('points', points)
.attr('fill', 'rgba(var(--aji-accent-rgb), 0.2)')
.attr('stroke', 'var(--aji-accent)')
.attr('stroke-width', 2);
つまずきポイント: - Math.PI / 2を忘れると12時方向ではなく3時方向スタートになる。最初の軸が右側に来てレイアウトがおかしくなった。
Step 5: 軸ラベルはHTML divで配置する
SVG <text>でラベルを書くとスマホで文字が切れる。「コミュニケーション」「専門性深度」は7〜8文字あり、SVG textは自動改行しないからだ。
NG例(SVG textでラベル):
svg.append('text')
.attr('x', labelX)
.attr('y', labelY)
.text('コミュニケーション'); // スマホ幅で確実に切れる
OK例(HTML divをposition:absoluteで配置):
.radar-wrap {
position: relative; /* これが必要 */
}
.radar-label {
position: absolute;
font-size: 12px;
color: #a7a9be;
max-width: 90px;
word-break: keep-all;
overflow-wrap: break-word;
text-align: center;
transform: translate(-50%, -50%); /* 中央揃え */
}
var label = document.createElement('div');
label.className = 'radar-label';
label.style.left = labelX + 'px';
label.style.top = labelY + 'px';
// スマホでは短縮形を使う
var isMobile = window.innerWidth <= 767;
var shortNames = {
'AIリテラシー': 'AIリテ',
'コミュニケーション': 'コミュ',
'専門性深度': '専門性',
'学習適応力': '学習力'
};
label.textContent = isMobile ? (shortNames[axisName] || axisName) : axisName;
chartContainer.appendChild(label);
Step 6: スマホのツールチップは下部固定パネルにする
スマホでバーやポイントをタップしたとき、マウス追従型のツールチップは画面外に出てしまって読めない。
OK例(画面下部固定パネル):
.mobile-panel {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #1a1a2e;
border-top: 2px solid var(--aji-accent);
padding: 16px;
max-height: 40vh;
overflow-y: auto;
z-index: 10000;
display: none; /* 最初は非表示 */
}
element.addEventListener('touchstart', function(e) {
e.preventDefault();
e.stopPropagation(); // これがないと「パネル外タップ」と誤判定される
var panel = document.querySelector('.mobile-panel');
panel.innerHTML = '<strong>' + d.name + '</strong>: ' + d.value + '点';
panel.style.display = 'block';
});
// パネル外タップで閉じる
document.addEventListener('touchstart', function() {
var panel = document.querySelector('.mobile-panel');
if (panel) panel.style.display = 'none';
});
つまずきポイント: e.stopPropagation()を忘れると、要素タップ→パネル表示→即座に「パネル外タップ」と判定→パネルが消える、という謎の動作になる。
つまずきポイントまとめ
-
<script src="CDN">が動かない: WordPressはコンテンツ内の外部スクリプトタグを無視する。動的ロードパターンを使う -
&&でJSエラー: WordPressのフィルターが&&に変換する。ネストifか三項演算子に書き直す -
戻るボタンで回答が消える: 状態管理に回答も含める。
state.answersに蓄積する -
軸ラベルがスマホで切れる: SVG textではなくHTML divを
position: absoluteで配置する。word-break: keep-allを忘れずに -
タップしてもパネルがすぐ消える:
e.stopPropagation()が必要。イベントバブリングを止めないとdocumentのtouchstartハンドラが反応する
