C# 使用 System.Drawing 绘制柱状图、折线图与扇形图
本文将展示如何利用 C# 的 System.Drawing 命名空间,在不依赖第三方控件的情况下,直接通过代码动态生成柱状图、折线图和扇形图。适用于需要从数据库读取数据并在页面中实时渲染统计报表的场景。
结果预览与原理简介
C# 提供了强大的 System.Drawing 命名空间(即 GDI+),允许开发人员通过对象如 Graphics、Pen、Brush 和 Bitmap 来绘制各种图形。对于统计图,核心逻辑通常包括:定义画布尺寸与背景、计算数据对应的像素坐标、绘制网格与标签、以及最后将内存中的图像输出为 JPEG 格式响应给前端。
以下三个部分分别对应三种最经典的统计图表。代码中使用了 SQL Server 作为数据源进行演示,实际应用中可替换为其他数据获取方式。
准备条件
- 开发环境:Visual Studio,支持 .NET Framework(注意
System.Drawing在非 Windows 环境下可能存在兼容性问题)。 - 引用命名空间:需引入
System.Drawing,System.Drawing.Imaging,System.Data.SqlClient等。 - 依赖项:文中代码假设存在下拉框控件(如
ddlTaget,ddlYear)用于筛选查询参数,以及数据库连接字符串配置。
第一步:绘制动态柱状图
柱状图适合用于对比不同类别的数据量。本例展示了如何对比“报名人数”与“通过人数”,并将两组柱子并列显示以便直观比较。
- 初始化画布与画笔
创建Bitmap实例作为画布,并使用Graphics.FromImage()获取绘图上下文。设置背景色并定义用于绘制的Pen和Brush。
int height = 500, width = 700; Bitmap image = new Bitmap(width, height); Graphics g = Graphics.FromImage(image); // ... 后续操作 ...
- 绘制网格线与坐标轴标签
使用循环绘制横向与纵向的辅助线,并遍历数组在相应位置绘制 X 轴(时间周期)和 Y 轴(数值刻度)的文字标签。
// 示例:绘制X轴标签
String[] n = { "第一期", "第二期", "...", "全年统计" };
for (int i = 0; i < n.Length; i++)
{
g.DrawString(n[i].ToString(), font, Brushes.Blue, x, 348);
x += 78;
}
- 填充数据并绘制柱体
从数据库读取数据存入数组后,根据当前 X 坐标和柱体高度(基于数据值转换)调用FillRectangle绘制实心矩形。每个柱体上方还需绘制具体数值标签。
- 输出图像
将绘制好的 Bitmap 保存至 MemoryStream,并清除响应流内容后以 “image/Jpeg” 类型二进制写出。
以下是柱状图的完整实现代码,包含了标题、边框、图例及完整的双柱对比绘制逻辑:
private void CreateBarChart()
{
int height = 500, width = 700;
Bitmap image = new Bitmap(width, height);
Graphics g = Graphics.FromImage(image);
try
{
// 1. 初始化背景与字体
g.Clear(Color.White);
Font font = new Font("Arial", 10, FontStyle.Regular);
Font font1 = new Font("宋体", 20, FontStyle.Bold);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.BlueViolet, 1.2f, true);
// 绘制背景与标题
g.FillRectangle(Brushes.WhiteSmoke, 0, 0, width, height);
g.DrawString(this.ddlTaget.SelectedItem.Text + " " + this.ddlYear.SelectedItem.Text + " 成绩统计柱状图", font1, brush, new PointF(70, 30));
g.DrawRectangle(new Pen(Color.Blue), 0, 0, image.Width - 1, image.Height - 1);
Pen mypen = new Pen(brush, 1);
// 2. 绘制网格
// 横向线条
int x = 100;
for (int i = 0; i < 14; i++) { g.DrawLine(mypen, x, 80, x, 340); x += 40; }
Pen mypen1 = new Pen(Color.Blue, 2);
x = 60;
g.DrawLine(mypen1, x, 80, x, 340);
// 纵向线条
int y = 106;
for (int i = 0; i < 9; i++) { g.DrawLine(mypen, 60, y, 620, y); y += 26; }
g.DrawLine(mypen1, 60, y, 620, y);
// 3. 绘制坐标轴文字
String[] n = { "第一期", "第二期", "第三期", "第四期", "上半年", "下半年", "全年统计" };
x = 78;
for (int i = 0; i < 7; i++) { g.DrawString(n[i], font, Brushes.Blue, x, 348); x += 78; }
String[] m = { "250","225", "200", "175", "150", "125", "100", "75", "50", "25", "0" };
y = 72;
for (int i = 0; i < 11; i++) { g.DrawString(m[i], font, Brushes.Blue, 25, y); y += 26; }
// 4. 数据读取与处理(此处仅示意结构,实际需替换为你的SQL逻辑)
// int[] Count1 = new int[7]; // 报名数据
// int[] Count2 = new int[7]; // 通过数据
// SqlConnection Con = new SqlConnection(...);
// ... 填充数据 ...
// 为演示代码完整性,这里模拟绘制第一个周期的逻辑
int[] Count1 = new int[7] { 100, 110, 120, 130, 210, 250, 450 };
int[] Count2 = new int[7] { 80, 90, 100, 110, 170, 200, 350 };
// 5. 绘制柱状体
x = 80;
Font font2 = new Font("Arial", 10, FontStyle.Bold);
SolidBrush mybrush = new SolidBrush(Color.Red);
SolidBrush mybrush2 = new SolidBrush(Color.Green);
// 循环绘制每一期数据 (此处演示第一期)
// 报名人数 (红色)
g.FillRectangle(mybrush, x, 340 - Count1[0], 20, Count1[0]);
g.DrawString(Count1[0].ToString(), font2, Brushes.Red, x, 340 - Count1[0] - 15);
// 通过人数 (绿色)
x += 20;
g.FillRectangle(mybrush2, x, 340 - Count2[0], 20, Count2[0]);
g.DrawString(Count2[0].ToString(), font2, Brushes.Green, x, 340 - Count2[0] - 15);
// 后续月份依此类推...
// 6. 绘制图例
g.DrawRectangle(new Pen(Brushes.Blue), 170, 400, 250, 50);
g.FillRectangle(Brushes.Red, 270, 410, 20, 10);
g.DrawString("报名人数", font2, Brushes.Red, 292, 408);
g.FillRectangle(Brushes.Green, 270, 430, 20, 10);
g.DrawString("通过人数", font2, Brushes.Green, 292, 428);
// 7. 输出图像
System.IO.MemoryStream ms = new System.IO.MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
Response.ClearContent();
Response.ContentType = "image/Jpeg";
Response.BinaryWrite(ms.ToArray());
}
finally
{
g.Dispose();
image.Dispose();
}
}
第二步:绘制折线统计图
折线图主要用于显示数据随时间变化的趋势。与柱状图类似,区别在于使用 DrawLines 方法连接代表数值的坐标点,并通过 Point[] 数组管理坐标数据。
核心步骤如下:
- 计算坐标点数组:根据读取到的数据值,计算每个时间点(第一期至全年)对应的 X 轴横坐标(固定间隔递增)和 Y 轴纵坐标(340减去数据值)。
- 绘制折线:创建
Point[]数组并填入坐标,使用g.DrawLines(pen, points)绘制线条。 - 添加数值标记:在每个数据点附近绘制具体的数值文本。
折线图完整代码如下:
private void CreateLineChart()
{
int height = 480, width = 700;
Bitmap image = new Bitmap(width, height);
Graphics g = Graphics.FromImage(image);
try
{
// 初始化
g.Clear(Color.White);
Font font = new Font("Arial", 9, FontStyle.Regular);
Font font1 = new Font("宋体", 20, FontStyle.Regular);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.Blue, 1.2f, true);
g.FillRectangle(Brushes.AliceBlue, 0, 0, width, height);
// 标题
g.DrawString(this.ddlTaget.SelectedItem.Text + " " + this.ddlYear.SelectedItem.Text + " 成绩统计折线图", font1, new SolidBrush(Color.Blue), new PointF(85, 30));
g.DrawRectangle(new Pen(Color.Blue), 0, 0, image.Width - 1, image.Height - 1);
Pen mypen = new Pen(brush, 1);
Pen mypen2 = new Pen(Color.Red, 2);
// 绘制坐标网格(略,同柱状图逻辑)
// ... 绘制网格代码 ...
// 数据点数组(报名人数)
int[] Count1 = new int[7] { 100, 110, 120, 130, 210, 250, 450 };
Point[] points1 = new Point[7];
// 填充坐标:X轴每隔80px一个刻度,Y轴从340处向下计算
points1[0].X = 60; points1[0].Y = 340 - Count1[0];
points1[1].X = 140; points1[1].Y = 340 - Count1[1];
points1[2].X = 220; points1[2].Y = 340 - Count1[2];
points1[3].X = 300; points1[3].Y = 340 - Count1[3];
points1[4].X = 380; points1[4].Y = 340 - Count1[4];
points1[5].X = 460; points1[5].Y = 340 - Count1[5];
points1[6].X = 540; points1[6].Y = 340 - Count1[6];
// 绘制报名人数折线
g.DrawLines(mypen2, points1);
Font font3 = new Font("Arial", 10, FontStyle.Bold);
for(int i=0; i<7; i++) {
// 简单放置数字,实际项目中可能需要更复杂的定位逻辑避免重叠
if(i==0) g.DrawString(Count1[i].ToString(), font3, Brushes.Red, 58, points1[i].Y - 20);
else if(i<4) g.DrawString(Count1[i].ToString(), font3, Brushes.Red, 138+i*80, points1[i].Y - 20);
else g.DrawString(Count1[i].ToString(), font3, Brushes.Red, 378+(i-4)*80, points1[i].Y - 20);
}
// 绘制通过人数折线(绿色,逻辑同上)
int[] Count2 = new int[7] { 80, 90, 100, 110, 170, 200, 350 };
Point[] points2 = new Point[7];
for(int i=0; i<7; i++) points2[i] = new Point(points1[i].X, 340 - Count2[i]);
Pen mypen3 = new Pen(Color.Green, 2);
g.DrawLines(mypen3, points2);
for(int i=0; i<7; i++) {
if(i==0) g.DrawString(Count2[i].ToString(), font3, Brushes.Green, 61, points2[i].Y - 15);
else if(i<4) g.DrawString(Count2[i].ToString(), font3, Brushes.Green, 131+i*80, points2[i].Y - 15);
else g.DrawString(Count2[i].ToString(), font3, Brushes.Green, 381+(i-4)*80, points2[i].Y - 15);
}
// 图例
g.DrawRectangle(new Pen(Brushes.Red), 180, 390, 250, 50);
g.FillRectangle(Brushes.Red, 270, 402, 20, 10);
g.DrawString("报名人数", font, Brushes.Red, 292, 400);
g.FillRectangle(Brushes.Green, 270, 422, 20, 10);
g.DrawString("通过人数", font, Brushes.Green, 292, 420);
// 输出
System.IO.MemoryStream ms = new System.IO.MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
Response.ClearContent();
Response.ContentType = "image/Jpeg";
Response.BinaryWrite(ms.ToArray());
}
finally
{
g.Dispose();
image.Dispose();
}
}
第三步:绘制扇形统计图(饼图)
扇形图常用于展示各部分占整体的比例。在 C# 中,直接使用 FillPie 方法即可绘制。需要注意 currentdegree 角度的累加,以确保扇形能正确拼接成完整的圆。
主要逻辑包括:
- 确定绘制区域:计算饼图的矩形区域(
Rectangle),通常位于画布中央偏上位置。 - 计算角度:根据每个数据项(如“通过人数”、“未通过人数”)占总数的百分比,乘以 360 度得到起始角度和扫过角度。
- 绘制扇区:调用
objgraphics.FillPie(brush, rectangle, startAngle, sweepAngle)依次绘制各个扇区。 - 添加统计信息:在图表下方或侧边绘制详细的统计数据文本框。
扇形统计图完整代码如下:
private void CreatePieChart()
{
// 数据库连接及数据读取(省略具体SQL细节)
// ... 读取 Total, count[0](总数), count[1](通过数) 等 ...
float Total = 450.0f; // 示例总数
float passCount = 350.0f; // 示例通过数
// 布局参数
int width = 350;
int height = width + 200; // 额外高度用于放图例
int titleHeight = 40;
Rectangle pieRect = new Rectangle(0, titleHeight, width, width);
Bitmap objbitmap = new Bitmap(width, height);
Graphics objgraphics = Graphics.FromImage(objbitmap);
// 清理背景
objgraphics.FillRectangle(new SolidBrush(Color.White), 0, 0, width, height);
objgraphics.FillRectangle(new SolidBrush(Color.Beige), pieRect);
// 颜色定义
ArrayList colors = new ArrayList();
Random rnd = new Random();
colors.Add(new SolidBrush(Color.FromArgb(rnd.Next(255), rnd.Next(255), rnd.Next(255)))); // 未通过颜色
colors.Add(new SolidBrush(Color.FromArgb(rnd.Next(255), rnd.Next(255), rnd.Next(255)))); // 通过颜色
// 绘制扇形
float currentdegree = 0.0f;
// 1. 绘制通过人数扇区
float passSweep = (passCount / Total) * 360;
objgraphics.FillPie((SolidBrush)colors[1], pieRect, currentdegree, passSweep);
currentdegree += passSweep;
// 2. 绘制未通过人数扇区
float failCount = Total - passCount;
float failSweep = (failCount / Total) * 360;
objgraphics.FillPie((SolidBrush)colors[0], pieRect, currentdegree, failSweep);
// 标题
SolidBrush blackbrush = new SolidBrush(Color.Black);
Font fonttitle = new Font("verdana", 10, FontStyle.Bold);
StringFormat sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
objgraphics.DrawString("机关单位成绩统计饼状图", fonttitle, blackbrush,
new Rectangle(0, 0, width, titleHeight), sf);
// 图例与详细信息
Font fontlegend = new Font("verdana", 9);
objgraphics.DrawString("----------------统计信息------------------",
new SolidBrush(Color.Blue), 20, height - 150);
objgraphics.DrawString("统计单位: 示例单位", fontlegend, blackbrush, 20, height - 130);
objgraphics.DrawString("总人数: " + Convert.ToInt32(Total), fontlegend, blackbrush, 20, height - 110);
// 绘制小方块标识
objgraphics.FillRectangle((SolidBrush)colors[1], 5, height - 90, 10, 10);
objgraphics.DrawString("通过人数: " + Convert.ToInt32(passCount), fontlegend, blackbrush, 20, height - 90);
objgraphics.FillRectangle((SolidBrush)colors[0], 5, height - 70, 10, 10);
objgraphics.DrawString("未通过: " + Convert.ToInt32(failCount), fontlegend, blackbrush, 20, height - 70);
// 输出
objbitmap.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
objgraphics.Dispose();
objbitmap.Dispose();
}
以上就是 C# 利用 System.Drawing 绘制柱状图、折线图和扇形图的详细内容,更多关于 C# 图表开发优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













