iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Modern Web

30 天手把手學會 Chart.js v4:從圖表基礎到互動式資料視覺化實戰系列 第 28

Day 28 - 30 天手把手學會 Chart.js|專案規劃與資料設計

  • 分享至 

  • xImage
  •  

前面 27 天,我們把 Chart.js 從「認識」到「精通」走過了一整輪:從基礎的折線圖、長條圖,到雷達圖、氣泡圖等各種圖表類型;從座標軸、圖例、動畫等細節設定,到資料串接(JSON/API/CSV)、動態更新與互動事件;再到外掛開發、樣式主題客製化,以及與 React、Vue、Angular 三大框架的整合。今天開始,我們要進入最後三天的綜合實戰專案(Capstone Project):Day 28 規劃專案與設計資料、Day 29 動手實作儀表板、Day 30 效能優化與總複習。跟前面 27 天「一天一個知識點」的節奏不同,今天完全不會出現新的 Chart.js API——我們要退後一步,練習一件比寫程式更容易被新手忽略、卻決定專案成敗的事:在動手寫 code 之前,先把「做什麼」「為誰做」「資料長怎樣」「用什麼圖表」想清楚。

一、為什麼「先規劃、再動手」是最容易被忽略的一步?

新手做資料視覺化專案時,很常見的順序是:「先找一份資料 => 打開編輯器 => 想到什麼圖表就刻什麼圖表」。這樣做通常在專案進行到一半,會遇到以下幾種情況:

  • 資料格式跟圖表需求兜不起來:Day 15 我們學過,Chart.js 的 datasets 需要的是「已經彙整好」的陣列(例如「每個月的支出總和」),但原始資料庫拿到的往往是「每一筆交易紀錄」。如果沒有先想清楚資料要怎麼從「明細」轉換成「彙整後的圖表資料」,寫到一半才發現要大改資料處理邏輯,非常浪費時間。
  • 選錯圖表類型,誤導讀者:例如把「逐月變化的趨勢」硬塞進圓餅圖(Pie Chart)呈現,或是把「佔比關係」畫成折線圖,讀者會很難一眼看懂資料真正想表達的意思。Day 3 ~ Day 11 學過的每一種圖表,其實都有各自最適合的「使用情境」,這件事應該在動手畫圖之前就決定好,而不是「先選喜歡的圖表,再硬套資料」。
  • 一開始就想做完所有功能:儀表板很容易越規劃越大——「要不要加深色模式?」「要不要支援多帳戶?」「要不要做 AI 消費建議?」如果沒有先劃定範疇,三天的時間根本做不完最基本的版本。
  • 忽略未來的資料量:之後會學到 decimation(資料抽稀)這個效能優化外掛,但如果資料結構設計時完全沒考慮「未來資料筆數可能上千上萬筆」,重構起來會比一開始就設計好複雜很多。

這就是為什麼專業的資料視覺化專案,往往會先畫「藍圖」再蓋房子:軟體開發中的規劃,就等同於建築的設計圖——把格局、管線、水電都想清楚,才不會蓋到一半才發現承重牆位置不對。今天的內容,就是帶你走過這張「藍圖」該長什麼樣子。

二、專案規劃的四個步驟

在動手之前,我們可以把「專案規劃」拆解成四個循序漸進的步驟,本篇文章的章節安排也是依照這個順序展開:

步驟 目的
(1) 需求分析 釐清「這個功能是為誰而做、要解決什麼問題」,避免一開始就陷入「要用哪個圖表」的技術細節
(2) 主題選定 從候選方案中,依照客觀標準挑出最適合的一個主題
(3) 範疇與資訊架構規劃 決定「這次要做多少功能」(Scope)與「畫面要怎麼分區塊呈現」(資訊架構)
(4) 資料結構設計與圖表對照 決定「資料長什麼樣子」(Data Modeling)以及「該用什麼圖表呈現」(圖表類型選用對照表)

完成這四步之後,我們還會多做一步「技術棧與資料夾規劃」,把 Day 29 動手實作前需要決定的環境設定都先確認好,讓明天可以直接進入寫程式的節奏,不用中途停下來做架構決策。

三、需求分析:用 User Story 釐清「做給誰看、為了什麼」

User Story(使用者故事) 是一種很常見、也很好上手的需求釐清技巧,格式很簡單:

身為 [某個角色],我想要 [做到某件事],以便 [達成某個目的]

這個技巧的重點,是強迫自己先想「目的」,而不是先想「用什麼技術/圖表實現」。舉例來說,如果我們正在規劃「個人財務儀表板」,可以先寫出幾條 User Story:

  • 身為一個想控制花費的上班族,我想要一眼看到本月各類別支出佔比,以便知道自己錢都花去哪了
  • 身為一個正在存錢買車的使用者,我想要看到近半年收支趨勢的變化,以便判斷自己的儲蓄速度夠不夠快
  • 身為一個設定月度預算的使用者,我想要比較「預算」與「實際花費」的差距,以便提早警覺是否快要超支

寫完這些故事後,你會發現「圖表類型」其實是最後才決定的事——先有「一眼看到佔比」的需求,才會想到適合用環狀圖(Doughnut Chart);先有「近半年趨勢變化」的需求,才會想到適合用折線圖(Line Chart)。這個「先有目的、才選圖表」的順序,也是第七節「圖表類型選用對照表」的設計邏輯。

💡 同樣的技巧也適用於另外兩個候選主題。例如「銷售數據分析」可以寫成「身為業務主管,我想要比較各分店本季業績排名,以便決定資源分配優先順序」;「網站流量監控」可以寫成「身為網站管理者,我想要即時看到流量突然暴增的時間點,以便判斷是否遭受異常流量攻擊」。不管選哪個主題,第一步永遠是先寫幾條 User Story,把「為誰做、為什麼做」講清楚。

四、選定專案主題:三個候選方案比較

候選主題:個人財務儀表板銷售數據分析網站流量監控

這三個主題都很適合拿來練習 Chart.js,但資料特性與難度略有不同,以下逐一分析:

比較面向 個人財務儀表板 銷售數據分析 網站流量監控
核心受眾 個人使用者,管理自己的收支 業務/營運主管,檢視業績表現 網站管理者/工程師,監控流量狀態
資料時間特性 以「日」為單位的歷史資料,偏靜態 以「月/季」為單位的歷史資料,偏靜態 以「分鐘/小時」為單位,偏即時、資料量大
典型資料量 中等(一般人一個月數十到數百筆交易) 中等(依商品/分店數量而定) 龐大(每分鐘都有新的請求紀錄)
適合的圖表類型 折線圖、環狀圖、長條圖、極座標圖 長條圖、混合圖表、雷達圖(多維度比較) 折線圖(含 zoom/decimation)、地理分佈長條圖
主要挑戰 資料分類(類別、帳戶)的彙整邏輯 多維度比較(產品 × 地區 × 時間)的資料切片 大量資料點的效能優化、即時更新頻率
難度 入門 ~ 中等 中等 中等~中高(牽涉效能與即時性)
資料取得方式 自建 Mock Data 即可,貼近生活容易發想 需要設計較完整的商業情境資料 需要模擬高頻率的即時資料流

4.1 個人財務儀表板

資料維度單純(時間、類別、帳戶、金額),但圖表類型變化豐富:收支趨勢、分類佔比、預算比較、資產配置都能各自對應到不同圖表。因為資料是「個人生活情境」,發想 Mock Data 也最直覺,很適合作為練習「資料建模」的入門題材。

4.2 銷售數據分析

資料通常包含「產品 × 地區 × 時間」多個維度,很適合練習「同一份原始資料,依照不同切角彙整出不同圖表」的能力(例如同一份訂單資料,可以彙整成「各產品業績排行」的長條圖,也可以彙整成「各分店月趨勢」的折線圖)。難度略高於財務儀表板,因為維度組合較複雜。

4.3 網站流量監控

最貼近「即時系統」的情境,會用到 Day 18 學過的輪詢(Polling)更新,以及 之後即將學到的 decimation 資料抽稀。因為資料量最大、更新頻率最高,三者之中技術難度最高,也最考驗效能規劃。

接下來將以「個人財務儀表板」作為實作範例,原因是它的資料維度單純、圖表類型多樣,最能完整覆蓋前面 27 天學過的圖表類型與技巧,也最適合在三天內做出一個「有模有樣」的成品。如果你對「銷售數據分析」或「網站流量監控」更有興趣,完全可以套用接下來教的同一套規劃方法(User Story => Scope => 資料建模 => 圖表對照),把資料主題換成你自己想做的內容,本篇後續章節提到的技巧一樣適用。

五、深入規劃:個人財務儀表板的範疇與資訊架構

5.1 專案範疇(Scope):用 MoSCoW 排優先順序

MoSCoW 是一種常見的範疇管理技巧,把功能依照優先順序分成四類:Must have(一定要有)Should have(應該要有)Could have(可以有,非必要)Won't have(這次不做)。這樣做的好處是:一旦時間不夠,可以很清楚地知道「先砍掉哪些功能」,而不會影響核心體驗。

分類 功能項目
Must have 本月收支總覽卡片(總收入/總支出/結餘)、收支趨勢折線圖、支出分類佔比環狀圖、交易明細列表
Should have 預算 vs 實際長條圖、時間區間切換(本月/近 3 個月/近半年)
Could have 資產配置極座標圖、CSV 資料匯出、深色模式(Dark Mode)
Won't have(這次不做) 多帳戶跨幣別換算、AI 消費建議、多人協作/權限管理

5.2 資訊架構(IA):拆解儀表板的區塊

決定好範疇之後,接著要把畫面拆解成幾個獨立的區塊,這就是 IA(Information Architecture,資訊架構) 規劃:

區塊 呈現內容 對應圖表 資料來源
(1) 總覽卡片區 本月總收入、總支出、結餘、與上月比較的百分比 數字卡片(非圖表) Transaction 彙整
(2) 收支趨勢區 近 N 個月的收入/支出走勢 折線圖(Line,含填色區域) Transaction 依月彙整
(3) 支出分類區 本月各類別支出佔比 環狀圖(Doughnut) Transaction 依類別彙整
(4) 預算比較區 各類別「預算」與「實際支出」對照 長條圖(Bar,兩個 dataset) Transaction + Budget
(5) 資產配置區(Could have) 各帳戶/投資項目的資產佔比 極座標圖(Polar Area) Account 彙整
(6) 交易明細區 可篩選的交易清單(日期、類別、關鍵字) 表格(非圖表) Transaction 原始明細

5.3 版面草圖(Wireframe)

在真正寫 HTML/CSS 之前,先用簡單的文字方塊畫出版面草圖(Wireframe),確認每個區塊在畫面上的相對位置與大小關係,之後 Day 29 排版時(Grid/Flexbox)才有明確的依據:

https://ithelp.ithome.com.tw/upload/images/20260817/20171829oxpcFoipOF.png

這張草圖不需要畫得多精美(用 Excalidraw、Figma ... 工具或甚至紙筆都可以),重點是先確認資訊架構與資料流向沒有問題,而不是花時間在視覺設計上——視覺細節可以留到 Day 29 排版時再慢慢調整。

六、資料結構設計(Data Modeling)

6.1 三個關鍵詞:Dimension、Measure、Granularity

在設計資料結構之前,先弄懂三個資料分析領域的基礎概念,之後看任何一份資料,都可以用這三個角度去拆解:

  • Measure(量值):可以被加總、平均、比較大小的數字,例如「金額」。
  • Dimension(維度):用來分類、篩選、分組量值的標籤,例如「類別」「帳戶」「日期」。同一筆金額,可以依照不同維度切出不同角度的統計結果。
  • Granularity(粒度):資料紀錄的細緻程度,例如「每一筆交易」是最細的粒度,「每日加總」「每月加總」則是較粗的粒度。圖表通常不會直接使用最細粒度的原始資料,而是先彙整成適合的粒度。

以「個人財務儀表板」為例:Measure 是「金額(amount)」,Dimension 是「類別(category)」「帳戶(account)」「日期(date)」「收支類型(type)」,而不同圖表需要的 Granularity 也不同——收支趨勢折線圖要「每月」的粒度,交易明細表格則要「每筆」的粒度。

6.2 定義資料實體(Entity)與欄位

依照第 5.2 節拆解出來的區塊,可以歸納出四個核心的 Entity(資料實體)

Transaction(交易紀錄)

欄位 型別 說明 範例
id string 交易唯一識別碼 "t20260305-01"
date string(ISO 8601) 交易日期 "2026-03-05"
type "income" | "expense" 收入或支出 "expense"
categoryId string 對應 Category 的 id "cat-food"
accountId string 對應 Account 的 id "acc-checking"
amount number 金額(一律存正數,方向由 type 決定) 320
note string 備註 "超市採買"

Category(類別)

欄位 型別 說明 範例
id string 類別識別碼 "cat-food"
name string 類別名稱 "餐飲"
type "income" | "expense" 屬於收入還是支出類別 "expense"
color string 圖表配色(十六進位色碼) "#f97316"

Account(帳戶)

欄位 型別 說明 範例
id string 帳戶識別碼 "acc-checking"
name string 帳戶名稱 "薪轉帳戶"
type string 帳戶類型(現金/銀行/信用卡/投資) "bank"
balance number 目前餘額(用於資產配置圖) 85000

Budget(預算)

欄位 型別 說明 範例
id string 預算識別碼 "b-2026-03-food"
categoryId string 對應 Category 的 id "cat-food"
month string(YYYY-MM 預算所屬月份 "2026-03"
limitAmount number 該類別當月預算上限 8000

這四個 Entity 之間用 categoryIdaccountId 互相關聯,就像資料庫的**外來鍵(Foreign Key)**設計一樣——這也是為什麼 Mock Data 或未來接後端 API 時,通常會回傳「交易明細」+「類別對照表」+「帳戶對照表」這種拆分開來的結構,而不是把類別名稱、顏色都重複寫死在每一筆交易紀錄裡。

6.3 從交易明細到圖表資料:Aggregation 的挑戰

Chart.js 要的資料格式,跟資料庫或 API 回傳的「原始明細」通常不一樣。以「收支趨勢折線圖」為例,我們需要的是「每個月的收入總和、支出總和」,而不是一筆一筆的交易紀錄,這中間需要一段 Aggregation(資料彙整) 邏輯,通常會用 reduce() 搭配「依 key 分組(groupBy)」的技巧完成:

// 將交易明細依「月份 + 收支類型」彙整成 Chart.js 需要的格式
function aggregateMonthlyTrend(transactions) {
  // 先用 reduce 依「月份」分組,累加 income/expense
  const grouped = transactions.reduce((acc, tx) => {
    const month = tx.date.slice(0, 7); // "2026-03-05" -> "2026-03"
    if (!acc[month]) {
      acc[month] = { income: 0, expense: 0 };
    }
    acc[month][tx.type] += tx.amount;
    return acc;
  }, {});

  // 依月份排序,轉換成 Chart.js 的 labels + datasets 結構
  const months = Object.keys(grouped).sort();
  return {
    labels: months,
    datasets: [
      {
        label: '收入',
        data: months.map((m) => grouped[m].income),
      },
      {
        label: '支出',
        data: months.map((m) => grouped[m].expense),
      },
    ],
  };
}

「支出分類佔比環狀圖」則需要換一種分組維度——改成依 categoryId 分組加總:

function aggregateCategoryBreakdown(transactions, categories) {
  const totals = transactions
    .filter((tx) => tx.type === 'expense')
    .reduce((acc, tx) => {
      acc[tx.categoryId] = (acc[tx.categoryId] || 0) + tx.amount;
      return acc;
    }, {});

  const categoryIds = Object.keys(totals);
  return {
    labels: categoryIds.map((id) => categories.find((c) => c.id === id).name),
    datasets: [
      {
        data: categoryIds.map((id) => totals[id]),
        backgroundColor: categoryIds.map((id) => categories.find((c) => c.id === id).color),
      },
    ],
  };
}

這兩段程式碼示範了同一份 transactions 原始資料,可以依照「不同的 Dimension」(月份 vs 類別)彙整出完全不同的圖表資料——這正是資料建模時,把「明細資料」與「彙整邏輯」分開設計的價值:原始資料只需要存一份,彙整方式可以隨著圖表需求任意增加

6.4 準備 Mock Data

規劃階段先手動準備一份合理的假資料(Mock Data),讓 Day 29 一開始就有資料可以測試,不用等後端 API 做完才能動手刻前端畫面:

{
  "categories": [
    { "id": "cat-salary", "name": "薪資", "type": "income", "color": "#22c55e" },
    { "id": "cat-food", "name": "餐飲", "type": "expense", "color": "#f97316" },
    { "id": "cat-transport", "name": "交通", "type": "expense", "color": "#3b82f6" },
    { "id": "cat-entertainment", "name": "娛樂", "type": "expense", "color": "#a855f7" }
  ],
  "accounts": [
    { "id": "acc-checking", "name": "薪轉帳戶", "type": "bank", "balance": 85000 },
    { "id": "acc-cash", "name": "現金", "type": "cash", "balance": 3200 }
  ],
  "transactions": [
    { "id": "t1", "date": "2026-03-03", "type": "income", "categoryId": "cat-salary", "accountId": "acc-checking", "amount": 52000, "note": "三月份薪資" },
    { "id": "t2", "date": "2026-03-05", "type": "expense", "categoryId": "cat-food", "accountId": "acc-cash", "amount": 320, "note": "超市採買" },
    { "id": "t3", "date": "2026-03-08", "type": "expense", "categoryId": "cat-transport", "accountId": "acc-checking", "amount": 1200, "note": "加油" },
    { "id": "t4", "date": "2026-03-15", "type": "expense", "categoryId": "cat-entertainment", "accountId": "acc-checking", "amount": 890, "note": "電影票" }
  ],
  "budgets": [
    { "id": "b-2026-03-food", "categoryId": "cat-food", "month": "2026-03", "limitAmount": 8000 },
    { "id": "b-2026-03-transport", "categoryId": "cat-transport", "month": "2026-03", "limitAmount": 3000 }
  ]
}

準備 Mock Data 時,記得涵蓋一些「邊界情況(Edge Case)」:金額特別大的一筆交易、某個類別完全沒有預算、跨月份的資料等等。如果 Mock Data 準備得太理想化(例如每個類別金額都差不多、資料筆數剛好整除),Day 29 實作時很容易漏掉「資料不齊全時圖表該怎麼呈現」的處理邏輯。

七、圖表類型選用對照表

原則很簡單:先確定「這張圖表要回答什麼問題」,再對照下表挑選圖表類型,而不是先選一個喜歡的圖表,再硬套資料進去。

7.1 通用對照表:依「目的」挑圖表

想呈現的目的 建議圖表類型 Chart.js type
呈現數值隨時間的變化趨勢 折線圖(Line Chart) 'line'
比較不同類別之間的數值大小 長條圖(Bar Chart) 'bar'
呈現「部分佔整體」的比例關係(總和等於 100%) 圓餅圖/環狀圖(Pie/Doughnut) 'pie' / 'doughnut'
強調單一類別的相對大小,但不要求嚴格加總為 100% 極座標圖(Polar Area) 'polarArea'
同時比較多個維度/指標的綜合表現 雷達圖(Radar Chart) 'radar'
呈現兩個數值變數之間的相關性 散佈圖(Scatter Chart) 'scatter'
呈現兩個數值變數的相關性,並用「點的大小」表示第三個維度 氣泡圖(Bubble Chart) 'bubble'
同時呈現「絕對數值」與「趨勢走向」(例如業績金額 + 成長率) 混合圖表(Mixed Chart:Bar + Line) 混合設定
大量時間序列資料點,需要縮放、平移瀏覽細節 折線圖 + chartjs-plugin-zoom + decimation 'line'

7.2 個人財務儀表板的圖表配置清單

把上面的通用對照表,套用到第 5.2 節拆解出來的每個區塊,就能得到今天規劃、明天直接照做的具體圖表清單:

區塊 想回答的問題 圖表類型 對應通用類別
收支趨勢區 「最近半年收入/支出怎麼變化?」 折線圖(Line,含 fill 區域) 隨時間變化的趨勢
支出分類區 「這個月的錢主要花在哪裡?」 環狀圖(Doughnut) 部分佔整體的比例
預算比較區 「哪些類別已經快要超支?」 長條圖(Bar,預算 vs 實際兩個 dataset) 類別數值比較
資產配置區 「我的資產分散在哪些帳戶?」 極座標圖(Polar Area) 強調相對大小

這張清單也回答了一個常見的新手疑問:「同樣是圓形的圖,環狀圖跟極座標圖到底該選哪個?」——環狀圖要求「每個扇形角度總和等於整個圓」,適合呈現嚴格的佔比關係(例如支出分類一定要加總等於 100% 的總支出);極座標圖的每個扇形角度是相等的,只有半徑長度隨數值變化,更適合單純比較「哪個類別數值比較大」,而不強調彼此加總的意義(例如各帳戶餘額,不見得需要被理解成「佔總資產的比例」)。

八、技術棧與專案資料夾規劃(為 Day 29 鋪路)

規劃到這裡,最後一步是把 Day 29 動手實作前需要決定的技術細節先確認好,避免明天寫程式寫到一半還要停下來做架構決策:

  • 前端:延續 Day 2 開始使用的做法——純 HTML/CSS/JavaScript,透過 <script type="module"> 搭配 ESM CDN(https://cdn.jsdelivr.net/npm/chart.js/+esm)匯入 Chart.js,不需要額外安裝 Vite/Webpack 等打包工具,就能使用 import 語法做模組化拆分(這也是之後「程式碼重構與模組化」要用到的基礎)。
  • 後端:延續 Day 16、Day 21 的做法——用 Node.js + Express 建立一支簡單的 Mock API,回傳今天準備好的 Mock Data,讓前端透過 fetch 取得資料,同時也能練習 Loading/錯誤處理的完整體驗。
  • 可選延伸:如果你已經完成 Day 25~27 的 React/Vue/Angular 整合練習,也完全可以把同一套資料設計、圖表配置清單,改用其中一個框架實作——例如用 Angular 搭配 Day 27 學過的 Lazy Loading 讓儀表板頁面獨立成一個延遲載入的路由,或用 Interceptor 統一為每個 API 請求加上錯誤處理,這些技巧在正式專案中都很實用。不過本系列接下來兩天的範例,仍會採用最貼近前面 27 天主線的 Vanilla JS 寫法,確保不需要額外的框架知識也能跟上進度。

規劃出來的專案資料夾結構如下,今天先確定架構,Day 29 會依照這個結構逐一把檔案生出來:

day28-personal-finance-dashboard/
├── server/                       # Mock API 後端(Day 29 動手做)
│   ├── package.json
│   ├── server.js
│   └── data/
│       ├── transactions.json     # 對應 6.4 節的 Mock Data
│       ├── categories.json
│       ├── accounts.json
│       └── budgets.json
└── public/                       # 前端頁面(Day 29 動手做)
    ├── index.html
    ├── css/
    │   └── style.css             # Grid/Flexbox 響應式版面
    └── js/
        ├── main.js               # 進入點:呼叫 API、初始化各張圖表
        ├── api.js                # 封裝 fetch,統一處理 Loading/錯誤
        ├── aggregate.js          # 對應 6.3 節的 groupBy/reduce 彙整邏輯
        └── charts/
            ├── trendChart.js     # 收支趨勢折線圖
            ├── categoryChart.js  # 支出分類環狀圖
            ├── budgetChart.js    # 預算 vs 實際長條圖
            └── allocationChart.js # 資產配置極座標圖

charts/ 資料夾依「一個檔案負責一張圖表」拆分,是刻意為之後「程式碼重構與模組化」預留的設計——今天先把資料夾骨架定下來,之後不管是新增圖表、或是替某張圖表加上效能優化,都能在對應的檔案裡獨立修改,不會互相干擾。


明天(Day 29)我們會依照今天規劃出來的資料結構、圖表配置清單與資料夾架構,正式動手把「個人財務儀表板」實作出來:整合多種圖表類型於單一頁面、加入時間區間切換與篩選器等互動功能,並用 CSS Grid/Flexbox 完成響應式版面配置,把今天畫在紙上的 Wireframe 真正變成一個可以操作的網頁。

參考資源


上一篇
Day 27 - 30 天手把手學會 Chart.js|與 Angular 整合
下一篇
Day 29 - 30 天手把手學會 Chart.js|專案實作
系列文
30 天手把手學會 Chart.js v4:從圖表基礎到互動式資料視覺化實戰29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言