庫存系統做到最後,資料都在資料庫裡躺得好好的——可是老闆想看的不是一列列的數字,而是「哪一類貨壓最多錢」。這時候把 Dictionary 統計結果丟給 WinForms 的 Chart 控件,就能畫出下面這張圖:上面是各分類的數量柱狀圖,下面是各分類的金額環形圖,一眼看出「生活類數量只有 20 件,卻壓了 77 萬的庫存」。

這張圖不是截別人的,就是本文這段程式碼實際跑出來的結果。很多教學把 Chart 當黑盒子,貼一段 code 說「這樣就會出圖」——但只要資料形狀一變就整個壞掉,因為你根本不知道每一行在做什麼。這篇反過來,把畫圖那段程式碼一行一行拆開講,看完你會知道:兩個圖表怎麼上下擺、每個資料點怎麼綁上去、顏色怎麼自動配。
先解決最容易卡的一步:NuGet 套件
在寫任何程式碼之前,先裝對套件,不然 Chart 這個型別根本找不到。這裡是新手最常卡死的地方:
- 要裝:社群維護的
WinForms.DataVisualization(相容 .NET 8) - 不要裝:官方的
System.Windows.Forms.DataVisualization(那是 .NET Framework 限定,在 .NET 8 裝不起來)
裝好之後,程式最上面引用命名空間——注意套件名和命名空間不一樣,這是另一個卡點:
using System.Windows.Forms.DataVisualization.Charting;套件叫 WinForms.DataVisualization,using 卻是 System.Windows.Forms.DataVisualization.Charting。記住這個落差,找不到 Chart 型別時第一個檢查這裡。
資料從哪來:Statistics 回傳的 decimal[2]
畫圖之前要有資料。庫存裡每個 Product 有「分類、數量、總金額」,我們先用一個 Dictionary 把同分類的加總起來:
public static Dictionary<string, decimal[]> Statistics(List<Product> all)
{
var stat = new Dictionary<string, decimal[]>();
foreach (var p in all)
{
if (!stat.ContainsKey(p.Category)) // 第一次看到這分類
stat[p.Category] = new decimal[2]; // 先開一格 [數量=0, 金額=0]
stat[p.Category][0] += p.Quantity; // 累加數量
stat[p.Category][1] += p.TotalValue; // 累加金額(數量 × 單價)
}
return stat;
}Chart 的三個零件
WinForms 的 Chart 控件由三層組成,順序記熟就不會亂:
1. Chart:整塊畫布(你從工具箱拖到表單上的那個控件)。 2. ChartArea:畫布裡的一個「座標系」。一個 Chart 可以放多個 ChartArea——我們就是靠這個把柱狀圖和環形圖上下分開。 3. Series:一組資料。柱狀圖是一個 Series、環形圖是另一個 Series,各自指定要畫在哪個 ChartArea。
流程永遠是:先鋪畫布 → 建座標系(ChartArea)→ 建資料組(Series)→ 把點 AddXY 進去。接下來兩段程式碼就是照這個順序走。
第一段:搭好版面(InitializeChartInfrastructure)
這個方法在建構子裡呼叫一次,負責把「兩個座標系上下擺好、圖例放底部」這種只需做一次的骨架搭起來。逐行看:
private void InitializeChartInfrastructure()
{
chart.ChartAreas.Clear();
chart.Legends.Clear();第一件事就是 Clear()。為什麼?因為你從工具箱把 Chart 拖到表單上時,Designer 會自動幫你塞一個空的 ChartArea1 和 Legend1 進去。如果不清掉,等一下自己加的座標系會跟它疊在一起打架。拖拉出來的 Chart 第一步一定先 Clear,這是 Designer 版最容易漏的陷阱。
// --- 1. 上:數量直條圖區域 ---
var areaQty = new ChartArea("AreaQuantity");
areaQty.AxisX.MajorGrid.Enabled = false;
areaQty.AxisY.MajorGrid.Enabled = false;
areaQty.Position = new ElementPosition(0, 0, 100, 48); // 佔上半版面
chart.ChartAreas.Add(areaQty);第二段:把資料畫上去(DrawChart)——本文重點
前面都是鋪路,這個方法才是真正「畫圖」的地方。它吃一個 Dictionary<string, decimal[]>,把每個分類變成柱狀圖的一根柱子+環形圖的一塊扇形。一段一段拆:
先清乾淨
private void DrawChart(Dictionary<string, decimal[]> stat)
{
chart.Series.Clear();
chart.Legends["L"].CustomItems.Clear();
var areaQty = chart.ChartAreas["AreaQuantity"];chart.Series.Clear():清掉舊資料組。這樣設計是為了「重畫」——資料變了就再呼叫一次 DrawChart,先清空再重畫,不會殘留舊資料。chart.Legends["L"].CustomItems.Clear():清掉圖例L的自訂項目(同理,避免重畫時累加)。
顏色怎麼來的:HSL 自動配色
上面一直用到的 GetColor,是為了讓每個分類自動拿到一個「均勻分佈、飽和度一致」的顏色,不用手動一個個指定色碼:
private static Color GetColor(int index, int total) =>
ColorFromHsl(360f * index / Math.Max(total, 1), 0.65f, 0.52f);360f * index / total:把 0~360 度的色相環(Hue)平均切成 total 份,第 index 個分類拿第 index 份。4 個分類就是 0°、90°、180°、270°——紅、綠、青、紫,剛好是成品圖那四個顏色。Math.Max(total, 1):防呆,避免total是 0 時除以零。0.65f(飽和度)、0.52f(亮度):固定值,讓所有顏色鮮豔度、明暗一致,看起來像同一套配色。
ColorFromHsl 則是把 HSL 換算成 WinForms 認得的 RGB Color:
常見卡點快速對照
把上面散落的坑集中成一張表,遇到問題照著查:
- 找不到 `Chart` 型別:NuGet 沒裝,或裝成官方 Framework 版。要裝社群版
WinForms.DataVisualization。 - 編譯錯誤「沒有 chart 的定義」:拖出來的
chart1/label1忘了改名成chart/lblTotal。 - 兩張圖疊在一起、畫面很亂但不報錯:ChartArea 的
Position沒設。 - 圖是空白的:忘了
ChartAreas.Add(...),或忘了在 DrawChart 開頭Clear()掉 Designer 自動加的空 Series/ChartArea。 - `NullReferenceException` 或 `IndexOutOfRange`:
Statistics裡忘了先new decimal[2]就直接+=。 - 環形圖標籤擠成一團:
sValue["PieLabelStyle"] = "Outside"沒設。
換一個字就換一種圖
看懂上面的結構後,你會發現「換圖表類型」其實只改一個字。SeriesChartType 那行:
ChartType = SeriesChartType.Column, // 直條圖
// ChartType = SeriesChartType.Bar, // 橫條圖
// ChartType = SeriesChartType.Line, // 折線圖
// ChartType = SeriesChartType.Pie, // 圓餅圖
// ChartType = SeriesChartType.Doughnut, // 環形圖資料綁定(AddXY 那段)完全不用動,Chart 會自己換一種畫法。這也是為什麼建議先把資料統計和畫圖分開——資料對了,要換什麼圖都只是一行的事。
這段程式碼是 C# 庫存整合專案的收尾。如果你想從變數、物件導向一路練到 WinForms 桌面應用、SQLite 資料庫、再到這種資料視覺化,C# .NET 實戰:桌面程式與資料庫開發 這門課用同一個「商品/庫存系統」當主線,把 CRUD 螺旋練四次,最後就是做出這個可以展示的專案。