尧图精选

C#实时曲线绘制与SQL数据库集成实战:从选型到性能优化

🕒 发布时间:2026/10/2 14:03:10 📁 来源:尧图网络
简介这份C#绘制曲线示例源码面向具备一定C#基础、希望掌握数据可视化与图形编程的开发者重点解决如何将SQL数据库中的数据读取后动态绘制成曲线的问题。资源包共25个文件约50KB以cs源代码、csproj项目文件、sln解决方案文件、sql数据库脚本及resx资源文件为主另含exe可执行程序、dll类库与pdb调试文件结构紧凑便于在Visual Studio中直接打开运行。已有748人学习下载适合作为课程设计或自学练手参考。源码完整演示了从建立数据库连接、执行SQL查询、解析结果集到使用GDI绘制曲线的全过程并附带建库脚本读者可据此理解数据驱动绘图的基本思路并在此基础上扩展异步加载、动态刷新与鼠标交互等优化功能提升C#图形编程与数据可视化能力。1. 从一条实时曲线说起C# 绘制曲线图为什么总绕不开 SQL 数据库很多做 C# 上位机的朋友都有过这样的场景设备每秒往数据库里写一批温度、压力、流量数据界面上要实时画出一条平滑曲线历史数据还要能回放、能缩放、能导出。真动手时才发现画曲线本身不难难的是数据从 SQL 数据库里取出来之后怎么和绘图控件对上、几万条点怎么不卡、时间轴怎么对齐。这套「C# 绘制曲线示例源码含 SQL 数据库」要解决的正是这条链路用 C# 把数据库里的时序数据读出来在 WinForms 或 WPF 上画成可交互的曲线图。它适合做工业上位机、设备监控、实验数据采集的开发者尤其是刚接触 C# 绘图、又必须和 SQL Server 打交道的人。下面我按自己实际做过的顺序把选型、建库、取数、绘图、避坑一条条讲清楚源码结构也会给到能直接抄的程度。2. 绘图方案怎么选GDI、Chart 控件还是第三方库2.1 三种主流画法的边界与适用场景在 C# 里画曲线绕不开三条路。第一条是System.Drawing的 GDI直接拿Graphics对象在Panel或PictureBox上DrawLine、DrawCurve。它的好处是零依赖、可控性最强坐标轴、网格、游标全部自己算坏处是什么都要自己写缩放和命中测试得手动实现。第二条是 WinForms 自带的System.Windows.Forms.DataVisualization.Charting.Chart控件拖一个到窗体上绑定Series就能出图支持缩放、滚动、图例开发速度最快。第三条是第三方库比如 OxyPlot、LiveCharts、ScottPlot跨平台、性能好、API 现代但引入外部依赖团队要接受。我一般的判断标准是如果只是内部工具、点数在几千以内、要快速交付直接用 Chart 控件如果要做实时刷新、每秒上千点、还要自定义交互用 GDI 自己画或者上 ScottPlot如果是 WPF 项目优先 OxyPlot 或 LiveCharts别硬套 WinForms 控件。标题里说的是「示例源码」通常这类源码为了让人看懂原理多半用 GDI 或 Chart 控件因为不依赖 NuGet 包拷过去就能编译。选型还要考虑一个现实问题数据库取数速度和绘图刷新速度要匹配。SQL Server 一次SELECT几万行大概几十到几百毫秒如果你用 Chart 控件每来一条数据就Points.Add再Invalidate界面会闪。所以选型时就要想好是「批量取数 一次性重绘」还是「增量取数 局部刷新」这决定了后面代码怎么写。2.2 用 Chart 控件画第一条曲线的最小可运行代码先给一段能直接跑的最小示例窗体上放一个Chart控件命名chart1加一个按钮btnDraw。这段代码不连数据库先把绘图这条链路跑通确认环境没问题。using System; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; namespace CurveDemo { public partial class Form1 : Form { public Form1() { InitializeComponent(); InitChart(); } // 初始化图表区域、坐标轴样式 private void InitChart() { chart1.Series.Clear(); chart1.ChartAreas.Clear(); ChartArea area new ChartArea(main); area.AxisX.Title 时间(s); area.AxisY.Title 温度(℃); area.AxisX.MajorGrid.LineColor Color.LightGray; area.AxisY.MajorGrid.LineColor Color.LightGray; // 关闭自动滚动避免数据多时坐标轴乱跳 area.AxisX.ScrollBar.Enabled false; chart1.ChartAreas.Add(area); Series series new Series(temp); series.ChartType SeriesChartType.Spline; // 平滑曲线 series.BorderWidth 2; series.Color Color.Red; series.XValueType ChartValueType.Double; chart1.Series.Add(series); } private void btnDraw_Click(object sender, EventArgs e) { chart1.Series[temp].Points.Clear(); // 模拟 200 个点实际项目里换成数据库查询结果 for (int i 0; i 200; i) { double x i * 0.5; double y 25 5 * Math.Sin(i * 0.1); chart1.Series[temp].Points.AddXY(x, y); } chart1.Invalidate(); } } }逻辑说明InitChart里先清空默认的 Series 和 ChartArea避免设计器自动生成的样式干扰ChartType选Spline是平滑曲线如果数据本身抖动大、不想被平滑掩盖真实波动改成Line。AddXY一次加一个点200 个点没问题但上万点就要用Points.DataBindXY批量绑定性能差一个数量级。参数说明BorderWidth控制线宽单位像素XValueType设成Double才能让 X 轴按数值比例排布默认是Auto遇到时间字符串会退化成等距排列曲线就失真了。AxisX.ScrollBar.Enabled关掉是因为示例数据少数据多了再打开配合IsZoomed用。提示System.Windows.Forms.DataVisualization在 .NET Framework 项目里默认可用但在 .NET 6/8 的 WinForms 项目里需要额外引用System.Windows.Forms.DataVisualizationNuGet 包否则编译不过。3. SQL 数据库这一层建表、取数和参数化查询3.1 时序数据表怎么设计才不拖累绘图曲线数据本质是「时间 数值」的序列建表时最容易犯的错是把时间存成字符串。字符串比较慢、无法做范围索引、排序还依赖格式。正确做法是用datetime2或bigint存毫秒时间戳。下面这张表是我在多个上位机项目里用过的结构字段不多但够用。CREATE TABLE CurveData ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, DeviceId INT NOT NULL, -- 设备编号多设备共表 SampleTime DATETIME2(3) NOT NULL, -- 毫秒精度采样时间 Value FLOAT NOT NULL, -- 采样值 Tag VARCHAR(32) NOT NULL -- 测点名称如 Temp1 ); -- 覆盖查询条件避免全表扫描 CREATE NONCLUSTERED INDEX IX_CurveData_Query ON CurveData (DeviceId, Tag, SampleTime) INCLUDE (Value);逻辑说明DeviceId Tag SampleTime建联合索引是因为绘图查询几乎永远是「某设备某测点某时间段」索引顺序要和 WHERE 条件顺序一致。INCLUDE (Value)把数值列带进索引查询时不用回表几百万行也能在百毫秒级返回。参数说明DATETIME2(3)的 3 表示毫秒精度够用且比DATETIME2(7)省空间。如果采样频率高于 1kHz考虑用BIGINT存 Unix 毫秒时间戳比较和索引更快。FLOAT对应 C# 的double别用REAL精度不够画曲线会出现台阶。3.2 用参数化查询把数据读进 DataTable取数这一步新手最容易写出字符串拼接的 SQL既慢又有注入风险。下面这段是标准写法用SqlDataAdapter一次填满DataTable再交给绘图层。using System.Data; using System.Data.SqlClient; public DataTable QueryCurve(int deviceId, string tag, DateTime from, DateTime to) { string sql SELECT SampleTime, Value FROM CurveData WHERE DeviceId DeviceId AND Tag Tag AND SampleTime From AND SampleTime To ORDER BY SampleTime; DataTable dt new DataTable(); using (SqlConnection conn new SqlConnection(Server.;DatabaseDemo;Integrated Securitytrue;)) using (SqlCommand cmd new SqlCommand(sql, conn)) using (SqlDataAdapter adapter new SqlDataAdapter(cmd)) { cmd.Parameters.Add(DeviceId, SqlDbType.Int).Value deviceId; cmd.Parameters.Add(Tag, SqlDbType.VarChar, 32).Value tag; cmd.Parameters.Add(From, SqlDbType.DateTime2).Value from; cmd.Parameters.Add(To, SqlDbType.DateTime2).Value to; conn.Open(); adapter.Fill(dt); } return dt; }逻辑说明using保证连接、命令、适配器都被释放上位机长时间运行最怕连接泄漏。SqlDataAdapter.Fill内部用 DataReader 逐行读比DataSet轻。参数用Add显式指定类型和长度VarChar的长度 32 要和表定义一致否则 SQL Server 会做隐式转换索引可能失效。参数说明连接字符串里Integrated Securitytrue是 Windows 身份验证部署到工控机时如果 SQL Server 和程序不在同一账户下改成User Idxx;Passwordxx;。时间段查询一定要带上下界别只写否则数据量随时间无限增长某天查询会突然变慢。3.3 大数据量下的分页与降采样当一条曲线要显示几十万个点时全查出来再画内存和渲染都扛不住。常见做法是降采样在 SQL 里按时间分桶取平均或者用ROW_NUMBER()分页。下面这个查询按每 10 秒一个桶取平均值把点数压到可控范围。SELECT DATEADD(SECOND, DATEDIFF(SECOND, 0, SampleTime) / 10 * 10, 0) AS BucketTime, AVG(Value) AS AvgValue FROM CurveData WHERE DeviceId DeviceId AND Tag Tag AND SampleTime BETWEEN From AND To GROUP BY DATEDIFF(SECOND, 0, SampleTime) / 10 ORDER BY BucketTime;逻辑说明DATEDIFF(SECOND, 0, SampleTime)把时间转成秒数除以 10 取整再乘 10就得到 10 秒对齐的桶。AVG做降采样曲线趋势保留毛刺被抹平。如果要做峰值保持把AVG换成MAX或MIN。参数说明桶大小 10 秒不是固定的按「总点数 / 屏幕像素宽度」估算一般让最终点数在 1000 到 2000 之间肉眼看起来足够平滑。桶太小降采样没意义桶太大曲线会失真尤其是尖峰信号。4. 把数据库数据画成曲线绑定、刷新与交互4.1 DataTable 到 Chart 的批量绑定上一章取到的DataTable直接喂给 Chart 控件用DataBindXY比循环AddXY快得多。下面这段把查询结果画到图上同时处理时间轴。private void DrawFromDatabase(int deviceId, string tag, DateTime from, DateTime to) { DataTable dt QueryCurve(deviceId, tag, from, to); if (dt.Rows.Count 0) return; // 时间转 OADateChart 内部按 double 处理 double[] xs new double[dt.Rows.Count]; double[] ys new double[dt.Rows.Count]; for (int i 0; i dt.Rows.Count; i) { xs[i] ((DateTime)dt.Rows[i][SampleTime]).ToOADate(); ys[i] Convert.ToDouble(dt.Rows[i][Value]); } Series s chart1.Series[temp]; s.Points.Clear(); s.Points.DataBindXY(xs, ys); // X 轴按时间格式化显示 chart1.ChartAreas[main].AxisX.LabelStyle.Format HH:mm:ss; chart1.ChartAreas[main].AxisX.IntervalType DateTimeIntervalType.Auto; chart1.Invalidate(); }逻辑说明ToOADate()把DateTime转成 OLE 自动化日期本质是 doubleChart 的 X 轴能正确按时间比例排布。DataBindXY一次性绑定两个数组内部批量处理比逐点AddXY快 5 到 10 倍。绑定前先Points.Clear()否则新旧数据会叠在一起。参数说明LabelStyle.Format设成HH:mm:ss是显示格式如果跨度是几天改成MM-dd HH:mm。IntervalType设Auto让控件自己算刻度间隔数据密集时手动设Interval更可控比如AxisX.Interval 60表示每 60 秒一个刻度。4.2 实时刷新定时器 增量追加实时曲线不能每次重查全表正确做法是记住上次查询的最大时间只取增量。下面用System.Windows.Forms.Timer每 500ms 拉一次新数据追加到曲线尾部。private DateTime _lastTime DateTime.Now; private void timer1_Tick(object sender, EventArgs e) { string sql SELECT SampleTime, Value FROM CurveData WHERE DeviceId DeviceId AND Tag Tag AND SampleTime LastTime ORDER BY SampleTime; // ... 执行查询得到 dt ... Series s chart1.Series[temp]; foreach (DataRow row in dt.Rows) { DateTime t (DateTime)row[SampleTime]; double v Convert.ToDouble(row[Value]); s.Points.AddXY(t.ToOADate(), v); _lastTime t; } // 只保留最近 2000 个点防止内存无限增长 while (s.Points.Count 2000) s.Points.RemoveAt(0); chart1.ChartAreas[main].AxisX.Minimum s.Points[0].XValue; chart1.ChartAreas[main].AxisX.Maximum s.Points[s.Points.Count - 1].XValue; chart1.Invalidate(); }逻辑说明_lastTime记录已取到的最大时间下次只查比它新的数据避免重复。追加后裁剪掉最老的点保持窗口滑动。手动设置AxisX.Minimum和Maximum让视图跟着数据滚动否则坐标轴会自动缩放导致曲线抖动。参数说明定时器间隔 500ms 是刷新频率和数据库压力的折中太快数据库扛不住太慢曲线不连续。保留点数 2000 对应屏幕宽度约 1000 像素每个像素两个点视觉上够用。如果采样频率高可以改成保留最近 60 秒的数据按时间裁剪而不是按点数。4.3 鼠标游标与数据点命中测试光有曲线不够用户要能看某个点的具体数值。Chart 控件自带HitTest可以拿到鼠标位置最近的数据点。private void chart1_MouseMove(object sender, MouseEventArgs e) { HitTestResult result chart1.HitTest(e.X, e.Y); if (result.ChartElementType ChartElementType.DataPoint) { int idx result.PointIndex; DataPoint p chart1.Series[temp].Points[idx]; double time DateTime.FromOADate(p.XValue).TimeOfDay.TotalHours; toolTip1.SetToolTip(chart1, $时间 {time:F2}h 值 {p.YValues[0]:F2}); } }逻辑说明HitTest返回命中的图表元素类型和点索引只有命中DataPoint才显示提示。FromOADate把 X 值还原成时间YValues[0]取第一个 Y 值多 Y 轴时会有多个。参数说明HitTest的命中范围默认是点周围几个像素数据密集时容易命中相邻点可以调HitTestResult的容差或者自己算最近点。ToolTip的显示延迟用toolTip1.InitialDelay控制默认 500ms实时监控场景可以调到 200ms。5. 避坑与排查曲线画不出来、卡顿、时间错乱的常见原因5.1 曲线一片空白数据明明查到了现象DataTable里Rows.Count大于 0但图上什么都没有。原因通常是 X 值类型不对。如果SampleTime直接以字符串形式AddXYChart 会把它当分类轴点与点等距排列数值全挤在一起或者超出可视范围。解决统一转成ToOADate()的 double并确认Series.XValueType ChartValueType.Double。另一个原因是AxisX.Minimum和Maximum被手动设成了固定值而数据不在这个区间内检查一下有没有残留的轴范围设置。5.2 数据量一大就卡死界面无响应现象查询几万行后界面冻结几秒鼠标转圈。原因有两个一是在 UI 线程里执行 SQL 查询和绘图二是逐点AddXY。解决查询放到Task.Run或BackgroundWorker里查完再Invoke回 UI 线程绑定绘图用DataBindXY批量绑定或者先降采样再画。如果还卡检查是不是每次刷新都Points.Clear()后重新绑定全量数据实时场景应该只追加增量。5.3 时间轴显示成 1900 年或者乱码现象X 轴标签显示1900-01-01或者一串数字。原因是ToOADate转换后没有设置LabelStyle.FormatChart 默认按数值显示。解决设置AxisX.LabelStyle.Format HH:mm:ss并且确认AxisX.IsMarginVisible false否则首尾会留白导致时间看起来偏移。如果用的是DateTime直接绑定注意 Chart 内部还是转 double格式化必须显式设。5.4 SQL 查询越来越慢索引没生效现象刚上线时查询很快数据积累到几百万行后查询要好几秒。原因是查询条件顺序和索引列顺序不匹配或者参数类型和列类型不一致导致隐式转换。解决用 SQL Server 的「显示实际执行计划」看是不是走了索引扫描而不是查找确认WHERE里DeviceId、Tag、SampleTime的顺序和索引一致参数用SqlDbType.DateTime2而不是SqlDbType.DateTime避免精度转换。另外定期做索引重建时序表写入频繁碎片增长快。5.5 多设备数据串线曲线混在一起现象查询某个设备的数据图上却混进了别的设备的值。原因是WHERE条件漏了DeviceId或者Tag或者参数赋值时搞错了变量。解决查询方法里把DeviceId和Tag作为必填参数SQL 里两个条件都要有绑定前检查DataTable的列值范围必要时在代码里加断言。多测点场景建议每个测点一个Series用不同颜色区分别全画在一条线上。6. 让曲线源码真正可复用的几个进阶习惯把上面这套跑通之后我一般会做三件事让它从「示例」变成「能进项目」。第一件是把绘图逻辑抽成一个CurveChartHelper类输入DataTable和Series名称内部处理时间转换、降采样、轴范围窗体代码只负责调方法。第二件是给查询加缓存同一时间段重复查询直接返回内存里的DataTable用MemoryCache设 30 秒过期实时刷新时命中率很高。第三件是导出功能把当前Series.Points写回 CSV 或者另存到数据库的归档表方便事后复盘。验证方法上我习惯用「已知函数」测绘图正确性往数据库插一段y sin(x)的数据画出来和数学软件对比看相位和幅值对不对。再插一段带尖峰的方波检查降采样有没有把尖峰抹掉。性能上用Stopwatch卡住查询和绑定两段耗时查询超过 200ms 就去看执行计划绑定超过 100ms 就考虑降采样。// 简单的耗时统计定位瓶颈在查询还是绘图 var sw System.Diagnostics.Stopwatch.StartNew(); DataTable dt QueryCurve(1, Temp1, from, to); long queryMs sw.ElapsedMilliseconds; sw.Restart(); BindToChart(dt); long drawMs sw.ElapsedMilliseconds; System.Diagnostics.Debug.WriteLine($查询 {queryMs}ms, 绘图 {drawMs}ms);参数说明查询耗时目标控制在 200ms 以内绘图控制在 100ms 以内超过就优化对应环节。这个阈值不是绝对的取决于刷新频率500ms 刷新一次的话两段加起来别超过 400ms否则定时器会堆积。最后说个我踩过的坑早期做实时曲线时我把Timer间隔设成 100ms数据库每秒被查 10 次跑了半天 SQL Server 连接池就满了。后来改成 500ms 加增量查询稳定跑几个月没问题。曲线这东西画出来容易画得稳、画得久才是真功夫。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →