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);
}
}
}
}
}