採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vue — vue-chartjs でデータ可視化

テクノロジ系 / L15 data-visualization — vue-chartjs でデータ可視化

Vue — vue-chartjs でデータ可視化

モジュール明示登録と再利用可能なチャート部品

data-visualization / ch01

このチャプターの到達ゴール

翌日できること
必要な chart.js モジュールを ChartJS.register で明示登録してチャートを描ける
register を1つ外すと描画が消えることを実機で確認し、何を登録すべきか tree-shaking の観点で言える
labels / dataprops 化して、同じチャート部品を別データで再利用できる
dataoptions を分離し、色を CSS 変数に逃がしてテーマ連動を壊さない

今日の題材:学習スキル自己評価ダッシュボード

5 分野(読解・実装・テスト・設計・運用)の理解度を レーダーチャートで可視化します。

データは架空。たとえばこんな配列です。

const labels = ['読解', '実装', 'テスト', '設計', '運用']
const scores = [4, 3, 2, 4, 3] // 5段階の自己評価

ゴールは「この5つの数字を、誰かに見せられる図にする」こと。

そして 同じ部品を月別売上の棒グラフにも使い回せる状態を目指します。

Part A — なぜ「明示登録」が要るのか

まず動かしてみる(完成版)

<script setup lang="ts">
import { Radar } from 'vue-chartjs'
import {
  Chart as ChartJS,
  RadialLinearScale, PointElement, LineElement,
  Filler, Tooltip, Legend,
} from 'chart.js'

ChartJS.register(
  RadialLinearScale, PointElement, LineElement,
  Filler, Tooltip, Legend,
)

const data = {
  labels: ['読解', '実装', 'テスト', '設計', '運用'],
  datasets: [{ label: '今月', data: [4, 3, 2, 4, 3] }],
}
</script>

<template>
  <Radar :data="data" />
</template>

これでレーダーチャートが描画されます。注目は ChartJS.register(...) の行です。

chart.js は「全部入り」ではない

昔のグラフライブラリは、import した瞬間に全機能が付いてきました。

chart.js は逆で、使う部品だけを自分で登録する設計です。

graph LR
  ALL[chart.js<br/>全機能] -->|tree-shaking| BUNDLE[実際のバンドル<br/>= register したものだけ]

理由は tree-shaking

「使っていないコードはビルド時に削ぎ落とす」仕組みに乗せるため、

chart.js は「何を使うか」をコード上で明示させます。

登録していない部品は、バンドルに含まれず存在しないも同然になります。

レーダーチャートに要る部品

レーダー1枚を描くだけでも、複数の部品の組み合わせです。

登録するもの役割
RadialLinearScale中心から放射状に伸びる軸(レーダー特有)
PointElement各頂点の点
LineElement頂点をつなぐ線
Filler線の内側の塗りつぶし
Tooltipホバー時の数値表示
Legend凡例(label の表示)

棒グラフなら BarElementCategoryScale / LinearScale

というように チャートの種類ごとに必要な部品が違います

register は「最初に1回」

ChartJS.register(...) は、アプリ起動時に1回効けば十分です。

  • 部品ごとのコンポーネントに毎回書く必要はない
  • 共通の入口(main.ts や専用の登録ファイル)にまとめるのが定石
  • 「描けない」と思ったら、まず その部品を register したかを疑う

> このトピックでは流れを追いやすいよう、各サンプルの <script setup> 内に

> register を書いています。実プロジェクトでは1箇所に集約してください。

Part B — 再利用可能なチャート部品を props で

べた書きの何が困るのか

さきほどの完成版は、データがコンポーネントの中に埋まっています

<script setup lang="ts">
const data = {
  labels: ['読解', '実装', 'テスト', '設計', '運用'],
  datasets: [{ label: '今月', data: [4, 3, 2, 4, 3] }],
}
</script>

これだと「先月分のレーダー」をもう1枚出したいとき、

同じファイルをまるごとコピーするしかありません。

月別売上の棒グラフにも当然使い回せない。

labels と data を props で受け取る

データを外から渡せる形にします。defineProps<Props>() で型を付けます。

<!-- SkillRadar.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { Radar } from 'vue-chartjs'
// (register は入口ファイルで実施済みとする)

interface Props {
  labels: string[]
  values: number[]
  seriesLabel?: string
}
const props = defineProps<Props>()

const chartData = computed(() => ({
  labels: props.labels,
  datasets: [{ label: props.seriesLabel ?? 'スコア', data: props.values }],
}))
</script>

<template>
  <Radar :data="chartData" />
</template>

同じ部品を2箇所で使い回す

呼び出し側は、データを渡すだけ。

<template>
  <!-- 今月 -->
  <SkillRadar :labels="fields" :values="thisMonth" series-label="今月" />
  <!-- 先月(同じ部品・別データ) -->
  <SkillRadar :labels="fields" :values="lastMonth" series-label="先月" />
</template>

部品の中身は一切書き換えていません。

これが「再利用可能なチャート部品」です。観測できるゴールは

「同じチャート部品を、別データで2箇所に置けること」。

props で来た配列は信用しすぎない

labels が5個なのに values が4個だと、レーダーは黙って崩れます。

API が不揃いの配列長で返してくることは普通に起こります。

const isValid = computed(() =>
  props.labels.length === props.values.length && props.values.length > 0
)
<template>
  <Radar v-if="isValid" :data="chartData" />
  <p v-else class="chart-warn">表示できるデータがありません</p>
</template>

「描けない入力を、描く前に弾く」。これも部品の責務です。

Part C — 登録漏れの罠を実機で体験する

いちばん効く学び方は「壊して観察する」

ここがこのトピックの核です。完成版の register から、1行だけコメントアウトします。

ChartJS.register(
  RadialLinearScale, PointElement, LineElement,
  // Filler,        ← これを外してみる
  Tooltip, Legend,
)

そして画面を更新してみてください。

何が起きるか

外したもの画面の症状
RadialLinearScale何も描画されない(軸がないので描きようがない)
Filler線は出るが内側の塗りが消える
Tooltipホバーしても数値が出ない

怖いのは RadialLinearScale を外したとき

赤いエラーが画面いっぱいに出るわけではなく、

ただ白い四角が残るだけのことがあります。コンソールに小さく

未登録の警告が出るだけで、見落としやすい。

「エラーが出ないバグ」がいちばん危険

graph TD
  A[register 漏れ] --> B{症状}
  B --> C[画面が真っ白]
  B --> D[一部だけ欠ける]
  C --> E[原因がコードに現れない<br/>= 探しにくい]
  D --> E
  • 例外で止まってくれれば、スタックトレースが場所を教えてくれる
  • register 漏れは静かに失敗するので、原因にたどり着きにくい
  • だから「描けないときは、まず register を疑う」を手癖にする

観測できるゴール:「register を1つコメントアウトすると描画が消える」を自分の目で確認したこと。

直し方は単純

外した部品を register に戻すだけ。

ChartJS.register(
  RadialLinearScale, PointElement, LineElement,
  Filler,            // ← 戻す
  Tooltip, Legend,
)

大事なのは手順ではなく、「描けない=登録漏れを最初に疑う」という反射

身についたかどうかです。一度自分で壊しておくと、本番でハマる時間が桁違いに短くなります。

Part D — data / options 分離と色の CSS 変数化

options を全部のコンポーネントにベタ書きしない

options(軸の最小・最大、レスポンシブ設定など)を各部品に直書きすると、

同じ設定が散らばって、変えるたびに全部直すことになります。

// ❌ チャートごとに毎回これを書いている
const options = {
  responsive: true,
  maintainAspectRatio: false,
  scales: { r: { min: 0, max: 5, beginAtZero: true } },
}

レーダー3枚あれば、この塊が3回コピペされる。

1箇所だけ直し忘れて、図ごとに目盛りが違う、が定番の事故です。

data と options は役割が違う

中身変わる頻度
datalabels と数値(何を描くか)データ取得のたびに変わる
options軸範囲・レスポンシブ(どう描くか)ほぼ固定
  • data は props で外から渡す(Part B)
  • options部品の中に1つだけ持つ、または共通ファイルから import する

「変わるもの」と「変わらないもの」を分けるのが分離の本質です。

responsive と maintainAspectRatio

レイアウト崩れの原因は、たいていこの2つの設定です。

const options = {
  responsive: true,           // 親要素のサイズに追従
  maintainAspectRatio: false, // 縦横比の固定を解除 = 親の高さに従う
}
  • maintainAspectRatio: true(既定)だと、横幅に対して高さが勝手に決まる
  • ダッシュボードで高さを CSS で決めたいなら false にして、

ラッパー要素に明示的な高さを与える

<template>
  <div class="chart-box"><Radar :data="d" :options="o" /></div>
</template>
<style scoped>
.chart-box { height: 320px; }
</style>

固定サイズ版とレスポンシブ版を並べて見る

崩れは「比べる」と一目で分かります。

<template>
  <!-- 固定サイズ:画面を狭めてもサイズが変わらない -->
  <div style="width: 300px; height: 300px;">
    <Radar :data="d" :options="{ responsive: false }" />
  </div>
  <!-- レスポンシブ:親に追従して縮む -->
  <div class="chart-box">
    <Radar :data="d" :options="{ responsive: true, maintainAspectRatio: false }" />
  </div>
</template>

ブラウザ幅を狭めて、片方だけ崩れる/追従するのを確認してください。

色をハードコードしない

// ❌ 16進をベタ書き:テーマを変えると全チャートを手修正
datasets: [{ data, backgroundColor: '#4f86c6', borderColor: '#2c5d9b' }]

色を直書きすると、ダークモードやブランド色変更のたびに

全チャートを探して回ることになります。CSS 変数に逃がします。

:root {
  --chart-accent: #4f86c6;
  --chart-fill: rgba(79, 134, 198, 0.25);
}
const css = getComputedStyle(document.documentElement)
const accent = css.getPropertyValue('--chart-accent').trim()
const fill = css.getPropertyValue('--chart-fill').trim()

datasets: [{ data, borderColor: accent, backgroundColor: fill }]

--chart-accent を1行変えれば、全チャートの色が揃って変わる。これが合格基準です。

まとめ — 再利用できるチャート部品の地図

graph TD
  REG[入口で register<br/>使う部品を明示登録] --> CMP[チャート部品]
  PROPS[props: labels / values] --> CMP
  OPT[options: 軸・responsive<br/>= 部品内に1つ] --> CMP
  VAR[CSS変数: 色] --> CMP
  CMP --> V1[今月レーダー]
  CMP --> V2[先月レーダー]
  CMP --> V3[棒グラフへ転用]

このチャプターで学んだこと

  • chart.js は tree-shaking 前提。使う部品を register しないと静かに描画されない
  • 描けないときは、エラーを探す前にまず register 漏れを疑う
  • labels / values を props 化すれば、同じ部品を別データで使い回せる
  • data(何を)と options(どう)を分け、色は CSS 変数でテーマ連動を守る

演習(別紙 hands-on)

「汚れた starter」をリファクタする:

  1. register を1つ外して描画が消えることを実機確認→ 戻して復旧
  2. べた書きのチャートを labels / valuesprops 化し、配列長バリデーションを追加
  3. 同じ部品を別データで2箇所に再利用(今月/先月)
  4. 固定サイズ版とレスポンシブ版を並べ、崩れを比較して修正
  5. ハードコードされた色を CSS 変数に巻き取り、1行変更で全チャートが変わることを確認