| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- 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
- {
- /// <summary>
- /// 动态折线图用户控件(基于 ScottPlot.WPF 的高性能版本)
- /// </summary>
- 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<DamageDto> 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<DamageDto> 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);
- }
- }
- }
-
- }
- }
|