using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ScottPlot;
using ScottPlot.WPF;
using SSPCTester.Devices.Interfaces;
namespace SSPCTester.UI.Controls;
public partial class CurvePlotControl : UserControl
{
private readonly bool _isDesignMode;
private WpfPlot? _plot;
public CurvePlotControl()
{
InitializeComponent();
_isDesignMode = DesignerProperties.GetIsInDesignMode(this);
if (_isDesignMode)
{
DesignPlaceholder.Visibility = Visibility.Visible;
return;
}
Loaded += (_, _) => InitPlot();
}
public static readonly DependencyProperty CurveProperty = DependencyProperty.Register(
nameof(Curve), typeof(CurveData), typeof(CurvePlotControl),
new PropertyMetadata(null, (d, e) => ((CurvePlotControl)d).Render()));
public static readonly DependencyProperty IsRisingProperty = DependencyProperty.Register(
nameof(IsRising), typeof(bool), typeof(CurvePlotControl),
new PropertyMetadata(true, (d, e) => ((CurvePlotControl)d).Render()));
public static readonly DependencyProperty VMaxProperty = DependencyProperty.Register(
nameof(VMax), typeof(double), typeof(CurvePlotControl),
new PropertyMetadata(28.0, (d, e) => ((CurvePlotControl)d).Render()));
public static readonly DependencyProperty ViewStartMsProperty = DependencyProperty.Register(
nameof(ViewStartMs), typeof(double?), typeof(CurvePlotControl),
new PropertyMetadata(null, (d, e) => ((CurvePlotControl)d).Render()));
public static readonly DependencyProperty ViewEndMsProperty = DependencyProperty.Register(
nameof(ViewEndMs), typeof(double?), typeof(CurvePlotControl),
new PropertyMetadata(null, (d, e) => ((CurvePlotControl)d).Render()));
public CurveData? Curve { get => (CurveData?)GetValue(CurveProperty); set => SetValue(CurveProperty, value); }
public bool IsRising { get => (bool)GetValue(IsRisingProperty); set => SetValue(IsRisingProperty, value); }
public double VMax { get => (double)GetValue(VMaxProperty); set => SetValue(VMaxProperty, value); }
public double? ViewStartMs { get => (double?)GetValue(ViewStartMsProperty); set => SetValue(ViewStartMsProperty, value); }
public double? ViewEndMs { get => (double?)GetValue(ViewEndMsProperty); set => SetValue(ViewEndMsProperty, value); }
private void InitPlot()
{
_plot ??= CreateRuntimePlot();
_plot.Plot.Title("");
_plot.Plot.XLabel("t (ms)");
_plot.Plot.YLabel("V");
_plot.Plot.Axes.AntiAlias(true);
_plot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
_plot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
_plot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#DFE4EA");
Render();
}
private WpfPlot CreateRuntimePlot()
{
var plot = new WpfPlot();
RootContainer.Children.Clear();
RootContainer.Children.Add(plot);
return plot;
}
private void Render()
{
if (_isDesignMode || !IsLoaded || _plot == null) return;
_plot.Plot.Clear();
var c = Curve;
if (c == null || c.Voltage.Length == 0)
{
_plot.Refresh();
return;
}
var points = BuildViewPoints(c);
if (points.Count == 0)
{
_plot.Refresh();
return;
}
var xs = new double[points.Count];
var ys = new double[points.Count];
// 以触发时刻为 0;x 单位 ms
for (int i = 0; i < points.Count; i++)
{
xs[i] = points[i].TimeMs;
ys[i] = points[i].Voltage;
}
var color = IsRising ? "#1A73C4" : "#D23B3B";
var sig = _plot.Plot.Add.ScatterLine(xs, ys, ScottPlot.Color.FromHex(color));
sig.LineWidth = 2;
sig.MarkerSize = 0;
// 阈值线
double threshold = IsRising ? VMax * 0.9 : VMax * 0.1;
var hLine = _plot.Plot.Add.HorizontalLine(threshold);
hLine.Color = ScottPlot.Color.FromHex("#98A1AD");
hLine.LinePattern = ScottPlot.LinePattern.Dashed;
hLine.LineWidth = 1;
var label = _plot.Plot.Add.Text($"{(IsRising ? 90 : 10)}%", xs[0], threshold);
label.LabelFontColor = ScottPlot.Color.FromHex("#98A1AD");
label.LabelFontSize = 10;
// 触发线 t=0
if (!HasViewWindow(out double start, out double end) || (start <= 0 && end >= 0))
{
var vLine = _plot.Plot.Add.VerticalLine(0);
vLine.Color = ScottPlot.Color.FromHex("#98A1AD");
vLine.LinePattern = ScottPlot.LinePattern.Dotted;
vLine.LineWidth = 1;
}
_plot.Plot.Axes.AutoScale();
_plot.Refresh();
}
private List<(double TimeMs, double Voltage)> BuildViewPoints(CurveData curve)
{
var points = new List<(double TimeMs, double Voltage)>(curve.Voltage.Length);
bool hasWindow = HasViewWindow(out double start, out double end);
for (int i = 0; i < curve.Voltage.Length; i++)
{
double timeMs = (curve.TimeAt(i) - curve.TriggerTimeSec) * 1000.0;
if (!hasWindow || (timeMs >= start && timeMs <= end))
points.Add((timeMs, curve.Voltage[i]));
}
return points;
}
private bool HasViewWindow(out double start, out double end)
{
start = ViewStartMs ?? 0;
end = ViewEndMs ?? 0;
return ViewStartMs.HasValue && ViewEndMs.HasValue
&& double.IsFinite(start) && double.IsFinite(end) && end > start;
}
/// 导出为 PNG 字节。
public byte[] ExportPng(int width = 800, int height = 300)
{
if (_plot == null)
return Array.Empty();
return _plot.Plot.GetImageBytes(width, height, ImageFormat.Png);
}
}