using Microsoft.Win32; using ScottPlot; using ScottPlot.Plottables; using ScottPlot.WPF; using SWRIS.Dtos; using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; namespace SWRIS.Controls { /// /// 动态折线图用户控件(基于 ScottPlot.WPF 的高性能版本) /// public partial class RecordLineChart : UserControl { private readonly Color[] _colors = new Color[] { new Color("#70a1d7"), new Color("#a1de93"), new Color("#f7f48b"), new Color("#f47c7c"), new Color("#c264fe"), new Color("#00fff5"), new Color("#f8b195"), new Color("#7a08fa") }; private const double MAXDAMAGE = 128; // 最大损伤值 private SignalXY[] _sensorSignals; private SignalXY _mergeSignal; private Scatter[] _makerScatters; private AxisLimits _axisLimit; public RecordLineChart() { InitializeComponent(); InitializePlot(); } private void InitializePlot() { WpfPlot.Menu?.Clear(); WpfPlot.Menu.Add("保存图片", pt => { OpenSaveImageDialog(pt); }); WpfPlot.Menu.Add("重置视图", pt => { WpfPlot.Plot.Axes.SetLimits(_axisLimit); WpfPlot.Refresh(); }); var plot = WpfPlot.Plot; plot.Clear(); plot.DataBackground.Color = Colors.Transparent; plot.FigureBackground.Color = Colors.Transparent; plot.Grid.MajorLineColor = new Color(59, 59, 123).WithOpacity(0.6); plot.Grid.XAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212); plot.Grid.YAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212); plot.Grid.XAxis.FrameLineStyle.Color = new Color(53, 53, 112); plot.Grid.XAxis.FrameLineStyle.Width = 3; plot.Grid.YAxis.FrameLineStyle.Color = new Color(53, 53, 112); plot.Grid.YAxis.FrameLineStyle.Width = 3; plot.Grid.XAxis.MajorTickStyle.Color = new Color(53, 53, 112); plot.Grid.XAxis.MajorTickStyle.Width = 1; plot.Grid.YAxis.MajorTickStyle.Color = new Color(53, 53, 112); plot.Grid.YAxis.MajorTickStyle.Width = 1; plot.Grid.YAxis.MinorTickStyle.Color = new Color(53, 53, 112); plot.Grid.XAxis.MinorTickStyle.Color = new Color(53, 53, 112); plot.Axes.Hairline(false); } public void AddDataPoints((double[] Positions, ushort[,] Damages) dataPoints, List alertDamages) { if (dataPoints.Damages == null || dataPoints.Positions == null) { return; } int sensorCount = dataPoints.Damages.GetLength(0); int sampleCount = dataPoints.Damages.GetLength(1); double startPoint = dataPoints.Positions.First(); double endPoint = dataPoints.Positions.Last(); _sensorSignals = new SignalXY[sensorCount]; double[] mergeData = new double[sampleCount]; // 先计算所有传感器的合并数据 for (int j = 0; j < sampleCount; j++) { double sum = 0; for (int i = 0; i < sensorCount; i++) { sum += dataPoints.Damages[i, j]; } mergeData[j] = sum / sensorCount; // 计算平均值 } AddMaker(dataPoints.Positions, mergeData, alertDamages); // 然后处理每个传感器的数据 for (int i = 0; i < sensorCount; i++) { ushort[] sensorData = new ushort[sampleCount]; for (int j = 0; j < sampleCount; j++) { sensorData[j] = dataPoints.Damages[i, j]; } var signalPlot = WpfPlot.Plot.Add.SignalXY(dataPoints.Positions, sensorData); signalPlot.LineWidth = 1; signalPlot.Color = _colors[i]; signalPlot.IsVisible = false; _sensorSignals[i] = signalPlot; } _mergeSignal = WpfPlot.Plot.Add.SignalXY(dataPoints.Positions, mergeData); _mergeSignal.LineWidth = 2; _mergeSignal.Color = new Color("#9664F3"); //差值 double offset = (endPoint - startPoint) * 0.1; _axisLimit = new AxisLimits(startPoint - offset, endPoint + offset, 0, MAXDAMAGE); WpfPlot.Plot.Axes.SetLimits(_axisLimit); WpfPlot.Refresh(); } public void SignalPlotVisible(int id, bool isVisible) { if (id > _sensorSignals.Length) return; if (id == 0) { _mergeSignal.IsVisible = isVisible; foreach (var maker in _makerScatters) { maker.IsVisible = isVisible; } } else { _sensorSignals[id - 1].IsVisible = isVisible; } WpfPlot.Refresh(); } private void AddMaker(double[] dataPoints, double[] mergeData, List alertDamages) { int markerCount = alertDamages.Count; _makerScatters = new Scatter[markerCount]; for (int i = 0; i < markerCount; i++) { int index = FindNearestIndex(dataPoints, alertDamages[i].DamagePoint); if (index < 0 || index >= dataPoints.Length) continue; var marker = WpfPlot.Plot.Add.Scatter(dataPoints[index], mergeData[index]); marker.LineWidth = 0; // 不显示连线 marker.MarkerSize = 10; marker.MarkerShape = MarkerShape.OpenCircle; marker.MarkerLineWidth = 2; switch (alertDamages[i].DamageLevel) { case Enums.DamageLevel.Mild: marker.Color = Color.FromHex("#00FF78"); break; case Enums.DamageLevel.Light: marker.Color = Color.FromHex("#FFF000"); break; case Enums.DamageLevel.Moderate: marker.Color = Color.FromHex("#FF9400"); break; case Enums.DamageLevel.Severe: marker.Color = Color.FromHex("#FF6C00"); break; case Enums.DamageLevel.Critical: marker.Color = Color.FromHex("#FF0000"); break; case Enums.DamageLevel.ExceededLimit: marker.Color = Color.FromHex("#FF008A"); break; } _makerScatters[i] = marker; } } private int FindNearestIndex(double[] positions, double target) { if (positions == null || positions.Length == 0) return -1; int index = Array.BinarySearch(positions, target); // 如果找到精确匹配 if (index >= 0) return index; // 如果没找到,BinarySearch 返回下一个更大元素的补码 int largerIndex = ~index; // 处理边界情况 if (largerIndex == 0) return 0; if (largerIndex == positions.Length) return positions.Length - 1; // 比较左右两个元素哪个更接近 double diffLeft = Math.Abs(positions[largerIndex - 1] - target); double diffRight = Math.Abs(positions[largerIndex] - target); return diffLeft < diffRight ? largerIndex - 1 : largerIndex; } public void OpenSaveImageDialog(Plot plot) { SaveFileDialog saveFileDialog = new SaveFileDialog { Filter = "PNG Files (*.png)|*.png|JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg|BMP Files (*.bmp)|*.bmp|WebP Files (*.webp)|*.webp|SVG Files (*.svg)|*.svg|All files (*.*)|*.*" }; bool? flag = saveFileDialog.ShowDialog(); if (flag.HasValue && flag == true && !string.IsNullOrEmpty(saveFileDialog.FileName)) { ImageFormat format; try { format = ImageFormats.FromFilename(saveFileDialog.FileName); } catch (ArgumentException) { MessageBox.Show("Unsupported image file format", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand); return; } try { PixelSize size = plot.RenderManager.LastRender.FigureRect.Size; plot.Save(saveFileDialog.FileName, (int)size.Width, (int)size.Height, format); } catch (Exception) { MessageBox.Show("Image save failed", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand); } } } } }