Skip to content

Commit 84c53fc

Browse files
Pie: Refactor and improve SVG rendering (ScottPlot#5020)
* Pie: Add Radius, RenderDonutSlice() & RenderSlice() - Change the hardcoded radius to a property - Use the existing method of Drawing to implement drawing - Reorganize the Render method * Drawing: Fix FillEllipticalAnnulus SVG export - SVG does not support SKCanvas.ClipPath * Update CHANGELOG.md --------- Co-authored-by: Scott W Harden <swharden@gmail.com>
1 parent ba33f83 commit 84c53fc

3 files changed

Lines changed: 63 additions & 59 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ _Not yet on NuGet..._
2121
* Fonts: Added new styling options for weight, slant, density, etc. (#5013, #4873) @aespitia @Christoph-Wagner
2222
* Labels: Added styling options for underline with customizable thickness and offset (#4893) @manaruto
2323
* Legend: Added the ability to customize default marker shape for legend items (#5005, #5006) @aespitia
24+
* Pie: Added a `Radius` property (instead of forcing `1.0`) and improved rendering and SVG export (#5020) @CoderPM2011 @aespitia
2425

2526
## ScottPlot 5.0.55
2627
_Published on [NuGet](https://www.nuget.org/profiles/ScottPlot) on 2025-03-22_

src/ScottPlot5/ScottPlot5/Drawing.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,15 @@ public static void FillEllipticalAnnulus(SKCanvas canvas, SKPaint paint, FillSty
512512
canvasState.Save();
513513
fillStyle.ApplyToPaint(paint, outerRect);
514514

515-
// Clip inner oval
516-
using SKPath path = new();
515+
using SKPath path = new()
516+
{
517+
FillType = SKPathFillType.EvenOdd
518+
};
519+
path.AddOval(outerRect.ToSKRect());
517520
path.AddOval(innerRect.ToSKRect());
518-
canvas.ClipPath(path, SKClipOperation.Difference, true);
519521

520-
canvas.DrawOval(outerRect.ToSKRect(), paint);
522+
canvas.DrawPath(path, paint);
523+
521524
canvasState.Restore();
522525
}
523526

src/ScottPlot5/ScottPlot5/Plottables/Pie.cs

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace ScottPlot.Plottables;
22

33
public class Pie : PieBase
44
{
5+
public double Radius { get; set; } = 1.0;
56
public double ExplodeFraction { get; set; } = 0;
67
public double DonutFraction { get; set; } = 0;
78

@@ -12,95 +13,94 @@ public Pie(IList<PieSlice> slices)
1213

1314
public override AxisLimits GetAxisLimits()
1415
{
15-
double radius = Math.Max(SliceLabelDistance, 1 + ExplodeFraction) + Padding;
16+
double radius = Math.Max(SliceLabelDistance, Radius + ExplodeFraction) + Padding;
1617
return new AxisLimits(-radius, radius, -radius, radius);
1718
}
1819

20+
protected virtual void RenderDonutSlice(
21+
RenderPack rp, SKPaint paint, LineStyle lineStyle, FillStyle fillStyle,
22+
float radius, float sliceAngle, float startAngle)
23+
{
24+
PixelRect outerRect = new(-radius, radius, -radius, radius);
25+
26+
// radius of the inner edge of the pie when donut mode is enabled
27+
float innerRadius = radius * (float)DonutFraction;
28+
PixelRect innerRect = new(-innerRadius, innerRadius, -innerRadius, innerRadius);
29+
30+
if (Math.Abs(sliceAngle) < 360)
31+
{
32+
Drawing.FillAnnularSector(rp.Canvas, paint, fillStyle, outerRect, innerRect, startAngle, sliceAngle);
33+
Drawing.DrawAnnularSector(rp.Canvas, paint, lineStyle, outerRect, innerRect, startAngle, sliceAngle);
34+
}
35+
else
36+
{
37+
Drawing.FillEllipticalAnnulus(rp.Canvas, paint, fillStyle, outerRect, innerRect);
38+
Drawing.DrawEllipticalAnnulus(rp.Canvas, paint, lineStyle, outerRect, innerRect);
39+
}
40+
}
41+
42+
protected virtual void RenderSlice(
43+
RenderPack rp, SKPaint paint, LineStyle lineStyle, FillStyle fillStyle,
44+
float radius, float sliceAngle, float startAngle)
45+
{
46+
PixelRect outerRect = new(-radius, radius, -radius, radius);
47+
48+
if (Math.Abs(sliceAngle) < 360)
49+
{
50+
Drawing.FillSector(rp.Canvas, paint, fillStyle, outerRect, startAngle, sliceAngle);
51+
Drawing.DrawSector(rp.Canvas, paint, lineStyle, outerRect, startAngle, sliceAngle);
52+
}
53+
else
54+
{
55+
Drawing.FillOval(rp.Canvas, paint, fillStyle, outerRect);
56+
Drawing.DrawOval(rp.Canvas, paint, lineStyle, outerRect);
57+
}
58+
}
59+
1960
public override void Render(RenderPack rp)
2061
{
21-
using SKPath path = new();
2262
using SKPaint paint = new() { IsAntialias = true };
2363

2464
Pixel origin = Axes.GetPixel(Coordinates.Origin);
2565

26-
float minX = Math.Abs(Axes.GetPixelX(1) - origin.X);
27-
float minY = Math.Abs(Axes.GetPixelY(1) - origin.Y);
66+
float minX = Math.Abs(Axes.GetPixelX(Radius) - origin.X);
67+
float minY = Math.Abs(Axes.GetPixelY(Radius) - origin.Y);
2868

2969
// radius of the outer edge of the pie
3070
float outerRadius = Math.Min(minX, minY);
31-
SKRect outerRect = new(-outerRadius, -outerRadius, outerRadius, outerRadius);
32-
33-
// radius of the inner edge of the pie when donut mode is enabled
34-
float innerRadius = outerRadius * (float)DonutFraction;
35-
SKRect innerRect = new(-innerRadius, -innerRadius, innerRadius, innerRadius);
3671

3772
double totalValue = Slices.Sum(s => s.Value);
3873
Angle totalAngle = Rotation;
3974
foreach (PieSlice slice in Slices)
4075
{
4176
using SKAutoCanvasRestore _ = new(rp.Canvas);
4277

43-
var percentage = slice.Value / totalValue;
44-
var sliceAngle = Angle.FromDegrees(percentage * 360);
78+
Angle sliceAngle = Angle.FromFraction(slice.Value / totalValue);
4579
Angle centerAngle = totalAngle + sliceAngle / 2;
4680

47-
Coordinates explosionOffset = new PolarCoordinates(ExplodeFraction * outerRadius, centerAngle).ToCartesian();
81+
PolarCoordinates slicePolar = new(ExplodeFraction * outerRadius, centerAngle);
82+
Coordinates explosionOffset = slicePolar.ToCartesian();
4883
rp.Canvas.Translate(
4984
(float)(origin.X + explosionOffset.X),
5085
(float)(origin.Y + explosionOffset.Y));
5186

52-
if (sliceAngle.Degrees == 360)
87+
if (DonutFraction > 0)
5388
{
54-
if (DonutFraction > 0)
55-
{
56-
// Clip inner oval
57-
// avoid clipping to inner oval line
58-
float innerRadius2 = innerRadius - 1;
59-
path.AddOval(new SKRect(
60-
-innerRadius2, -innerRadius2, innerRadius2, innerRadius2));
61-
rp.Canvas.ClipPath(path, SKClipOperation.Difference);
62-
path.Reset();
63-
64-
// Draw inner oval line
65-
path.AddOval(innerRect);
66-
LineStyle.ApplyToPaint(paint);
67-
rp.Canvas.DrawPath(path, paint);
68-
}
69-
70-
path.AddOval(outerRect);
89+
RenderDonutSlice(rp, paint, LineStyle, slice.Fill, outerRadius, (float)sliceAngle.Degrees, (float)totalAngle.Degrees);
7190
}
7291
else
7392
{
74-
Coordinates ptInnerHome = new PolarCoordinates(innerRadius, totalAngle).ToCartesian();
75-
Coordinates ptOuterHome = new PolarCoordinates(outerRadius, totalAngle).ToCartesian();
76-
Coordinates ptInnerRotated = new PolarCoordinates(innerRadius, totalAngle + sliceAngle).ToCartesian();
77-
78-
path.MoveTo(new SKPoint((float)ptInnerHome.X, (float)ptInnerHome.Y));
79-
path.LineTo(new SKPoint((float)ptOuterHome.X, (float)ptOuterHome.Y));
80-
path.ArcTo(outerRect,
81-
(float)totalAngle.Degrees,
82-
(float)sliceAngle.Degrees,
83-
false);
84-
path.LineTo(new SKPoint((float)ptInnerRotated.X, (float)ptInnerRotated.Y));
85-
path.ArcTo(innerRect,
86-
(float)(totalAngle + sliceAngle).Degrees,
87-
(float)-sliceAngle.Degrees,
88-
false);
89-
path.Close();
93+
RenderSlice(rp, paint, LineStyle, slice.Fill, outerRadius, (float)sliceAngle.Degrees, (float)totalAngle.Degrees);
9094
}
9195

92-
PixelRect rect = new(origin, outerRadius);
93-
Drawing.FillPath(rp.Canvas, paint, path, slice.Fill, rect);
94-
95-
Drawing.DrawPath(rp.Canvas, paint, path, LineStyle);
96-
97-
Coordinates polar = new PolarCoordinates(1.0 * SliceLabelDistance, centerAngle).ToCartesian();
98-
polar.Y = -polar.Y;
99-
Pixel px = Axes.GetPixel(polar) - origin;
96+
Coordinates textPolar = slicePolar
97+
.WithRadius(Radius * SliceLabelDistance)
98+
.ToCartesian();
99+
textPolar.Y = -textPolar.Y;
100+
Pixel px = Axes.GetPixel(textPolar) - origin;
100101
slice.LabelStyle.Render(rp.Canvas, px, paint);
101102

102103
totalAngle += sliceAngle;
103-
path.Reset();
104104
}
105105
}
106106
}

0 commit comments

Comments
 (0)