在前幾篇文章中,我們完成了《喵語日誌》的文字對話、情緒標記和照片分享模組。
隨著使用者天天寫日記、紀錄心情,這些散落在對話視窗裡的資料會越來越多。如果只靠文字回顧,很難對自己的心理狀態有巨觀的理解。
例如:
這週整體心情是偏開心還是偏低落?
最近有沒有特別容易低潮的日子?
哪些活動或情境常伴隨著好心情?
今天我們的目標,就是引入 Chart.js 圖表庫,將這些日常紀錄的心情點滴,轉化為清晰美麗的情緒起伏走勢圖。
透過視覺化的折線圖,讓使用者能一眼看懂自己這週或這個月的心情變化,發覺那些低潮與高光的軌跡。
在前端圖表庫的選擇上,我選用了生態系成熟且支援度極佳的 Chart.js,以及它的 React 封裝庫 react-chartjs-2。
在終端機中執行:
npm install chart.js react-chartjs-2
如果使用 React 19 遇到 peer dependencies 警告,可以加上 --legacy-peer-deps 參數:
npm install chart.js react-chartjs-2 --legacy-peer-deps
Chart.js v3+ 採用模組化設計,使用前必須在元件中註冊需要的元件,避免未使用的模組塞爆打包檔。
註冊的元件包含:
CategoryScale:X 軸使用的分類刻度。
LinearScale:Y 軸使用的線性刻度。
PointElement 和 LineElement:折線圖的點和線。
Title、Tooltip、Legend:標題、提示框和圖例。
Filler:用於填充曲線下方區域。
要畫出折線圖,首先需要把抽象的情緒轉化為 Y 軸的數值分數。
我定義了一個簡單的對應關係:
這樣就可以把每天的情緒紀錄,轉換成 1~5 的數值,作為折線圖的 Y 軸數據。
為了保持《喵語日誌》一貫的療癒風格,我對圖表做了一些視覺調整:
平滑曲線: 設定 tension: 0.4,讓折線呈現自然的圓弧狀。
暖色漸層: 使用粉紅色系填充曲線下方區域。
Y 軸標籤: 透過 ticks.callback 將數字轉換為貓咪表情,不再只是冰冷的數字。
在 src/components/MoodChart.tsx 完整實作如下::
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
type ChartData,
type ChartOptions,
} from "chart.js";
// 1. 模組註冊
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
);
// 2. 抽取 Emoji 字典,避免重複宣告
const MOOD_EMOJIS: Record<number, string> = {
1: "🙀",
2: "😿",
3: "😐",
4: "😺",
5: "😻",
};
export interface MoodLog {
date: string;
mood: string;
score: number;
}
interface MoodChartProps {
moodLogs: MoodLog[];
period: "week" | "month";
}
export default function MoodChart({ moodLogs }: MoodChartProps) {
const labels = moodLogs.map((log) => log.date);
const scores = moodLogs.map((log) => log.score);
// 折線圖資料與樣式設定
const data: ChartData<"line"> = {
labels,
datasets: [
{
label: "心情分數",
data: scores,
borderColor: "#f43f5e",
backgroundColor: "rgba(244, 63, 94, 0.15)",
borderWidth: 3,
fill: true,
tension: 0.4,
pointRadius: 5,
pointBackgroundColor: "#f43f5e",
pointBorderColor: "#ffffff",
pointBorderWidth: 2,
},
],
};
// 圖表客製化選項
const options: ChartOptions<"line"> = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: (context) => {
const val = context.raw as number;
return `心情:${MOOD_EMOJIS[val] || val}`;
},
},
},
},
scales: {
y: {
min: 1,
max: 5,
ticks: {
stepSize: 1,
callback: (value) => MOOD_EMOJIS[value as number] || "",
},
},
x: {
grid: { display: false },
},
},
};
return (
<div className="h-64 w-full">
<Line data={data} options={options} />
</div>
);
}
為了讓操作體驗更像原生手機 App,我把原本的上下堆疊結構,改成透過底部導覽列進行分頁切換,並在圖表上方加入本週/本月的時間區間切換按鈕。
在 src/App.tsx 中整合頁籤與狀態控制:
import { useState } from "react";
import ChatContainer from "./components/ChatContainer";
import MoodChart, { MoodLog } from "./components/MoodChart";
type Tab = "chat" | "chart";
export default function App() {
const [activeTab, setActiveTab] = useState<Tab>("chat");
const [period, setPeriod] = useState<"week" | "month">("week");
// 模擬心情紀錄資料
const moodLogs: MoodLog[] = [
{ date: "8/5", mood: "Good", score: 4 },
{ date: "8/6", mood: "Great", score: 5 },
{ date: "8/7", mood: "Okay", score: 3 },
{ date: "8/8", mood: "Low", score: 2 },
{ date: "8/9", mood: "Good", score: 4 },
{ date: "8/10", mood: "Great", score: 5 },
{ date: "8/11", mood: "Good", score: 4 },
];
return (
<main className="mx-auto flex min-h-screen w-full max-w-md flex-col bg-stone-50">
{/* 主要視窗區塊 */}
<div className="flex-1 p-4">
{activeTab === "chat" ? (
<ChatContainer />
) : (
<div>
{/* 週/月切換標籤 */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-stone-800">情緒走勢</h2>
<div className="flex gap-2">
<button
onClick={() => setPeriod("week")}
className={`rounded-full px-3 py-1 text-xs transition ${
period === "week"
? "bg-rose-500 text-white"
: "bg-stone-200 text-stone-600"
}`}
>
本週
</button>
<button
onClick={() => setPeriod("month")}
className={`rounded-full px-3 py-1 text-xs transition ${
period === "month"
? "bg-rose-500 text-white"
: "bg-stone-200 text-stone-600"
}`}
>
本月
</button>
</div>
</div>
{/* 圖表元件 */}
<MoodChart moodLogs={moodLogs} period={period} />
</div>
)}
</div>
{/* 底部導覽列 */}
<nav className="flex h-16 w-full items-center justify-around border-t border-rose-100/60 bg-white/90">
<button
onClick={() => setActiveTab("chat")}
className={`text-sm font-medium transition ${
activeTab === "chat" ? "text-rose-500" : "text-stone-400"
}`}
>
喵喵對話
</button>
<button
onClick={() => setActiveTab("chart")}
className={`text-sm font-medium transition ${
activeTab === "chart" ? "text-rose-500" : "text-stone-400"
}`}
>
情緒分析
</button>
</nav>
</main>
);
}
當使用者點擊底部導覽列的「情緒分析」按鈕時,主視窗會順暢切換到情緒圖表。
點擊右上角的「本週/本月」按鈕,曲線會即時更新。游標移到點位上時,Tooltip 也會跳出對應的心情標籤。

整體畫面圓潤且富有質感,符合《喵語日誌》的療癒風格。
這次實作讓我深刻體會到,資料視覺化不僅是把數據畫出來,更重要的是選對呈現方式與視覺風格。從安裝並註冊 Chart.js 核心模組、將抽象情緒轉換為 1~5 分的數值,到自訂 Emoji 刻度與暖色平滑曲線,原本硬邦邦的圖表也能變得像繪本一樣溫暖。而整合底部導覽列與週月切換功能後,《喵語日誌》的模組切換變得更具體系感,為後續功能的擴充奠定了良好的 UI 骨架。
明天我們將進入全新的主題!我們將為《喵語日誌》引入遊戲化機制,實作「連續紀錄 3 天」、「上傳美食照」等成就卡片元件與徽章牆 UI,透過成就解鎖的回饋,提升使用者的紀錄動力與黏著度!喵~