はじめに (対象読者・この記事でわかること)
この記事は、Web開発に興味がある方や、データを視覚的に表現したいと考えている方を対象にしています。特に、JavaScriptの基本的な知識がある方を想定しています。 この記事を読むことで、インラインSVGを使って動的なレーダーチャートを実装する方法がわかります。また、データの更新に応じてチャートがリアルタイムで変化する仕組みの作り方も学べます。 最近、ユーザーインターフェースにおけるデータ可視化の重要性が高まっています。特に、複数の指標を一覧できるレーダーチャートは、スキル評価や製品比較などで有用です。本記事では、手軽に実装できるSVGベースのレーダーチャートの作り方を解説します。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 - HTML/CSSの基本的な知識 - JavaScriptの基本的な知識 - SVGの基本的な理解(必須ではありませんが、あると理解が深まります)
レーダーチャートの概要と必要性
レーダーチャート(レーダーグラフ、スパイダーチャートとも呼ばれる)は、複数の変量を放射状に配置したグラフで、各変量の値を頂点とする多角形でデータを表現します。この形式は、複数の項目を比較する際に特に有効で、スキル評価、製品比較、パフォーマンス分析など幅広い用途で利用されています。
Webアプリケーションでレーダーチャートを実装する方法はいくつかありますが、SVG(Scalable Vector Graphics)を使用する方法は以下のような利点があります。
- 高解像度での表示が可能で、拡大しても画質が劣化しない
- CSSやJavaScriptと連携しやすく、動的な更新が容易
- 軽量で、ブラウザネイティブでサポートされている
- アニメーション効果を追加しやすい
特にインラインSVGを使用することで、HTML内に直接記述できるため、外部ファイルの読み込みが不要になり、シンプルな実装が可能です。
インラインSVGでレーダーチャートを実装する方法
それでは、実際にインラインSVGでレーダーチャートを実装する手順を解説します。ここでは、基本的なレーダーチャートから始め、徐々に機能を追加していく形で進めていきます。
ステップ1:HTMLの基本構造の作成
まずは、レーダーチャートを表示するための基本的なHTML構造を作成します。以下のように、SVGを表示するためのコンテナを用意します。
Html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>レーダーチャートの実装</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="container"> <h1>スキル評価レーダーチャート</h1> <div id="radar-chart"></div> <div class="controls"> <button id="update-data">データを更新</button> </div> </div> <script src="script.js"></script> </body> </html>
ステップ2:SVGでレーダーチャートの土台を作成
次に、JavaScriptでSVGの基本構造を作成し、レーダーチャートの土台となるグリッド線を描画します。
Javascript// script.js document.addEventListener('DOMContentLoaded', function() { // レーダーチャートの設定 const config = { centerX: 200, // 中心X座標 centerY: 200, // 中心Y座標 radius: 150, // 半径 levels: 5, // レベル数 angleLines: 6, // 角度線の数 maxValue: 100 // 最大値 }; // データ const skills = [ { name: 'JavaScript', value: 85 }, { name: 'HTML/CSS', value: 90 }, { name: 'React', value: 75 }, { name: 'Node.js', value: 70 }, { name: 'UI/UXデザイン', value: 65 }, { name: 'コミュニケーション', value: 80 } ]; // SVGコンテナの取得 const radarChart = document.getElementById('radar-chart'); // SVG要素の作成 const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '400'); svg.setAttribute('height', '400'); svg.setAttribute('viewBox', '0 0 400 400'); // グループ要素の作成 const gridGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); gridGroup.setAttribute('class', 'grid'); // データ領域のグループ const dataGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); dataGroup.setAttribute('class', 'data'); // ラベルのグループ const labelGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g'); labelGroup.setAttribute('class', 'labels'); // グリッド線の描画 drawGridLines(gridGroup, config); // データ領域の描画 drawDataArea(dataGroup, skills, config); // ラベルの描画 drawLabels(labelGroup, skills, config); // グループをSVGに追加 svg.appendChild(gridGroup); svg.appendChild(dataGroup); svg.appendChild(labelGroup); // SVGをコンテナに追加 radarChart.appendChild(svg); }); // グリッド線の描画関数 function drawGridLines(container, config) { const { centerX, centerY, radius, levels, angleLines } = config; // レベルごとの円を描画 for (let i = 1; i <= levels; i++) { const levelRadius = (radius / levels) * i; const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', centerX); circle.setAttribute('cy', centerY); circle.setAttribute('r', levelRadius); circle.setAttribute('fill', 'none'); circle.setAttribute('stroke', '#ddd'); circle.setAttribute('stroke-width', '1'); container.appendChild(circle); } // 角度線を描画 for (let i = 0; i < angleLines; i++) { const angle = (Math.PI * 2 / angleLines) * i - Math.PI / 2; const x = centerX + Math.cos(angle) * radius; const y = centerY + Math.sin(angle) * radius; const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); line.setAttribute('x1', centerX); line.setAttribute('y1', centerY); line.setAttribute('x2', x); line.setAttribute('y2', y); line.setAttribute('stroke', '#ddd'); line.setAttribute('stroke-width', '1'); container.appendChild(line); } }
ステップ3:データ領域の描画
次に、実際のデータをレーダーチャート上に表示する部分を実装します。各スキルの値に基づいて多角形を描画します。
Javascript// データ領域の描画関数 function drawDataArea(container, skills, config) { const { centerX, centerY, radius, maxValue } = config; // ポイントを格納する配列 const points = []; // 各スキルのポイントを計算 skills.forEach((skill, index) => { const angle = (Math.PI * 2 / skills.length) * index - Math.PI / 2; const distance = (skill.value / maxValue) * radius; const x = centerX + Math.cos(angle) * distance; const y = centerY + Math.sin(angle) * distance; points.push(`${x},${y}`); }); // 多角形の作成 const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon'); polygon.setAttribute('points', points.join(' ')); polygon.setAttribute('fill', 'rgba(54, 162, 235, 0.2)'); polygon.setAttribute('stroke', 'rgba(54, 162, 235, 1)'); polygon.setAttribute('stroke-width', '2'); container.appendChild(polygon); } // ラベルの描画関数 function drawLabels(container, skills, config) { const { centerX, centerY, radius } = config; skills.forEach((skill, index) => { const angle = (Math.PI * 2 / skills.length) * index - Math.PI / 2; const labelRadius = radius + 20; // ラベルの位置 const x = centerX + Math.cos(angle) * labelRadius; const y = centerY + Math.sin(angle) * labelRadius; // テキスト要素の作成 const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); text.setAttribute('x', x); text.setAttribute('y', y); text.setAttribute('text-anchor', 'middle'); text.setAttribute('dominant-baseline', 'middle'); text.setAttribute('font-size', '12'); text.setAttribute('fill', '#333'); text.textContent = skill.name; container.appendChild(text); }); }
ステップ4:スタイルの適用
次に、CSSで見た目を整えます。以下のようにスタイルシートを作成します。
Css/* styles.css */ body { font-family: 'Arial', sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; } .container { max-width: 800px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } h1 { color: #333; text-align: center; margin-bottom: 30px; } #radar-chart { display: flex; justify-content: center; margin-bottom: 20px; } .controls { text-align: center; } button { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } button:hover { background-color: #2980b9; }
ステップ5:データの更新機能の追加
最後に、ボタンクリック時にデータを更新する機能を実装します。これにより、動的なレーダーチャートが完成します。
Javascript// script.jsの末尾に追加 // データ更新ボタンのイベントリスナー document.getElementById('update-data').addEventListener('click', function() { // ランダムなデータを生成 const updatedSkills = skills.map(skill => ({ ...skill, value: Math.floor(Math.random() * 100) })); // 既存のデータ領域を削除 const dataGroup = document.querySelector('.data'); dataGroup.innerHTML = ''; // 新しいデータ領域を描画 drawDataArea(dataGroup, updatedSkills, config); });
ハマった点やエラー解決
レーダーチャートの実装では、いくつかの点でハマりました。
-
角度の計算ミス 初期段階では、角度の計算に誤りがあり、レーダーチャートが正しく表示されませんでした。特に、SVGの座標系ではY軸が下向きであるため、通常の数学的な座標系と異なることに気づくまで時間がかかりました。解決策として、すべての角度計算で
- Math.PI / 2を加えることで、12時の方向を基準点にすることで正しい表示が得られるようになりました。 -
データ更新時の再描画 データを更新する際に、最初はSVG全体を再生成しようとしていましたが、これではパフォーマンスが低下しました。解決策として、データ領域のみを削除して再描画するように変更し、スムーズな更新を実現しました。
-
ラベルの位置調整 ラベルの位置がチャートと重なってしまい、読みにくい問題がありました。解決策として、ラベルの位置を半径より少し外側に配置するように調整しました。
まとめ
本記事では、インラインSVGを使用して動的なレーダーチャートを実装する方法を解説しました。
- SVGの基本構造を理解し、グリッド線を描画する方法
- データに基づいて多角形を描画し、レーダーチャートを完成させる方法
- データ更新時にチャートを動的に変更する方法
- 見た目を整えるためのCSSの適用方法
この記事を通して、Webページでデータを視覚的に表現する一つの手法を学び、実際のプロジェクトでも応用できるようになったことと思います。今後は、アニメーション効果の追加や、複数のデータセットの比較表示機能など、さらに高度な機能の実装についても記事にする予定です。
参考資料
- MDN Web Docs - SVG
- W3C SVG Specification
- レーダーチャートの概要 - Wikipedia
- データ可視化のベストプラクティス - Data Visualization Society