拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C# WinForms Chart控件多Y轴图表实现方案,解决上位机多路信号同屏显示

简介一份面向C# WinForm开发者的Chart控件多Y轴同显完整源码包针对同一图表中多个数据系列量级不同导致刻度互相遮挡的问题给出了从ChartArea创建到轴坐标布局的整套实现。压缩包共117个文件、约21.19MB以cs源码为核心辅以dll运行库、xml与config配置、resx界面资源、sln工程文件等是一个可编译运行的monitoring示例项目。实现内容包括动态添加多个ChartArea、为每个区域配置独立的AxisY最小值与最大值、通过Series.ChartArea将数据系列关联到对应Y轴以及利用Position属性和IsEndLabelVisible控制多轴显示位置确保不同量级数据在同一图表中互不干扰。已有2746人学习下载适合需要实现多轴图表展示的WinForm开发人员与初学Chart高级用法的读者参考。 做上位机的人最常碰到的图表需求就是多路信号同时上屏。温度、压力、流量、电压、电流量纲不同数值范围可能差几十倍硬塞到同一个Y轴里小信号的曲线会被压成一根直线现场根本没法判断状态。更要命的是调试时你往往要把这几路信号放在一张图里对照着看趋势这就必须让图表控件同时显示多个Y轴。这篇文章就把C# WinForms下实现多Y轴显示的核心思路和全部代码整理出来用的是VS自带的Chart控件不依赖第三方库直接能跑。1. 先搞清楚多Y轴解决的问题是什么1.1 上位机场景里的“轴打架”先看一个我实际做过的项目一套温度老化测试台要同时监控箱内温度、加热功率和循环流量。温度在几十摄氏度波动功率可能上千瓦流量只有每分钟几升这三个数据扔进同一个坐标系里功率曲线占满整个Y轴温度和流量就直接贴在坐标轴底部别说看趋势连曲线的颜色都分不清楚。这种问题在数据采集类上位机里太常见了。特别是有时候还要把模拟量和开关量放一起看数值级别差距进一步被拉大。多Y轴方案的本质就是给不同量纲的信号分配独立的刻度空间让每条曲线都能在自己的坐标系里正常展开同时又能共享同一根X轴通常是时间轴保证对照查看时位置一致。1.2 内置Chart控件 vs 第三方库我的选型依据做C#上位机实现图表的方案其实不少。我用表格列一下主流方案方案多Y轴支持部署复杂度维护活跃度上手成本VS自带Chart控件支持方案灵活零依赖随.NET走微软官方维护更新慢但稳定低ZedGraph非常强YAxisList机制需引入DLL基本停止更新中ScottPlot支持多轴API较新NuGet引用非常活跃低LiveCharts支持NuGet引用版本变动较大中我的建议是项目里如果只是2到6路信号优先用自带Chart控件。理由很简单做上位机的人最怕的就是交付时客户环境缺DLL、版本冲突、授权问题自带控件部署到哪台Windows机器上都不会出幺蛾子。等真遇到需要缩放联动、十字光标、大数据量交互这种高级功能时再考虑上ScottPlot也不迟。2. 多Y轴实现的底层原理与两个分支方案2.1 Chart控件的坐标轴体系AxisY 与 AxisY2在动手写代码前先理解WinForms Chart的坐标轴模型。一个ChartArea绘图区默认有两条X轴AxisX和AxisX2和两条Y轴AxisY和AxisY2其中AxisX、AxisY是主轴AxisX2、AxisY2是副轴。关键点来了在同一个ChartArea里一个Series只能绑定到四条轴中的某一条。想实现两个Y轴最简单的办法就是把一路信号设置成 YAxisType Secondary绑定到AxisY2上。但问题是一个ChartArea里只有一条AxisY2所以你最多只能做两个Y轴。如果项目里超过两个Y轴怎么办就需要换一个思路创建多个ChartArea让每个ChartArea拥有自己独立的Y轴同时通过对齐手段让所有ChartArea共用同一套X轴位置视觉上仍表现为“一张图多个Y轴”。这也是这篇文章要重点展开的方案。2.2 方案一单绘图区双轴适合2路信号如果只需显示两路信号代码是最简单的。在Series上设置YAxisType即可var series1 new Series(温度); series1.ChartType SeriesChartType.Line; series1.YAxisType AxisType.Primary; // 默认主轴 var series2 new Series(功率); series2.ChartType SeriesChartType.Line; series2.YAxisType AxisType.Secondary; // 绑定副轴 series2.ChartArea chart1.ChartAreas[0].Name; // 副轴Y轴标签颜色建议和曲线颜色一致 chart1.ChartAreas[0].AxisY2.LabelStyle.ForeColor series2.Color; chart1.ChartAreas[0].AxisY2.TitleForeColor series2.Color;这种方式优点就是简单缺点也很明显只能两个Y轴而且左侧主轴和右侧副轴的刻度范围是独立自动计算的数据差距大时两条曲线虽然都能显示但很难在视觉上对比。2.3 方案二多绘图区堆叠共享X轴支持N个Y轴既然一个ChartArea最多两个Y轴N个Y轴就用N个ChartArea垂直堆叠。每个ChartArea负责一路信号的绘制和独立刻度但所有Area内嵌绘图区InnerPlotPosition的左右边界必须完全对齐这样X轴就相当于共享了。这个方案的精髓就两句话每个Area的绘图区纵向分布、横向对齐。纵向分布靠Position控制横向对齐靠InnerPlotPosition或AlignWithChartArea控制。方法本身不难但很多初学者在这一步会摔跟头因为默认情况下多个ChartArea会全部叠在一个区域里根本看不到预期效果。下一章直接给完整可运行的代码。3. 完整代码三路信号多Y轴同屏显示这里我以“温度-压力-流量”三路模拟信号为例给出完整实现。新建一个WinForms项目窗体上放一个Chart控件命名为chart1和一个Timer控件命名为timer1Interval设置为100ms然后贴代码。3.1 窗体加载时的初始化加载事件里调用初始化方法创建三个ChartArea和三个Seriesprivate void Form1_Load(object sender, EventArgs e) { InitChartLayout(); InitSeries(); timer1.Start(); }3.2 多轴布局与数据绑定核心代码下面的代码是关键先创建三个ChartArea手动设置各自的Position实现垂直堆叠private void InitChartLayout() { chart1.SuspendLayout(); chart1.ChartAreas.Clear(); chart1.Series.Clear(); string[] areaNames { AreaTemp, AreaPress, AreaFlow }; // 三个绘图区垂直排列区域高度和坐标按百分比设置 // 顶部留出标题空间底部给X轴刻度留空间 float[] yPositions { 8f, 37f, 66f }; float[] heights { 27f, 27f, 29f }; for (int i 0; i areaNames.Length; i) { var area new ChartArea(areaNames[i]); chart1.ChartAreas.Add(area); // 设置ChartArea整体位置单位是百分比 area.Position new ElementPosition(0, yPositions[i], 100, heights[i]); // 设置内嵌绘图区位置统一左右边界 // 左侧留12%给Y轴标题和刻度右侧留12%余量 if (i areaNames.Length - 1) { // 最下面一个Area要展示X轴刻度绘图区高度少留一些 area.InnerPlotPosition new ElementPosition(12, 4, 76, 72); } else { area.InnerPlotPosition new ElementPosition(12, 4, 76, 92); } } // 配置每个Area的轴显示 ConfigureArea(chart1.ChartAreas[AreaTemp], false, Color.OrangeRed, 温度 ℃); ConfigureArea(chart1.ChartAreas[AreaPress], false, Color.SteelBlue, 压力 kPa); ConfigureArea(chart1.ChartAreas[AreaFlow], true, Color.MediumSeaGreen, 流量 L/min); chart1.ResumeLayout(); }ConfigureArea方法负责隐藏多余的X轴、设置Y轴标题和颜色private void ConfigureArea(ChartArea area, bool showXAxis, Color seriesColor, string yTitle) { // 只有最下面的区域显示X轴刻度其余区域隐藏视觉上X轴是共享的 area.AxisX.LabelStyle.Enabled showXAxis; area.AxisX.MajorTickMark.Enabled showXAxis; area.AxisX.MajorGrid.Enabled false; area.AxisX.LineColor Color.DimGray; // 第一个区域保留横向网格线其他区域关闭避免画面太乱 if (area.Name AreaTemp) { area.AxisY.MajorGrid.Enabled true; area.AxisY.MajorGrid.LineColor Color.LightGray; area.AxisY.MajorGrid.LineDashStyle ChartDashStyle.Dash; } else { area.AxisY.MajorGrid.Enabled false; } area.AxisY.Title yTitle; area.AxisY.TitleForeColor seriesColor; area.AxisY.LabelStyle.ForeColor seriesColor; area.AxisY.LineColor seriesColor; }然后创建三个Series并绑定到各自ChartAreaprivate void InitSeries() { AddSeries(温度, AreaTemp, Color.OrangeRed); AddSeries(压力, AreaPress, Color.SteelBlue); AddSeries(流量, AreaFlow, Color.MediumSeaGreen); } private void AddSeries(string name, string areaName, Color color) { var series new Series(name); series.ChartArea areaName; series.ChartType SeriesChartType.Line; series.BorderWidth 2; series.Color color; series.XValueType ChartValueType.Double; series.YValueType ChartValueType.Double; chart1.Series.Add(series); }3.3 模拟数据刷新与运行效果调整Timer的Tick事件里生成模拟数据同时控制X轴范围滚动private int _currentIndex; private readonly Random _rand new Random(); private void timer1_Tick(object sender, EventArgs e) { int x _currentIndex; double temp 25 10 * Math.Sin(x * 0.05) _rand.NextDouble() * 2; double press 100 20 * Math.Sin(x * 0.08 0.5) _rand.NextDouble() * 3; double flow 10 3 * Math.Cos(x * 0.03) _rand.NextDouble() * 0.5; chart1.Series[温度].Points.AddXY(x, temp); chart1.Series[压力].Points.AddXY(x, press); chart1.Series[流量].Points.AddXY(x, flow); // 限制最大点数防止内存和绘制压力过大 int maxPoints 200; foreach (var series in chart1.Series) { while (series.Points.Count maxPoints) { series.Points.RemoveAt(0); } } // 同步刷新每个绘图区的X轴范围实现滚动效果 double minX Math.Max(0, x - maxPoints); foreach (ChartArea area in chart1.ChartAreas) { area.AxisX.Minimum minX; area.AxisX.Maximum x; } }到这程序就能跑起来了。三个图表区从上到下排列对应温度、压力、流量三路信号左边各有自己的Y轴标题和刻度X轴只在最下方显示三条曲线的变化趋势可以清晰对照。运行后如果觉得某个区域太挤或太宽微调InitChartLayout里的yPositions和heights数组就行。4. 常见问题与排查技巧实录4.1 副轴曲线不显示或数据看不到这个问题在单绘图区双轴方案里特别常见。明明Series已经绑定了AxisY2运行后曲线却看不到或者点开了图表设置才发现曲线被压缩在边缘。第一个排查点Y轴范围。Chart控件的Y轴默认是Auto如果数据点里有几个特别大的离群值自动范围会把正常曲线压扁。解决办法是手动给AxisY2设置Minimum和Maximum比如功率范围是0到2000就明确写死。第二个排查点确认Series的颜色和AxisY2标签颜色有没有混在一起如果曲线颜色很浅在白色背景下基本看不见建议在主轴上用深色副轴上用高饱和颜色。4.2 多绘图区X轴不对齐的坑三个Area都设置了Position但运行后X轴的格子错位有的区域曲线起点偏左有的偏右这种情况九成是内嵌绘图区InnerPlotPosition不一致导致的。每个Area的Y轴标题和刻度宽度不同如果不手动指定InnerPlotPositionChart控件会按各自内容自动计算最后就是三个区域的可视数据区宽度不一样。解决办法就是我在代码里写的手动统一InnerPlotPosition的X和Width值。这里有一个细节Y轴标题不要写太长比如“进水管路瞬时流量 L/min”这种左侧12%的空间根本放不下标题会自动截断或者把绘图区挤得更窄。我一般控制在6个汉字以内单位放在第二行或者直接用缩写。4.3 实时刷新卡顿的优化方法刚开始做实时曲线时我最粗暴的做法是每个tick给所有Series调用一次AddXY同时让Chart控件自动调整轴范围结果数据跑到5万点的时候界面明显掉帧。优化思路有三层。第一层限制点数超过200个点就RemoveAt(0)这条对大多数现场数据足够。第二层批量添加数据点如果数据是连续采集堵在缓冲区里的优先用DataPointCollection.AddXY循环加入或者先用一个List收集若干点再一次AddData减少UI线程的频繁绘制。第三层在tick里用chart1.SuspendLayout()和chart1.ResumeLayout()包住数据更新逻辑注意ResumeLayout之后要调用一次chart1.Invalidate()确保重绘。实测下来200个点、100ms刷新周期CPU占用基本可以忽略。5. 把多Y轴图表做得更专业5.1 颜色联动让现场工程师一眼分清多Y轴图表的可读性很大程度上取决于颜色设计。我的习惯是每路信号的主色确定后把对应Y轴的标题、刻度、线条颜色统一下来。代码里ConfigureArea已经做了这件事Y轴标题颜色和曲线颜色一致。这样在图上你看到一条橙色曲线扭头看左侧橙色的Y轴就知道它在第几个区域不用再去对照图例。还有个细节如果一条曲线的数据范围很小颜色又浅而它对应Y轴的刻度颜色是深色看起来会特别不协调。建议Y轴刻度颜色都用中等饱和度的色系避免用浅黄、浅蓝这类低对比色现场工控屏的亮度和角度差异很大低对比度配色会被直接吞掉。5.2 动态Y轴范围自适应模拟数据很好办范围固定。但实际工况里信号范围会漂移如果Y轴范围写死曲线可能跑出视野。比较好的做法是维护每路信号的最大最小值在每次添加数据点的时候更新然后给Y轴范围加10%左右的边距private double _minTemp double.MaxValue; private double _maxTemp double.MinValue; private double _minPress double.MaxValue; private double _maxPress double.MinValue; private double _minFlow double.MaxValue; private double _maxFlow double.MinValue; private void UpdateYAxisRange() { var areaTemp chart1.ChartAreas[AreaTemp]; if (_minTemp double.MaxValue) { double pad (_maxTemp - _minTemp) * 0.1; areaTemp.AxisY.Minimum _minTemp - pad; areaTemp.AxisY.Maximum _maxTemp pad; } // 压力和流量同理 }动态范围有个需要注意的点如果出现一个异常大的瞬间尖峰Y轴范围会被瞬间拉得很大正常波形又变成一条直线。我通常会在计算前做一次过滤比如超过正常上限三倍的数据点不参与范围计算只保留在曲线上标记一下具体怎么处理看项目对异常值的容忍度。5.3 什么时候该换第三方库内置Chart能解决80%的上位机图表需求但如果你要做跨区域曲线联动缩放、数据量超过几十万点、需要十字光标实时追踪、或者要嵌入到偏数据展示型的产品里内置Chart就比较吃力了。这时候我建议看ScottPlot它的多Y轴API比Chart控件简单直接性能也更好。不过凡事有取舍。ScottPlot在WinForms里的版本迭代比较快不同大版本的API差异明显如果项目上线后没人持续跟进升级后期维护反而是个负担。内置Chart虽然老但API十年没怎么变过网上资料和社区方案也多遇到问题基本都能搜到答案。6. 一点个人经验我在实际项目里摸索出来的经验是多Y轴图表的难点从来不在控件本身而在于数据对齐和范围管理。三路信号如果采样时间戳对不齐无论用多少Y轴画出来的趋势图都是错位的现场工程师一眼就能看出问题。所以在我自己动手的时候不管用哪个方案第一件事永远是确认所有Series的X值是否同步、滚动范围是否一致然后再去调颜色、调布局。最后再分享一个小技巧调试时把ChartArea的Position和InnerPlotPosition先用大数字调比如位置设成50%、宽度设成10%让区域边界明显变形确认每个区的位置方向对不对然后再改回目标值。这样做比盲猜百分比省时间得多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门