using ScottPlot; using SSPCTester.Devices.Interfaces; using SSPCTester.Logic.Calculation; namespace SSPCTester.Logic.Report; public static class CurveImageRenderer { public static byte[] RenderChannel( int channel, CurveData rising, CurveData falling, double? viewStartMs = null, double? viewEndMs = null, int width = 900, int height = 360) { var plot = new Plot(); double start = viewStartMs ?? 0; double end = viewEndMs ?? 0; bool hasWindow = IsValidWindow(viewStartMs, viewEndMs); plot.Title(hasWindow ? $"CH{channel:00} ON / OFF Curve ({start:F3}-{end:F3} ms)" : $"CH{channel:00} ON / OFF Curve"); plot.XLabel("t (ms)"); plot.YLabel("V"); plot.FigureBackground.Color = Color.FromHex("#FBFCFD"); plot.DataBackground.Color = Color.FromHex("#FBFCFD"); plot.Grid.MajorLineColor = Color.FromHex("#DFE4EA"); AddCurve(plot, rising, Color.FromHex("#1A73C4"), "ON", viewStartMs, viewEndMs); AddCurve(plot, falling, Color.FromHex("#D23B3B"), "OFF", viewStartMs, viewEndMs); double vMax = CurveMetrics.EstimateVMax(rising.Voltage); AddThreshold(plot, vMax * 0.9, "90%"); AddThreshold(plot, vMax * 0.1, "10%"); if (!hasWindow || (start <= 0 && end >= 0)) { var trigger = plot.Add.VerticalLine(0); trigger.Color = Color.FromHex("#98A1AD"); trigger.LinePattern = LinePattern.Dotted; trigger.LineWidth = 1; } plot.ShowLegend(); plot.Axes.AutoScale(); return plot.GetImageBytes(width, height, ImageFormat.Png); } private static void AddCurve(Plot plot, CurveData curve, Color color, string label, double? viewStartMs, double? viewEndMs) { if (curve.Voltage.Length == 0) return; var points = BuildViewPoints(curve, viewStartMs, viewEndMs); if (points.Count == 0) return; var xs = new double[points.Count]; var ys = new double[points.Count]; for (int i = 0; i < points.Count; i++) { xs[i] = points[i].TimeMs; ys[i] = points[i].Voltage; } var signal = plot.Add.ScatterLine(xs, ys, color); signal.LineWidth = 2; signal.MarkerSize = 0; signal.LegendText = label; } private static List<(double TimeMs, double Voltage)> BuildViewPoints(CurveData curve, double? viewStartMs, double? viewEndMs) { var points = new List<(double TimeMs, double Voltage)>(curve.Voltage.Length); double start = viewStartMs ?? 0; double end = viewEndMs ?? 0; bool hasWindow = IsValidWindow(viewStartMs, viewEndMs); 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 static bool IsValidWindow(double? viewStartMs, double? viewEndMs) => viewStartMs is double start && viewEndMs is double end && double.IsFinite(start) && double.IsFinite(end) && end > start; private static void AddThreshold(Plot plot, double volts, string label) { if (!double.IsFinite(volts)) return; var line = plot.Add.HorizontalLine(volts); line.Color = Color.FromHex("#98A1AD"); line.LinePattern = LinePattern.Dashed; line.LineWidth = 1; line.LegendText = label; } }