前端可視化家庭賬單:用 ECharts 實現支出統計與趨勢分析
在家庭財務管理中,直觀地看懂錢花到了哪裏、花得是否穩定,是提高消費意識與優化預算的關鍵。本文以 ECharts 為核心,構建一個可視化的家庭賬單分析:包括支出分類統計、月度趨勢分析、交互篩選與性能優化建議,幫助你在瀏覽器端快速落地一個實用的可視化面板。
適用場景
- 需要按類別統計支出佔比並快速定位高頻支出項
- 需要觀察月度支出變化趨勢並識別異常波動
- 希望在不引入後端的前提下,完成本地或前端的數據分析與展示
數據模型設計
為後續統計與可視化,建議將每筆賬單設計為結構化數據:
[
{
"date": "2025-01-03",
"category": "餐飲",
"amount": 56.5,
"paymentMethod": "信用卡",
"note": "外賣"
}
]
關鍵字段説明:
date:YYYY-MM-DD字符串,便於按月聚合category:分類名稱,例如餐飲、交通、居住、教育、醫療、娛樂等amount:支出金額,統一為正數paymentMethod:支付方式,按需篩選或做子維度統計
基礎搭建
選擇純前端頁面即可運行,使用 CDN 引入 ECharts:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>家庭賬單可視化</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.card { background: #fff; border: 1px solid #eee; border-radius: 8px; padding: 8px; }
.title { font-weight: 600; margin: 8px 0; }
.chart { height: 320px; }
</style>
</head>
<body>
<div class="grid">
<div class="card">
<div class="title">支出分類佔比</div>
<div id="chart-pie" class="chart"></div>
</div>
<div class="card">
<div class="title">月度支出趨勢</div>
<div id="chart-line" class="chart"></div>
</div>
</div>
<script>
const bills = [
{ date: '2025-01-03', category: '餐飲', amount: 56.5, paymentMethod: '信用卡' },
{ date: '2025-01-05', category: '交通', amount: 18, paymentMethod: '現金' },
{ date: '2025-01-08', category: '居住', amount: 2200, paymentMethod: '轉賬' },
{ date: '2025-02-01', category: '餐飲', amount: 78.2, paymentMethod: '信用卡' },
{ date: '2025-02-06', category: '娛樂', amount: 120, paymentMethod: '信用卡' },
{ date: '2025-02-09', category: '交通', amount: 16, paymentMethod: '現金' },
{ date: '2025-03-02', category: '餐飲', amount: 65.1, paymentMethod: '信用卡' },
{ date: '2025-03-17', category: '教育', amount: 320, paymentMethod: '轉賬' },
{ date: '2025-03-26', category: '醫療', amount: 180, paymentMethod: '信用卡' },
{ date: '2025-03-28', category: '居住', amount: 2200, paymentMethod: '轉賬' }
];
function parseMonth(dateStr) {
const d = new Date(dateStr);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
return `${y}-${m}`;
}
function sumByCategory(list) {
const map = new Map();
for (const b of list) {
map.set(b.category, (map.get(b.category) || 0) + b.amount);
}
return Array.from(map, ([category, total]) => ({ category, total }));
}
function sumByMonth(list) {
const map = new Map();
for (const b of list) {
const key = parseMonth(b.date);
map.set(key, (map.get(key) || 0) + b.amount);
}
return Array.from(map, ([month, total]) => ({ month, total })).sort((a, b) => a.month.localeCompare(b.month));
}
const pieChart = echarts.init(document.getElementById('chart-pie'));
const lineChart = echarts.init(document.getElementById('chart-line'));
const categoryTotals = sumByCategory(bills);
const pieOption = {
tooltip: {},
legend: { top: 'bottom' },
series: [
{
type: 'pie',
radius: ['40%', '70%'],
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
data: categoryTotals.map(o => ({ name: o.category, value: Number(o.total.toFixed(2)) }))
}
]
};
const monthTotals = sumByMonth(bills);
const lineOption = {
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: monthTotals.map(o => o.month) },
yAxis: { type: 'value' },
dataZoom: [{ type: 'inside' }, { type: 'slider' }],
series: [
{
name: '月支出',
type: 'line',
smooth: true,
showSymbol: false,
areaStyle: { opacity: 0.2 },
data: monthTotals.map(o => Number(o.total.toFixed(2)))
}
]
};
pieChart.setOption(pieOption);
lineChart.setOption(lineOption);
window.addEventListener('resize', function () {
pieChart.resize();
lineChart.resize();
});
</script>
</body>
</html>
要點:
- 使用
Map做聚合,減少中間對象的開銷 - 餅圖展示分類佔比,折線圖展示月度趨勢
- 開啓
dataZoom,兼顧短期與長期數據的瀏覽體驗
支出統計:類別分佈
- 將所有賬單按
category聚合求和,並按需排序 - 餅圖適合看比例結構,若類別較多可切換為水平條形圖以增強可讀性
- 可配合
legend、selected實現類別篩選
趨勢分析:月度變化
- 依據
date轉換成YYYY-MM進行月度聚合 - 折線圖的
smooth能提升趨勢觀感,搭配areaStyle強化視覺層次 - 可在異常峯值處使用
markPoint或visualMap進行突出標記
交互增強
- 時間維度篩選:按年、按月或自定義區間篩選並重新渲染
- 類別篩選:使用圖例勾選或下拉框控制類別數據是否參與統計
- 多圖聯動:點擊餅圖某分類時,聯動折線圖僅展示該分類在各月的趨勢
性能與數據質量
- 數據量較大時,儘量在聚合前做去噪與無效記錄過濾
- 前端聚合建議使用原生結構與一次遍歷完成,避免多次 map/reduce 疊加
- 以
dataset統一數據源可降低多圖表的重複數據轉換成本
擴展建議
- 疊加預算線:在折線圖上疊加每月預算閾值,超出則高亮
- 子維度細分:同一類別按
paymentMethod分組,觀察支付方式的偏好 - 導出報表:將聚合結果導出為 CSV,便於長期歸檔
完整示例(含類別聯動)
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
<style>
.toolbar { margin-bottom: 12px; }
.chart { height: 300px; }
</style>
</head>
<body>
<div class="toolbar">
<select id="categoryFilter">
<option value="all">全部類別</option>
<option>餐飲</option>
<option>交通</option>
<option>居住</option>
<option>娛樂</option>
<option>教育</option>
<option>醫療</option>
</select>
</div>
<div id="pie" class="chart"></div>
<div id="line" class="chart"></div>
<script>
const bills = [
{ date: '2025-01-03', category: '餐飲', amount: 56.5 },
{ date: '2025-01-05', category: '交通', amount: 18 },
{ date: '2025-01-08', category: '居住', amount: 2200 },
{ date: '2025-02-01', category: '餐飲', amount: 78.2 },
{ date: '2025-02-06', category: '娛樂', amount: 120 },
{ date: '2025-02-09', category: '交通', amount: 16 },
{ date: '2025-03-02', category: '餐飲', amount: 65.1 },
{ date: '2025-03-17', category: '教育', amount: 320 },
{ date: '2025-03-26', category: '醫療', amount: 180 },
{ date: '2025-03-28', category: '居住', amount: 2200 }
];
function parseMonth(s) {
const d = new Date(s);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
return `${y}-${m}`;
}
function sumByCategory(list) {
const map = new Map();
for (const b of list) map.set(b.category, (map.get(b.category) || 0) + b.amount);
return Array.from(map, ([category, total]) => ({ category, total }));
}
function sumByMonth(list) {
const map = new Map();
for (const b of list) {
const k = parseMonth(b.date);
map.set(k, (map.get(k) || 0) + b.amount);
}
return Array.from(map, ([month, total]) => ({ month, total })).sort((a, b) => a.month.localeCompare(b.month));
}
const pie = echarts.init(document.getElementById('pie'));
const line = echarts.init(document.getElementById('line'));
function renderAll(filteredBills) {
const catTotals = sumByCategory(filteredBills);
const pieOption = {
tooltip: {},
legend: { top: 'bottom' },
series: [
{ type: 'pie', radius: ['40%', '70%'], data: catTotals.map(o => ({ name: o.category, value: Number(o.total.toFixed(2)) })) }
]
};
const monthTotals = sumByMonth(filteredBills);
const lineOption = {
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: monthTotals.map(o => o.month) },
yAxis: { type: 'value' },
series: [
{ name: '月支出', type: 'line', smooth: true, showSymbol: false, data: monthTotals.map(o => Number(o.total.toFixed(2))) }
],
dataZoom: [{ type: 'inside' }, { type: 'slider' }]
};
pie.setOption(pieOption);
line.setOption(lineOption);
}
renderAll(bills);
document.getElementById('categoryFilter').addEventListener('change', function (e) {
const value = e.target.value;
const next = value === 'all' ? bills : bills.filter(b => b.category === value);
renderAll(next);
});
window.addEventListener('resize', function () {
pie.resize();
line.resize();
});
</script>
</body>
</html>
總結
- 數據結構化是基礎,聚合策略決定統計的可靠性與性能
- ECharts 提供豐富圖形與交互能力,覆蓋佔比與趨勢兩大核心需求
- 可視化不是終點,結合預算線、異常提醒與導出能力,才能形成閉環的家庭財務管理工具
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。