Vue.js + Chart.js(vue-chartjs)を使用して、リアルタイム高度データの折れ線グラフを実装。
2025 年 7 月 11 日
src/
├── App.vue # 親コンポーネント(状態管理)
├── dashboard/
│ ├── Altimeter.vue # 高度データ取得・表示
│ └── AltimeterChart.vue # グラフ表示
└── devices/
└── ...
- Altimeter.vue → API からデータ取得
- Altimeter.vue →
emit('altitude-updated', data)で親に送信 - App.vue → データを受け取り、
altitudeLogs配列に蓄積 - App.vue →
:altitudeLogs="altitudeLogs"でチャートに props 渡し - AltimeterChart.vue → watch でリアクティブ更新
問題: 従来のdata()メソッドから<script setup>構文への書き換え
解決:
// 従来
export default {
data() {
return { count: 0 }
}
}
// <script setup>
const count = ref(0)問題: 折れ線グラフが表示されない
解決: LineElementとPointElementの登録を追加
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
PointElement,
LineElement,
CategoryScale,
LinearScale,
} from "chart.js";
ChartJS.register(
Title,
Tooltip,
Legend,
PointElement,
LineElement,
CategoryScale,
LinearScale
);問題: データが更新されてもグラフが変化しない
解決: watch でchartDataを新しいオブジェクトに置き換え
watch(
() => props.altitudeLogs,
(logs) => {
chartData.value = {
labels: logs.map((log) => log.received_time),
datasets: [
{
/* 新しいデータセット */
},
],
};
},
{ deep: true, immediate: true }
);問題: グラフ更新時のアニメーションが不要 解決: Chart.js オプションでアニメーション無効化
const chartOptions = ref({
animation: { duration: 0 },
responsiveAnimationDuration: 0,
});問題: グラフ領域が他のコンポーネントと大きさが合わない 解決:
- CSS Grid 設定:
grid-auto-rows: minmax(300px, 1fr) - Chart.js 設定:
maintainAspectRatio: false - コンテナー設定:
width: 100%; height: 100%
問題: 配列内の全ての値が同時に変化してしまう 解決: オブジェクトのコピーを作成して送信
// 問題のあるコード
emit("altitude-updated", altitudeValue.value);
// 修正後
emit("altitude-updated", {
id: altitudeValue.value.id,
altitude: altitudeValue.value.altitude,
received_time: altitudeValue.value.received_time,
});<script setup lang="ts">
import type { AltimeterData } from "./dashboard/Altimeter.vue";
const altitudeLogs = ref<AltimeterData[]>([]);
const handleAltitudeUpdate = (newData: AltimeterData) => {
altitudeLogs.value.push(newData);
if (altitudeLogs.value.length > 100) {
altitudeLogs.value.shift(); // 最新100件のみ保持
}
};
</script>
<template>
<Altimeter @altitude-updated="handleAltitudeUpdate" />
<AltimeterChart :altitudeLogs="altitudeLogs" />
</template><script setup lang="ts">
const emit = defineEmits<{
"altitude-updated": [data: AltimeterData];
}>();
async function fetchData() {
// APIからデータ取得
const data: AltimeterData = await response.json();
// 新しいオブジェクトとして送信(重要!)
emit("altitude-updated", {
id: data.id,
altitude: data.altitude,
received_time: data.received_time,
});
}
</script><script setup lang="ts">
import { Line } from "vue-chartjs";
import { Chart as ChartJS /* 必要なコンポーネント */ } from "chart.js";
ChartJS.register(/* 必要なコンポーネント */);
const props = defineProps<{ altitudeLogs: AltimeterData[] }>();
const chartData = ref({
labels: [] as string[],
datasets: [
{
data: [] as number[],
fill: true, // 塗りつぶし有効
backgroundColor: "rgba(75, 192, 192, 0.2)", // 半透明背景
borderColor: "rgb(75, 192, 192)",
tension: 0.1,
},
],
});
const chartOptions = ref({
responsive: true,
maintainAspectRatio: false,
animation: { duration: 0 },
plugins: { legend: { display: false } },
scales: { x: { display: false } },
});
watch(
() => props.altitudeLogs,
(logs) => {
chartData.value = {
labels: logs.map((log) => log.received_time),
datasets: [
{
/* 新しいデータセット */
},
],
};
},
{ deep: true, immediate: true }
);
</script>- Vue のリアクティブシステム: オブジェクトの参照に注意し、必要に応じてコピーを作成
- Chart.js の設定: レスポンシブ対応とアニメーション制御が重要
- コンポーネント間通信: props down, events up パターンの徹底
- デバッグ: console.log を活用した段階的な問題解決
- CSS Grid:
minmax()とgapを使った柔軟なレイアウト
- アニメーション無効化でリアルタイム更新を高速化
- 配列サイズ制限(100 件)でメモリ使用量抑制
immediate: trueで初期描画の確実な実行
- 複数データセットの対応
- 時系列フィルタリング機能
- データエクスポート機能
- リアルタイム更新頻度の調整機能
- Chart.js コンポーネント未登録:
LineElement,PointElementの登録忘れ - TypeScript エラー: 初期配列の型指定
[] as string[] - リアクティブ更新されない:
watchのdeep: trueオプション忘れ - 参照問題: オブジェクトのシャローコピー忘れ
// データフローの確認
console.log("Altitude updated:", newData);
console.log("Total logs count:", altitudeLogs.value.length);
console.log("Chart updating with logs:", logs.length, "items");この実装により、安定したリアルタイムグラフ表示システムが完成しました。