統計報表功能常需繪製圖表,若不想引入 ZedGraph 等第三方元件,透過 System.Drawing 命名空間的 GDI+ 功能即可自行實作長條圖與折線圖。本文提供兩個完整、可直接套用的 Render 方法,支援自訂標題、尺寸、資料來源(DataTable),並內建座標軸、圖例、數值標籤等繪圖細節。
public Image Render(string title, int width, int height, DataTable chartTable)
參數說明: chartTable 需包含至少三欄:第 0 欄與第 1 欄為兩組數值,第 2 欄為 X 軸標籤文字。
Bitmap 與 Graphics,清除背景highPoint,作為 Y 軸縮放基準barWidth 與起始座標 barOriginBitmap// 計算最大值
float highPoint = 1;
foreach (DataRow dr in dt.Rows)
{
highPoint = Math.Max(highPoint, Convert.ToSingle(dr[0]));
highPoint = Math.Max(highPoint, Convert.ToSingle(dr[1]));
}
// 長條寬度計算
float barWidth = (width - left) / (dt.Rows.Count * 3 + 1);
PointF barOrigin = new PointF(left + barWidth, 0);
// 迴圈繪製雙系列長條
for (int i = 0; i < dt.Rows.Count; i++)
{
// 系列 1(紅)
float barHeight = Convert.ToSingle(dt.Rows[i][0]) * (height - 2 * top) / highPoint;
barOrigin.Y = height - barHeight - top;
g.FillRectangle(Brushes.Red, barOrigin.X, barOrigin.Y, barWidth, barHeight);
g.DrawString(dt.Rows[i][2].ToString(), fontBottom, Brushes.Black, barOrigin.X, height - top); // 底部標籤
g.DrawString(dt.Rows[i][0].ToString(), fontTop, Brushes.Red, barOrigin.X, barOrigin.Y - 3 * fontTop.Size / 2); // 頂部數值
barOrigin.X += barWidth;
// 系列 2(藍)
barHeight = Convert.ToSingle(dt.Rows[i][1]) * (height - 2 * top) / highPoint;
barOrigin.Y = height - barHeight - top;
g.FillRectangle(Brushes.Blue, barOrigin.X, barOrigin.Y, barWidth, barHeight);
g.DrawString(dt.Rows[i][1].ToString(), fontTop, Brushes.Blue, barOrigin.X, barOrigin.Y - 3 * fontTop.Size / 2);
barOrigin.X += barWidth * 2; // 留出間距
}
FillEllipse 繪製資料點圓點PointF[] 陣列g.DrawLines 連線成曲線PointF[] pt1 = new PointF[dt.Rows.Count];
PointF[] pt2 = new PointF[dt.Rows.Count];
for (int i = 0; i < dt.Rows.Count; i++)
{
// 系列 1 點位
float barHeight = Convert.ToSingle(dt.Rows[i][0]) * (height - 2 * top) / highPoint;
barOrigin.Y = height - barHeight - top;
g.FillEllipse(Brushes.Red, barOrigin.X - 3, barOrigin.Y - 3, 6, 6);
pt1[i] = new PointF(barOrigin.X, barOrigin.Y);
g.DrawString(dt.Rows[i][0].ToString(), fontTop, Brushes.Red, barOrigin.X, barOrigin.Y - 2 * fontTop.Size);
// 系列 2 點位
barHeight = Convert.ToSingle(dt.Rows[i][1]) * (height - 2 * top) / highPoint;
barOrigin.Y = height - barHeight - top;
g.FillEllipse(Brushes.Blue, barOrigin.X - 3, barOrigin.Y - 3, 6, 6);
pt2[i] = new PointF(barOrigin.X, barOrigin.Y);
g.DrawString(dt.Rows[i][1].ToString(), fontTop, Brushes.Blue, barOrigin.X, barOrigin.Y - 2 * fontTop.Size);
barOrigin.X += barWidth;
}
// 繪製連線
g.DrawLines(new Pen(Brushes.Red, 1), pt1);
g.DrawLines(new Pen(Brushes.Blue, 1), pt2);
| 功能 | 實作方式 |
|---|---|
| 動態字型大小 | 根據長條寬度/資料位數自動計算,並限制最大/最小值 |
| 座標軸繪製 | 藍色粗線(2px)繪製 Y 軸與 X 軸 |
| 資料來源驗證 | 折線圖額外檢查 chartTable == null 顯示「無資料」 |
| 例外處理 | 統一 try-catch 包裹,發生錯誤回傳空白圖片 |
DataTable dt = new DataTable();
dt.Columns.Add("系列A", typeof(double));
dt.Columns.Add("系列B", typeof(double));
dt.Columns.Add("標籤", typeof(string));
dt.Rows.Add(80, 60, "1月");
dt.Rows.Add(95, 70, "2月");
dt.Rows.Add(110, 85, "3月");
// ... 更多資料
Image barChart = Render("月度業績對比", 600, 400, dt);
pictureBox1.Image = barChart;
// 或存檔
barChart.Save("chart.png", ImageFormat.Png);
PictureBox.Image 已具雙緩衝,無閃爍MouseMove 事件與座標換算System.Drawing.Drawing2D.Metafile作者:半支煙阿傑 | 出處:CSDN 部落格