243 lines
8.3 KiB
C#
243 lines
8.3 KiB
C#
using ScottPlot;
|
|
using SSPCTester.Devices.Interfaces;
|
|
using SSPCTester.Logic.Calculation;
|
|
|
|
namespace SSPCTester.Logic.Report;
|
|
|
|
public static class CurveImageRenderer
|
|
{
|
|
public static byte[] RenderRisingChannel(
|
|
int channel,
|
|
CurveData curve,
|
|
double? viewStartMs = null,
|
|
double? viewEndMs = null,
|
|
int width = 900,
|
|
int height = 360) =>
|
|
RenderSingleChannel(
|
|
channel,
|
|
curve,
|
|
"ON",
|
|
Color.FromHex("#1A73C4"),
|
|
true,
|
|
viewStartMs,
|
|
viewEndMs,
|
|
width,
|
|
height);
|
|
|
|
public static byte[] RenderFallingChannel(
|
|
int channel,
|
|
CurveData curve,
|
|
double? viewStartMs = null,
|
|
double? viewEndMs = null,
|
|
int width = 900,
|
|
int height = 360) =>
|
|
RenderSingleChannel(
|
|
channel,
|
|
curve,
|
|
"OFF",
|
|
Color.FromHex("#D23B3B"),
|
|
false,
|
|
viewStartMs,
|
|
viewEndMs,
|
|
width,
|
|
height);
|
|
|
|
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%");
|
|
AddTransitionMark(plot, rising, vMax, true, viewStartMs, viewEndMs);
|
|
AddTransitionMark(plot, falling, vMax, false, viewStartMs, viewEndMs);
|
|
|
|
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 byte[] RenderSingleChannel(
|
|
int channel,
|
|
CurveData curve,
|
|
string phaseLabel,
|
|
Color color,
|
|
bool isRising,
|
|
double? viewStartMs,
|
|
double? viewEndMs,
|
|
int width,
|
|
int height)
|
|
{
|
|
var plot = new Plot();
|
|
double start = viewStartMs ?? 0;
|
|
double end = viewEndMs ?? 0;
|
|
bool hasWindow = IsValidWindow(viewStartMs, viewEndMs);
|
|
plot.Title(hasWindow
|
|
? $"CH{channel:00} {phaseLabel} Curve ({start:F3}-{end:F3} ms)"
|
|
: $"CH{channel:00} {phaseLabel} 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, curve, color, phaseLabel, viewStartMs, viewEndMs);
|
|
|
|
double vMax = CurveMetrics.EstimateVMax(curve.Voltage);
|
|
AddThreshold(plot, vMax * 0.9, "90%");
|
|
AddThreshold(plot, vMax * 0.1, "10%");
|
|
AddTransitionMark(plot, curve, vMax, isRising, viewStartMs, viewEndMs);
|
|
|
|
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;
|
|
}
|
|
|
|
private static void AddTransitionMark(
|
|
Plot plot,
|
|
CurveData curve,
|
|
double vMax,
|
|
bool isRising,
|
|
double? viewStartMs,
|
|
double? viewEndMs)
|
|
{
|
|
if (!double.IsFinite(vMax) || vMax <= 0) return;
|
|
|
|
var mark = isRising
|
|
? CurveMetrics.AnalyzeRising(curve, vMax)
|
|
: CurveMetrics.AnalyzeFalling(curve, vMax);
|
|
if (!mark.IsValid) return;
|
|
|
|
bool hasWindow = IsValidWindow(viewStartMs, viewEndMs);
|
|
double start = viewStartMs ?? 0;
|
|
double end = viewEndMs ?? 0;
|
|
|
|
string startLabel = isRising ? "TON" : "TOFF";
|
|
string endLabel = isRising ? "T90" : "T10";
|
|
|
|
if (IsVisibleInView(mark.StartTimeMs, hasWindow, start, end))
|
|
AddMarkerLine(plot, mark.StartTimeMs, mark.StartThresholdV, startLabel, Color.FromHex("#1C8A2E"));
|
|
|
|
if (IsVisibleInView(mark.EndTimeMs, hasWindow, start, end))
|
|
AddMarkerLine(plot, mark.EndTimeMs, mark.EndThresholdV, endLabel, Color.FromHex("#D93025"));
|
|
|
|
double middleTime = (mark.StartTimeMs + mark.EndTimeMs) / 2.0;
|
|
if (IsVisibleInView(middleTime, hasWindow, start, end))
|
|
{
|
|
var points = BuildViewPoints(curve, viewStartMs, viewEndMs);
|
|
if (points.Count == 0) return;
|
|
|
|
double minY = points.Min(x => x.Voltage);
|
|
double maxY = points.Max(x => x.Voltage);
|
|
double labelY = minY + (maxY - minY) * 0.86;
|
|
var duration = plot.Add.Text($"{startLabel}-{endLabel} {mark.DurationMs:F3} ms", middleTime, labelY);
|
|
duration.LabelFontColor = Color.FromHex("#2F3A45");
|
|
duration.LabelFontSize = 11;
|
|
}
|
|
}
|
|
|
|
private static void AddMarkerLine(Plot plot, double timeMs, double thresholdV, string text, Color color)
|
|
{
|
|
var line = plot.Add.VerticalLine(timeMs);
|
|
line.Color = color;
|
|
line.LineWidth = 2;
|
|
|
|
var label = plot.Add.Text(text, timeMs, thresholdV);
|
|
label.LabelFontColor = color;
|
|
label.LabelFontSize = 11;
|
|
}
|
|
|
|
private static bool IsVisibleInView(double timeMs, bool hasWindow, double viewStart, double viewEnd) =>
|
|
double.IsFinite(timeMs) && (!hasWindow || (timeMs >= viewStart && timeMs <= viewEnd));
|
|
}
|