feat: add RenderSingleSeriesToPng for standalone underlying chart
This commit is contained in:
@@ -187,6 +187,130 @@ public static class SkiaChartRendererV2
|
|||||||
return imgData.ToArray();
|
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<byte>();
|
||||||
|
|
||||||
|
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<UnderlyingChartPoint> 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
|
// Titolo
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Reference in New Issue
Block a user