diff --git a/CertReports.Syncfusion/Services/Implementations/SkiaChartRendererV2.cs b/CertReports.Syncfusion/Services/Implementations/SkiaChartRendererV2.cs index 38d5276..9fcc76c 100644 --- a/CertReports.Syncfusion/Services/Implementations/SkiaChartRendererV2.cs +++ b/CertReports.Syncfusion/Services/Implementations/SkiaChartRendererV2.cs @@ -187,6 +187,130 @@ public static class SkiaChartRendererV2 return imgData.ToArray(); } + // ═══════════════════════════════════════════════════════════════════ + // Entry point — grafico standalone singolo sottostante (no certificato) + // ═══════════════════════════════════════════════════════════════════ + + public static byte[] RenderSingleSeriesToPng(UnderlyingChartData data, int width = 1100, int height = 700, bool jpeg = false) + { + using var surface = SKSurface.Create(new SKImageInfo(width, height)); + var canvas = surface.Canvas; + canvas.Clear(SKColors.White); + + float titleBottom = DrawUnderlyingTitle(canvas, width, data.Nome); + + float marginLeft = 70; + float marginRight = 40; + float marginTop = titleBottom + 10; + float marginBottom = 45; + + var plotArea = new SKRect(marginLeft, marginTop, width - marginRight, height - marginBottom); + + var points = data.Points.OrderBy(p => p.Date).ToList(); + if (points.Count == 0) + return Array.Empty(); + + DateTime minDate = points[0].Date; + DateTime maxDate = points[^1].Date; + double minY = (double)points.Min(p => p.Px); + double maxY = (double)points.Max(p => p.Px); + double range = maxY - minY; + if (range == 0) range = Math.Max(minY * 0.1, 1); + double margin = range * 0.1; + minY -= margin; + maxY += margin; + + DrawGrid(canvas, plotArea, minDate, maxDate, minY, maxY); + DrawUnderlyingAxisLabels(canvas, plotArea, minDate, maxDate, minY, maxY); + DrawUnderlyingSeries(canvas, plotArea, points, minDate, maxDate, minY, maxY); + + using var borderPaint = new SKPaint + { + Color = SKColors.Gray, StrokeWidth = 1, + Style = SKPaintStyle.Stroke, IsAntialias = true, + }; + canvas.DrawRect(plotArea, borderPaint); + + using var image = surface.Snapshot(); + using var imgData = jpeg + ? image.Encode(SKEncodedImageFormat.Jpeg, 90) + : image.Encode(SKEncodedImageFormat.Png, 95); + return imgData.ToArray(); + } + + private static float DrawUnderlyingTitle(SKCanvas canvas, int width, string nome) + { + float y = 15f; + var boldFont = CreateFont(13f, bold: true); + using var titlePaint = new SKPaint { Color = TitleColor, IsAntialias = true }; + canvas.DrawText(nome, width / 2f, y + 13, SKTextAlign.Center, boldFont, titlePaint); + return y + 22 + 5; + } + + private static void DrawUnderlyingAxisLabels(SKCanvas canvas, SKRect area, + DateTime minDate, DateTime maxDate, double minY, double maxY) + { + var font = CreateFont(11); + using var paint = new SKPaint { Color = SKColors.DimGray, IsAntialias = true }; + + int ySteps = 8; + for (int i = 0; i <= ySteps; i++) + { + double val = maxY - ((maxY - minY) / ySteps) * i; + float y = area.Top + (area.Height / ySteps) * i; + canvas.DrawText($"{val:F2}", area.Left - 55, y + 4, SKTextAlign.Left, font, paint); + } + + double totalDays = (maxDate - minDate).TotalDays; + int intervalMonths = XAxisIntervalMonths(minDate, maxDate); + string fmt = totalDays > 365 ? "MMM yy" : "dd/MM/yy"; + + if (intervalMonths > 0) + { + var d = XAxisStart(minDate, intervalMonths); + while (d <= maxDate) + { + float x = DateToX(d, area, minDate, maxDate); + if (x >= area.Left && x <= area.Right) + canvas.DrawText(d.ToString(fmt), x, area.Bottom + 20, SKTextAlign.Center, font, paint); + d = d.AddMonths(intervalMonths); + } + } + else + { + canvas.DrawText(minDate.ToString("dd/MM/yy"), area.Left, area.Bottom + 20, SKTextAlign.Left, font, paint); + canvas.DrawText(maxDate.ToString("dd/MM/yy"), area.Right, area.Bottom + 20, SKTextAlign.Right, font, paint); + } + } + + private static void DrawUnderlyingSeries(SKCanvas canvas, SKRect area, + List points, + DateTime minDate, DateTime maxDate, double minY, double maxY) + { + using var paint = new SKPaint + { + Color = CertColor, StrokeWidth = 2f, + Style = SKPaintStyle.Stroke, IsAntialias = true, + StrokeCap = SKStrokeCap.Round, StrokeJoin = SKStrokeJoin.Round, + }; + + using var path = new SKPath(); + bool first = true; + foreach (var pt in points) + { + float x = DateToX(pt.Date, area, minDate, maxDate); + float y = ValueToY((double)pt.Px, area, minY, maxY); + y = Math.Max(area.Top, Math.Min(area.Bottom, y)); + if (first) { path.MoveTo(x, y); first = false; } + else path.LineTo(x, y); + } + + canvas.Save(); + canvas.ClipRect(area); + canvas.DrawPath(path, paint); + canvas.Restore(); + } + // ═══════════════════════════════════════════════════════════════════ // Titolo // ═══════════════════════════════════════════════════════════════════