using OxyPlot; using OxyPlot.Axes; using OxyPlot.Series; using OxyPlot.WindowsForms; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace 全自动水压检测仪 { /// /// 图表管理器 - 负责实时曲线图的创建和数据更新 /// 使用统一Y轴显示实时压力和压力设定值,便于数据对比 /// public class ChartManager { private PlotView _plotView; private PlotModel _plotModel; private LineSeries _pressureSeries; // 实时压力值(地址3130) private LineSeries _pressureSetSeries; // 压力设定值(地址2400) private List _pressureData; private List _pressureSetData; private const int MAX_DATA_POINTS = 3600; // 最多显示3600个数据点(1小时) private double _initialMinX = 0; // 记录初始最小X值 private bool _isUserZooming = false; // 用户是否正在缩放 private double _maxPressureValue = 0; // 记录最大压力值用于动态调整Y轴 /// /// 初始化图表并添加到指定面板 /// public void InitializeChart(Control targetPanel) { if (targetPanel == null) throw new ArgumentNullException(nameof(targetPanel)); // 清除占位文字 targetPanel.Text = null; // 初始化数据集合 _pressureData = new List(); _pressureSetData = new List(); // 创建PlotModel _plotModel = new PlotModel { Title = "", Background = OxyColors.White, PlotAreaBorderColor = OxyColor.FromRgb(180, 180, 180), PlotAreaBorderThickness = new OxyThickness(1.5), Padding = new OxyThickness(10, 10, 20, 10) }; // 配置图例 - 放在图表外面顶部 _plotModel.Legends.Add(new OxyPlot.Legends.Legend { LegendPosition = OxyPlot.Legends.LegendPosition.TopCenter, LegendOrientation = OxyPlot.Legends.LegendOrientation.Horizontal, LegendPlacement = OxyPlot.Legends.LegendPlacement.Outside, LegendBackground = OxyColor.FromAColor(250, OxyColors.White), LegendBorder = OxyColor.FromRgb(200, 200, 200), LegendBorderThickness = 1, LegendPadding = 10, LegendMargin = 8, LegendSymbolLength = 30, LegendSymbolMargin = 10, LegendFontSize = 12, LegendItemSpacing = 20 }); // 配置X轴(时间轴 - 使用测试时间总秒数) var xAxis = new LinearAxis { Position = AxisPosition.Bottom, Title = "测试时间", TitleFontSize = 13, TitleFontWeight = OxyPlot.FontWeights.Bold, FontSize = 11, MajorGridlineStyle = LineStyle.Solid, MajorGridlineColor = OxyColor.FromRgb(220, 220, 220), MajorGridlineThickness = 1, MinorGridlineStyle = LineStyle.Dot, MinorGridlineColor = OxyColor.FromRgb(240, 240, 240), MinorGridlineThickness = 0.5, Minimum = 0, Maximum = 60, IsPanEnabled = true, IsZoomEnabled = true, // 格式化X轴标签为HH:MM:SS格式 LabelFormatter = value => { int totalSec = (int)value; int h = totalSec / 3600; int m = (totalSec % 3600) / 60; int s = totalSec % 60; return $"{h:D2}:{m:D2}:{s:D2}"; } }; _plotModel.Axes.Add(xAxis); // 配置Y轴(统一显示实时压力和压力设定值) var yAxis = new LinearAxis { Position = AxisPosition.Left, Title = "压力 (PSI)", TitleFontSize = 13, TitleFontWeight = OxyPlot.FontWeights.Bold, FontSize = 11, MajorGridlineStyle = LineStyle.Solid, MajorGridlineColor = OxyColor.FromRgb(220, 220, 220), MajorGridlineThickness = 1, MinorGridlineStyle = LineStyle.Dot, MinorGridlineColor = OxyColor.FromRgb(240, 240, 240), MinorGridlineThickness = 0.5, StringFormat = "F2", Minimum = 0, Maximum = 10, // 初始最大值,会动态调整 Key = "YAxis", IsPanEnabled = true, IsZoomEnabled = true }; _plotModel.Axes.Add(yAxis); // 添加实时压力曲线 - 使用蓝色实线 _pressureSeries = new LineSeries { Title = "实时压力", Color = OxyColor.FromRgb(30, 144, 255), // 道奇蓝 StrokeThickness = 2.5, MarkerType = MarkerType.Circle, MarkerSize = 5, MarkerFill = OxyColor.FromRgb(30, 144, 255), MarkerStroke = OxyColors.White, MarkerStrokeThickness = 1.5, LineStyle = LineStyle.Solid, YAxisKey = "YAxis" }; _plotModel.Series.Add(_pressureSeries); // 添加压力设定值曲线 - 使用红色虚线 _pressureSetSeries = new LineSeries { Title = "压力设定值", Color = OxyColor.FromRgb(220, 20, 60), // 深红色 StrokeThickness = 2.5, MarkerType = MarkerType.Square, MarkerSize = 5, MarkerFill = OxyColor.FromRgb(220, 20, 60), MarkerStroke = OxyColors.White, MarkerStrokeThickness = 1.5, LineStyle = LineStyle.Dash, YAxisKey = "YAxis" }; _plotModel.Series.Add(_pressureSetSeries); // 创建PlotView控件 _plotView = new PlotView { Model = _plotModel, Dock = DockStyle.Fill, BackColor = System.Drawing.Color.White }; // 监听鼠标事件,检测用户交互(缩放和平移) _plotView.MouseDown += (s, e) => { _isUserZooming = true; }; _plotView.MouseWheel += (s, e) => { _isUserZooming = true; }; // 添加双击重置功能 _plotView.MouseDoubleClick += (s, e) => { if (e.Button == System.Windows.Forms.MouseButtons.Left) { ResetZoom(); } }; // 添加到目标面板 targetPanel.Controls.Add(_plotView); } /// /// 添加新的数据点 /// /// 实时压力值 /// 压力设定值(来自"压力设置(PSI)",地址2400) /// 测试时间(总秒数) public void AddDataPoint(double pressure, double pressureSetValue, int totalSeconds) { if (_pressureSeries == null || _pressureSetSeries == null || _plotModel == null) return; // 添加数据点(使用测试时间总秒数作为X轴值) _pressureData.Add(new DataPoint(totalSeconds, pressure)); _pressureSetData.Add(new DataPoint(totalSeconds, pressureSetValue)); // 记录初始最小X值(第一个数据点的时间) if (_pressureData.Count == 1) { _initialMinX = totalSeconds; } // 更新最大压力值(用于动态调整Y轴) double currentMaxPressure = Math.Max(pressure, pressureSetValue); if (currentMaxPressure > _maxPressureValue) { _maxPressureValue = currentMaxPressure; } // 限制数据点数量,保持图表流畅(保留最近3600个点) if (_pressureData.Count > MAX_DATA_POINTS) { _pressureData.RemoveAt(0); _pressureSetData.RemoveAt(0); // 更新初始最小X值 if (_pressureData.Count > 0) { _initialMinX = _pressureData[0].X; } } // 更新系列数据 _pressureSeries.Points.Clear(); _pressureSeries.Points.AddRange(_pressureData); _pressureSetSeries.Points.Clear(); _pressureSetSeries.Points.AddRange(_pressureSetData); // 只有在非用户缩放状态下才自动调整轴范围 if (!_isUserZooming) { // 调整X轴范围 if (_pressureData.Count > 0) { var xAxis = _plotModel.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom); if (xAxis != null) { // 始终从初始时间开始显示 double minX = _initialMinX; double maxX = _pressureData.Max(p => p.X); xAxis.Minimum = minX; xAxis.Maximum = maxX + 5; // 留一点余量 xAxis.Reset(); } } // 动态调整Y轴范围 - 比实际最大值大20%,确保曲线美观 var yAxis = _plotModel.Axes.FirstOrDefault(a => a.Key == "YAxis"); if (yAxis != null && _maxPressureValue > 0) { // Y轴最大值设置为实际最大值的1.2倍,最小为10 double yMax = Math.Max(_maxPressureValue * 1.2, 10); // 向上取整到合适的刻度 if (yMax < 10) yMax = Math.Ceiling(yMax); else if (yMax < 100) yMax = Math.Ceiling(yMax / 5) * 5; else yMax = Math.Ceiling(yMax / 10) * 10; yAxis.Minimum = 0; yAxis.Maximum = yMax; yAxis.Reset(); } } // 刷新图表 _plotModel.InvalidatePlot(true); } /// /// 清除所有数据 /// public void ClearData() { _pressureData?.Clear(); _pressureSetData?.Clear(); if (_pressureSeries != null) { _pressureSeries.Points.Clear(); } if (_pressureSetSeries != null) { _pressureSetSeries.Points.Clear(); } // 重置X轴范围 var xAxis = _plotModel?.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom); if (xAxis != null) { xAxis.Minimum = 0; xAxis.Maximum = 60; xAxis.Reset(); } // 重置Y轴范围 var yAxis = _plotModel?.Axes.FirstOrDefault(a => a.Key == "YAxis"); if (yAxis != null) { yAxis.Minimum = 0; yAxis.Maximum = 10; yAxis.Reset(); } // 重置初始最小X值、用户缩放状态和最大压力值 _initialMinX = 0; _isUserZooming = false; _maxPressureValue = 0; _plotModel?.InvalidatePlot(true); } /// /// 重置缩放状态,恢复自动跟随模式 /// public void ResetZoom() { _isUserZooming = false; if (_pressureData.Count > 0) { // 重置X轴 var xAxis = _plotModel?.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom); if (xAxis != null) { double minX = _initialMinX; double maxX = _pressureData.Max(p => p.X); xAxis.Minimum = minX; xAxis.Maximum = maxX + 5; xAxis.Reset(); } // 重置Y轴 var yAxis = _plotModel?.Axes.FirstOrDefault(a => a.Key == "YAxis"); if (yAxis != null && _maxPressureValue > 0) { double yMax = Math.Max(_maxPressureValue * 1.2, 10); if (yMax < 10) yMax = Math.Ceiling(yMax); else if (yMax < 100) yMax = Math.Ceiling(yMax / 5) * 5; else yMax = Math.Ceiling(yMax / 10) * 10; yAxis.Minimum = 0; yAxis.Maximum = yMax; yAxis.Reset(); } } _plotModel?.InvalidatePlot(true); } /// /// 更新图表配置(例如切换温度模式时) /// /// 是否为高温模式 public void UpdateChartMode(bool isHighTemperatureMode) { if (_plotModel == null || _plotModel.Series.Count < 2) return; // 可以根据模式调整显示样式(如果需要) _plotModel.InvalidatePlot(true); } /// /// 释放资源 /// public void Dispose() { _plotView?.Dispose(); _plotModel = null; _pressureSeries = null; _pressureSetSeries = null; _pressureData = null; _pressureSetData = null; } } }