Skip to content

Commit 645b92a

Browse files
committed
Merge branch 'feature/svg_chart_and_shape' of https://github.com/EPPlusSoftware/EPPlus into feature/svg_chart_and_shape
2 parents ef3ce28 + b06a24d commit 645b92a

15 files changed

Lines changed: 181 additions & 54 deletions

File tree

src/EPPlus.Export.ImageRenderer.Test/Chart/PieChartTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77

88
namespace EPPlus.Export.ImageRenderer.Tests.Chart
99
{
10-
internal class PieChartTests : TestBase
10+
[TestClass]
11+
public class PieChartTests : TestBase
1112
{
1213
[TestMethod]
13-
public void GenerateSvgForColumnCharts1()
14+
public void GenerateSvgForPieChart()
1415
{
1516
ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project");
1617
using (var p = OpenTemplatePackage("BasicPieChart.xlsx"))
@@ -22,7 +23,7 @@ public void GenerateSvgForColumnCharts1()
2223
foreach (ExcelChart c in ws.Drawings)
2324
{
2425
var svg = renderer.RenderDrawingToSvg(c);
25-
SaveTextFileToWorkbook($"svg\\ChartForSvg{ix++}.svg", svg);
26+
SaveTextFileToWorkbook($"svg\\PieChartForSvg2{ix++}.svg", svg);
2627
}
2728
}
2829
}

src/EPPlus.Export.ImageRenderer.Test/TextRenderTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ public void VerifyVerticalAlignCenter()
325325
var svgShape = new SvgShape(shape);
326326
SvgTextBodyItem tbItem = svgShape.TextBodySvg;
327327

328+
//EPPlusImageRenderer.ImageRenderer.RenderDrawingToSvg(shape);
329+
328330
//Appears off by 1-2 px bc of border width
329331
Assert.AreEqual(190d, tbItem.Bounds.GlobalTop.PointToPixel(),1.0);
330332
}

src/EPPlus.Export.ImageRenderer/ImageRenderer.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Date Author Change
2525
using EPPlusImageRenderer.Svg;
2626
using OfficeOpenXml.Drawing;
2727
using OfficeOpenXml.Drawing.Chart;
28+
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
2829
using OfficeOpenXml.Utils;
2930
using OfficeOpenXml.Utils.EnumUtils;
3031
using System;
@@ -61,6 +62,7 @@ public enum RenderItemClasses
6162
Rect,
6263
TextBox,
6364
Shape,
65+
CircleSegment
6466
}
6567

6668
public Dictionary<string, object> GetItemProperties(RenderItemClasses item)
@@ -69,12 +71,74 @@ public Dictionary<string, object> GetItemProperties(RenderItemClasses item)
6971
{
7072
case RenderItemClasses.Rect:
7173
return new Dictionary<string, object> { { "Top", 10d }, { "Left", 10d }, { "Width", 10d }, { "Height", 10d }, {"Opacity", 0.8 }, {"Fill", Color.Goldenrod } };
74+
case RenderItemClasses.CircleSegment:
75+
return new Dictionary<string, object> { { "angle", 25d }, { "radius", 10d }, { "cx", 20d }, { "cy", 20d } };
7276
case RenderItemClasses.TextBox:
7377
default:
7478
throw new NotImplementedException("This class has not been implemented as an option yet");
79+
7580
}
7681
}
7782

83+
private string RenderCircleSegment(DrawingBase baseItem, Dictionary<string, object> itemProperties)
84+
{
85+
return RenderCircleSegment((double)itemProperties["angle"], (double)itemProperties["radius"], (double)itemProperties["cx"], (double)itemProperties["cy"]);
86+
}
87+
88+
string RenderCircleSegment(double angle, double radius, double cx, double cy)
89+
{
90+
var angleRadians = angle * (Math.PI / 180.0d);
91+
92+
var xPoint = cx + (radius * Math.Cos(angleRadians));
93+
var yPoint = cy + (radius * Math.Sin(angleRadians));
94+
95+
Coordinate endPoint = new Coordinate(xPoint, yPoint);
96+
97+
var baseBB = new BoundingBox();
98+
99+
//96x96 px
100+
baseBB.Width = 72;
101+
baseBB.Height = 72;
102+
103+
var baseItem = new DrawingItemForTesting(baseBB);
104+
105+
BoundingBox parent = new BoundingBox();
106+
107+
var slice = new SvgRenderPathItem(baseItem, baseItem.Bounds);
108+
109+
var startPoint = new Coordinate(cx, 0);
110+
111+
var moveCenter = new PathCommands(PathCommandType.Move, slice, cx / baseItem.Bounds.Width, cy / baseItem.Bounds.Height);
112+
var lineToStart = new PathCommands(PathCommandType.Line, slice, startPoint.X / baseItem.Bounds.Width, startPoint.Y / baseItem.Bounds.Height);
113+
114+
115+
116+
var w = baseItem.Bounds.Width.PointToPixel();
117+
var h = baseItem.Bounds.Height.PointToPixel();
118+
119+
var radX = radius.PointToPixel() / w;
120+
var radY = radius.PointToPixel() / h;
121+
122+
var arcCommand = new PathCommands(PathCommandType.Arc, slice, new double[] { radX, radY, 0, 0, 1, endPoint.X / baseItem.Bounds.Width, endPoint.Y / baseItem.Bounds.Height });
123+
124+
slice.Commands.Add(moveCenter);
125+
slice.Commands.Add(lineToStart);
126+
slice.Commands.Add(arcCommand);
127+
128+
slice.FillColor = "red";
129+
slice.BorderColor = "green";
130+
131+
baseItem.RenderItems.Add(slice);
132+
133+
var sb = new StringBuilder();
134+
135+
baseItem.Render(sb);
136+
137+
return sb.ToString();
138+
139+
//return baseItem;
140+
}
141+
78142
/// <summary>
79143
/// For Testing the specific renderItem class
80144
/// </summary>
@@ -325,12 +389,19 @@ private RenderItem GenerateRect(DrawingBase baseItem, Dictionary<string, object>
325389
return rectItem;
326390
}
327391

392+
private string GenerateFromCircle(RenderItemClasses preset, DrawingBase baseItem, Dictionary<string, object> itemProperties)
393+
{
394+
return RenderCircleSegment(baseItem, itemProperties);
395+
}
396+
328397
private RenderItem GenerateFromClasses(RenderItemClasses preset, DrawingBase baseItem, Dictionary<string, object> itemProperties)
329398
{
330399
switch (preset)
331400
{
332401
case RenderItemClasses.Rect:
333402
return GenerateRect(baseItem, itemProperties);
403+
case RenderItemClasses.CircleSegment:
404+
//return RenderCircleSegment(baseItem, itemProperties);
334405
case RenderItemClasses.TextBox:
335406

336407

src/EPPlus.Export.ImageRenderer/RenderItems/Shared/ParagraphItem.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ void GenerateTextFragments(ExcelDrawingTextRunCollection runs)
249249
var txtRun = runs[i];
250250
var runFont = txtRun.GetMeasurementFont();
251251

252-
fonts.Add(runFont);
253252
fonts.Add(runFont);
254253
runContents.Add(txtRun.Text);
255254
fontSizes.Add(runFont.Size);
@@ -482,7 +481,6 @@ private void AddLinesAndTextRuns(ExcelDrawingParagraph p, string textIfEmpty)
482481
}
483482
Bounds.Height = runLineSpacing + lastDescent;
484483
Bounds.Width = greatestWidth;
485-
Bounds.Width = greatestWidth;
486484
}
487485

488486
List<TextLineSimple> WrapToSimpleTextLines(ExcelDrawingParagraph p)

src/EPPlus.Export.ImageRenderer/RenderItems/Shared/TextBodyItem.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,14 @@ internal virtual void ImportTextBody(ExcelTextBody body, ExcelHorizontalAlignmen
152152
VerticalAlignment = body.Anchor;
153153

154154
//We already apply bounds top via the parent Transform
155-
double paragraphStartY = 0;
155+
double currentHeight = 0;
156156
double largestWidth = double.MinValue;
157157

158158
foreach (var paragraph in body.Paragraphs)
159159
{
160-
ImportParagraph(paragraph, paragraphStartY);
160+
ImportParagraph(paragraph, currentHeight);
161161
var addedPara = Paragraphs.Last();
162-
paragraphStartY = addedPara.Bounds.Bottom;
162+
currentHeight = addedPara.Bounds.Bottom;
163163
largestWidth = Math.Max(largestWidth, addedPara.Bounds.Width);
164164
}
165165

@@ -170,7 +170,7 @@ internal virtual void ImportTextBody(ExcelTextBody body, ExcelHorizontalAlignmen
170170

171171
if (Paragraphs != null && Paragraphs.Count() > 0)
172172
{
173-
Bounds.Height = paragraphStartY;
173+
Bounds.Height = currentHeight;
174174
}
175175

176176
Bounds.Top = GetAlignmentVertical();
@@ -243,7 +243,6 @@ private double GetAlignmentVertical()
243243
break;
244244
//Center means center of a Shape's ENTIRE bounding box height.
245245
//Not center of the Inset GetRectangle
246-
//Not center of the Inset GetRectangle
247246
case eTextAnchoringType.Center:
248247
alignmentY = (MaxHeight - Bounds.Height)/2 + Bounds.Top;
249248
break;

src/EPPlus.Export.ImageRenderer/ShapeDefinitions/ShapeDefinition.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ Date Author Change
1717
using System.Diagnostics;
1818
using System.Globalization;
1919
using System.Linq;
20-
using System.Security.Cryptography.Xml;
2120

2221
namespace EPPlusImageRenderer.ShapeDefinitions
2322
{

src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/ChartTypeDrawer.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using EPPlus.Export.ImageRenderer.Utils;
1+
using EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers;
2+
using EPPlus.Export.ImageRenderer.Utils;
23
using EPPlusImageRenderer.RenderItems;
34
using EPPlusImageRenderer.Svg;
45
using OfficeOpenXml;
@@ -82,6 +83,9 @@ internal static List<ChartTypeDrawer> Create(SvgChart svgChart)
8283
case eChartType.BarStacked100:
8384
drawers.Add(new BarColumnChartTypeDrawer(svgChart, (ExcelBarChart)ct));
8485
break;
86+
case eChartType.Pie:
87+
drawers.Add(new PieChartTypeDrawer(svgChart, (ExcelPieChart)ct));
88+
break;
8589
default:
8690
throw new NotImplementedException($"No Svg support for Chart type {ct} is implemented.");
8791
}

src/EPPlus.Export.ImageRenderer/Svg/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
using EPPlus.Graphics;
1+
using EPPlus.Fonts.OpenType.Utils;
2+
using EPPlus.Graphics;
23
using EPPlusImageRenderer;
34
using EPPlusImageRenderer.RenderItems;
45
using EPPlusImageRenderer.Svg;
5-
using OfficeOpenXml.Drawing;
66
using OfficeOpenXml.Drawing.Chart;
7-
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
7+
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
88
using OfficeOpenXml.Utils.TypeConversion;
99
using System;
1010
using System.Collections.Generic;
11-
using System.Linq;
12-
using System.Text;
11+
using System.Drawing;
1312

1413
namespace EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers
1514
{
@@ -32,13 +31,8 @@ public PieChartTypeDrawer(SvgChart chart, ExcelPieChart chartType) : base(chart,
3231

3332
foreach (ExcelPieChartSerie serie in chartType.Series)
3433
{
35-
List<object> valValue, catValue;
36-
37-
valValue = LoadSeriesValues(serie.Series, serie.NumberLiteralsY, serie.StringLiteralsY);
38-
catValue = LoadSeriesValues(serie.XSeries, serie.NumberLiteralsX, serie.StringLiteralsX);
39-
40-
catValues.Add(catValue);
41-
valValues.Add(valValue);
34+
valValues = LoadSeriesValues(serie.Series, serie.NumberLiteralsY, serie.StringLiteralsY);
35+
catValues = LoadSeriesValues(serie.XSeries, serie.NumberLiteralsX, serie.StringLiteralsX);
4236

4337
serCounter++;
4438
}
@@ -48,46 +42,112 @@ public PieChartTypeDrawer(SvgChart chart, ExcelPieChart chartType) : base(chart,
4842

4943
for(int i = 0; i< valValues.Count; i++)
5044
{
51-
valValuesDoubles.Add(ConvertUtil.GetValueDouble(valValues[i], false, true));
45+
var origValue = valValues[i];
46+
var dValue = ConvertUtil.GetValueDouble(origValue, false, true);
47+
valValuesDoubles.Add(dValue);
5248
valTotal += valValuesDoubles[i];
5349
}
5450

5551
var cx = _svgChart.Plotarea.Rectangle.Bounds.Width / 2;
5652
var cy = _svgChart.Plotarea.Rectangle.Bounds.Height / 2;
5753

58-
var radius = Math.Min(_svgChart.Plotarea.Rectangle.Bounds.Height, _svgChart.Plotarea.Rectangle.Bounds.Width);
54+
var radius = Math.Min(cx, cy);
55+
56+
groupItem.Bounds.Left = radius;
57+
groupItem.Bounds.Top = radius;
5958

59+
var prevAngle = -90d;
6060

6161
for (int i = 0; i < valValues.Count; i++)
6262
{
6363
valPercent.Add(valValuesDoubles[i] / valTotal);
64-
circleSectorAngle.Add(valPercent[i] / 360d);
65-
var xPoint = cx + (radius * Math.Cos(circleSectorAngle[i]));
66-
var yPoint = cy + (radius * Math.Sin(circleSectorAngle[i]));
64+
65+
var angle = valPercent[i] * 360d;
66+
67+
angle += prevAngle;
68+
69+
circleSectorAngle.Add(angle);
70+
71+
var angleRadians = angle * (Math.PI / 180.0d);
72+
73+
var xPoint = cx + (radius * Math.Cos(angleRadians));
74+
var yPoint = cy + (radius * Math.Sin(angleRadians));
6775
endCoordOffsetFromCenterOfCircle.Add(new Coordinate(xPoint, yPoint));
76+
77+
prevAngle = angle;
6878
}
6979

7080
var count = Math.Min(catValues.Count, valValues.Count);
71-
for (var i = 0; i < catValues.Count; i++)
81+
82+
for(int i = 0; i < chartType.Series.Count; i++)
7283
{
7384
var serie = (ExcelPieChartSerie)chartType.Series[i];
85+
for (var j = 0; j < catValues.Count; j++)
86+
{
87+
var total = ConvertUtil.GetValueDouble(catValues[j], false, true);
7488

75-
var total = ConvertUtil.GetValueDouble(catValues[i], false, true);
76-
77-
var dataPoints = new List<BoundingBox>();
78-
79-
//Add the slice.
80-
AddSlice(chartType, serie, catValues, valValues, dataPoints, count, i, radius);
81-
89+
var dataPoints = new List<BoundingBox>();
8290

91+
//Add the slice.
92+
AddSlice(chartType, serie, catValues, valValues, dataPoints, count, j, radius);
93+
}
8394
}
95+
8496
RenderItems.Add(new SvgEndGroupItem(ChartRenderer, null));
8597
}
8698

8799
private void AddSlice(ExcelPieChart chartType, ExcelPieChartSerie serie, List<object> catSeries, List<object> valSeries, List<BoundingBox> dataPoints, int seriesCount, int position, double radius)
88100
{
89-
var slice = new SvgRenderEllipseItem(ChartRenderer, _svgChart.Plotarea.Rectangle.Bounds);
101+
var slice = new SvgRenderPathItem(ChartRenderer, _svgChart.Plotarea.Rectangle.Bounds);
102+
103+
var cx = _svgChart.Plotarea.Rectangle.Bounds.Width / 2;
104+
var cy = _svgChart.Plotarea.Rectangle.Bounds.Height / 2;
105+
106+
var moveCenter = new PathCommands(PathCommandType.Move, slice, cx/_svgChart.Bounds.Width, cy/_svgChart.Bounds.Height);
107+
Coordinate startPoint;
108+
if(position != 0)
109+
{
110+
startPoint = new Coordinate(endCoordOffsetFromCenterOfCircle[position - 1].X, endCoordOffsetFromCenterOfCircle[position - 1].Y);
111+
}
112+
else
113+
{
114+
startPoint = new Coordinate(cx, 0);
115+
}
116+
117+
var lineToStart = new PathCommands(PathCommandType.Line, slice, startPoint.X / _svgChart.Bounds.Width, startPoint.Y / _svgChart.Bounds.Height);
118+
119+
var lineToEndPoint = new PathCommands(PathCommandType.Line, slice, endCoordOffsetFromCenterOfCircle[position].X / _svgChart.Bounds.Width, endCoordOffsetFromCenterOfCircle[position].Y / _svgChart.Bounds.Height);
120+
121+
var w = _svgChart.Plotarea.Rectangle.Bounds.Width.PointToPixel();
122+
var h = _svgChart.Plotarea.Rectangle.Bounds.Height.PointToPixel();
123+
124+
var radX = radius.PointToPixel() / w;
125+
var radY = radius.PointToPixel() / h;
126+
127+
var arcCommand = new PathCommands(PathCommandType.Arc, slice, new double[] { radX, radY, 0, 0, 1, endCoordOffsetFromCenterOfCircle[position].X / _svgChart.Bounds.Width, endCoordOffsetFromCenterOfCircle[position].Y / _svgChart.Bounds.Height });
128+
129+
slice.Commands.Add(moveCenter);
130+
slice.Commands.Add(lineToStart);
131+
132+
if (position == 0)
133+
{
134+
serie.Fill.Color = Color.Red;
135+
serie.Border.Fill.Color = Color.DarkOrange;
136+
}
137+
else if(position == 1)
138+
{
139+
serie.Fill.Color = Color.Green;
140+
serie.Border.Fill.Color = Color.DarkGreen;
141+
}
142+
else if(position == 2)
143+
{
144+
serie.Fill.Color = Color.Blue;
145+
serie.Border.Fill.Color = Color.DarkBlue;
146+
}
147+
slice.Commands.Add(arcCommand);
90148

149+
//slice.Commands.Add(moveCenter);
150+
//slice.Commands.Add(lineToEndPoint);
91151

92152
slice.SetDrawingPropertiesFill(serie.Fill, chartType.StyleManager.Style.SeriesAxis.FillReference.Color);
93153
slice.SetDrawingPropertiesBorder(serie.Border, chartType.StyleManager.Style.SeriesAxis.BorderReference.Color, true);

src/EPPlus.Export.ImageRenderer/Svg/Chart/SvgChart.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ public SvgChart(ExcelChart chart) : base(chart)
5454
Legend = null;
5555
}
5656

57-
HorizontalAxis = GetAxis(false);
58-
VerticalAxis = GetAxis(true);
57+
if(Chart.Axis.Length != 0)
58+
{
59+
HorizontalAxis = GetAxis(false);
60+
VerticalAxis = GetAxis(true);
61+
}
5962

6063
if(chart.Axis.Length > 2)
6164
{

0 commit comments

Comments
 (0)