この記事でできること
- GEO/LLMO/AIOのスコアリングロジック(6カテゴリ×各5問、100点満点)の実装手順を再現できる
- D3.jsのレーダーチャートをWordPress/SWELL環境で動かすための設計パターンを学べる
- WordPress特有のJS制約(
&&禁止・<script src>無視)への対処法を理解できる
完成形のツールはこちらで公開中: SEO/GEO/LLMOスコアチェッカー(AI Japan Index)
環境・前提
- JavaScript: ES5互換(
const/let使用可、&&禁止はWordPress固有制約) - D3.js: v7(動的ロード方式)
- デプロイ先: WordPress / SWELLテーマ(HTMLブロック埋め込み)
- バックエンド: なし(フロントエンド完結)
- 外部API: 使用しない
完成形
6カテゴリ(コンテンツ品質・構造化データ・E-E-A-T・AI引用可能性・鮮度・UX)の30問に「はい/一部/いいえ」で回答すると、100点満点スコア・D3.jsレーダーチャート・A〜Eグレード・改善提案が表示される。動作確認はこちらでできる。
手順
Step 1: データ構造を定義する
まず6カテゴリ30問のデータをJSONとして定義する。各質問は id, categoryId, text, points を持つ。
var QUESTIONS = [
// カテゴリA: コンテンツ品質(合計25点)
{
id: 'A1',
categoryId: 'content',
text: 'ページ冒頭200語以内でメインの質問に直接回答しているか',
points: 5
},
{
id: 'A2',
categoryId: 'content',
text: '150〜200語ごとに統計データ(出典付き)を含んでいるか',
points: 5
},
// ... A3, A4, A5
// カテゴリB: 構造化データ・技術基盤(合計20点)
{
id: 'B1',
categoryId: 'technical',
text: 'JSON-LD Schema Markup(Article/FAQ/HowTo等)を実装しているか',
points: 4
},
// ... B2〜B5
// カテゴリC〜F: 同様に定義
];
var CATEGORIES = [
{ id: 'content', label: 'コンテンツ品質', maxScore: 25 },
{ id: 'technical', label: '構造化データ・技術基盤', maxScore: 20 },
{ id: 'eeat', label: 'E-E-A-T・権威性', maxScore: 20 },
{ id: 'citation', label: 'AI引用可能性', maxScore: 15 },
{ id: 'freshness', label: '鮮度・更新頻度', maxScore: 10 },
{ id: 'ux', label: 'モバイル・UX', maxScore: 10 }
];
ポイント: && を使う場合はWordPressで破壊されるため、後述のStep 4で対策する。
Step 2: スコア計算ロジックを実装する
3段階回答(はい=1.0、一部=0.5、いいえ=0)でスコアを計算する。
function calcCategoryScore(categoryId, answers) {
var questions = QUESTIONS.filter(function(q) {
return q.categoryId === categoryId;
});
var score = 0;
var maxScore = 0;
questions.forEach(function(q) {
maxScore += q.points;
var ans = answers[q.id];
if (ans === 'yes') {
score += q.points;
} else if (ans === 'partial') {
score += q.points * 0.5;
}
// 'no' は 0点(加算なし)
});
return { score: score, max: maxScore };
}
function calcTotalScore(answers) {
var total = 0;
CATEGORIES.forEach(function(cat) {
var result = calcCategoryScore(cat.id, answers);
total += result.score;
});
return Math.round(total);
}
Step 3: グレード判定を実装する
function getGrade(totalScore) {
if (totalScore >= 85) { return { grade: 'A', label: 'AI検索最適化済み' }; }
if (totalScore >= 70) { return { grade: 'B', label: 'AI対応済み' }; }
if (totalScore >= 55) { return { grade: 'C', label: '改善必要' }; }
if (totalScore >= 40) { return { grade: 'D', label: '大幅改善必要' }; }
return { grade: 'E', label: '未対応' };
}
Step 4: D3.jsをWordPress対応で動的ロードする
つまずきポイント1: <script src="https://cdn.d3js.org/..."> を投稿コンテンツ内に書いても、SWELLテーマが読み込みを無視する。
OK: 動的ロード方式(D3ブートストラップ)
function loadD3AndInit() {
if (typeof d3 !== 'undefined') {
initAll();
return;
}
var script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js';
script.onload = function() { initAll(); };
document.head.appendChild(script);
}
document.addEventListener('DOMContentLoaded', function() {
loadD3AndInit();
});
つまずきポイント2: && 演算子の禁止。WordPressのwpautopフィルターが && を && にHTMLエンティティ変換し、JSが構文エラーで停止する。
// NG: WordPressで破壊される
if (a && b) { doSomething(); }
// OK: ネストifで書き換え
if (a) {
if (b) { doSomething(); }
}
Step 5: D3.jsレーダーチャートを描画する
function drawRadar(svgEl, categoryScores) {
var n = CATEGORIES.length;
var angleSlice = (Math.PI * 2) / n;
var radius = 120; // レーダー半径(px)
var g = d3.select(svgEl)
.append('g')
.attr('transform', 'translate(' + (svgEl.clientWidth / 2) + ',' + (svgEl.clientHeight / 2) + ')');
// 背景グリッド線(25/50/75/100のレベル)
[0.25, 0.5, 0.75, 1.0].forEach(function(level) {
var points = CATEGORIES.map(function(cat, i) {
var angle = angleSlice * i - Math.PI / 2;
return [
Math.cos(angle) * radius * level,
Math.sin(angle) * radius * level
];
});
g.append('polygon')
.attr('points', points.map(function(p) { return p[0] + ',' + p[1]; }).join(' '))
.attr('fill', 'none')
.attr('stroke', '#2a2a4a')
.attr('stroke-width', 1);
});
// 軸線
CATEGORIES.forEach(function(cat, i) {
var angle = angleSlice * i - Math.PI / 2;
g.append('line')
.attr('x1', 0).attr('y1', 0)
.attr('x2', Math.cos(angle) * radius)
.attr('y2', Math.sin(angle) * radius)
.attr('stroke', '#2a2a4a')
.attr('stroke-width', 1);
});
// スコアの多角形
var dataPoints = CATEGORIES.map(function(cat, i) {
var angle = angleSlice * i - Math.PI / 2;
var r = (categoryScores[cat.id].score / categoryScores[cat.id].max) * radius;
return [Math.cos(angle) * r, Math.sin(angle) * r];
});
g.append('polygon')
.attr('points', dataPoints.map(function(p) { return p[0] + ',' + p[1]; }).join(' '))
.attr('fill', 'rgba(147, 105, 168, 0.3)')
.attr('stroke', '#9369a8')
.attr('stroke-width', 2);
}
Step 6: カテゴリラベルをHTMLで配置する
つまずきポイント3: SVGの <text> でカテゴリ名(日本語6〜8文字)を描くと、スマホ幅で見切れる。
OK: HTMLのdivをposition absoluteで配置する
function placeRadarLabels(containerEl, svgEl, radius) {
var n = CATEGORIES.length;
var angleSlice = (Math.PI * 2) / n;
var svgRect = svgEl.getBoundingClientRect();
var containerRect = containerEl.getBoundingClientRect();
var cx = svgRect.left - containerRect.left + svgEl.clientWidth / 2;
var cy = svgRect.top - containerRect.top + svgEl.clientHeight / 2;
// 既存ラベルをクリア
var existing = containerEl.querySelectorAll('.radar-label');
existing.forEach(function(el) { el.remove(); });
CATEGORIES.forEach(function(cat, i) {
var angle = angleSlice * i - Math.PI / 2;
var x = cx + Math.cos(angle) * (radius + 28);
var y = cy + Math.sin(angle) * (radius + 28);
var label = document.createElement('div');
label.className = 'radar-label';
label.textContent = cat.label;
label.style.cssText = [
'position:absolute',
'left:' + x + 'px',
'top:' + y + 'px',
'transform:translate(-50%,-50%)',
'font-size:11px',
'color:#a7a9be',
'white-space:nowrap',
'max-width:80px',
'word-break:keep-all',
'overflow-wrap:break-word',
'text-align:center'
].join(';');
containerEl.appendChild(label);
});
}
Step 7: 動作確認
- ブラウザで開き、30問全てに「はい」と回答 → スコアが100点になるか確認する
- 全て「いいえ」と回答 → スコアが0点・グレードEになるか確認する
- スマホ幅(375px)でカテゴリラベルが枠内に収まっているか確認する
- WordPress環境にデプロイ後、DevToolsのConsoleにJSエラーが出ていないか確認する
つまずきポイントまとめ
-
<script src="CDN">がWordPress/SWELLで無視される:document.createElement('script')での動的ロードに変更する -
&&がWordPressに破壊される: ネストifまたは三項演算子に書き換える(全JSファイルでgrep必須) - SVGテキストがスマホで見切れる: 日本語5文字超のラベルはHTMLのdivで代替する
-
<!-- wp:html -->ブロックを使う: フルHTMLドキュメント(DOCTYPE)形式だとwpautopがscriptタグ内に<p>を挿入してJSが壊れる
まとめ
今回学んだこと:
- D3.jsレーダーチャートの基本実装(グリッド・軸線・データポリゴン・ラベル配置)
- WordPress/SWELL固有の3つのJS制約とその回避パターン
- 6カテゴリスコアリングの配点設計における一次ソース(Princeton GEO研究等)の使い方
- フロントエンド完結でセルフ診断ツールを作る場合の設計トレードオフ
関連ツール・データ: AI Japan Index — 全ツール一覧
