diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs new file mode 100644 index 0000000000..fa0eaab6af --- /dev/null +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs @@ -0,0 +1,31 @@ +using OfficeOpenXml; +using OfficeOpenXml.Drawing.Chart; + +namespace EPPlus.Export.ImageRenderer.Tests.Chart +{ + [TestClass] + public class SvgDataTableTests : TestBase + { + [TestMethod] + public void GenerateSvgForLineChartWithDataTable() + { + ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenTemplatePackage("SvgDataTable.xlsx")) + { + var ws = p.Workbook.Worksheets[0]; + + //var ix = 2; + //var c = ws.Drawings[ix]; + //var svg = c.ToSvg(); + //SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); + + var ix = 0; + foreach (ExcelChart c in ws.Drawings) + { + var svg = c.ToSvg(); + SaveTextFileToWorkbook($"svg\\DataTable_sheet1_{ix++}.svg", svg); + } + } + } + } +} diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs index 8d46b25c7f..e685e0d1d9 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs @@ -26,17 +26,17 @@ public void GenerateSvgForLineCharts_sheet1() { var ws = p.Workbook.Worksheets[0]; - //var ix = 4; - //var c = ws.Drawings[ix]; - //var svg = c.ToSvg(); - //SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); - - for (int i = 0; i < ws.Drawings.Count; i++) - { - var c = ws.Drawings[i]; - var svg = c.ToSvg(); - SaveTextFileToWorkbook($"svg\\ChartForSvg{i}.svg", svg); - } + var ix = 1; + var c = ws.Drawings[ix]; + var svg = c.ToSvg(); + SaveTextFileToWorkbook($"svg\\ChartForSvg_ind{ix++}.svg", svg); + + //for (int i = 0; i < ws.Drawings.Count; i++) + //{ + // var c = ws.Drawings[i]; + // var svg = c.ToSvg(); + // SaveTextFileToWorkbook($"svg\\ChartForSvg{i}.svg", svg); + //} } } diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs index 3e27f7e214..4d500d4edd 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs @@ -11,6 +11,22 @@ namespace EPPlus.Export.ImageRenderer.Tests.Chart public class PieChartTests : TestBase { + [TestMethod] + public void ReadAndExportSimpleExplosion() + { + ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); + using (var p = OpenTemplatePackage("PieChartSvgALL.xlsx")) + { + var ws = p.Workbook.Worksheets[1]; + + var explosion = ws.Drawings["PieExplosion30"]; + + var explodeSvg = explosion.ToSvg(); + + SaveTextFileToWorkbook($"svg\\pie30_new.svg", explodeSvg); + } + } + [TestMethod] public void ReadAndCreateSvgsAll() { @@ -19,9 +35,9 @@ public void ReadAndCreateSvgsAll() { var ws = p.Workbook.Worksheets[4]; - var explosion = ws.Drawings["ExplosionAll"]; + var manySlices = ws.Drawings["ManySlices_Rot"]; - var explodeSvg = explosion.ToSvg(); + var manySlicesSvg = manySlices.ToSvg(); for (int i = 0; i < p.Workbook.Worksheets.Count; i++) { @@ -100,7 +116,7 @@ public void GenerateEPPlusPieCharts() for (int i = 0; i < ws.Drawings.Count; i++) { - var c = ws.Drawings[i]; + var c = ws.Drawings[1]; var svg = c.ToSvg(); SaveTextFileToWorkbook($"svg\\Pie{i}.svg", svg); } diff --git a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs index 3fa92152d8..a63fa7fbf4 100644 --- a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs @@ -14,8 +14,13 @@ namespace EPPlus.Export.ImageRenderer.Tests.DrawingShapeRenderer { [TestClass] public class SvgStandAloneTests : TestBase - { - + { + public SvgStandAloneTests() + { +#if (Core) + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + } private GroupRenderItem GenerateShapeRenderer() { BoundingBox bounds = new BoundingBox(0, 0, 500, 500); @@ -25,13 +30,12 @@ private GroupRenderItem GenerateShapeRenderer() var baseGroup = new GroupRenderItem(bounds); - var background = new RectRenderItem(baseGroup.Bounds); + var background = new RectRenderItem(baseGroup); background.Width = bounds.Width; background.Height = bounds.Height; - background.FillColor = "aliceBlue"; + background.Style.FillColor = "aliceBlue"; - baseGroup.AddChildItem(background); return baseGroup; } @@ -41,23 +45,23 @@ private GroupRenderItem GenerateGroupRenderItem() var baseGroup = new GroupRenderItem(bounds); - var background = new RectRenderItem(baseGroup.Bounds); + var background = new RectRenderItem(baseGroup); background.Width = bounds.Width; background.Height = bounds.Height; - background.FillColor = "aliceBlue"; + background.Style.FillColor = "aliceBlue"; - baseGroup.AddChildItem(background); + //baseGroup.AddChildItem(background); return baseGroup; } - private void GenerateSvgFile(string fileName, BoundingBox bounds, params RenderItem[] items) + private void GenerateSvgFile(string fileName, BoundingBox bounds, params Transform[] items) { StringBuilder sb = new StringBuilder(); var svgShapeRenderer = new SvgShapeRenderer(bounds, sb, new SvgRenderOptions()); - List renderItems = items.ToList(); + List renderItems = items.ToList(); svgShapeRenderer.Render(renderItems); var svg = sb.ToString(); @@ -69,7 +73,7 @@ private void GenerateSvgFile(string fileName, BoundingBox bounds, params RenderI public void SvgRectTest() { var baseGroup = GenerateShapeRenderer(); - GenerateSvgFile("rectStandAlone", baseGroup.Bounds, baseGroup); + GenerateSvgFile("rectStandAlone", baseGroup, baseGroup); } [TestMethod] @@ -85,32 +89,30 @@ public void SvgTextRun() rt.SubFamily = OfficeOpenXml.Interfaces.Fonts.FontSubFamily.Regular; rt.Size = 12f; - var textRun = new SvgTextRunRenderItem(baseGroup.Bounds, rt, rt.Text, true); + var textRun = new SvgTextRunRenderItem(baseGroup, rt, rt.Text, true); //Add size of text since svg renders text upwards from the start point. textRun.YPosition = rt.Size; - baseGroup.AddChildItem(textRun); - - GenerateSvgFile("textRunStandAlone", baseGroup.Bounds, baseGroup); + GenerateSvgFile("textRunStandAlone", baseGroup, baseGroup); } private void GenerateTextBodyFile(string fileName, GroupRenderItem baseGroup, SvgTextBodyRenderItem textBody) { StringBuilder sb = new StringBuilder(); var options = new SvgRenderOptions(); - var svgShapeRenderer = new SvgShapeRenderer(baseGroup.Bounds, sb, options); + var svgShapeRenderer = new SvgShapeRenderer(baseGroup, sb, options); - var background = new RectRenderItem(baseGroup.Bounds); + var background = new RectRenderItem(baseGroup); - background.Width = baseGroup.Bounds.Width; - background.Height = baseGroup.Bounds.Height; - background.FillColor = "aliceBlue"; + background.Width = baseGroup.Width; + background.Height = baseGroup.Height; + background.Style.FillColor = "aliceBlue"; baseGroup.AddChildItem(textBody); baseGroup.AddChildItem(background); - List items = new List() { baseGroup }; + var items = new List() { baseGroup }; svgShapeRenderer.Render(items); @@ -133,7 +135,7 @@ private SvgTextBodyRenderItem GenerateTextBody(GroupRenderItem baseGroup) } var renderContext = new RenderContext(() => engine); - var textBody = new SvgTextBodyRenderItem(renderContext, baseGroup.Bounds, true); + var textBody = new SvgTextBodyRenderItem(renderContext, baseGroup, true); //Aptos Narrow is Default font for a text that does not define its own font' //As we do not have it, this fallbacks to archivo narrow and then to Old Metrics for Aptos var paragraph = textBody.AddParagraph("Hello"); @@ -144,10 +146,10 @@ private SvgTextBodyRenderItem GenerateTextBody(GroupRenderItem baseGroup) rtItem.FontColor = Color.DarkGreen; var para2 = textBody.AddParagraph(rtItem); - textBody.AddChildItem(paragraph); - textBody.AddChildItem(para2); + //textBody.AddChildItem(paragraph); + //textBody.AddChildItem(para2); - baseGroup.AddChildItem(textBody); + //baseGroup.AddChildItem(textBody); return textBody; } @@ -157,7 +159,7 @@ public void SvgTextBodyTest() { var baseGroup = GenerateGroupRenderItem(); var textBody = GenerateTextBody(baseGroup); - GenerateSvgFile("standAloneTextBody", baseGroup.Bounds, baseGroup); + GenerateSvgFile("standAloneTextBody", baseGroup, baseGroup); } [TestMethod] @@ -181,17 +183,17 @@ public void SvgTextBodyTestCenterAlignmentGenerated() //new day beckons is the largest line in the centered paragraph[0] //Assert that the first line has been centered appropriately - Assert.AreEqual(9.890869140625d, textBody.Paragraphs[0].Runs[0].Bounds.Left, delta); - Assert.AreEqual(33.142333984375d, textBody.Paragraphs[0].Runs[1].Bounds.Left, delta); - Assert.AreEqual(59.509033203125d, textBody.Paragraphs[0].Runs[2].Bounds.Left, delta); - Assert.AreEqual(0d, textBody.Paragraphs[0].Runs[3].Bounds.Left); + Assert.AreEqual(9.890869140625d, textBody.Paragraphs[0].Runs[0].Left, delta); + Assert.AreEqual(33.142333984375d, textBody.Paragraphs[0].Runs[1].Left, delta); + Assert.AreEqual(59.509033203125d, textBody.Paragraphs[0].Runs[2].Left, delta); + Assert.AreEqual(0d, textBody.Paragraphs[0].Runs[3].Left); - Assert.AreEqual(5.08, textBody.Paragraphs[1].Runs[0].Bounds.Left, delta); - Assert.AreEqual(0d, textBody.Paragraphs[1].Runs[1].Bounds.Left); + Assert.AreEqual(5.08, textBody.Paragraphs[1].Runs[0].Left, delta); + Assert.AreEqual(0d, textBody.Paragraphs[1].Runs[1].Left); //Assert that the second paragraph has been moved correctly - Assert.AreEqual(26.85546875d, textBody.Paragraphs[1].Bounds.Top); - GenerateSvgFile("textBodyAlignCenter", baseGroup.Bounds, baseGroup); + Assert.AreEqual(26.85546875d, textBody.Paragraphs[1].Top); + GenerateSvgFile("textBodyAlignCenter", baseGroup, baseGroup); } [TestMethod] public void SvgTextBodyTestRightAlignmentGenerated() @@ -214,15 +216,15 @@ public void SvgTextBodyTestRightAlignmentGenerated() double delta = 0.001; //Assert that the first line has been aligned correctly - Assert.AreEqual(19.78173828125d, textBody.Paragraphs[0].Runs[0].Bounds.Left, delta); - Assert.AreEqual(43.033203125d, textBody.Paragraphs[0].Runs[1].Bounds.Left, delta); - Assert.AreEqual(69.39990234375d, textBody.Paragraphs[0].Runs[2].Bounds.Left, delta); - Assert.AreEqual(0d, textBody.Paragraphs[0].Runs[3].Bounds.Left); + Assert.AreEqual(19.78173828125d, textBody.Paragraphs[0].Runs[0].Left, delta); + Assert.AreEqual(43.033203125d, textBody.Paragraphs[0].Runs[1].Left, delta); + Assert.AreEqual(69.39990234375d, textBody.Paragraphs[0].Runs[2].Left, delta); + Assert.AreEqual(0d, textBody.Paragraphs[0].Runs[3].Left); - Assert.AreEqual(10.16d, textBody.Paragraphs[1].Runs[0].Bounds.Left, delta); - Assert.AreEqual(0d, textBody.Paragraphs[1].Runs[1].Bounds.Left); + Assert.AreEqual(10.16d, textBody.Paragraphs[1].Runs[0].Left, delta); + Assert.AreEqual(0d, textBody.Paragraphs[1].Runs[1].Left); - GenerateSvgFile("textBodyAlignRight", baseGroup.Bounds, baseGroup); + GenerateSvgFile("textBodyAlignRight", baseGroup, baseGroup); } [TestMethod] @@ -242,18 +244,18 @@ public void SvgTextBodyVerticalAlignmentGenerated() textBody.AutoSize = false; textBody.Height = 500; - textBody.Bounds.Top = 0; + textBody.Top = 0; textBody.VerticalAlignment = TextAnchoringType.Center; - textBody.Bounds.Top = textBody.GetAlignmentVertical(); + textBody.Top = textBody.GetAlignmentVertical(); double delta = 0.001; - GenerateSvgFile("textBodyAlignVCenter", baseGroup.Bounds, baseGroup); + GenerateSvgFile("textBodyAlignVCenter", baseGroup, baseGroup); //This appears to be entirely accurate when comparing to excel - Assert.AreEqual(215.02026414871216, textBody.Bounds.Top, delta); + Assert.AreEqual(215.02026414871216, textBody.Top, delta); //This is our old expected value. - //Assert.AreEqual(180.04052829742432d, textBody.Bounds.Top, delta); + //Assert.AreEqual(180.04052829742432d, textBody.Top, delta); //Interestingly 500/2 = 250 //and 250 - 35 = 215 //And 215 - 35 = 180 @@ -278,21 +280,21 @@ public void SvgTextBodyVerticalAlignmentBottomGenerated() textBody.AutoSize = false; textBody.Height = 500; - textBody.Bounds.Top = 0; + textBody.Top = 0; textBody.VerticalAlignment = TextAnchoringType.Bottom; - textBody.Bounds.Top = textBody.GetAlignmentVertical(); + textBody.Top = textBody.GetAlignmentVertical(); double delta = 0.001; - Assert.AreEqual(430.04052829742432d, textBody.Bounds.Top, delta); + Assert.AreEqual(430.04052829742432d, textBody.Top, delta); - GenerateSvgFile("textBodyAlignVBottom", baseGroup.Bounds, baseGroup); + GenerateSvgFile("textBodyAlignVBottom", baseGroup, baseGroup); } private RenderTextbox GenerateTextBox(out GroupRenderItem group) { group = GenerateGroupRenderItem(); - var textbox = new RenderTextbox(group.Bounds, 500d, 500d); + var textbox = new RenderTextbox(group, 500d, 500d); var engine = new OpenTypeFontEngine(x => x.SearchSystemDirectories = true); var rc = new RenderContext(() => engine); @@ -301,7 +303,7 @@ private RenderTextbox GenerateTextBox(out GroupRenderItem group) Assert.Inconclusive("Font not found. This is expected behaviour on web."); } - textbox.TextBody = new SvgTextBodyRenderItem(rc, group.Bounds, true); + textbox.TextBody = new SvgTextBodyRenderItem(rc, group, true); var paragraph = textbox.TextBody.AddParagraph("Hello"); @@ -311,7 +313,7 @@ private RenderTextbox GenerateTextBox(out GroupRenderItem group) rtItem.FontColor = Color.DarkGreen; var para2 = textbox.TextBody.AddParagraph(rtItem); - textbox.Rectangle.FillColor = "#F9F6C4"; + textbox.Rectangle.Style.FillColor = "#F9F6C4"; return textbox; } @@ -320,14 +322,14 @@ private RenderTextbox GenerateTextBox(out GroupRenderItem group) public void BasicTextBox() { var textbox = GenerateTextBox(out GroupRenderItem group); - textbox.AppendRenderItems(group.RenderItems); + textbox.PrepareForRender(); double delta = 0.001; Assert.AreEqual(115.2d, textbox.Width, delta, $"textbox.Width was {textbox.Width}, not 107.952 as expected"); Assert.AreEqual(34.979735851287842d, textbox.Height, delta, $"textbox.Height was {textbox.Height}, not 34.978 as expected"); - GenerateSvgFile("BasicTextBox", group.Bounds, group); + GenerateSvgFile("BasicTextBox", group, group); } [TestMethod] @@ -340,22 +342,22 @@ public void TextBoxWithMargins() textbox.LeftMargin = 10d; textbox.TopMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.PrepareForRender(); //Assert local position unchanged Assert.AreEqual(0d, textbox.TextBody.Left); Assert.AreEqual(0d, textbox.TextBody.Top); //Assert global position changed - Assert.AreEqual(10d, textbox.TextBody.Bounds.Position.X); - Assert.AreEqual(10d, textbox.TextBody.Bounds.Position.Y); + Assert.AreEqual(10d, textbox.TextBody.Position.X); + Assert.AreEqual(10d, textbox.TextBody.Position.Y); //Assert width and height changed by margins Assert.AreEqual(125.2, textbox.Width, delta); Assert.AreEqual(44.979735851287842d, textbox.Height, delta); - GenerateSvgFile("MarginTextBox", group.Bounds, group); + GenerateSvgFile("MarginTextBox", group, group); } @@ -371,13 +373,13 @@ public void TextBoxWithAllMargins() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.PrepareForRender(); //Assert width and height changed by margins Assert.AreEqual(135.2d, textbox.Width, delta); Assert.AreEqual(54.979735851287842d, textbox.Height, delta); - GenerateSvgFile("AllMarginsTextBox", group.Bounds, group); + GenerateSvgFile("AllMarginsTextBox", group, group); } @@ -404,13 +406,13 @@ public void TextBoxWithAllMarginsANDTextbodyChanged() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.PrepareForRender(); //Assert width and height changed by margins and textbody Assert.AreEqual(150.2d, textbox.Width, delta); Assert.AreEqual(69.979735851287842d, textbox.Height, delta); - GenerateSvgFile("TextAnchor_TextBox", group.Bounds, group); + GenerateSvgFile("TextAnchor_TextBox", group, group); } } } diff --git a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs new file mode 100644 index 0000000000..73fd765bc7 --- /dev/null +++ b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs @@ -0,0 +1,197 @@ +using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.DrawingRenderer.Svg; +using EPPlus.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.DrawingRenderer.Tests.Shared +{ + [TestClass] + public class MultiContainerTest : TestBase + { + [TestMethod] + public void VerifySimpleChildContent() + { + var world = new BoundingBox(0, 0, 500, 500); + + var container = new MultiContainerItem(world); + + container.Left = 5; + container.Top = 5; + + var child1 = new BoundingBox(); + child1.Left = 3; + child1.Top = 3; + + child1.Width = 25; + child1.Height = 16; + + child1.Parent = container; + + Assert.AreEqual(3, container.ContentLeft); + Assert.AreEqual(3, container.ContentTop); + Assert.AreEqual(child1.Width, container.ContentWidth); + Assert.AreEqual(child1.Height, container.ContentHeight); + Assert.AreEqual(child1.Width + child1.Left, container.ContentRight); + Assert.AreEqual(child1.Height + child1.Top, container.ContentBottom); + } + + [TestMethod] + public void VerifyGrandChildContentWidthHeight() + { + var world = new BoundingBox(0, 0, 500, 500); + + var container = new MultiContainerItem(world); + + container.Left = 5; + container.Top = 5; + + var child1 = new BoundingBox(); + child1.Left = 3; + child1.Top = 3; + + child1.Width = 25; + child1.Height = 16; + + child1.Parent = container; + + var grandChild = new BoundingBox(); + grandChild.Parent = child1; + + grandChild.Width = 60; + grandChild.Height = 100; + + Assert.AreEqual(3, container.ContentLeft); + Assert.AreEqual(3, container.ContentTop); + Assert.AreEqual(60, container.ContentWidth); + Assert.AreEqual(100, container.ContentHeight); + Assert.AreEqual(63, container.ContentRight); + Assert.AreEqual(103, container.ContentBottom); + } + + private void GenerateSvgFile(string fileName, BoundingBox bounds, params Transform[] items) + { + + StringBuilder sb = new StringBuilder(); + var svgShapeRenderer = new SvgShapeRenderer(bounds, sb, new SvgRenderOptions()); + + List renderItems = items.ToList(); + svgShapeRenderer.Render(renderItems); + + var svg = sb.ToString(); + + SaveTextFileToWorkbook($"svg\\{fileName}.svg", svg); + } + + [TestMethod] + public void VerifySimpleChildContentNegativeTopLeft() + { + var world = new BoundingBox(0, 0, 500, 500); + + var container = new MultiContainerItem(world); + + container.Left = 5; + container.Top = 5; + + var child1 = new BoundingBox(); + child1.Left = -3; + child1.Top = -3; + + child1.Width = 25; + child1.Height = 16; + + child1.Parent = container; + + Assert.AreEqual(child1.Left, container.ContentLeft); + Assert.AreEqual(child1.Top, container.ContentTop); + Assert.AreEqual(child1.Width, container.ContentWidth); + Assert.AreEqual(child1.Height, container.ContentHeight); + Assert.AreEqual(child1.Width + child1.Left, container.ContentRight); + Assert.AreEqual(child1.Height + child1.Top, container.ContentBottom); + } + + [TestMethod] + public void VerifyGrandChildContentTopLeftNegative() + { + var world = new GroupRenderItem(); + world.Width = 500; + world.Height = 500; + + var container = new MultiContainerItem(world); + + container.Left = 5; + container.Top = 5; + + var child1 = new GroupRenderItem(); + child1.Left = 3; + child1.Top = 3; + + child1.Width = 25; + child1.Height = 16; + + child1.Parent = container; + + var grandChild = new GroupRenderItem(); + grandChild.Parent = child1; + + grandChild.Left = -5; + //This puts grandchild Y at -1 globally + //A bit weird but technically allowed in e.g. SVG + grandChild.Top = -9; + + grandChild.Width = 15; + grandChild.Height = 2; + + Assert.AreEqual(-2, container.ContentLeft); + Assert.AreEqual(-6, container.ContentTop); + Assert.AreEqual(30, container.ContentWidth); + //In this Case. + //and the distance between 3(child) + 16 (child height) + //The distance between -9 (grandChild) and 3(child) is 6 + //Alternatively. Globally our extremes are at -1 and 24 giving a total distance of ABS(-1-24) = 25 + Assert.AreEqual(19 + 6, container.ContentHeight); + Assert.AreEqual(28, container.ContentRight); + Assert.AreEqual(19, container.ContentBottom); + + //As this may be hard to visualize here's a very simple svg file that illustrates this setup: + /* + + + + + + + + + + + + + */ + + //If you wish you can also generate a similar file from the existing structure: + //RectRenderItem totalSize = new RectRenderItem(world); + //totalSize.Name = "TotalSize_GlobalRect"; + //totalSize.Left = 3; + //totalSize.Top = -1; + //totalSize.Width = 30; + //totalSize.Height = 25; + //totalSize.Style.FillColor = "green"; + //totalSize.Style.FillOpacity = 0.2d; + + ////Will be drawing a rect which has it's TopLeft in the RightBottom of the case above + //RectRenderItem ExtremeRightBottom = new RectRenderItem(world); + //ExtremeRightBottom.Name = "ExtremeRightBottom_GlobalRect"; + //ExtremeRightBottom.Left = 33; + //ExtremeRightBottom.Top = 24; + //ExtremeRightBottom.Width = 5; + //ExtremeRightBottom.Height = 5; + //ExtremeRightBottom.Style.FillColor = "blue"; + //ExtremeRightBottom.Style.FillOpacity = 0.2d; + + //GenerateSvgFile("VerifyGrandChildContentTopLeftNegative", world, world.ChildObjects.ToArray()); + } + } +} diff --git a/src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs new file mode 100644 index 0000000000..40231a6bbb --- /dev/null +++ b/src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs @@ -0,0 +1,22 @@ +//using EPPlus.DrawingRenderer.RenderItems; +//using EPPlus.Export.ImageRenderer.RenderItems.Shared; +//using EPPlusImageRenderer.RenderItems; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; + +//namespace EPPlus.Export.ImageRenderer.RenderItems +//{ +// internal class ContainerRenderItem : ContainerItem +// { +// public ContainerRenderItem(RenderItem innerItem, RenderItem outerItem) : base(innerItem, outerItem) +// { +// } + +// public override RenderItem Clone() +// { +// throw new NotImplementedException(); +// } +// } +//} \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs new file mode 100644 index 0000000000..d95c5f5ba8 --- /dev/null +++ b/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs @@ -0,0 +1,116 @@ +using EPPlus.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.DrawingRenderer.RenderItems +{ + public class MultiContainerItem : GroupRenderItem + { + public override RenderItemType Type => RenderItemType.Group; + + private Rect ContentBounds { get; set; } + + public MultiContainerItem(BoundingBox parent) : base(parent) + { + ContentBounds = new Rect(0, 0); + } + + private static void GetExtremeGlobalBounds(Transform t, ref double minLeft, ref double minTop, ref double maxRight, ref double maxBottom) + { + if (t is BoundingBox bb) + { + if (bb.GlobalRight > maxRight) + { + maxRight = bb.GlobalRight; + } + if (bb.GlobalBottom > maxBottom) + { + maxBottom = bb.GlobalBottom; + } + } + + if (t.Position.X < minLeft) + { + minLeft = t.Position.X; + + if (minLeft > maxRight) + { + maxRight = minLeft; + } + } + if (t.Position.Y < minTop) + { + minTop = t.Position.Y; + + if (minTop > maxBottom) + { + maxBottom = minTop; + } + } + } + + private void GetExtremeGlobalBoundsOfAllChildrenRecursive(Transform t, ref double minLeft, ref double minTop, ref double maxRight, ref double maxBottom) + { + GetExtremeGlobalBounds(t, ref minLeft, ref minTop, ref maxRight, ref maxBottom); + if (t.ChildObjects != null && t.ChildObjects.Count > 0) + { + foreach (var child in t.ChildObjects) + { + GetExtremeGlobalBoundsOfAllChildrenRecursive(child, ref minLeft, ref minTop, ref maxRight, ref maxBottom); + } + } + } + + List cachedChildObjects = null; + + private void UpdateContentBounds() + { + if(cachedChildObjects != null) + { + if (cachedChildObjects.SequenceEqual(ChildObjects)) + { + //No need to update if it's exactly the same + //TODO: Verify this is not considered sequence equal if children of childobjects change. + return; + } + } + + if(ChildObjects != null && ChildObjects.Count > 0) + { + var minLeft = double.MaxValue; + var minTop = double.MaxValue; + var maxRight = double.MinValue; + var maxBottom = double.MinValue; + + foreach (var child in ChildObjects) + { + GetExtremeGlobalBoundsOfAllChildrenRecursive(child, ref minLeft, ref minTop, ref maxRight, ref maxBottom); + } + + var TopLeftLocal = TransformPointToLocal(new Graphics.Geometry.Vector2(minLeft, minTop)); + var BottomRightLocal = TransformPointToLocal(new Graphics.Geometry.Vector2(maxRight, maxBottom)); + + ContentBounds.Left = TopLeftLocal.X; + ContentBounds.Top = TopLeftLocal.Y; + ContentBounds.Right = BottomRightLocal.X; + ContentBounds.Bottom = BottomRightLocal.Y; + + cachedChildObjects = ChildObjects; + } + } + + public double ContentTop { get { UpdateContentBounds(); return ContentBounds.Top; } } + public double ContentLeft { get { UpdateContentBounds(); return ContentBounds.Left; } } + public double ContentWidth { get { UpdateContentBounds(); return ContentBounds.Width; } } + public double ContentHeight { get { UpdateContentBounds(); return ContentBounds.Height; } } + public double ContentRight { get { UpdateContentBounds(); return ContentBounds.Right; } } + public double ContentBottom { get { UpdateContentBounds(); return ContentBounds.Bottom; } } + + public override RenderItem Clone() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/EPPlus.DrawingRenderer/RenderItems/PathCommands.cs b/src/EPPlus.DrawingRenderer/RenderItems/PathCommand.cs similarity index 88% rename from src/EPPlus.DrawingRenderer/RenderItems/PathCommands.cs rename to src/EPPlus.DrawingRenderer/RenderItems/PathCommand.cs index 224d0de5af..c83db27b07 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/PathCommands.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/PathCommand.cs @@ -20,9 +20,9 @@ Date Author Change namespace EPPlusImageRenderer { - public class PathCommands + public class PathCommand { - public PathCommands(PathCommandType type, params double[] coordinates) + public PathCommand(PathCommandType type, params double[] coordinates) { Type = type; Coordinates = coordinates; @@ -30,9 +30,9 @@ public PathCommands(PathCommandType type, params double[] coordinates) //public SvgRenderItem RenderItem{ get; set;} public PathCommandType Type { get; } public double[] Coordinates { get; set; } - public PathCommands Clone() + public PathCommand Clone() { - return new PathCommands(Type) + return new PathCommand(Type) { Coordinates = (double[])Coordinates.Clone(), }; diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 04672d758b..8cc5c065f4 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -27,7 +27,7 @@ public enum FillType /// The compound line type. Used for underlining text /// public enum CompoundLineStyle - { + { /// /// Double lines with equal width /// @@ -87,27 +87,27 @@ public double X { get { - return Bounds.Left; + return Left; } set { - Bounds.Left = value; + Left = value; } } public double Y { get { - return Bounds.Top; + return Top; } set { - Bounds.Top = value; + Top = value; } } public override RenderItem Clone() { - var clone = new UseReferenceRenderItem((BoundingBox)Bounds.Parent, Href); + var clone = new UseReferenceRenderItem((BoundingBox)Parent, Href); CloneBase(clone); return clone; } @@ -118,31 +118,21 @@ public RectRenderItem() : base() { } - public RectRenderItem(BoundingBox parent) : base(parent) + public RectRenderItem(Transform parent) : base(parent) { } public override RenderItemType Type => RenderItemType.Rect; - public double Left { get { return Bounds.Left; } set { Bounds.Left = value; } } - public double Top { get { return Bounds.Top; } set { Bounds.Top = value; } } - public double Width { get { return Bounds.Width; } set { Bounds.Width = value; } } - public double Height { get { return Bounds.Height; } set { Bounds.Height = value; } } - public double Right { get { return Bounds.Left + Width; } } - public double Bottom { get { return Bounds.Top + Height; } } - public double GlobalLeft => Bounds.GlobalLeft; - public double GlobalTop => Bounds.GlobalTop; - public double GlobalRight => Bounds.GlobalLeft + Width; - public double GlobalBottom => Bounds.GlobalTop + Height; public double RoundedCornerRadius { get; set; } public override RenderItem Clone() { - var clone = new RectRenderItem((BoundingBox)Bounds.Parent) + var clone = new RectRenderItem(Parent) { RoundedCornerRadius = RoundedCornerRadius, }; - clone.Bounds.Width = Bounds.Width; - clone.Bounds.Height = Bounds.Height; + clone.Width = Width; + clone.Height = Height; CloneBase(clone); return clone; @@ -155,131 +145,91 @@ public GroupRenderItem(BoundingBox parent) : base(parent) } public GroupRenderItem(BoundingBox parent, double rotation) : base(parent) { - Rotation = rotation; + LocalRotation = rotation; } public GroupRenderItem() : base() { - Bounds.Parent = TranslationOffset; + Parent = TranslationOffset; } public GroupRenderItem(double localXPos, double localYPos) : this() { - TranslationOffset = new Graphics.Point(localXPos, localYPos); + TranslationOffset = new Graphics.TranformPoint(localXPos, localYPos); } + public Coordinate GroupScale = null; + public double GroupRotation { get; set;} public GroupRenderItem(BoundingBox parent, double rotation, Transform rotationPoint = null) : this(0, 0) { TranslationOffset.Parent = parent; - Rotation = rotation; + LocalRotation = rotation; if (rotationPoint != null) { - RotationPoint = new Graphics.Point(rotationPoint.LocalPosition.X, rotationPoint.LocalPosition.Y); + RotationPoint = new Graphics.TranformPoint(rotationPoint.LocalPosition.X, rotationPoint.LocalPosition.Y); } } - /// - /// The top position of the group. This is the position of the group relative to its parent. The child items are positioned relative to this position. - /// - public double Top { get { return Bounds.Top; } set { Bounds.Top = value; } } - /// - /// The left position of the group. This is the position of the group relative to its parent. The child items are positioned relative to this position. - /// - public double Left { get { return Bounds.Left; } set { Bounds.Left = value; } } public override RenderItemType Type => RenderItemType.Group; - public string TextAnchor { get; set; } - public double Rotation { get; set; } - public string GroupTransform = ""; - public List RenderItems { get; } = new List(); - Graphics.Point _altRotationPoint = null; - /// - /// The translated position of this item in points - /// Also the parent position of the group item - /// (This may seem strange but it ensures the the translation is seen - /// immediately in the global position of GroupItem without affecting local position) - /// - public Graphics.Point TranslationOffset = new Graphics.Point(0, 0); - public Graphics.Point RotationPoint - { - get - { - if (_altRotationPoint == null) - { - return TranslationOffset; - } - return _altRotationPoint; - } - set - { - _altRotationPoint = value; - } - } - - public Coordinate Scale = null; + //Note: This does not take negative child items into acount + //TODO: Fix that - internal void SetRotationPointToCenterOfGroup(double rotation = double.NaN) - { - RotationPoint = new Graphics.Point(Bounds.Width / 2, Bounds.Height / 2); - - if (double.IsNaN(rotation) == false) - { - Rotation = rotation; - } - } - - public void AddChildItem(RenderItem item) + public void AddChildItem(Transform item) { //item.Bounds.Parent = TranslationOffset; //This incorrectly sets the parent bounds to zero. Intended? - RenderItems.Add(item); - - Bounds.Width = item.Bounds.Right > Bounds.Width ? item.Bounds.Right : Bounds.Width; - Bounds.Height = item.Bounds.Bottom > Bounds.Height ? item.Bounds.Bottom : Bounds.Height; - } + ChildObjects.Add(item); - /// - /// Create a subGroup beneath the ParentGroup - /// (Or beneath altOverrideBounds but add the renderitems to the parentGroup) This is strange and due to legacy - /// - /// Some RenderItem type - /// Class name of the subgroup for easier debugging - /// The RenderItems to place within the group - /// The parent group of this item - public void AddSubGroupingOfRenderItems(string subGroupName, List Items) where T : RenderItem - { - if (Items != null) + if (item is BoundingBox bb) { - //Create subGroup - var subGroup = new GroupRenderItem(this.Bounds); - subGroup.Bounds.Name = subGroupName; - - //Add items to subGroup - foreach (var renderItem in Items) - { - subGroup.RenderItems.Add(renderItem); - } - - //Add subGroup to parent group - this.RenderItems.Add(subGroup); + Width = bb.Right > Width ? bb.Right : Width; + Height = bb.Bottom > Height ? bb.Bottom : Height; } } + ///// + ///// Create a subGroup beneath the ParentGroup + ///// (Or beneath altOverrideBounds but add the renderitems to the parentGroup) This is strange and due to legacy + ///// + ///// Some RenderItem type + ///// Class name of the subgroup for easier debugging + ///// The RenderItems to place within the group + ///// The parent group of this item + //public void AddSubGroupingOfRenderItems(string subGroupName, List Items) where T : RenderItem + //{ + // if (Items != null) + // { + // //Create subGroup + // var subGroup = new GroupRenderItem(this); + // subGroup.Name = subGroupName; + + // //Add items to subGroup + // foreach (var renderItem in Items) + // { + // subGroup.ChildObjects.Add(renderItem); + // } + + // //Add subGroup to parent group + // this.ChildObjects.Add(subGroup); + // } + //} + public override RenderItem Clone() { - var item = new GroupRenderItem(Bounds) + var item = new GroupRenderItem(this) { Rotation = Rotation, TextAnchor = TextAnchor, TransformOrigin = TransformOrigin, - GroupTransform = GroupTransform, + // GroupTransform = GroupTransform, RotationPoint = RotationPoint, - Scale = Scale, + GroupScale = GroupScale, }; CloneBase(item); - foreach(var child in RenderItems) + foreach(var child in ChildObjects) { - item.RenderItems.Add(child.Clone()); + item.ChildObjects.Add(((RenderItem)child).Clone()); } return item; } @@ -289,14 +239,14 @@ public override RenderItem Clone() public class PathRenderItem : RenderItem { public override RenderItemType Type => RenderItemType.Path; - public PathRenderItem(BoundingBox parent) : base(parent) + public PathRenderItem(Transform parent) : base(parent) { } - public List Commands { get; } = new List(); + public List Commands { get; } = new List(); public override RenderItem Clone() { - var clone = new PathRenderItem(Bounds); + var clone = new PathRenderItem(Parent); CloneBase(clone); foreach(var cmd in Commands) { @@ -307,7 +257,7 @@ public override RenderItem Clone() } public class EllipseRenderItem : RenderItem { - public EllipseRenderItem(BoundingBox parent) : base(parent) + public EllipseRenderItem(Transform parent) : base(parent) { } @@ -318,7 +268,7 @@ public EllipseRenderItem(BoundingBox parent) : base(parent) public double Ry { get; set; } public override RenderItem Clone() { - var clone = new EllipseRenderItem((BoundingBox)Bounds.Parent) + var clone = new EllipseRenderItem(Parent) { Cx = Cx, Cy = Cy, @@ -331,7 +281,7 @@ public override RenderItem Clone() } public class LineRenderItem : RenderItem { - public LineRenderItem(BoundingBox parent) : base(parent) + public LineRenderItem(Transform parent) : base(parent) { } @@ -391,12 +341,12 @@ private void UpdateBounds() var sizeX = Math.Abs(X2 - X1); var sizeY = Math.Abs(Y2 - Y1); - Bounds.LocalPosition = new Vector2(px, py); - Bounds.Size = new Vector2(sizeX, sizeY); + LocalPosition = new Vector2(px, py); + Size = new Vector2(sizeX, sizeY); } public override RenderItem Clone() { - var clone = new LineRenderItem((BoundingBox)Bounds.Parent); + var clone = new LineRenderItem(Parent); CloneBase(clone); clone._x1 = X1; clone._y1 = Y1; @@ -409,27 +359,11 @@ public override RenderItem Clone() } public abstract class DrawingObject { - public virtual void AppendRenderItems(List renderItems) { } + public virtual void AppendRenderItems(List renderItems) { } } - public abstract class RenderItem : RenderItemBase + public class RenderItemStyle { - protected RenderItem() - { - } - protected RenderItem(BoundingBox parent) - { - Bounds.Parent = parent; - } - //internal abstract void GetBounds(out double il, out double it, out double ir, out double ib); - public virtual void GetBounds(out double il, out double it, out double ir, out double ib) - { - il = Bounds.Left; - it = Bounds.Top; - ir = Bounds.Right; - ib = Bounds.Bottom; - } public string DefId { get; set; } - //internal bool IsEndOfGroup { get; set; } = false; public string FillColor { get; set; } public string FilterName { get; set; } public RenderGradientFill GradientFill { get; set; } @@ -450,30 +384,9 @@ public virtual void GetBounds(out double il, out double it, out double ir, out d public PathFillMode FillColorSource { get; set; } = PathFillMode.Norm; public PathFillMode BorderColorSource { get; set; } = PathFillMode.Norm; public double? GlowRadius { get; set; } - public double? GlowOpacity{ get; set; } + public double? GlowOpacity { get; set; } public string GlowColor { get; set; } public RenderShadowEffect OuterShadowEffect { get; set; } - - /// - /// The origin point for any transform actions in svg. - /// Normally/Default 0,0 - /// - public Coordinate TransformOrigin { get; set; } = null; - - protected void CloneBase(RenderItem item) - { - item.FillColor = FillColor; - item.FillOpacity = FillOpacity; - item.BorderWidth = BorderWidth; - item.BorderColor = BorderColor; - item.BorderDashArray = BorderDashArray; - item.BorderDashOffset = BorderDashOffset; - item.BorderOpacity = BorderOpacity; - item.LineJoin = LineJoin; - item.LineCap = LineCap; - item.FillColorSource = FillColorSource; - } - public abstract RenderItem Clone(); internal void GetOuterShadowColor(out string shadowColor, out double opacity) { if (OuterShadowEffect == null) @@ -499,22 +412,45 @@ internal void GetOuterShadowColor(out string shadowColor, out double opacity) internal string GetFilterKey() { - return $"{GlowColor} {GlowRadius} { OuterShadowEffect?.GetKey()}"; + return $"{GlowColor} {GlowRadius} {OuterShadowEffect?.GetKey()}"; + } + + internal RenderItemStyle Clone() + { + var item = new RenderItemStyle(); + item.FillColor = FillColor; + item.FillOpacity = FillOpacity; + item.BorderWidth = BorderWidth; + item.BorderColor = BorderColor; + item.BorderDashArray = BorderDashArray; + item.BorderDashOffset = BorderDashOffset; + item.BorderOpacity = BorderOpacity; + item.LineJoin = LineJoin; + item.LineCap = LineCap; + item.FillColorSource = FillColorSource; + return item; } } - /// - /// Base class for any item rendered. - /// - public abstract class RenderItemBase + public abstract class RenderItem : BoundingBox { - public BoundingBox Bounds = new BoundingBox(); + protected RenderItem() + { + } + protected RenderItem(Transform parent) + { + Parent = parent; + } public abstract RenderItemType Type { get; } - public virtual void GetBounds(out double il, out double it, out double ir, out double ib) + public RenderItemStyle Style{ get; private set; } = new RenderItemStyle(); + /// + /// The origin point for any transform actions in svg. + /// Normally/Default 0,0 + /// + public Coordinate TransformOrigin { get; set; } = null; + protected void CloneBase(RenderItem item) { - il = Bounds.Left; - it = Bounds.Top; - ir = Bounds.Right; - ib = Bounds.Bottom; + item.Style = Style.Clone(); } + public abstract RenderItem Clone(); } } \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs index af92527cf8..d40cf02d93 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs @@ -1,11 +1,6 @@ using EPPlus.Export.ImageRenderer.RenderItems.Shared; using EPPlus.Graphics; using OfficeOpenXml.Interfaces.RichText; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace EPPlus.DrawingRenderer.RenderItems.SvgItem { diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs index 09e18a74f5..38fa4a8cd2 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs @@ -140,12 +140,12 @@ protected bool LinespacingIsExact protected ParagraphRenderItem(RenderContext renderContext, BoundingBox parent, bool setFallbackDefaultFont = true) : base(parent) { RenderContext = renderContext; - Bounds.Name = "Paragraph"; + Name = "Paragraph"; if (setFallbackDefaultFont) { var defaultFont = new MeasurementFont { FontFamily = "Aptos Narrow", Size = 11, Style = MeasurementFontStyles.Regular }; DefaultParagraphFont = new FontFormatBase(defaultFont); - FillColor = "black"; + Style.FillColor = "black"; } } @@ -153,7 +153,7 @@ protected ParagraphRenderItem(RenderContext renderContext, BoundingBox parent, R : this(renderContext, parent, setFallbackDefaultFont) { InitBasedOnParent(textBody); - Bounds.Name = "Paragraph"; + Name = "Paragraph"; } protected ParagraphRenderItem(RenderContext renderContext, BoundingBox parent, RenderTextBody textBody, string text, bool setFallbackDefaultFont = true) @@ -184,13 +184,13 @@ protected double GetAlignmentHorizontal(TextAlignment txAlignment) { case TextAlignment.Left: default: - x = Bounds.Left + LeftMargin; + x = Left + LeftMargin; break; case TextAlignment.Center: - x = (Bounds.Right / 2) + LeftMargin - RightMargin; + x = (Right / 2) + LeftMargin - RightMargin; break; case TextAlignment.Right: - x = Bounds.Right - RightMargin; + x = Right - RightMargin; break; } @@ -207,14 +207,14 @@ void InitBasedOnParent(RenderTextBody textBody) if (AutoSize == false) { - Bounds.Width = textBody.Width; - Bounds.Height = textBody.Height; + Width = textBody.Width; + Height = textBody.Height; } else { //Set to max until measured - Bounds.Width = ParentMaxWidth; - Bounds.Height = ParentMaxHeight; + Width = ParentMaxWidth; + Height = ParentMaxHeight; } } @@ -236,7 +236,7 @@ TextLineCollection WrapFragmentsToLines(List? fragments = nul } else { - maxWidthInPoints = Bounds.Width; + maxWidthInPoints = Width; } return _layoutSystem.Wrap(maxWidthInPoints); } @@ -314,7 +314,7 @@ protected void WrapTextFragmentsAndGenerateTextRuns() combinedHeight = Lines.GetHeightOfCollection(_lsMultiplier, lineSpacingResult); - Bounds.Width = widthOfLargestLine + RightMargin; + Width = widthOfLargestLine + RightMargin; //SetHorizontalAlignment(widthOfLargestLine); int lineIdx = 0; @@ -333,13 +333,13 @@ protected void WrapTextFragmentsAndGenerateTextRuns() rtIdx = _layoutSystem.InputFragments.IndexOf(lineFragment.OriginalTextFragment); } - var run = CreateTextRun(Bounds, displayText, rtIdx); + var run = CreateTextRun(this, displayText, rtIdx); //Potentially we could import styling here instead but that leads to multiple issues. //We may need to move it back here for auto-size reasons run.YPosition = Lines.GetBaseLinePosition(lineIdx, lineSpacingResult); - run.Bounds.Left = prevWidth; - run.Bounds.Width = lineFragment.Width; + run.Left = prevWidth; + run.Width = lineFragment.Width; prevWidth += lineFragment.Width; Runs.Add(run); @@ -347,7 +347,7 @@ protected void WrapTextFragmentsAndGenerateTextRuns() lineIdx++; } } - Bounds.Height = combinedHeight; + Height = combinedHeight; } protected double CalculatePrevWidthBasedOnAlignment(double lineDist) @@ -374,13 +374,13 @@ protected void SetHorizontalAlignment(double widthOfLargestLine) { //Bounds of the paragraph should be bounds of the text itself. //Therefore we must know the starting point to set accurate left and offset from left. - Bounds.Left = GetAlignmentHorizontal(HorizontalAlignment) - (widthOfLargestLine / 2); + Left = GetAlignmentHorizontal(HorizontalAlignment) - (widthOfLargestLine / 2); } else { //Bounds of the paragraph should be bounds of the text itself. //Therefore we must know the starting point to set accurate left and offset from left. - Bounds.Left = 0; + Left = 0; } } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs index 182917b2d3..7c8bbc1f37 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs @@ -36,22 +36,22 @@ public abstract class RenderTextBody : GroupRenderItem public RenderTextBody(RenderContext renderContext, BoundingBox parent, bool autoSize) { RenderContext = renderContext; - Bounds.Parent = parent; + Parent = parent; AutoSize = autoSize; MaxWidth = parent.Width; MaxHeight = parent.Height; - Bounds.Name = "Textbody"; + Name = "Textbody"; } public RenderTextBody(RenderContext renderContext, BoundingBox parent, double left, double top, double maxWidth, double maxHeight, bool clampedToParent = false, bool autoSize=false) : this(renderContext, parent, autoSize) { RenderContext = renderContext; - Bounds.Left = left; - Bounds.Top = top; - Bounds.Width = maxWidth; - Bounds.Height = maxHeight; + Left = left; + Top = top; + Width = maxWidth; + Height = maxHeight; MaxWidth = maxWidth; MaxHeight = maxHeight; - Bounds.Name = "Textbody"; + Name = "Textbody"; } protected RenderContext RenderContext { get; private set; } @@ -61,16 +61,6 @@ public RenderTextBody(RenderContext renderContext, BoundingBox parent, double le public string Text { get; set; } public double MaxWidth { get; set; } public double MaxHeight { get; set; } - /// - /// Shorthand for Bounds.Width - /// - public double Width { get { return Bounds.Width; } set { Bounds.Width = value; } } - - /// - /// Shorthand for Bounds.Height - /// - public double Height { get { return Bounds.Height; } set { Bounds.Height = value; } } - public bool AutoSize { get; set; } public double TopMargin { get; set; } public double BottomMargin { get; set; } @@ -79,16 +69,16 @@ public RenderTextBody(RenderContext renderContext, BoundingBox parent, double le public string FontColorString { get; set; } - public void AppendRenderItems(List renderItems) + public void AppendRenderItems(List renderItems) { //foreach(var item in Paragraphs) //{ // AddChildItem(item); //} //GroupRenderItem groupItem; - //if (Bounds.Parent.Rotation == 0) //If the parent is rotated, we should not apply rotation again. This is usually when the parent is a textbox. + //if (Parent.Rotation == 0) //If the parent is rotated, we should not apply rotation again. This is usually when the parent is a textbox. //{ - // groupItem = new GroupRenderItem(Bounds, Bounds.Rotation); + // groupItem = new GroupRenderItem(Bounds, Rotation); //} //else //{ @@ -104,8 +94,8 @@ public void AppendRenderItems(List renderItems) //Set bounds position to be translation //Posibly remove translationOffset and make it always be bounds? //But then we will have an inaccurate bounding box if a child object has negative position. - //TranslationOffset.Left = Bounds.Left; - //TranslationOffset.Top = Bounds.Top; + //TranslationOffset.Left = Left; + //TranslationOffset.Top = Top; renderItems.Add(this); @@ -119,14 +109,14 @@ public void AppendRenderItems(List renderItems) public ParagraphRenderItem AddParagraph(IRichTextFormatSimple rtFormat) { - var paragraph = CreateParagraph(Bounds, rtFormat); + var paragraph = CreateParagraph(this, rtFormat); AdjustAndAddParagraph(paragraph); return paragraph; } public ParagraphRenderItem AddParagraph(string text = null) { - var paragraph = CreateParagraph(Bounds, text); + var paragraph = CreateParagraph(this, text); AdjustAndAddParagraph(paragraph); return paragraph; } @@ -140,16 +130,16 @@ public void ApplyAutoSize() foreach(var paragraph in Paragraphs) { - currentHeight += paragraph.Bounds.Height; + currentHeight += paragraph.Height; - if (currentWidth < paragraph.Bounds.Width || currentWidth == MaxWidth) + if (currentWidth < paragraph.Width || currentWidth == MaxWidth) { - currentWidth = paragraph.Bounds.Width; + currentWidth = paragraph.Width; } } - Bounds.Width = currentWidth; - Bounds.Height = currentHeight; + Width = currentWidth; + Height = currentHeight; } } @@ -161,7 +151,7 @@ public void RecalculateParagraphs() { if(Paragraphs != null && Paragraphs.Count != 0) { - double lastParagraphBottom = Paragraphs[0].Bounds.Top; + double lastParagraphBottom = Paragraphs[0].Top; double smallestLeft = double.MaxValue; double largestWidth = double.MinValue; @@ -169,23 +159,23 @@ public void RecalculateParagraphs() foreach (var paragraph in Paragraphs) { - paragraph.Bounds.Top = lastParagraphBottom; - lastParagraphBottom = paragraph.Bounds.Bottom; + paragraph.Top = lastParagraphBottom; + lastParagraphBottom = paragraph.Bottom; - smallestLeft = Math.Min(smallestLeft, paragraph.Bounds.Left); - largestWidth = Math.Max(largestWidth, paragraph.Bounds.Width); - totalHeight += paragraph.Bounds.Height; + smallestLeft = Math.Min(smallestLeft, paragraph.Left); + largestWidth = Math.Max(largestWidth, paragraph.Width); + totalHeight += paragraph.Height; } - ContentBounds.Top = Paragraphs[0].Bounds.Top; + ContentBounds.Top = Paragraphs[0].Top; ContentBounds.Left = smallestLeft; ContentBounds.Width = largestWidth; ContentBounds.Height = totalHeight; if (AutoSize) { - Bounds.Height = totalHeight; - Bounds.Width = ContentBounds.Width; + Height = totalHeight; + Width = ContentBounds.Width; } } } @@ -197,23 +187,23 @@ public void RecalculateParagraphs() private void AdjustAndAddParagraph(ParagraphRenderItem paragraph) { - paragraph.Bounds.Name = $"Container{Paragraphs.Count}"; - paragraph.Bounds.Top = GetTopToAddNextParagraphAt(); + paragraph.Name = $"Container{Paragraphs.Count}"; + paragraph.Top = GetTopToAddNextParagraphAt(); if (AutoSize) { if (Paragraphs.Count == 0) { - Bounds.Height = paragraph.Bounds.Height; + Height = paragraph.Height; } else { - Bounds.Height += paragraph.Bounds.Height; + Height += paragraph.Height; } - if (Bounds.Width < paragraph.Bounds.Width || (Bounds.Width == MaxWidth && Paragraphs.Count == 0)) + if (Width < paragraph.Width || (Width == MaxWidth && Paragraphs.Count == 0)) { - Bounds.Width = paragraph.Bounds.Width; + Width = paragraph.Width; } } Paragraphs.Add(paragraph); @@ -226,7 +216,7 @@ private double GetTopToAddNextParagraphAt() if (Paragraphs.Count != 0) { - paragraphTop = Paragraphs.Last().Bounds.Bottom; + paragraphTop = Paragraphs.Last().Bottom; } return paragraphTop; } @@ -243,18 +233,18 @@ public double GetAlignmentVertical() switch (VerticalAlignment) { case TextAnchoringType.Top: - alignmentY = Bounds.Top; + alignmentY = Top; break; //Center means center of a Shape's ENTIRE bounding box height. //Not center of the Inset GetRectangle case TextAnchoringType.Center: if(AutoSize == false) { - alignmentY = (Bounds.Height - ContentBounds.Height) / 2d; + alignmentY = (Height - ContentBounds.Height) / 2d; } break; case TextAnchoringType.Bottom: - alignmentY = Bounds.Height - ContentBounds.Height; + alignmentY = Height - ContentBounds.Height; break; } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs index 1e1fa4dd36..baf7755d0e 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -16,8 +16,10 @@ public void Init(BoundingBox parent, double maxWidth, double maxHeight) { Parent = parent; _group = new GroupRenderItem(Parent); - _rectangle = new RectRenderItem(_group.Bounds); - _marginGroup = new GroupRenderItem(_group.Bounds); + _group.Name = "Textbox_Group"; + _rectangle = new RectRenderItem(_group); + _rectangle.Name = "Textbox_BG"; + _marginGroup = new GroupRenderItem(_group); //TextBody = new RenderTextBody(Rectangle.Bounds, true); //TextBody.MaxWidth = maxWidth; //TextBody.MaxHeight = maxHeight; @@ -30,8 +32,8 @@ public RenderTextbox(BoundingBox parent, double maxWidth, double maxHeight) public string Name { - get { return _group.Bounds.Name; } - set { _group.Bounds.Name = value; } + get { return _group.Name; } + set { _group.Name = value; } } //The origin point of the entire textbox itself (its outermost left and top point) @@ -45,8 +47,8 @@ public RectRenderItem Rectangle { get { - _rectangle.Bounds.Width = Width; - _rectangle.Bounds.Height = Height; + _rectangle.Width = Width; + _rectangle.Height = Height; return _rectangle; } set @@ -63,37 +65,37 @@ public virtual RenderTextBody TextBody set { _textBody = value; //Margins should affect textbody global position in real-time - _textBody.Bounds.Parent = _marginGroup.Bounds; + _textBody.Parent = _marginGroup; } } public double Left { get { - return _group.Bounds.Left; + return _group.Left; } set { - _group.Bounds.Left = value; + _group.Left = value; } } public double Top { get { - return _group.Bounds.Top; + return _group.Top; } set { - _group.Bounds.Top = value; + _group.Top = value; } } public double Width { get { - return LeftMargin + (TextBody?.Bounds?.Width ?? 0D) + RightMargin + TextBody.Left; + return LeftMargin + (TextBody?.Width ?? 0D) + RightMargin + TextBody.Left; } } public double WidthRotated @@ -110,7 +112,7 @@ public double Height { get { - return TopMargin + (TextBody?.Bounds.Height ?? 0d) + BottomMargin + TextBody.Top; + return TopMargin + (TextBody?.Height ?? 0d) + BottomMargin + TextBody.Top; } } public double HeightWithRotation @@ -188,51 +190,48 @@ public double GetActualBottom() { return Top + GetActualHeight(); } - public override void AppendRenderItems(List renderItems) - { - var rect = Rectangle; + public void PrepareForRender() + { //As the rect item is inside the group, we set the left and right to the group and top and left on the rect to 0. - _group.Bounds.Left = Left; - _group.Bounds.Top = Top; - _group.Bounds.Width = Width; - _group.Bounds.Height = Height; + _group.Left = Left; + _group.Top = Top; + _group.Width = Width; + _group.Height = Height; _group.TextAnchor = TextAnchor.ToEnumString(); - renderItems.Add(_group); - rect.Top = 0; - rect.Left = 0; + Rectangle.Top = 0; + Rectangle.Left = 0; if (TextBody.AutoSize) { TextBody.ApplyAutoSize(); } - rect.Width = Width; - rect.Height = Height; + Rectangle.Width = Width; + Rectangle.Height = Height; var titleItem = new TitleRenderItem("TextBox group"); - _group.RenderItems.Add(titleItem); + _group.ChildObjects.Add(titleItem); //The rect shound encapse the text element, so we need to set the left depending on the text anchor. if (TextAnchor == eTextAnchor.Middle) { - _group.Bounds.Left += -(rect.Bounds.Width / 2); + _group.Left += -(Rectangle.Width / 2); } else if (TextAnchor == eTextAnchor.End) { if (Math.Abs(Rotation) == 45) { const double COS45 = 0.70710678118654757; //Constant for Math.Sin(Math.PI / 4) --45 degrees - _group.Bounds.Left += -(rect.Bounds.Width * COS45); - _group.Bounds.Top += (rect.Bounds.Width * COS45); + _group.Left += -(Rectangle.Width * COS45); + _group.Top += (Rectangle.Width * COS45); } else { - _group.Bounds.Left += rect.Bounds.Height / 2; - _group.Bounds.Top += (rect.Bounds.Width); + _group.Left += Rectangle.Height / 2; + _group.Top += (Rectangle.Width); } } - _group.RenderItems.Add(rect); //The textbox should be in local-space. //If e.g. a user changes textbody left and right, changing margin on the parent should not change the Local coordinates @@ -242,9 +241,66 @@ public override void AppendRenderItems(List renderItems) var marginTitleItem = new TitleRenderItem("TextBox Margin Group"); _marginGroup.AddChildItem(marginTitleItem); + } + public override void AppendRenderItems(List renderItems) + { + PrepareForRender(); + renderItems.Add(_group); + //var rect = Rectangle; + + ////As the rect item is inside the group, we set the left and right to the group and top and left on the rect to 0. + //_group.Left = Left; + //_group.Top = Top; + //_group.Width = Width; + //_group.Height = Height; + + //_group.TextAnchor = TextAnchor.ToEnumString(); + //renderItems.Add(_group); + //rect.Top = 0; + //rect.Left = 0; + + //if (TextBody.AutoSize) + //{ + // TextBody.ApplyAutoSize(); + //} + + //rect.Width = Width; + //rect.Height = Height; + + //var titleItem = new TitleRenderItem("TextBox group"); + //_group.ChildObjects.Add(titleItem); + ////The rect shound encapse the text element, so we need to set the left depending on the text anchor. + //if (TextAnchor == eTextAnchor.Middle) + //{ + // _group.Left += -(rect.Width / 2); + //} + //else if (TextAnchor == eTextAnchor.End) + //{ + // if (Math.Abs(Rotation) == 45) + // { + // const double COS45 = 0.70710678118654757; //Constant for Math.Sin(Math.PI / 4) --45 degrees + // _group.Left += -(rect.Width * COS45); + // _group.Top += (rect.Width * COS45); + // } + // else + // { + // _group.Left += rect.Height / 2; + // _group.Top += (rect.Width); + // } + //} + //_group.ChildObjects.Add(rect); + + ////The textbox should be in local-space. + ////If e.g. a user changes textbody left and right, changing margin on the parent should not change the Local coordinates + ////Therefore a group in-between should hold the margins + //_marginGroup.Left = LeftMargin; + //_marginGroup.Top = TopMargin; + + //var marginTitleItem = new TitleRenderItem("TextBox Margin Group"); + //_marginGroup.AddChildItem(marginTitleItem); - _group.AddChildItem(_marginGroup); - TextBody.AppendRenderItems(_marginGroup.RenderItems); + //_group.AddChildItem(_marginGroup); + //TextBody.AppendRenderItems(_marginGroup.ChildObjects); } /// /// How the text is anchored. diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs index 0421f49cfd..6916f09f64 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs @@ -135,12 +135,12 @@ public abstract class TextRunRenderItem : RenderItem public double ClippingHeight { get; protected set; } = double.NaN; public TextRunRenderItem(BoundingBox parent) : base(parent) { - Bounds.Name = "TextRun"; + Name = "TextRun"; } public TextRunRenderItem(BoundingBox parent, string text, int origRtIdx) : base(parent) { - Bounds.Name = "TextRun"; + Name = "TextRun"; _currentText = text; OriginalRtIdx = origRtIdx; } @@ -153,7 +153,7 @@ public void ImportFontData(IFontFormatBase font) public void ImportRichTextData(IRichTextFormatSimple rt) { InitializeBase(rt); - FillColor = "#" + rt.FontColor.To6CharHexStringImage(); + Style.FillColor = "#" + rt.FontColor.To6CharHexStringImage(); _underLineType = (int)rt.UnderlineType == -1 ? eDrawingUnderLineType.None : (eDrawingUnderLineType)rt.UnderlineType; _underlineColor = rt.UnderlineColor; } @@ -161,7 +161,7 @@ public void ImportRichTextData(IRichTextFormatSimple rt) public void ImportRichTextData(IRichTextFormatDrawing rt) { InitializeBase(rt); - FillColor = "#" + rt.FontColor.To6CharHexStringImage(); + Style.FillColor = "#" + rt.FontColor.To6CharHexStringImage(); _baseline = rt.Baseline; _strikeType = (int)rt.StrikeType == -1 ? eDrawingStrikeType.No : rt.StrikeType; _underLineType = (int)rt.UnderlineType == -1 ? eDrawingUnderLineType.None : rt.UnderlineType; @@ -170,7 +170,7 @@ public void ImportRichTextData(IRichTextFormatDrawing rt) internal protected void InitializeBase(IFontFormatBase font) { //Should be ascent-only? - Bounds.Height = font.Size; + Height = font.Size; FontSizeInPixels = ((double)font.Size).PointToPixel(true); //Copy, as callers pass shared instances (e.g. the paragraph default font) and the //run adjusts its own size for sub- and superscript. @@ -187,7 +187,7 @@ internal protected void InitializeBase(IFontFormatBase font) /// private TextRunRenderItem(BoundingBox parent, string origText, string currentText, IFontFormatBase font) : base(parent) { - Bounds.Name = "TextRun"; + Name = "TextRun"; //possibly no longer neccesary _originalText = origText; @@ -195,7 +195,7 @@ private TextRunRenderItem(BoundingBox parent, string origText, string currentTex _currentText = currentText; //Should be ascent-only? - Bounds.Height = font.Size; + Height = font.Size; //Possibly no longer neccesary Lines = Regex.Split(_currentText, "\r\n|\r|\n").ToList(); @@ -220,9 +220,9 @@ public TextRunRenderItem(BoundingBox parent, string text, IFontFormatBase font, internal protected void CalculateClippingHeightFromTextBodyParent() { //To get clipping height we need to get the textbody bounds - if (Bounds.Parent != null && Bounds.Parent.Parent != null && Bounds.Parent.Parent.Parent != null) + if (Parent != null && Parent.Parent != null && Parent.Parent.Parent != null) { - ClippingHeight = Bounds.Parent.Parent.Parent.Position.Y + Bounds.Parent.Parent.Parent.Size.Y; + ClippingHeight = Parent.Parent.Parent.Position.Y + Parent.Parent.Parent.Size.Y; } } } diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/Interfaces/IShapeRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/Interfaces/IShapeRenderer.cs index c80957f05b..019df6749c 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/Interfaces/IShapeRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/Interfaces/IShapeRenderer.cs @@ -12,9 +12,9 @@ public interface IShapeRenderer { IBasicIShapesRenderer BasicShapesRenderer { get; } T OutputStream { get; } - bool PreRender(List items); + bool PreRender(List items); BoundingBox Bounds { get; } string ViewBox { get; set; } - bool Render(List items); + bool Render(List items); } } diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs index 558f6057a4..215f866d58 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs @@ -19,49 +19,50 @@ protected SvgBaseRenderer(StringBuilder outputStream) : base(outputStream) /// protected void RenderBaseToSpecified(T item, StringBuilder sb) { - if (item.Bounds.Name != null) + var style = item.Style; + if (item.Name != null) { - sb.Append($" class=\"{item.Bounds.Name}\" "); + sb.Append($" class=\"{item.Name}\" "); } - if (string.IsNullOrEmpty(item.DefId) == false) + if (string.IsNullOrEmpty(style.DefId) == false) { - sb.Append($"id=\"{item.DefId}\" "); + sb.Append($"id=\"{style.DefId}\" "); } - if (string.IsNullOrEmpty(item.FillColor) == false) + if (string.IsNullOrEmpty(style.FillColor) == false) { - sb.Append($"fill=\"{item.FillColor}\" "); + sb.Append($"fill=\"{style.FillColor}\" "); } //If fill is null it may in e.g. Rect still get the color black which can have an opacity - if (item.FillOpacity != null && item.FillOpacity != 1) + if (style.FillOpacity != null && style.FillOpacity != 1) { - sb.Append($"opacity=\"{item.FillOpacity.Value.ToString(CultureInfo.InvariantCulture)}\" "); + sb.Append($"opacity=\"{style.FillOpacity.Value.ToString(CultureInfo.InvariantCulture)}\" "); } - if (string.IsNullOrEmpty(item.FilterName) == false) + if (string.IsNullOrEmpty(style.FilterName) == false) { - sb.Append($"filter=\"{item.FilterName}\" "); + sb.Append($"filter=\"{style.FilterName}\" "); } - if (item.BorderWidth.HasValue) + if (style.BorderWidth.HasValue) { - if (string.IsNullOrEmpty(item.BorderColor) == false) + if (string.IsNullOrEmpty(style.BorderColor) == false) { - sb.Append($"stroke=\"{item.BorderColor}\" "); + sb.Append($"stroke=\"{style.BorderColor}\" "); } - var v = item.BorderWidth.Value * Constants.EMU_PER_POINT / Constants.EMU_PER_PIXEL; + var v = style.BorderWidth.Value * Constants.EMU_PER_POINT / Constants.EMU_PER_PIXEL; sb.Append($"stroke-width=\"{v.ToString(CultureInfo.InvariantCulture)}\" "); - if (item.BorderDashArray != null) + if (style.BorderDashArray != null) { - var BorderDashArrayStr = item.BorderDashArray.Select(x => + var BorderDashArrayStr = style.BorderDashArray.Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray(); sb.Append($"stroke-dasharray=\"" + $"{string.Join(",", BorderDashArrayStr)}\" "); } - if (item.BorderOpacity.HasValue) + if (style.BorderOpacity.HasValue) { - sb.Append($" stroke-opacity=\"{(Math.Round(item.BorderOpacity.Value * 100)).ToString(CultureInfo.InvariantCulture)}%\" "); + sb.Append($" stroke-opacity=\"{(Math.Round(style.BorderOpacity.Value * 100)).ToString(CultureInfo.InvariantCulture)}%\" "); } } @@ -70,9 +71,9 @@ protected void RenderBaseToSpecified(T item, StringBuilder sb) sb.Append($" transform-origin=\"{item.TransformOrigin.X.ToString(CultureInfo.InvariantCulture)} {item.TransformOrigin.Y.ToString(CultureInfo.InvariantCulture)}\" "); } - if (item.StrokeMiterLimit.HasValue) + if (style.StrokeMiterLimit.HasValue) { - sb.Append($"stroke-miterlimit =\"{item.StrokeMiterLimit}\" "); + sb.Append($"stroke-miterlimit =\"{style.StrokeMiterLimit}\" "); } } @@ -83,24 +84,25 @@ protected void RenderBase(T item) } protected void RenderCompoundItems(T li, double? borderWidth, string color, string filter) { - var tmpBorderWidth = li.BorderWidth; + var style = li.Style; + var tmpBorderWidth = style.BorderWidth; string tmpBorderColor = null; - li.BorderWidth = borderWidth ?? li.BorderWidth; + style.BorderWidth = borderWidth ?? style.BorderWidth; if (string.IsNullOrEmpty(color) == false) { - tmpBorderColor = li.BorderColor; - li.BorderColor = color; + tmpBorderColor = style.BorderColor; + style.BorderColor = color; } RenderBase(li); var sb = OutputStream; - if (li.LineCap != LineCap.Flat) + if (style.LineCap != LineCap.Flat) { - sb.AppendFormat(" stroke-linecap=\"{0}\"", li.LineCap == LineCap.Round ? "round" : "square"); + sb.AppendFormat(" stroke-linecap=\"{0}\"", style.LineCap == LineCap.Round ? "round" : "square"); } - if (li.LineJoin != LineJoin.Miter) + if (style.LineJoin != LineJoin.Miter) { - sb.AppendFormat(" stroke-linejoin=\"{0}\"", li.LineJoin.ToEnumString()); + sb.AppendFormat(" stroke-linejoin=\"{0}\"", style.LineJoin.ToEnumString()); } if (string.IsNullOrEmpty(filter) == false) @@ -110,12 +112,11 @@ protected void RenderCompoundItems(T li, double? borderWidth, string color, stri sb.AppendFormat("/>"); - li.BorderWidth = tmpBorderWidth; + style.BorderWidth = tmpBorderWidth; if (string.IsNullOrEmpty(color) == false) { - li.BorderColor = tmpBorderColor; + style.BorderColor = tmpBorderColor; } } - } } diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs index b817175020..f87c9a6cd6 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs @@ -10,8 +10,6 @@ using System.Text; using OfficeOpenXml.Utils; using EPPlus.Fonts.OpenType.Utils; -using System.Security.Cryptography.Xml; -using System.ComponentModel; namespace EPPlus.DrawingRenderer { @@ -41,14 +39,15 @@ public SvgShapeRenderer(BoundingBox bounds, StringBuilder outputStream, SvgRende public BoundingBox Bounds { get; } public string ViewBox { get; set; } - public bool Render(List items) + public bool Render(List items) { OutputStream.Clear(); OutputStream.Append($""); PreRender(items); foreach (var item in items) { - switch (item.Type) + if(item is RenderItem renderItem) + switch (renderItem.Type) { case RenderItemType.Group: BasicShapesRenderer.GroupRenderer.Render((GroupRenderItem)item); @@ -83,36 +82,42 @@ private string GetViewBoxAttr() return $" viewbox=\"{ViewBox}\""; } - public void PreRenderGroup(List items, StringBuilder defSb, HashSet hs, ref int ix) + public void PreRenderGroup(List items, StringBuilder defSb, HashSet hs, ref int ix) { - foreach (RenderItem item in items) + foreach (var t in items) { - if (item is GroupRenderItem group) + if (t is RenderItem item) { - PreRenderGroup(group.RenderItems, defSb, hs, ref ix); - //foreach(var child in group.RenderItems) - //{ - // WriteDefsForRenderItem(defSb, hs, ref ix, child); - //} - } - else - { - WriteDefsForRenderItem(defSb, hs, ref ix, item); + if (item is GroupRenderItem group) + { + PreRenderGroup(group.ChildObjects, defSb, hs, ref ix); + //foreach(var child in group.RenderItems) + //{ + // WriteDefsForRenderItem(defSb, hs, ref ix, child); + //} + } + else + { + WriteDefsForRenderItem(defSb, hs, ref ix, item); + } } } } - public bool PreRender(List items) + public bool PreRender(List items) { var defSb = new StringBuilder(); var hs = new HashSet(); var ix = 1; - foreach (RenderItem item in items) - { - PreRenderGroup(items, defSb, hs, ref ix); - } + //foreach (var item in items) + //{ + // if (item is RenderItem) + // { + PreRenderGroup(items, defSb, hs, ref ix); + // } + //} if (defSb.Length > 0) { @@ -129,66 +134,67 @@ public bool PreRender(List items) private void WriteDefsForRenderItem(StringBuilder defSb, HashSet hs, ref int ix, RenderItem item) { string filter = ""; - if (item.GradientFill != null) + var style = item.Style; + if (style.GradientFill != null) { - var key = item.GradientFill.GetKey(); + var key = style.GradientFill.GetKey(); if (_defsCache.TryGetValue(key, out string? name) == false) { - name = WriteGradient($"Gradient{ix}", defSb, hs, item, item.GradientFill, item.FillColorSource); + name = WriteGradient($"Gradient{ix}", defSb, hs, item, style.GradientFill, style.FillColorSource); _defsCache[key] = name; } - item.FillColor = $"Url(#{name})"; + style.FillColor = $"Url(#{name})"; } - else if (item.PatternFill != null) + else if (style.PatternFill != null) { - var key = item.PatternFill.GetKey(); + var key = style.PatternFill.GetKey(); if (_defsCache.TryGetValue(key, out string? name) == false) { - name = WritePattern($"Pattern{item.PatternFill.PatternType}{ix}", defSb, hs, item.PatternFill, item.FillColorSource); + name = WritePattern($"Pattern{style.PatternFill.PatternType}{ix}", defSb, hs, style.PatternFill, style.FillColorSource); _defsCache[key] = name; } - item.FillColor = $"Url(#{name})"; + style.FillColor = $"Url(#{name})"; } - else if (item.BlipFill != null) + else if (style.BlipFill != null) { - var key = item.BlipFill.GetKey(); + var key = style.BlipFill.GetKey(); if (_defsCache.TryGetValue(key, out string? name) == false) { - if (item.FillColorSource != PathFillMode.Norm) + if (style.FillColorSource != PathFillMode.Norm) { - item.FilterName = GetFilterName(ix); + style.FilterName = GetFilterName(ix); } name = WriteBlip("Blip", defSb, hs, item, ref filter); _defsCache[key] = name; } - item.FillColor = $"Url(#{name})"; + style.FillColor = $"Url(#{name})"; } - if (item.BorderGradientFill != null) + if (style.BorderGradientFill != null) { - var key = item.BorderGradientFill.GetKey() + item.Bounds.UniqueKey; + var key = style.BorderGradientFill.GetKey() + item.UniqueKey; if (_defsCache.TryGetValue(key, out string? name) == false) { - name = WriteGradient($"StrokeGradient{ix}", defSb, hs, item, item.BorderGradientFill, item.BorderColorSource); + name = WriteGradient($"StrokeGradient{ix}", defSb, hs, item, style.BorderGradientFill, style.BorderColorSource); _defsCache[key] = name; } - item.BorderColor = $"Url(#{name})"; + style.BorderColor = $"Url(#{name})"; } - if (item.GlowColor != null) + if (style.GlowColor != null) { - var key = item.GetFilterKey(); + var key = style.GetFilterKey(); if (_defsCache.TryGetValue(key, out string? name) == false) { - if (string.IsNullOrEmpty(item.FilterName)) + if (string.IsNullOrEmpty(style.FilterName)) { name = GetFilterName(ix); - item.FilterName = $"Url(#{name})"; + style.FilterName = $"Url(#{name})"; filter = $""; - double stdev = (item.GlowRadius??0) / 4D; + double stdev = (style.GlowRadius??0) / 4D; filter += $"" + $"" + - $"" + + $"" + $"" + $""; @@ -197,32 +203,32 @@ private void WriteDefsForRenderItem(StringBuilder defSb, HashSet hs, ref } else { - item.FilterName = $"Url(#{name})"; + style.FilterName = $"Url(#{name})"; } } - if (item.OuterShadowEffect != null) + if (style.OuterShadowEffect != null) { - if (string.IsNullOrEmpty(item.FilterName)) + if (string.IsNullOrEmpty(style.FilterName)) { - var key = item.GetFilterKey(); + var key = style.GetFilterKey(); if (_defsCache.TryGetValue(key, out string? name) == false) { var filterName = GetFilterName(ix); - item.FilterName = $"Url(#{filterName})"; + style.FilterName = $"Url(#{filterName})"; filter = $""; _defsCache[key] = filterName; } else { - item.FilterName = $"Url(#{name})"; + style.FilterName = $"Url(#{name})"; } } if (string.IsNullOrEmpty(filter) == false) { - item.GetOuterShadowColor(out string shadowColor, out double opacity); - var dx = Math.Round(item.OuterShadowEffect.Distance * Math.Cos(MathHelper.Radians(item.OuterShadowEffect.Direction ?? 0D)), 2); - var dy = Math.Round(item.OuterShadowEffect.Distance * Math.Sin(MathHelper.Radians(item.OuterShadowEffect.Direction ?? 0D)), 2); - var blurRadius = (item.OuterShadowEffect.BlurRadius ?? 0D) / 2; + style.GetOuterShadowColor(out string shadowColor, out double opacity); + var dx = Math.Round(style.OuterShadowEffect.Distance * Math.Cos(MathHelper.Radians(style.OuterShadowEffect.Direction ?? 0D)), 2); + var dy = Math.Round(style.OuterShadowEffect.Distance * Math.Sin(MathHelper.Radians(style.OuterShadowEffect.Direction ?? 0D)), 2); + var blurRadius = (style.OuterShadowEffect.BlurRadius ?? 0D) / 2; filter += $""; } } @@ -240,37 +246,37 @@ private static string GetFilterName(int ix) private string WriteBlip(string namePrefix, StringBuilder defSb, HashSet hs, RenderItem item, ref string filter) { - //, item.BlipFill, item.FillColorSource + var style = item.Style; var name = $"{namePrefix}"; - var fillMode = item.FillColorSource; + var fillMode = style.FillColorSource; if (fillMode != PathFillMode.Norm) { - if (hs.Contains(item.FilterName) == false) + if (hs.Contains(style.FilterName) == false) { switch (fillMode) { case PathFillMode.Lighten: - filter = $""; + filter = $""; break; case PathFillMode.LightenLess: - filter = $""; + filter = $""; break; case PathFillMode.DarkenLess: - filter = $""; + filter = $""; break; case PathFillMode.Darken: - filter = $""; + filter = $""; break; } } - hs.Add(item.FilterName); + hs.Add(style.FilterName); } if (hs.Contains(name)) return name; hs.Add(name); - defSb.Append($""); - defSb.Append($""); + defSb.Append($""); + defSb.Append($""); defSb.Append($""); return name; } @@ -600,20 +606,20 @@ private string GetXy(RenderItem item, UserSpaceSettings userSpace, double? angle switch (userSpace) { case UserSpaceSettings.UserSpaceOnUse_Parent: - l = item.Bounds.Left; - t = item.Bounds.Top; + l = item.Left; + t = item.Top; break; case UserSpaceSettings.UserSpaceOnUse_Global: - l = item.Bounds.Left; - t = item.Bounds.Top; + l = item.Left; + t = item.Top; break; default: l = t = 0; break; } - var w = item.Bounds.Width; - var h = item.Bounds.Height; + var w = item.Width; + var h = item.Height; double dx = Math.Cos(theta); double dy = Math.Sin(theta); @@ -628,11 +634,11 @@ private string GetXy(RenderItem item, UserSpaceSettings userSpace, double? angle double x1 = cx - halfX, y1 = cy - halfY; double x2 = cx + halfX, y2 = cy + halfY; - if(item.DefId == "xGridLine") + if(item.Style.DefId == "xGridLine") { //If global, has to stretch the whole length. //This case is special because it stretches in the same direction as the attempted gradient - x1 = item.Bounds.Left; + x1 = item.Left; x2 = w - x1; } diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 8f9fe0170d..447f95def9 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -24,16 +24,24 @@ public override void Render(GroupRenderItem item) string fillPropery = ""; //We may need special handling for fill-color 'none' as it is sometimes transparent and sometimes un-applied. - if (string.IsNullOrEmpty(item.FillColor) == false) + if (string.IsNullOrEmpty(item.Style.FillColor) == false) { - fillPropery = $" fill=\"{item.FillColor}\" "; + fillPropery = $" fill=\"{item.Style.FillColor}\" "; } - OutputStream.Append($""); + OutputStream.Append($""); - foreach (var childItem in item.RenderItems) + foreach (var childItem in item.ChildObjects) { - _shapeRenderer.Render(childItem); + if (childItem is RenderItem renderItem) + { + _shapeRenderer.Render(renderItem); + } + else + { + //Is not render item but its children could contain renderItems + //Handle it as if it was a group? + } } OutputStream.Append(""); @@ -50,7 +58,7 @@ string GetCombinedTransformString(GroupRenderItem item) // positionStr = string.Format(transformTranslate, item.TranslationOffset.Left.PointToPixelString(), item.TranslationOffset.Top.PointToPixelString()) + " "; //} - positionStr = string.Format("translate({0}, {1})", item.Bounds.Left.PointToPixelString(), item.Bounds.Top.PointToPixelString()) + " "; + positionStr = string.Format("translate({0}, {1})", item.Left.PointToPixelString(), item.Top.PointToPixelString()) + " "; return positionStr + rotationStr + scalingStr; } @@ -70,9 +78,9 @@ string GetScalingStr(GroupRenderItem item) { var scaleStr = string.Empty; - if (item.Scale != null) + if (item.GroupScale != null) { - scaleStr = string.Format(transformScale, item.Scale.X.ToString(CultureInfo.InvariantCulture), item.Scale.Y.ToString(CultureInfo.InvariantCulture)) + " "; + scaleStr = string.Format(transformScale, item.GroupScale.X.ToString(CultureInfo.InvariantCulture), item.GroupScale.Y.ToString(CultureInfo.InvariantCulture)) + " "; } return scaleStr; @@ -80,9 +88,20 @@ string GetScalingStr(GroupRenderItem item) string GetRotationStr(GroupRenderItem item) { - if (double.IsNaN(item.Rotation) == false && item.Rotation!=0) + if(double.IsNaN(item.GroupRotation) == false && item.GroupRotation!= 0) + { + string rot = item.GroupRotation.ToString(CultureInfo.InvariantCulture); + + if (item.RotationPoint != null && item.RotationPoint != item.TranslationOffset) + { + rot += $", {item.RotationPoint.Left.PointToPixelString()}, {item.RotationPoint.Top.PointToPixelString()}" + " "; + } + + return string.Format(transformRotate, rot); + } + else if (double.IsNaN(item.LocalRotation) == false && item.LocalRotation != 0) { - string rot = item.Rotation.ToString(CultureInfo.InvariantCulture); + string rot = item.LocalRotation.ToString(CultureInfo.InvariantCulture); if (item.RotationPoint != null && item.RotationPoint != item.TranslationOffset) { diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs index edca4f7704..c4acda910c 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs @@ -13,37 +13,38 @@ public SvgLineRenderer(StringBuilder outputStream) : base(outputStream) public override void Render(LineRenderItem item) { var li = (LineRenderItem)item; + var style = li.Style; StringBuilder sb = OutputStream; //Draw transparent lines to create the compond line effect, as SVG does not support compound lines natively - switch (li.CompoundLineStyle) + switch (style.CompoundLineStyle) { case CompoundLineStyle.Double: - li.LineCap = LineCap.Flat; + style.LineCap = LineCap.Flat; var name = $"double-stroke-{Guid.NewGuid().ToString()}"; sb.Append($""); - RenderLineItem(li, li.BorderWidth, "white", null); - RenderLineItem(li, li.BorderWidth * (3D / 7D), "black", null); - sb.Append($""); + RenderLineItem(li, style.BorderWidth, "white", null); + RenderLineItem(li, style.BorderWidth * (3D / 7D), "black", null); + sb.Append($""); break; case CompoundLineStyle.DoubleThickThin: - WriteThickThin(li, "double-thick-thin-stroke-{0}", (li.BorderWidth ?? 1D) * 1D / 7D); + WriteThickThin(li, "double-thick-thin-stroke-{0}", (style.BorderWidth ?? 1D) * 1D / 7D); break; case CompoundLineStyle.DoubleThinThick: - WriteThickThin(li, "double-thin-thick-stroke-{0}", ((li.BorderWidth ?? 1D) * 1D / 7D) * -1); + WriteThickThin(li, "double-thin-thick-stroke-{0}", ((style.BorderWidth ?? 1D) * 1D / 7D) * -1); break; case CompoundLineStyle.TripleThinThickThin: var guid = Guid.NewGuid().ToString(); - var gapOffset = 5 * li.BorderWidth.Value / 16; + var gapOffset = 5 * style.BorderWidth.Value / 16; name = $"triple-stroke-{guid}"; sb.Append($""); sb.Append($""); sb.Append($""); sb.Append($""); - RenderLineItem(li, li.BorderWidth, "white", null); - RenderLineItem(li, li.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-left-{guid})\""); - RenderLineItem(li, li.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-right-{guid})\""); - sb.Append($""); + RenderLineItem(li, style.BorderWidth, "white", null); + RenderLineItem(li, style.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-left-{guid})\""); + RenderLineItem(li, style.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-right-{guid})\""); + sb.Append($""); break; default: RenderLineItem(li, null, null, null); @@ -59,9 +60,9 @@ private void WriteThickThin(LineRenderItem li, string name, double gapOffset) sb.Append(""); sb.Append($""); sb.Append($""); - RenderLineItem(li, li.BorderWidth, "white", null); - RenderLineItem(li, li.BorderWidth * (1D / 4D), "black", $"filter=\"url(#{gapFilterName})\""); - sb.Append($""); + RenderLineItem(li, li.Style.BorderWidth, "white", null); + RenderLineItem(li, li.Style.BorderWidth * (1D / 4D), "black", $"filter=\"url(#{gapFilterName})\""); + sb.Append($""); } internal string Suffix = "px"; diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs index 0c3c9381da..a0ef177910 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs @@ -21,7 +21,7 @@ public override void Render(ParagraphRenderItem item) var sb = OutputStream; var fontSize = item.DefaultParagraphFont.Size.PointToPixel().ToString(CultureInfo.InvariantCulture); - sb.AppendLine($""); + sb.AppendLine($""); sb.AppendLine("paragraph "); diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs index 9fe9acde34..7b50155b05 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs @@ -16,7 +16,8 @@ public override void Render(PathRenderItem path) { StringBuilder sb = OutputStream; //Draw transparent lines to create the compond line effect, as SVG does not support compound lines natively - switch (path.CompoundLineStyle) + var style = path.Style; + switch (style.CompoundLineStyle) { case CompoundLineStyle.Single: RenderPathItem(path, null, null, null); @@ -25,28 +26,28 @@ public override void Render(PathRenderItem path) var name = $"double-stroke-{Guid.NewGuid().ToString()}"; sb.Append($""); - RenderPathItem(path, path.BorderWidth, "white", null); - RenderPathItem(path, path.BorderWidth * (3D / 7D), "black", null); - sb.Append($""); + RenderPathItem(path, style.BorderWidth, "white", null); + RenderPathItem(path, style.BorderWidth * (3D / 7D), "black", null); + sb.Append($""); break; case CompoundLineStyle.DoubleThickThin: - WriteThickThin(path, (path.BorderWidth ?? 1D) * 1D / 7D); + WriteThickThin(path, (style.BorderWidth ?? 1D) * 1D / 7D); break; case CompoundLineStyle.DoubleThinThick: - WriteThickThin(path, ((path.BorderWidth ?? 1D) * 1D / 7D) * -1); + WriteThickThin(path, ((style.BorderWidth ?? 1D) * 1D / 7D) * -1); break; case CompoundLineStyle.TripleThinThickThin: var guid = Guid.NewGuid().ToString(); - var gapOffset = 5 * (path.BorderWidth??1D) / 16; + var gapOffset = 5 * (style.BorderWidth??1D) / 16; name = $"triple-stroke-{guid}"; sb.Append($""); sb.Append($""); sb.Append($""); sb.Append($""); - RenderPathItem(path, path.BorderWidth, "white", null); - RenderPathItem(path, path.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-left-{guid})\""); - RenderPathItem(path, path.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-right-{guid})\""); - sb.Append($""); + RenderPathItem(path, style.BorderWidth, "white", null); + RenderPathItem(path, style.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-left-{guid})\""); + RenderPathItem(path, style.BorderWidth * (1D / 8D), "black", $"filter=\"url(#gap-right-{guid})\""); + sb.Append($""); break; } } @@ -61,7 +62,7 @@ private void RenderPathItem(PathRenderItem path, double? borderWidth, string col RenderCompoundItems(path, borderWidth, color, filter); } - private void RenderPathCommand(PathCommands pc) + private void RenderPathCommand(PathCommand pc) { OutputStream.Append(pc.Type.AsCommandChar()); for (int i = 0; i < pc.Coordinates.Length; i++) @@ -91,9 +92,9 @@ private void WriteThickThin(PathRenderItem path, double gapOffset) OutputStream.Append(""); OutputStream.Append($""); OutputStream.Append($""); - RenderPathItem(path, path.BorderWidth, "white", null); - RenderPathItem(path, path.BorderWidth * (1 / 4D), "black", $"filter=\"url(#{gapFilterName})\""); - OutputStream.Append($""); + RenderPathItem(path, path.Style.BorderWidth, "white", null); + RenderPathItem(path, path.Style.BorderWidth * (1 / 4D), "black", $"filter=\"url(#{gapFilterName})\""); + OutputStream.Append($""); } } } diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs index d1c9783ae8..8f67b207ef 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs @@ -103,9 +103,9 @@ public override void Render(TextRunRenderItem textRun) var sbStartidx = OutputStream.Length -1; string finalString = ""; - var xString = $"x =\"{(textRun.Bounds.Left.PointToPixelString())}\" "; + var xString = $"x =\"{(textRun.Left.PointToPixelString())}\" "; - var currentYEndPos = textRun.Bounds.Position.Y; // Global position Y + var currentYEndPos = textRun.Position.Y; // Global position Y finalString += $"() { 0.5d, 0.5d }); + + Assert.AreEqual(0, pie.Slices[0].StartDegrees); + Assert.AreEqual(180, pie.Slices[0].EndDegrees); + Assert.AreEqual(180, pie.Slices[0].SweepAngleInDegrees); + + Assert.AreEqual(180, pie.Slices[1].StartDegrees); + Assert.AreEqual(360, pie.Slices[1].EndDegrees); + Assert.AreEqual(180, pie.Slices[1].SweepAngleInDegrees); + + } + + + [TestMethod] + public void PieTransformGenerateDifferentSizeSlices() + { + PieTransform pie = new PieTransform(10d); + + pie.GenerateSlicesAndClearPrevious(new List() { 0.4d, 0.2d, 0.4d }); + + Assert.AreEqual(0, pie.Slices[0].StartDegrees); + Assert.AreEqual(144, pie.Slices[0].EndDegrees); + Assert.AreEqual(144, pie.Slices[0].SweepAngleInDegrees); + + Assert.AreEqual(144, pie.Slices[1].StartDegrees); + Assert.AreEqual(216, pie.Slices[1].EndDegrees); + Assert.AreEqual(72, pie.Slices[1].SweepAngleInDegrees); + + Assert.AreEqual(216, pie.Slices[2].StartDegrees); + Assert.AreEqual(360, pie.Slices[2].EndDegrees); + Assert.AreEqual(144, pie.Slices[2].SweepAngleInDegrees); + } + } +} diff --git a/src/EPPlus.Graphics/BoundingBox.cs b/src/EPPlus.Graphics/BoundingBox.cs index a90b4dede1..b3b7c3700c 100644 --- a/src/EPPlus.Graphics/BoundingBox.cs +++ b/src/EPPlus.Graphics/BoundingBox.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; namespace EPPlus.Graphics { @@ -22,46 +23,22 @@ public BoundingBox(double left, double top, double width, double height) : base( /// /// Y pos (min) /// - public double Top + public virtual double Top { get { return LocalPosition.Y; } set { LocalPosition = new Vector2(LocalPosition.X, value); - //var tmpHeight = Height != 0 ? Height : 0; - - //var currentPosition = Transform.LocalPosition; - //currentPosition.Y = value; - //Transform.LocalPosition = currentPosition; - - ////Recalculate bottom position correctly - ////if (tmpHeight != 0) - ////{ - // //Height = tmpHeight; - // Bottom = Top + tmpHeight; - ////} } } /// /// X pos (min) /// - public double Left + public virtual double Left { get { return LocalPosition.X; } set { - //var tmpWidth = Width != 0 ? Width : 0; - - //var currentPosition = Transform.LocalPosition; - //currentPosition.X = value; - //Transform.LocalPosition = currentPosition; - - //Recalculate Right position correctly - //if (tmpWidth != 0) - //{ - //Right = Left + tmpWidth; - //Width = tmpWidth; - //} LocalPosition = new Vector2(value, LocalPosition.Y); } } @@ -124,6 +101,87 @@ public double GlobalTop return Position.Y; } } + public double GlobalRight => GlobalLeft + Width; + + public double GlobalBottom => GlobalTop + Height; + + public string TextAnchor { get; set; } + //public double Rotation { get; set; } + //public string GroupTransform = ""; + //public List RenderItems { get; } = new List(); + + Graphics.TranformPoint _altRotationPoint = null; + /// + /// The translated position of this item in points + /// Also the parent position of the group item + /// (This may seem strange but it ensures the the translation is seen + /// immediately in the global position of GroupItem without affecting local position) + /// + public Graphics.TranformPoint TranslationOffset = new Graphics.TranformPoint(0, 0); + public Graphics.TranformPoint RotationPoint + { + get + { + if (_altRotationPoint == null) + { + return TranslationOffset; + } + return _altRotationPoint; + } + set + { + _altRotationPoint = value; + } + } + + //public Coordinate Scale = null; + + internal void SetRotationPointToCenterOfGroup(double rotation = double.NaN) + { + RotationPoint = new Graphics.TranformPoint(Width / 2, Height / 2); + + if (double.IsNaN(rotation) == false) + { + Rotation = rotation; + } + } + public double GroupWidth + { + get + { + var left = Left; + var right = Right; + foreach (var item in ChildObjects) + { + if (item is BoundingBox bb) + { + left = bb.Left > left ? bb.Left : left; + right = bb.Right > right ? bb.Right : right; + } + } + return right - left; + } + } + + //Note: This does not take negative child items into acount + //TODO: Fix that + public double GroupHeight + { + get + { + var top = Top; + var bottom = Bottom; + foreach (var item in ChildObjects) + { + if (item is BoundingBox bb) + { + top = bb.Top > top ? bb.Top : top; + bottom = bb.Bottom > bottom ? bb.Bottom : bottom; + } + } + return bottom - top; + } + } public string UniqueKey { diff --git a/src/EPPlus.Graphics/Geometry/GeoMathUtils.cs b/src/EPPlus.Graphics/Geometry/GeoMathUtils.cs new file mode 100644 index 0000000000..3ee57d4b8b --- /dev/null +++ b/src/EPPlus.Graphics/Geometry/GeoMathUtils.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EPPlus.Graphics.Geometry +{ + internal static class GeoMathUtils + { + internal static double DegreesToRadians(double degree) + { + return degree * (Math.Round((double)System.Math.PI, 14) / 180d); + } + + internal static double RadiansToDegrees(double radians) => radians * (180d / (Math.Round((double)System.Math.PI, 14))); + + + internal static Vector2 GetSwingPointAtDegrees(double degrees, double length, Vector2 rotationOriginPoint) + { + var angleRadians = DegreesToRadians(degrees); + return GetSwingPointAtRadians(angleRadians, length, rotationOriginPoint); + } + + /// + /// rotate around the point with a length offset. + /// useful for e.g. getting a point on a circle + /// + /// + /// + /// + /// + internal static Vector2 GetSwingPointAtRadians(double radians, double length, Vector2 rotationOriginPoint) + { + var xPointGlobal = rotationOriginPoint.X + (length * Math.Cos(radians)); + var yPointGlobal = rotationOriginPoint.Y + (length * Math.Sin(radians)); + + var swingPoint = new Vector2(xPointGlobal, yPointGlobal); + + return swingPoint; + } + } +} diff --git a/src/EPPlus.Graphics/Primitives/CircularArc.cs b/src/EPPlus.Graphics/Primitives/CircularArc.cs new file mode 100644 index 0000000000..4192d2ce62 --- /dev/null +++ b/src/EPPlus.Graphics/Primitives/CircularArc.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace EPPlus.Graphics.Primitives +{ + internal class CircularArc + { + Vector2 ChordA; + Vector2 ChordB; + + //Diameter + double D; + + //(AB) + double Width; + + //(CP) + double Height; + } +} diff --git a/src/EPPlus.Graphics/Primitives/PieSliceBase.cs b/src/EPPlus.Graphics/Primitives/PieSliceBase.cs new file mode 100644 index 0000000000..b07724906d --- /dev/null +++ b/src/EPPlus.Graphics/Primitives/PieSliceBase.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EPPlus.Graphics.Geometry; +using System.Text; + +namespace EPPlus.Graphics.Primitives +{ + public class PieSliceBase + { + Vector2 _startPoint; + Vector2 _endPoint; + Vector2 _midPoint; + + public Vector2 StartPoint + { + get + { + CalculateSliceArcPoints(); + return _startPoint; + } + private set + { + _startPoint = value; + } + } + + public Vector2 EndPoint + { + get + { + CalculateSliceArcPoints(); + return _endPoint; + } + private set + { + _endPoint = value; + } + } + + public Vector2 MidPoint + { + get + { + CalculateSliceArcPoints(); + return _midPoint; + } + private set + { + _midPoint = value; + } + } + + /// + /// Vector from center of circle to outer middle of Pie-Slice + /// This is the vector the slice is translated along in a pie-explosion + /// + public Vector2 ExplosionVector { get { return MidPoint - Circle.Center; } } + + internal double StartDegrees; + internal double EndDegrees; + + internal double SweepAngleInDegrees { get { return EndDegrees - StartDegrees; } } + + internal PrimitiveCircle Circle = null; + + internal PieSliceBase(double startDegrees, double endDegrees, PrimitiveCircle circle) + { + Circle = circle; + Initialize(startDegrees, endDegrees); + } + + internal PieSliceBase(double degrees, double prevSliceDegrees, double radius, Vector2 originPoint) + { + if (Circle == null) + { + Circle = new PrimitiveCircle(originPoint, radius); + } + + Initialize(degrees, prevSliceDegrees); + } + + + void Initialize(double startDegrees, double endDegrees) + { + StartDegrees = startDegrees; + EndDegrees = endDegrees; + CalculateSliceArcPoints(); + } + + void CalculateSliceArcPoints() + { + StartPoint = Circle.GetPointOnCircle(StartDegrees); + + EndPoint = Circle.GetPointOnCircle(EndDegrees + StartDegrees); + + //The degrees of the midpoint + var halfDegrees = EndDegrees / 2; + + //We add prev at this point since we don't want to halve the previous angle only the current one + MidPoint = Circle.GetPointOnCircle(halfDegrees + StartDegrees); + } + } +} diff --git a/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs b/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs new file mode 100644 index 0000000000..dd8883bd6b --- /dev/null +++ b/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs @@ -0,0 +1,22 @@ +using EPPlus.Graphics.Geometry; +using System.Threading.Tasks; + +namespace EPPlus.Graphics.Primitives +{ + public class PrimitiveCircle + { + internal Vector2 Center; + internal double Radius; + + internal PrimitiveCircle(Vector2 center, double radius) + { + Center = center; + Radius = radius; + } + + internal Vector2 GetPointOnCircle(double degrees) + { + return GeoMathUtils.GetSwingPointAtDegrees(degrees, Radius, Center); + } + } +} diff --git a/src/EPPlus.Graphics/Point.cs b/src/EPPlus.Graphics/TranformPoint.cs similarity index 84% rename from src/EPPlus.Graphics/Point.cs rename to src/EPPlus.Graphics/TranformPoint.cs index 3e5d032350..6c011e1e5c 100644 --- a/src/EPPlus.Graphics/Point.cs +++ b/src/EPPlus.Graphics/TranformPoint.cs @@ -6,7 +6,7 @@ namespace EPPlus.Graphics { - public class Point : Transform + public class TranformPoint : Transform { public double Top { @@ -26,12 +26,12 @@ public double Left } } - public Point() + public TranformPoint() { } - public Point(double x, double y) + public TranformPoint(double x, double y) { Left = x; Top = y; diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs new file mode 100644 index 0000000000..3eda7d2df8 --- /dev/null +++ b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs @@ -0,0 +1,67 @@ +using EPPlus.Graphics.Primitives; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace EPPlus.Graphics.TransformPrimitives +{ + public class PieSliceCollection : IEnumerable + { + const double MaximumDegrees = 360; + //double totalDegrees = 0; + + double totalValue = 0; + + List _percentages = new List(); + List _slices = new List(); + + internal PieSliceCollection() + { + + } + + public void Add(PieSliceBase slice, double percentage) + { + _percentages.Add(percentage); + _slices.Add(slice); + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _slices.Count; i++) + { + yield return _slices[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Clear() + { + _slices.Clear(); + _percentages.Clear(); + } + + /// + /// Collection containing the richtext objects + /// + /// + /// + public PieSliceBase this[int Index] + { + get + { + var item = _slices[Index]; + return item; + } + } + + } +} diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs new file mode 100644 index 0000000000..00d846ce44 --- /dev/null +++ b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs @@ -0,0 +1,35 @@ +using EPPlus.Graphics.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Graphics.TransformPrimitives +{ + public class PieTransform : TransformCircle + { + public PieSliceCollection Slices = new PieSliceCollection(); + + public PieTransform(Transform parent, double radius) : base(parent, radius) { } + + public PieTransform(double radius) : base(radius) { } + + /// + /// + /// + /// Between 0 and 1 + public void GenerateSlicesAndClearPrevious(IEnumerable SlicePercentages) + { + double prevSliceDegrees = 0; + Slices.Clear(); + + foreach (var percentage in SlicePercentages) + { + var degrees = percentage * 360d; + PieSliceBase slice = new PieSliceBase(prevSliceDegrees, prevSliceDegrees + degrees, Circle); + Slices.Add(slice, percentage); + prevSliceDegrees = slice.EndDegrees; + } + } + } +} diff --git a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs new file mode 100644 index 0000000000..3abd14b03f --- /dev/null +++ b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using EPPlus.Graphics.Geometry; +using EPPlus.Graphics.Primitives; + +namespace EPPlus.Graphics.TransformPrimitives +{ + //Position refers to the center of the circle + public class TransformCircle : Transform + { + public PrimitiveCircle Circle; + + protected double Radius { get { return Circle.Radius; } } + + internal TransformCircle(double radius) : base() + { + Circle = new PrimitiveCircle(Vector2.Zero, radius); + } + + internal TransformCircle(Transform parent, double radius) : base(Vector2.Zero, Vector2.One, parent) + { + Circle = new PrimitiveCircle(Vector2.Zero, radius); + } + + internal Vector2 GetPointOnCircleGlobal(double degrees) + { + var ptOnCircle = Circle.GetPointOnCircle(degrees); + return new Vector2(Position.X + ptOnCircle.X, Position.Y + ptOnCircle.Y); + } + + internal Vector2 GetPointOnCircleLocal(double degrees) + { + var ptOnCircle = Circle.GetPointOnCircle(degrees); + return new Vector2(LocalPosition.X + ptOnCircle.X, LocalPosition.Y + ptOnCircle.Y); + } + + + //TranformPoint CreateChildPointOnCircleAt(double degrees) + //{ + // var pointOnCircleGlobal = GetPointOnCircle(degrees); + + // var point = new TranformPoint(pointOnCircleGlobal.X, pointOnCircleGlobal.Y); + // point.Parent = this; + // point.Name = Name + $"_At{degrees}"; + + // return point; + //} + } +} diff --git a/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs b/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs index ce0cf52961..2a73a93150 100644 --- a/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs @@ -79,7 +79,7 @@ internal bool InvertIfNegative /// /// A collection of the individual datapoints /// - public ExcelChartDataPointCollection DataPoints + public ExcelChartDataPointCollection DataPoints { get { diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs index 7de2abf586..7f18a05562 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs @@ -957,7 +957,7 @@ internal void GetSeriesValues(out bool isCount, out List> values) { if (ct.XAxis.Id == Id || ct.YAxis.Id == Id) { - AddFromSerie(l, serie.XSeries, serie.NumberLiteralsX, serie.StringLiteralsX, true, new string[] { serie.HeaderAddress?.Address, serie.GetHeaderText(ix) }); + serie.AddFromSerie(l, true, true, new string[] { serie.HeaderAddress?.Address, serie.GetHeaderText(ix) }); } } } @@ -965,11 +965,12 @@ internal void GetSeriesValues(out bool isCount, out List> values) { if (ct.YAxis.Id == Id) { - AddFromSerie(l, serie.Series, serie.NumberLiteralsY, serie.StringLiteralsY, false, null, pl); + //AddFromSerie(l, serie.Series, serie.NumberLiteralsY, serie.StringLiteralsY, false, null, pl); + serie.AddFromSerie(l, false, false, null, pl); } else if (ct.XAxis.Id == Id) { - AddFromSerie(l, serie.XSeries, serie.NumberLiteralsX, serie.StringLiteralsX, false, null); + serie.AddFromSerie(l, true, false, null); } } pl = l; @@ -1032,152 +1033,5 @@ private void AddCountFromSeries(List l, string address, double[] numberL l.Add(Math.Max(a.Rows, a.Columns)); } } - - private void AddFromSerie(List list, string address, double[] numberLiterals, string[] stringLiterals, bool useText, string[] headerInfo, List prevList=null) - { - var isStacked = _chart.IsTypeStacked() && prevList != null; - if (numberLiterals?.Length > 0) - { - for (int i = 0; i < numberLiterals.Length; i++) - { - var n = numberLiterals[i]; - if (isStacked) - { - n += GetSerieValue(prevList, i); - } - list.Add(n); - } - } - else if (stringLiterals?.Length > 0) - { - foreach (var s in stringLiterals) - { - list.Add(s); - } - } - else - { - var a = new ExcelAddressBase(address); - if (a.ExternalReferenceIndex > 0) - { - GetExternalValues(list, useText, headerInfo, prevList, isStacked, a); - } - else - { - GetInternalValues(list, useText, headerInfo, prevList, isStacked, a); - } - } - } - - private void GetInternalValues(List list, bool useText, string[] headerInfo, List prevList, bool isStacked, ExcelAddressBase a) - { - var ws = _chart.WorkSheet.Workbook.Worksheets[a.WorkSheetName]; - if (ws != null) - { - GetValuesFromWorksheet(list, useText, headerInfo, prevList, isStacked, a, ws); - } - } - - private void GetValuesFromWorksheet(List list, bool useText, string[] headerInfo, List prevList, bool isStacked, ExcelAddressBase a, ExcelWorksheet ws) - { - var range = ws.Cells[a.Address]; - var i = 0; - for (var r = 0; r < range.Rows; r++) - { - for (var c = 0; c < range.Columns; c++) - { - var v = range.Offset(r, c, 1, 1); - if (useText) - { - if (headerInfo == null) - { - list.Add(v.Text); - } - else - { - if(v.Style.Numberformat.IsDateFormat && !(v.Value is DateTime) && ConvertUtil.IsNumericOrDate(v.Value)) - { - var d = ConvertUtil.GetValueDouble(v.Value); - list.Add(new object[] { v.Text, a.Address, headerInfo[1], DateTime.FromOADate(d) }); //Last value is for sorting. - } - else - { - list.Add(new object[] { v.Text, a.Address, headerInfo[1], v.Value }); //Last value is for sorting. - } - } - } - else - { - double d = ConvertUtil.GetValueDouble(v.Value, false, true); - if (double.IsNaN((double)d)) d = 0; - if (isStacked) - { - var pv = GetSerieValue(prevList, i++); - d += pv; - } - list.Add(d); - } - } - } - } - - private void GetExternalValues(List list, bool useText, string[] headerInfo, List prevList, bool isStacked, ExcelAddressBase a) - { - var wb = _chart.WorkSheet.Workbook; - if (wb.ExternalLinks.Count > a.ExternalReferenceIndex) return; - - var extRef = wb.ExternalLinks[a.ExternalReferenceIndex-1]; - if (extRef.ExternalLinkType != ExternalReferences.eExternalLinkType.ExternalWorkbook) return; - var extWb = extRef as ExcelExternalWorkbook; - if (extWb.Package == null) - { - var ws = extWb.CachedWorksheets[a.WorkSheetName]; - if (ws != null) - { - var i = 0; - for (var r = a._fromRow; r <= a._toRow; r++) - { - for (var c = a._fromCol; c <= a._toCol; c++) - { - var v = ws.CellValues[r, c]; - if (useText) - { - // if (headerInfo == null) - // { - // list.Add(v.Text); - // } - // else - // { - // list.Add(new string[] { v.Text, a.Address, headerInfo[1] }); - // } - list.Add(v.Value); - } - else - { - double d = ConvertUtil.GetValueDouble(v.Value, false, true); - if (double.IsNaN((double)d)) d = 0; - if (isStacked) - { - var pv = GetSerieValue(prevList, i++); - d += pv; - } - list.Add(d); - //} - } - } - } - } - } - } - private double GetSerieValue(List prevList, int i) - { - if (prevList.Count > i && ConvertUtil.IsExcelNumeric(prevList[i])) - { - var v=ConvertUtil.GetValueDouble(prevList[i], true, true); - if (double.IsNaN(v)) return 0d; - return v; - } - return 0d; - } } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataTable.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataTable.cs index 9ebe70e0ef..522cbcddef 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataTable.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataTable.cs @@ -14,6 +14,7 @@ Date Author Change using OfficeOpenXml.Drawing.Interfaces; using OfficeOpenXml.Drawing.Style.Effect; using OfficeOpenXml.Drawing.Style.ThreeD; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.Style; namespace OfficeOpenXml.Drawing.Chart @@ -135,6 +136,7 @@ public ExcelDrawingBorder Border } } ExcelTextFont _font = null; + const string FontPath = "c:txPr/a:p/a:pPr/a:defRPr"; /// /// Access font properties /// @@ -149,11 +151,18 @@ public ExcelTextFont Font CreateNode("c:txPr/a:bodyPr"); CreateNode("c:txPr/a:lstStyle"); } - _font = new ExcelTextFontXml(_chart, NameSpaceManager, TopNode, "c:txPr/a:p/a:pPr/a:defRPr", SchemaNodeOrder); + _font = new ExcelTextFontXml(_chart, NameSpaceManager, TopNode, FontPath, SchemaNodeOrder); } return _font; } } + internal bool HasFont + { + get + { + return ExistsNode(FontPath); + } + } ExcelTextBody _textBody = null; /// /// Access to text body properties diff --git a/src/EPPlus/Drawing/Chart/ExcelChartSerie.cs b/src/EPPlus/Drawing/Chart/ExcelChartSerie.cs index 253e53a472..3df29a0aec 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartSerie.cs @@ -16,6 +16,7 @@ Date Author Change using OfficeOpenXml.Drawing.Style.ThreeD; using OfficeOpenXml.ExternalReferences; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; using System.Globalization; @@ -324,5 +325,167 @@ private string GetAddressValue(ExcelAddressBase address) } return ExcelErrorValue.Values.Ref; } + + internal List GetValues(bool useX, bool useString) + { + var l = new List(); + AddFromSerie(l, useX, useString, null); + return l; + } + internal void AddFromSerie(List list, bool useX, bool useText, string[] headerInfo, List prevList = null) + { + string address; + double[] numberLiterals; + string[] stringLiterals; + + if(useX) + { + address = XSeries; + numberLiterals = NumberLiteralsX; + stringLiterals = StringLiteralsX; + } + else + { + address = Series; + numberLiterals = NumberLiteralsY; + stringLiterals = StringLiteralsY; + } + + var isStacked = _chart.IsTypeStacked() && prevList != null; + if (numberLiterals?.Length > 0) + { + for (int i = 0; i < numberLiterals.Length; i++) + { + var n = numberLiterals[i]; + if (isStacked) + { + n += GetSerieValue(prevList, i); + } + list.Add(n); + } + } + else if (stringLiterals?.Length > 0) + { + foreach (var s in stringLiterals) + { + list.Add(s); + } + } + else + { + var a = new ExcelAddressBase(address); + if (a.ExternalReferenceIndex > 0) + { + GetExternalValues(list, useText, prevList, isStacked, a); + } + else + { + GetInternalValues(list, useText, headerInfo, prevList, isStacked, a); + } + } + } + + private void GetInternalValues(List list, bool useText, string[] headerInfo, List prevList, bool isStacked, ExcelAddressBase a) + { + var ws = _chart.WorkSheet.Workbook.Worksheets[a.WorkSheetName]; + if (ws != null) + { + GetValuesFromWorksheet(list, useText, headerInfo, prevList, isStacked, a, ws); + } + } + + private void GetValuesFromWorksheet(List list, bool useText, string[] headerInfo, List prevList, bool isStacked, ExcelAddressBase a, ExcelWorksheet ws) + { + var range = ws.Cells[a.Address]; + var i = 0; + for (var r = 0; r < range.Rows; r++) + { + for (var c = 0; c < range.Columns; c++) + { + var v = range.Offset(r, c, 1, 1); + if (useText) + { + if (headerInfo == null) + { + list.Add(v.Text); + } + else + { + if (v.Style.Numberformat.IsDateFormat && !(v.Value is DateTime) && ConvertUtil.IsNumericOrDate(v.Value)) + { + var d = ConvertUtil.GetValueDouble(v.Value); + list.Add(new object[] { v.Text, a.Address, headerInfo[1], DateTime.FromOADate(d) }); //Last value is for sorting. + } + else + { + list.Add(new object[] { v.Text, a.Address, headerInfo[1], v.Value }); //Last value is for sorting. + } + } + } + else + { + double d = ConvertUtil.GetValueDouble(v.Value, false, true); + if (double.IsNaN((double)d)) d = 0; + if (isStacked) + { + var pv = GetSerieValue(prevList, i++); + d += pv; + } + list.Add(d); + } + } + } + } + + private void GetExternalValues(List list, bool useText, List prevList, bool isStacked, ExcelAddressBase a) + { + var wb = _chart.WorkSheet.Workbook; + if (wb.ExternalLinks.Count > a.ExternalReferenceIndex) return; + + var extRef = wb.ExternalLinks[a.ExternalReferenceIndex - 1]; + if (extRef.ExternalLinkType != ExternalReferences.eExternalLinkType.ExternalWorkbook) return; + var extWb = extRef as ExcelExternalWorkbook; + if (extWb.Package == null) + { + var ws = extWb.CachedWorksheets[a.WorkSheetName]; + if (ws != null) + { + var i = 0; + for (var r = a._fromRow; r <= a._toRow; r++) + { + for (var c = a._fromCol; c <= a._toCol; c++) + { + var v = ws.CellValues[r, c]; + if (useText) + { + + list.Add(v.Value); + } + else + { + double d = ConvertUtil.GetValueDouble(v.Value, false, true); + if (double.IsNaN((double)d)) d = 0; + if (isStacked) + { + var pv = GetSerieValue(prevList, i++); + d += pv; + } + list.Add(d); + } + } + } + } + } + } + private double GetSerieValue(List prevList, int i) + { + if (prevList.Count > i && ConvertUtil.IsExcelNumeric(prevList[i])) + { + var v = ConvertUtil.GetValueDouble(prevList[i], true, true); + if (double.IsNaN(v)) return 0d; + return v; + } + return 0d; + } } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs index ee3d05f682..f928d21c2c 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs @@ -1077,6 +1077,7 @@ private static string AddSmooth(eChartType chartType) return ""; } } + #endregion } } diff --git a/src/EPPlus/Drawing/ImageHandling/ImageReader.cs b/src/EPPlus/Drawing/ImageHandling/ImageReader.cs index a764e55350..2524acac5d 100644 --- a/src/EPPlus/Drawing/ImageHandling/ImageReader.cs +++ b/src/EPPlus/Drawing/ImageHandling/ImageReader.cs @@ -19,6 +19,10 @@ Date Author Change using OfficeOpenXml.Utils; using System.Threading; using OfficeOpenXml.Utils.FileUtils; +using OfficeOpenXml.FormulaParsing.Excel.Functions; +using System.Globalization; + + #if !NET35 using System.Threading.Tasks; @@ -863,7 +867,7 @@ private static double GetSvgUnit(string v) factor = 90; v = v.Substring(0, v.Length - 2); } - if (double.TryParse(v, out double value)) + if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out double value)) { return value * factor; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs index 507f55c95b..729e5be3be 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs @@ -20,7 +20,7 @@ internal class CategoryAxisScaleCalculator internal static AxisScale CalculateHorizontalAxisByWidth(ref List values, ITextShaper shaper, float fontSize, AxisOptions options) { - var plotAreaWidth = options.ChartSize.Bounds.Width; + var plotAreaWidth = options.ChartSize.Width; List displayValues = GetUniqueValues(values).Select(x => (object)x.ToString()).ToList(); var uniqeItems = displayValues.Count; @@ -88,7 +88,7 @@ internal static AxisScale CalculateHorizontalAxisByWidth(ref List values } internal static AxisScale CalculateVerticalAxisByHeight(ref List values, ITextShaper shaper, float fontSize, AxisOptions options) { - var plotAreaHeight = options.ChartSize.Bounds.Height; + var plotAreaHeight = options.ChartSize.Height; List displayValues = GetUniqueValues(values).Select(x => (object)x.ToString()).ToList(); var uniqeItems = displayValues.Count; var textHeight = GetTextHeight(shaper, displayValues[0].ToString(), fontSize); diff --git a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs index 747ad07430..b5e052bb5e 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs @@ -323,7 +323,7 @@ private static double GetTextHeight(ITextShaper shaper, string text, float fontS internal static AxisScale CalculateByWidthAllowDiagonal(List values, double min, double max, ITextShaper shaper, float fontSize, AxisOptions options) { var ax = options.Axis; - var plotAreaWidth = options.ChartSize.Bounds.Width; + var plotAreaWidth = options.ChartSize.Width; int interval; eTimeUnit unit; string format = GetNumberFormat(options); @@ -458,7 +458,7 @@ private static void GetStartInterval(double count, double span, out int interval interval = 1; unit = eTimeUnit.Days; } - if(blockSize < 15) + else if(blockSize < 15) { interval = 7; unit = eTimeUnit.Days; @@ -503,6 +503,9 @@ private static void AddIntervall(ref int interval, ref eTimeUnit unit) interval = 7; //Week break; case 7: + interval = 14; + break; + case 14: interval = 1; //Month unit = eTimeUnit.Months; break; @@ -513,14 +516,36 @@ private static void AddIntervall(ref int interval, ref eTimeUnit unit) } else if (unit == eTimeUnit.Months) { - unit = eTimeUnit.Years; + switch (interval) + { + case 1: + interval = 2; + break; + case 2: + interval = 3; //Week + break; + case 3: + interval = 6; + break; + default: + interval = 1; + unit = eTimeUnit.Years; + break; + } } else if (unit == eTimeUnit.Years) { - interval++; + var magnitude = Math.Pow(10, Math.Floor(Math.Log10(interval))); + var fraction = interval / magnitude; + + double nice; + if (fraction <= 1) nice = 2; + else if (fraction <= 2) nice = 5; + else nice = 10; + + interval = (int)Math.Round(nice * magnitude); } } - private static bool FitAsHorizontalText(ITextShaper shaper, float fontSize, double min, double max, int interval, eTimeUnit unit, double width) { var minMargin = 2; //2 Points diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs index 9279662b0c..7afb4e8e3c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs @@ -16,10 +16,11 @@ Date Author Change using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using System.Collections.Generic; using System.Drawing; +using EPPlus.Graphics; namespace EPPlusImageRenderer.Svg { - internal class ChartAreaRenderer : ChartDrawingDefaultObject + internal class ChartAreaRenderer : ChartDrawingObjectWithBackground { public ChartAreaRenderer(ChartRenderer sc, SvgRenderOptions options) : base(sc) { @@ -38,7 +39,7 @@ public ChartAreaRenderer(ChartRenderer sc, SvgRenderOptions options) : base(sc) internal override Color? DefaultFillColor { get => GetDefaultFillColor(); } internal override Color? DefaultBorderColor { get => GetDefaultBorderColor(); } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { renderItems.Add(Rectangle); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 33de520963..370c56e608 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -12,12 +12,9 @@ Date Author Change *************************************************************************************************/ using EPPlus.DrawingRenderer; using EPPlus.DrawingRenderer.RenderItems; -using EPPlus.Export.ImageRenderer; -using EPPlus.Export.ImageRenderer.RenderItems; using EPPlus.Export.ImageRenderer.RenderItems.SvgItem; using EPPlus.Export.ImageRenderer.Svg.Chart.Util; using EPPlus.Export.Renderer; -using EPPlus.Fonts.OpenType; using EPPlus.Fonts.OpenType.Integration; using EPPlus.Fonts.OpenType.Integration.DataHolders; using EPPlus.Fonts.OpenType.Utils; @@ -28,25 +25,19 @@ Date Author Change using OfficeOpenXml.Drawing.Chart.Style; using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.Drawing.Renderer.TextBox; -using OfficeOpenXml.FormulaParsing.Excel.Functions; -using OfficeOpenXml.FormulaParsing.Excel.Functions.DateAndTime; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; -using OfficeOpenXml.FormulaParsing.Utilities; using OfficeOpenXml.Style; using OfficeOpenXml.Style.XmlAccess; -using OfficeOpenXml.Utils.EnumUtils; using OfficeOpenXml.Utils.String; using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Security.AccessControl; namespace EPPlusImageRenderer.Svg { - internal class ChartAxisRenderer : ChartDrawingDefaultObject, IDrawingChartAxis + internal class ChartAxisRenderer : ChartDrawingObjectWithBackground, IDrawingChartAxis { private const double COS45 = 0.70710678118654757; //Constant for Math.Sin(Math.PI / 4) --45 degrees @@ -79,9 +70,13 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s Min = min ?? 0D; Max = max ?? (Values.Count > 0 ? ConvertUtil.GetValueDouble(Values[Values.Count - 1], false, true) : 0D); MajorUnit = majorUnit ?? 1; - if (AutoAxisType == eAxisType.Cat || IsDateAutoAxis || IsDateScale) + if(AutoAxisType == eAxisType.Cat && !(IsDateAutoAxis || IsDateScale)) { - MinorUnit = ax.MinorUnit ?? 1; + MinorUnit = (ax.MinorUnit ?? MajorUnit) / 2D; + } + else if(IsDateAutoAxis || IsDateScale) + { + MinorUnit = ax.MinorUnit ?? 1; } else { @@ -122,13 +117,13 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s } } - Rectangle.FillColor = "none"; + Rectangle.Style.FillColor = "none"; - Line = new LineRenderItem(Rectangle.Bounds); - Line.SetDrawingPropertiesBorder(ChartRenderer.Theme, ax.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, ax.Border.IsEmpty==true || ax.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 1); - if(Line.BorderWidth < 1) + Line = new LineRenderItem(Rectangle); + Line.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, ax.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, ax.Border.IsEmpty==true || ax.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 1); + if(Line.Style.BorderWidth < 1) { - Line.BorderWidth = 1; + Line.Style.BorderWidth = 1; } } } @@ -235,7 +230,16 @@ private double GetTextWidest(ExcelChartAxisStandard ax) } return widest; } + //internal GetAxisWidthForPlotarea() + //{ + //} + //internal double GetAxisHeightForPlotarea() + //{ + // var pa = ChartRenderer.Plotarea; + + // var + //} public List Values { get; @@ -256,12 +260,12 @@ public List Values public eTimeUnit? MajorDateUnit { get; set; } public eTextOrientation LabelOrientation { get; set; } public bool IsDateAutoAxis { get; set; } - public bool IsNumericAutoAxis { get; set; } //TODO: Not used? Removed if not used. public bool IsDateScale { get; private set; } = false; + public bool IsCount { get; set; } /// /// Create a subGroup beneath the ParentGroup @@ -276,48 +280,47 @@ private void AddSubGroupingOfRenderItems(string subGroupName, List Items, if (Items != null) { //Create subGroup - var subGroup = new GroupRenderItem(parentGroup.Bounds); - subGroup.Bounds.Name = subGroupName; + var subGroup = new GroupRenderItem(parentGroup); + subGroup.Name = subGroupName; //Add items to subGroup foreach (var renderItem in Items) { - subGroup.RenderItems.Add(renderItem); + subGroup.ChildObjects.Add(renderItem); } - - //Add subGroup to parent group - parentGroup.RenderItems.Add(subGroup); } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { - var AxisGroup = new GroupRenderItem(ChartRenderer.Bounds); - AxisGroup.Bounds.Name = $"Axis_{Axis.Index}"; + var axisGroup = new GroupRenderItem(ChartRenderer.Bounds); + axisGroup.Name = $"Axis_{Axis.Index}"; + axisGroup.Top = Rectangle.Top; + axisGroup.Left = Rectangle.Left; - Title?.AppendRenderItems(AxisGroup.RenderItems); + Title?.AppendRenderItems(renderItems); //Title?.Render(sb); - if(Rectangle!=null || Rectangle.Width==0 || Rectangle.Height==0) AxisGroup.RenderItems.Add(Rectangle); + if(Rectangle!=null || Rectangle.Width==0 || Rectangle.Height==0) axisGroup.ChildObjects.Add(Rectangle); var plotareaGroup = ChartRenderer.Plotarea.Group; AddSubGroupingOfRenderItems("MinorGridLines", MinorGridlinePositions, plotareaGroup); AddSubGroupingOfRenderItems("MajorGridLines", MajorGridlinePositions, plotareaGroup); - if (Line != null) AxisGroup.RenderItems.Add(Line); + if (Line != null) axisGroup.ChildObjects.Add(Line); var TickMarkGroup = new GroupRenderItem(ChartRenderer.Bounds); - TickMarkGroup.Bounds.Name = $"Axis_{Axis.Index}_TickMarkGroup"; + TickMarkGroup.Name = $"Axis_{Axis.Index}_TickMarkGroup"; AddSubGroupingOfRenderItems("MinorTickMarkPositions", MinorTickMarkPositions, TickMarkGroup); AddSubGroupingOfRenderItems("MajorTickMarkPositions", MajorTickMarkPositions, TickMarkGroup); if(MinorTickMarkPositions != null || MajorTickMarkPositions != null) { - AxisGroup.RenderItems.Add(TickMarkGroup); + axisGroup.ChildObjects.Add(TickMarkGroup); } - renderItems.Add(AxisGroup); + renderItems.Add(axisGroup); //The axis text boxes is rendered later as they have a higher Z-order. } @@ -354,9 +357,11 @@ internal void AddTickmarksAndValues(List DefItems) if (AxisValues != null && AxisValues.Count > 0 && Axis.Deleted==false && Axis.LabelPosition != eTickLabelPosition.None) { - Textboxes = new ChartAxisTextBoxes(ChartRenderer); - Textboxes.AxisName = $"Axis_{Axis.Index}_Textboxes"; - Textboxes.TextBoxes = GetAxisValueTextBoxes(); + Textboxes = new ChartAxisTextBoxes(this) + { + AxisName = $"Axis_{Axis.Index}_Textboxes", + TextBoxes = GetAxisValueTextBoxes() + }; } } @@ -475,7 +480,7 @@ private List GetAxisValueTextBoxes() } } - var tb = new DrawingTextBox(ChartRenderer.RenderContext, Chart, Rectangle.Bounds, x, y, width, height, maxWidth, maxHeight); + var tb = new DrawingTextBox(ChartRenderer.RenderContext, Chart, Rectangle, x, y, width, height, maxWidth, maxHeight); if (LabelOrientation == eTextOrientation.Diagonal) { tb.Rotation = -45; @@ -495,16 +500,22 @@ private List GetAxisValueTextBoxes() var p = Axis.TextBody.Paragraphs.FirstOrDefault(); - if (p.HorizontalAlignment != eTextAlignment.Center && Axis.AxisType != eAxisType.Val && (Axis.AxisPosition == eAxisPosition.Bottom || Axis.AxisPosition == eAxisPosition.Top)) + if (p != null) + { + if (p.HorizontalAlignment != eTextAlignment.Center && Axis.AxisType != eAxisType.Val && (Axis.AxisPosition == eAxisPosition.Bottom || Axis.AxisPosition == eAxisPosition.Top)) + { + //Horizontal axises are always center aligned visually + //Should be broken out as input to ImportParagraph instead of changing the base item + p.HorizontalAlignment = eTextAlignment.Center; + } + tb.ImportParagraph(p, 0, t); + } + else { - //Horizontal axises are always center aligned visually - //Should be broken out as input to ImportParagraph instead of changing the base item - p.HorizontalAlignment = eTextAlignment.Center; + tb.AddText(t); } - tb.ImportParagraph(p, 0, t); - - tb.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, Axis.Fill, axisStyle?.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); + tb.Rectangle.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, Axis.Fill, axisStyle?.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); if (widest < tb.Width) { @@ -540,7 +551,7 @@ private List GetAxisValueTextBoxes() { var min = ConvertUtil.GetValueDouble(Values[0]); var max = ConvertUtil.GetValueDouble(Values.Last()); - var minUnit = (max - min) / MinorUnit; + var minUnit = (max - min+1) / MinorUnit; majorWidth = Rectangle.Width / minUnit; } else @@ -605,6 +616,43 @@ private List GetAxisValueTextBoxes() } } } + else if(LabelOrientation==eTextOrientation.Horizontal) + { + double majorWidth; + var lblAlignment = (Axis as ExcelChartAxisStandard)?.LabelAlignment ?? OfficeOpenXml.eAxisLabelAlignment.Center; + if (IsDateAutoAxis || IsDateScale) + { + var min = ConvertUtil.GetValueDouble(Values[0]); + var max = ConvertUtil.GetValueDouble(Values.Last()); + var minUnit = (max - min) / MinorUnit; + majorWidth = Rectangle.Width / minUnit; + } + else + { + majorWidth = Rectangle.Width / AxisValues.Count; + } + foreach (var tb in ret) + { + if(tb.Width > majorWidth) //If the box is smaller than the text box with, center the label. + { + tb.Left += -tb.Width / 2; + } + else + { + switch (lblAlignment) + { + case OfficeOpenXml.eAxisLabelAlignment.Left: + break; + case OfficeOpenXml.eAxisLabelAlignment.Center: + tb.Left += majorWidth / 2 - tb.Width / 2; + break; + case OfficeOpenXml.eAxisLabelAlignment.Right: + tb.Left += majorWidth - tb.Width; + break; + } + } + } + } return ret; } @@ -618,7 +666,7 @@ private double GetAxisItemLeft(int i) { if (Axis.IsVertical) { - return Rectangle.Left; + return 0; } else { @@ -633,7 +681,7 @@ private double GetAxisItemLeft(int i) { majorWidth = Rectangle.Width / (AxisValues.Count - 1); } - var majorTickStartingPosition = Rectangle.Left + majorWidth * i; + var majorTickStartingPosition = majorWidth * i; return majorTickStartingPosition; } else @@ -644,14 +692,14 @@ private double GetAxisItemLeft(int i) double majorWidth; if (IsDateAutoAxis || IsDateScale) { - majorWidth = Rectangle.Width * (v - Min) / (Max - Min); + majorWidth = Rectangle.Width * (v - Min) / (Max - Min + 1); } else { majorWidth = Rectangle.Width * (v - Min) / (Max - Min); } - return Rectangle.Left + majorWidth; + return majorWidth; } //} } @@ -665,9 +713,9 @@ private double GetAxisItemTop(int i, double width, double height) { case eTextOrientation.Vertical: case eTextOrientation.Diagonal: - return Rectangle.Bottom; + return Rectangle.Height; default: - return Rectangle.Bottom - height - TopMargin; + return Rectangle.Height - height - TopMargin; } } else if (Axis.ActualAxisPosition == eActualAxisPosition.Bottom || Axis.ActualAxisPosition == eActualAxisPosition.BottomSecond) @@ -677,33 +725,33 @@ private double GetAxisItemTop(int i, double width, double height) case eTextOrientation.Vertical: if (Axis.LabelPosition == eTickLabelPosition.Low) { - return Rectangle.Bottom - width; + return Rectangle.Height - width; } else { - return Rectangle.Top; + return 0; } case eTextOrientation.Diagonal: if (Axis.LabelPosition == eTickLabelPosition.Low) { - return Rectangle.Bottom - (width + height)* COS45; + return Rectangle.Height - (width + height)* COS45; } else { - return Rectangle.Top; + return 0; } default: if (Axis.LabelPosition == eTickLabelPosition.Low) { - return Rectangle.Bottom - height; + return Rectangle.Height - height; } else if (Axis.LabelPosition == eTickLabelPosition.NextTo) { - return Rectangle.Top + BottomMargin; + return BottomMargin; } else //TODO:Add support for hight. { - return Rectangle.Top + BottomMargin; + return BottomMargin; } } } @@ -712,12 +760,12 @@ private double GetAxisItemTop(int i, double width, double height) if (Axis.AxisType == eAxisType.Cat || Axis.AxisType == eAxisType.Date) { var majorHeight = Rectangle.Height / (AxisValues.Count); - return Rectangle.Top + majorHeight * (AxisValues.Count - i) - ((majorHeight / 2) + height / 2); + return majorHeight * (AxisValues.Count - i) - ((majorHeight / 2) + height / 2); } else { var majorHeight = Rectangle.Height / (AxisValues.Count-1); - return Rectangle.Top + majorHeight * (AxisValues.Count - i - 1); + return majorHeight * (AxisValues.Count - i - 1); //return Rectangle.Top + majorHeight * (AxisValues.Count - i - 1); } } @@ -730,16 +778,13 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou var tms = new List(); double min, max, addMinor=0D; - if(double.IsNaN(parentUnit)==false && parentUnit==units) - { - addMinor = parentUnit / 2; - } + if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis==false) { - min = 0; - if(AxisValues != null) + if (AxisValues != null) { + min = 1; if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) { max = AxisValues.Count; @@ -751,6 +796,7 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou } else { + min = 0; max = 0; } } @@ -770,47 +816,48 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou tickMarkWidthOutside = tickMarkWidth; } - var diff = min == 0 ? max - min : max - min + 1; - var maxPos = max == 0 ? max : max + 1; + var diff = IsCatAx() ? max - min + 1 : max - min; + var maxPos = IsCatAx() ? max + 1 : max; double d = min + addMinor; - while (d <= maxPos) + maxPos += units * 1e-12; //To avoid rounding errors when comparing d to maxPos, we add a small epsilon value to the comparison. + while (d <= maxPos ) { var addPosition = (d - min); - if (double.IsNaN(parentUnit) || - (dateUnit.HasValue==false && addPosition % parentUnit != 0) || - (dateUnit.HasValue==true && IsMinorDateUnit(dateUnit.Value, parentUnit, d))) - { + //if (double.IsNaN(parentUnit) || + // (dateUnit.HasValue==false && addPosition % parentUnit != 0) || + // (dateUnit.HasValue==true && IsMinorDateUnit(dateUnit.Value, parentUnit, d))) + //{ double x1, y1, x2, y2; switch (Axis.ActualAxisPosition) { case eActualAxisPosition.Left: case eActualAxisPosition.LeftSecond: - y1 = (float)(Rectangle.Top + Rectangle.Height - (addPosition / diff * Rectangle.Height)); + y1 = (float)(Rectangle.Height - (addPosition / diff * Rectangle.Height)); y2 = y1; - x1 = (float)Rectangle.Right - tickMarkWidthOutside; - x2 = (float)Rectangle.Right + tickMarkWidthInside; + x1 = (float)Rectangle.Width - tickMarkWidthOutside; + x2 = (float)Rectangle.Width + tickMarkWidthInside; break; case eActualAxisPosition.Right: case eActualAxisPosition.RightSecond: - y1 = (float)(Rectangle.Top + Rectangle.Height - (addPosition / diff * Rectangle.Height)); + y1 = (float)(Rectangle.Height - (addPosition / diff * Rectangle.Height)); y2 = y1; - x1 = (float)Rectangle.Left - tickMarkWidthInside; - x2 = (float)Rectangle.Left + tickMarkWidthOutside; + x1 = (float)-tickMarkWidthInside; + x2 = (float)tickMarkWidthOutside; break; case eActualAxisPosition.Top: case eActualAxisPosition.TopSecond: - x1 = (float)(Rectangle.Left + (addPosition / diff * Rectangle.Width)); + x1 = (float)((addPosition / diff * Rectangle.Width)); x2 = x1; - y1 = (float)Rectangle.Bottom - tickMarkWidthOutside; - y2 = (float)Rectangle.Bottom + tickMarkWidthInside; + y1 = (float)Rectangle.Height - tickMarkWidthOutside; + y2 = (float)Rectangle.Height + tickMarkWidthInside; break; case eActualAxisPosition.Bottom: case eActualAxisPosition.BottomSecond: - x1 = (float)(Rectangle.Left + (addPosition / diff * Rectangle.Width)); + x1 = (float)((addPosition / diff * Rectangle.Width)); x2 = x1; - y1 = (float)Rectangle.Top - tickMarkWidthInside; - y2 = (float)Rectangle.Top + tickMarkWidthOutside; + y1 = (float)-tickMarkWidthInside; + y2 = (float)tickMarkWidthOutside; break; default: throw new InvalidOperationException("Invalid axis position"); @@ -820,13 +867,13 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou tm.Y1 = y1; tm.X2 = x2; tm.Y2 = y2; - tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, Axis.Border, axisStyle?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); - if(tm.BorderWidth < 0.75) //Excel seems to have this as minimum width for tick marks, so we enforce it here to make sure they are visible. + tm.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, Axis.Border, axisStyle?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); + if(tm.Style.BorderWidth < 0.75) //Excel seems to have this as minimum width for tick marks, so we enforce it here to make sure they are visible. { - tm.BorderWidth = 0.75; + tm.Style.BorderWidth = 0.75; } tms.Add(tm); - } + //} if (units == 0) break; switch (dateUnit) { @@ -850,7 +897,7 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou break; } } - return tms; + return tms; } private bool IsMinorDateUnit(eTimeUnit dateUnit, double parentUnit, double d) @@ -889,23 +936,23 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw var pa = ChartRenderer.Plotarea; var diff = Max - min; - List points = new List(); + List points = new List(); var group = ChartRenderer.Plotarea.Group; for (double d = min; d <= Max; d += units) { - if(d==min && Line!=null && Line.BorderWidth>0) continue; + //if(d==min && Line!=null && Line.BorderWidth>0) continue; //TODO: Check this. Removed as min is not always the first gridline, and this causes the first gridline to be missing in some cases. if (double.IsNaN(parentUnit) || (d % parentUnit != 0)) { switch (Axis.AxisPosition) { case eAxisPosition.Left: case eAxisPosition.Right: - points.Add(new EPPlus.Graphics.Point(0f, (float)(pa.Rectangle.Height - ((d - min) / diff * pa.Rectangle.Height)))); + points.Add(new EPPlus.Graphics.TranformPoint(0f, (float)(pa.Rectangle.Height - ((d - min) / diff * pa.Rectangle.Height)))); break; case eAxisPosition.Top: case eAxisPosition.Bottom: var xValue = (float)(((d - min) / diff * pa.Rectangle.Width)); - points.Add(new EPPlus.Graphics.Point(xValue, 0f)); + points.Add(new EPPlus.Graphics.TranformPoint(xValue, 0f)); break; default: throw new InvalidOperationException("Invalid axis position."); @@ -946,14 +993,14 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw tm.X2 = x2; tm.Y2 = y2; - if(id == "xGridLine") - { - tm.Bounds.Width = pa.Rectangle.Width; - } + //if(id == "xGridLine") + //{ + // tm.Width = pa.Rectangle.Width; + //} //var lineWidth = lineItem.Width <= 0 ? 0.75 : lineItem.Width; - tm.SetDrawingPropertiesBorder(ChartRenderer.Theme, lineItem, styleEntry?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); + tm.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, lineItem, styleEntry?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); - tm.DefId = id; + tm.Style.DefId = id; tms.Add(tm); @@ -989,6 +1036,7 @@ private ExcelChartStyleEntry GetAxisStyleEntry() switch (Axis.AxisType) { case eAxisType.Cat: + case eAxisType.Date: axisStyle = Chart.StyleManager.Style?.CategoryAxis; break; case eAxisType.Serie: @@ -1006,7 +1054,7 @@ internal double GetPositionInPlotarea(double val, bool startValue=false) { if (Axis.AxisPosition == eAxisPosition.Left || Axis.AxisPosition == eAxisPosition.Right) { - if (AutoAxisType == eAxisType.Cat && IsNumericAutoAxis == false && IsDateAutoAxis==false) + if(AutoAxisType == eAxisType.Cat && IsDateAutoAxis==false) { var majorHeight = ChartRenderer.Plotarea.Rectangle.Height / Max; if(startValue) @@ -1033,7 +1081,7 @@ internal double GetPositionInPlotarea(double val, bool startValue=false) } else { - if (Axis.AxisType == eAxisType.Cat && IsNumericAutoAxis == false && IsDateAutoAxis == false) + if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis == false) { var majorWidth = ChartRenderer.Plotarea.Rectangle.Width / Max; if (startValue) @@ -1070,6 +1118,7 @@ internal double GetPositionInPlotarea(double val, bool startValue=false) protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, out double? min, out double? max, out double? majorUnit, out eTimeUnit? dateUnit, out eTextOrientation orientation) { var values = ax.GetAxisValues(out bool isCount, out bool isNumeric, out bool isDate); + IsCount = isCount; var options = new AxisOptions { LockedMin = ax.MinValue, @@ -1164,7 +1213,7 @@ protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, AxisScale res; if (ax.IsVertical) { - res = DateAxisScaleCalculator.CalculateByWidthHeight(options.ChartSize.Bounds.Height, min ?? 0D, max ?? 0D, shaper, fontSize, options); + res = DateAxisScaleCalculator.CalculateByWidthHeight(options.ChartSize.Height, min ?? 0D, max ?? 0D, shaper, fontSize, options); } else { @@ -1220,7 +1269,6 @@ protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, majorUnit = res.MajorInterval; dateUnit = null; orientation = eTextOrientation.Horizontal; - IsNumericAutoAxis = false; } return l; @@ -1287,6 +1335,7 @@ private bool ShouldHavePadding() { return GetDefaultBorderColorForElement(ChartElement.Axis, (int)Chart.Style); } + internal override Color? DefaultBorderColor => GetDefaultBorderColor(); internal double GetCrossesValue() { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs index 94fa61e83c..143137789d 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs @@ -17,6 +17,7 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using System.Collections.Generic; using System.Drawing; +using EPPlus.Graphics; namespace EPPlusImageRenderer.Svg { @@ -25,9 +26,10 @@ internal class ChartAxisTextBoxes : ChartDrawingObject internal string AxisName = ""; internal override Color? DefaultFillColor { get; } - - internal ChartAxisTextBoxes(ChartRenderer chart) : base(chart) + internal ChartAxisRenderer _axis; + internal ChartAxisTextBoxes(ChartAxisRenderer axis) : base(axis.ChartRenderer) { + _axis = axis; DefaultFillColor = Color.Transparent; } @@ -37,19 +39,35 @@ internal List TextBoxes set; }=new List(); - public override void AppendRenderItems(List renderItems) + + public override void AppendRenderItems(List renderItems) { if (TextBoxes != null && TextBoxes.Count > 0) { - var AxisTxtBoxGroup = new GroupRenderItem(ChartRenderer.Bounds); - AxisTxtBoxGroup.Bounds.Name = AxisName; + var axisTxtBoxGroup = new GroupRenderItem(ChartRenderer.Bounds); + axisTxtBoxGroup.Name = AxisName; + axisTxtBoxGroup.Top = _axis.Rectangle.Top; + axisTxtBoxGroup.Left = _axis.Rectangle.Left; + _axis.Rectangle.Top = 0; + _axis.Rectangle.Left = 0; foreach (var tb in TextBoxes) { - tb.AppendRenderItems(AxisTxtBoxGroup.RenderItems); + tb.AppendRenderItems(axisTxtBoxGroup.ChildObjects); } - renderItems.Add(AxisTxtBoxGroup); + renderItems.Add(axisTxtBoxGroup); } } + internal override Color? DefaultBorderColor => null; + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 068447bdbe..34046c77f8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -12,24 +12,457 @@ Date Author Change *************************************************************************************************/ using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.DrawingRenderer.RenderItems.SvgItem; +using EPPlus.DrawingRenderer.ShapeDefinitions; +using EPPlus.Export.ImageRenderer.Svg.Chart.Util; +using EPPlus.Graphics; using EPPlusImageRenderer; +using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Svg; +using OfficeOpenXml.Core; +using OfficeOpenXml.Core.Worksheet.Fonts.GenericFontMetrics; +using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; +using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; +using OfficeOpenXml.Interfaces.Drawing.Text; +using OfficeOpenXml.Style; +using OfficeOpenXml.Style.XmlAccess; +using OfficeOpenXml.Utils.String; using System; using System.Collections.Generic; +using System.Drawing; +using System.Linq; namespace OfficeOpenXml.Drawing.Renderer.Chart { - internal class ChartDataTableRenderer : ChartDrawingObject + internal class ChartDataTableRenderer : ChartDrawingObjectWithBackground, ILegendKeyContainer { + ExcelChartDataTable _dataTable; + float _marginItemsWidth; + public double _maxWidth, _maxHeight; + public float MarginItemsWidth => _marginItemsWidth; + List _seriesHeadersMeasure = new List(); + public double MaxWidth => _maxWidth; + + public double MaxHeight => _maxHeight; + public List SeriesHeadersMeasure => _seriesHeadersMeasure; + + public List SeriesIcon { get; }=new List(); + public List> DataTableRenderItems { get; set; } = new List>(); + + internal override Color? DefaultFillColor => GetDefaultFillColor(); + + internal override Color? DefaultBorderColor => GetDefaultBorderColor(); + + public eLegendPosition Position => eLegendPosition.Left; + + public EPPlusReadOnlyList Entries => null; + + public ExcelTextBody TextBody => _dataTable.TextBody; + + double _dataTableWidth, _columnsWidth; internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) { - var chartDataTable = svgChart.Chart.PlotArea.DataTable; + _dataTable = svgChart.Chart.PlotArea.DataTable; + Rectangle = new RectRenderItem(svgChart.Plotarea.Rectangle); + LeftMargin = RightMargin = 3; + TopMargin = BottomMargin = 3; + MeasurementFont mf; + if(_dataTable.HasFont) + { + mf = _dataTable.Font.GetMeasureFont(); + } + else + { + mf = Chart.Font.GetMeasureFont(); + } + + _marginItemsWidth = mf.Size / 2; + _maxWidth = svgChart.ChartArea.Rectangle.Width - svgChart.ChartArea.LeftMargin - svgChart.ChartArea.RightMargin; + _maxHeight = svgChart.ChartArea.Rectangle.Height * (3D / 4D) - svgChart.ChartArea.TopMargin - svgChart.ChartArea.RightMargin; //We set max height to 3/4 of the chart area height. The data table will be placed below the plot area. The remaining 1/4 of the chart area height is reserved for the legend and plot area. + Rectangle.Width = _maxWidth; //Set to max. Adjust later to actual width + Rectangle.Height = _maxHeight; //Set to max. Adjust later to actual height + var items = new List>(); + var headers = new List(); + items.Add(headers); + + ExcelDrawingParagraph paragraph; + if (_dataTable.HasFont) + { + paragraph = _dataTable.TextBody.Paragraphs.FirstOrDefault(); + } + else + { + paragraph = null; + } + + double entryWidth = 0, entryHeight=0; + var values = svgChart.HorizontalAxis.Axis.GetAxisValues(out _, out _, out _); + var horizontaValues = GetFormattedValues(svgChart, values); + foreach (var v in horizontaValues) + { + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + if (paragraph == null) + { + tb.AddParagraph(v); + } + else + { + tb.ImportParagraph(paragraph, 0, v); + } + headers.Add(tb); + if (entryWidth < tb.Width) + { + entryWidth = tb.Width; + } + if(entryHeight < tb.Height) + { + entryHeight = tb.Height; + } + _seriesHeadersMeasure.Add(new TextMeasurement((float)tb.Width, (float)tb.Height)); + } + + DataTableRenderItems.Add(headers); + + _dataTableWidth = GetDataTableWidth(svgChart, _dataTable); + + //Create the source data for the data table from the series. + DrawingLegendSerie pSls = null; + int index = 0; + int seriesIndex=0; + var maxIconLength = LegendIconRenderer.GetIconLength(Chart, entryHeight); + foreach (var ct in svgChart.Chart.PlotArea.ChartTypes) + { + foreach (ExcelChartStandardSerie serie in ct.Series) + { + //Create the legend column + var sls = new DrawingLegendSerie(); + if(ct.IsTypeLine()) + { + LegendIconRenderer.SetLineLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + SeriesIcon.Add(sls); + } + else if (ct.IsTypeColumn() || ct.IsTypeBar()) + { + LegendIconRenderer.SetBarLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + SeriesIcon.Add(sls); + } + else if (ct.IsTypePie()) + { + LegendIconRenderer.SetPieLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + } + var rows = AddSerieValues(svgChart, _maxWidth, _maxHeight, serie); + pSls = sls; + var formattedValues = serie.GetValues(false, true); + var l=new List(); + foreach (var v in formattedValues) + { + var tb = new DrawingTextBody(RenderContext, ChartRenderer.Chart, Rectangle, true); + if (paragraph == null) + { + tb.AddParagraph(v.ToString()); + } + else + { + tb.ImportParagraph(paragraph, 0, v.ToString()); + } + l.Add(tb); + } + DataTableRenderItems.Add(l); + } + seriesIndex++; + } + _columnsWidth = (_dataTableWidth - (GetLegendColWidth()+LeftMargin+RightMargin)) / headers.Count; + SetColumnWidth(entryHeight); + } + + + private void SetColumnWidth(double entryWidth) + { + var height = GetLegendColHeight(1); + var r = 1; + foreach (var lc in SeriesIcon) + { + if (_dataTable.ShowKeys) + { + //if (lc.MarkerIcon != null) + //{ + // lc.MarkerIcon.Height += height; + // if (lc.MarkerBackground != null) + // { + // lc.MarkerBackground.Height += height; + // } + //} + if(lc.SeriesIcon.Top > lc.Textbox.Top) + { + var diff = lc.SeriesIcon.Top - lc.Textbox.Top; + if(lc.SeriesIcon is LineRenderItem line) + { + line.Y1 = line.Y2 = height + diff; + } + else + { + lc.SeriesIcon.Top = height + diff; + } + lc.Textbox.Top = height; + } + else + { + var diff = lc.Textbox.Top - lc.SeriesIcon.Top; + if (lc.SeriesIcon is LineRenderItem line) + { + line.Y1 = line.Y2 = height; + } + else + { + lc.SeriesIcon.Top = height; + } + lc.Textbox.Top = height + diff; + } + } + else + { + lc.Textbox.Left = height; + } + height += GetLegendColHeight(r++); + } + + double y = 0; + r = 0; + var lcw = GetLegendColWidth(); + foreach (var row in DataTableRenderItems) + { + var x = lcw + LeftMargin + RightMargin; + foreach (var cell in row) + { + cell.Left += x + (_columnsWidth / 2)-(cell.Width / 2); + cell.Top += y; + x += _columnsWidth; + } + y += GetLegendColHeight(r++); + } + Rectangle.Height = y; + Rectangle.Width = lcw + _columnsWidth * DataTableRenderItems[0].Count; + } + + const float MarginIconText = 1.5f; + private double GetLegendColWidth() + { + if (SeriesIcon.Count == 0) return 0; + var w = SeriesIcon.Max(x=>x.Textbox.Width); + if(_dataTable.ShowKeys) + { + return w + SeriesIcon.Max(x=>x.SeriesIcon?.Width??0) + MarginIconText; + } + return w; + } + private double GetLegendColHeight(int row) + { + var h = 0D; + if (row > 0) + { + h = SeriesIcon[row-1].Textbox.Height; + if (_dataTable.ShowKeys) + { + var sih = SeriesIcon[row-1].SeriesIcon.Height; + var mih = SeriesIcon[row-1].MarkerIcon?.Height ?? 0D; + h = Math.Max(mih, Math.Max(h, sih)); + } + } + foreach(var cell in DataTableRenderItems[row]) + { + if (cell.Height > h) h = cell.Height; + } + return h; + } + + private double GetDataTableWidth(ChartRenderer svgChart, ExcelChartDataTable chartDataTable) + { + var width = svgChart.ChartArea.Rectangle.Width - svgChart.ChartArea.LeftMargin - svgChart.ChartArea.RightMargin; + if(svgChart.Legend==null || svgChart.Chart.Legend.Position==eLegendPosition.Top || svgChart.Chart.Legend.Position == eLegendPosition.Bottom) + { + return width; + } + else + { + return width - svgChart.Legend.Rectangle.Width - svgChart.Legend.RightMargin; + } + } + private List GetFormattedValues(ChartRenderer svgChart, List values) + { + var format = svgChart.HorizontalAxis.Axis.FormatOrFirstValueFormat; + var nf = new ExcelFormatTranslator(format, 0); + //Excel replaces the format with a default date format if the axis is date based. + if (nf.DataType == ExcelNumberFormatXml.eFormatType.DateTime) + { + if (format == "m/d/yyyy") + { + var sdFormat = ExcelNumberFormat.GetFromBuildInFromID(14); //14 is standard regional short date. + nf = new ExcelFormatTranslator(sdFormat, 14); + } + } + var displayValues = new List(); + foreach (var v in values) + { + var s = ValueToTextHandler.FormatValue(v, false, nf, null, out bool isValidFormat); + displayValues.Add(s); + } + return displayValues; + } + private List AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, ExcelChartSerie serie) + { + var ret = new List(); + if (string.IsNullOrEmpty(serie.Series)) + { + var a = new ExcelAddressBase(serie.Series); + var ws = svgChart.Chart.WorkSheet; + if (ws != null) + { + var range = ws.Cells[a.Address]; + foreach (var cell in range) + { + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(cell.Text); + ret.Add(tb); + } + } + } + else if (serie.StringLiteralsY != null && serie.StringLiteralsY.Length > 0) + { + foreach (var se in serie.StringLiteralsY) + { + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(se); + ret.Add(tb); + } + } + else if (serie.NumberLiteralsY != null && serie.NumberLiteralsY.Length > 0) + { + foreach (var nl in serie.NumberLiteralsY) + { + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(nl.ToString()); + ret.Add(tb); + } + } + + return ret; + } + + internal override Color? GetDefaultFillColor() + { + return GetDefaultFillColorForElement(ChartElement.DataTable, (int)Chart.Style); + } + + internal override Color? GetDefaultBorderColor() + { + return GetDefaultBorderColorForElement(ChartElement.DataTable, (int)Chart.Style); + } + + public override void AppendRenderItems(List renderItems) + { + var group = new GroupRenderItem(ChartRenderer.Bounds); + group.Name = "DataTable"; + group.Left = ChartRenderer.ChartArea.LeftMargin; + group.Top = GetTop(); + //Render series header column. + foreach (var li in SeriesIcon) + { + if (_dataTable.ShowKeys) + { + group.ChildObjects.Add(li.SeriesIcon); + if (li.MarkerBackground != null) group.ChildObjects.Add(li.MarkerBackground); + if (li.MarkerIcon != null) group.ChildObjects.Add(li.MarkerIcon); + } + group.ChildObjects.Add(li.Textbox); + } + + foreach (var row in DataTableRenderItems) + { + foreach (var cell in row) + { + group.ChildObjects.Add(cell); + } + } + if(_dataTable.ShowOutline) + { + CreateOutlineRenderItem(group); + } + if(_dataTable.ShowHorizontalBorder) + { + CreateHorizontalBorderRenderItem(group); + } + if (_dataTable.ShowVerticalBorder) + { + CreateVerticalBorderRenderItem(group); + } + renderItems.Add(group); } - public override void AppendRenderItems(List renderItems) + + private void CreateVerticalBorderRenderItem(GroupRenderItem group) + { + var path = new PathRenderItem(group); + var lcw = GetLegendColWidth() + LeftMargin + RightMargin; + var bottom = DataTableRenderItems[DataTableRenderItems.Count - 1][0].Bottom; + for (int c=1;c < DataTableRenderItems[0].Count; c++) + { + var left = lcw + c*_columnsWidth; + path.Commands.Add(new PathCommand(PathCommandType.Move, left, 0, left, bottom)); + } + path.Style.FillOpacity = 100; + path.Style.FillColor = "none"; + path.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _dataTable.Border, ChartRenderer.Chart.StyleManager?.Style?.DataTable.BorderReference.Color, _dataTable.Border.Width > 0, GetDefaultBorderColor, 0.75); + } + + private PathRenderItem CreateHorizontalBorderRenderItem(GroupRenderItem group) + { + var path = new PathRenderItem(group); + var lcw = GetLegendColWidth() + LeftMargin + RightMargin; + var right = lcw + _columnsWidth * DataTableRenderItems[0].Count; + for (var r = 1; r < DataTableRenderItems.Count;r++) + { + var top = DataTableRenderItems[r][0].Top; + path.Commands.Add(new PathCommand(PathCommandType.Move, 0, top, right, top)); + } + path.Style.FillOpacity = 100; + path.Style.FillColor = "none"; + path.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _dataTable.Border, ChartRenderer.Chart.StyleManager?.Style?.DataTable.BorderReference.Color, _dataTable.Border.Width > 0, GetDefaultBorderColor, 0.75); + return path; + } + + private PathRenderItem CreateOutlineRenderItem(GroupRenderItem group) + { + var path = new PathRenderItem(group); + + var bl = DataTableRenderItems[DataTableRenderItems.Count - 1][0]; + var fc = DataTableRenderItems[1][0]; + var lcw = GetLegendColWidth() + LeftMargin + RightMargin; + var right = lcw + _columnsWidth * DataTableRenderItems[0].Count; + path.Commands.Add(new PathCommand(PathCommandType.Move, lcw, 0, right, 0, right, bl.Bottom, lcw, bl.Bottom)); + path.Commands.Add(new PathCommand(PathCommandType.End)); + path.Commands.Add(new PathCommand(PathCommandType.Move, 0, fc.Top, right, fc.Top, right, bl.Bottom, 0, bl.Bottom)); + path.Commands.Add(new PathCommand(PathCommandType.End)); + path.Style.FillOpacity = 100; + path.Style.FillColor = "none"; + path.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _dataTable.Border, ChartRenderer.Chart.StyleManager?.Style?.DataTable.BorderReference.Color, _dataTable.Border.Width > 0, GetDefaultBorderColor, 0.75); + return path; + } + + private double GetTop() { - base.AppendRenderItems(renderItems); + var bottom = ChartRenderer.Plotarea.Rectangle.Bottom; + if (ChartRenderer.HorizontalAxis?.Axis.Deleted == false && (ChartRenderer.HorizontalAxis.Rectangle?.Bottom??0) > bottom) + { + bottom = ChartRenderer.HorizontalAxis.Rectangle.Bottom; + } + if (ChartRenderer.SecondHorizontalAxis?.Axis.Deleted == false && (ChartRenderer.SecondHorizontalAxis.Rectangle?.Bottom ?? 0) > bottom) + { + bottom = ChartRenderer.SecondHorizontalAxis.Rectangle.Bottom; + } + return bottom+TopMargin; } - } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs index 7f59f29a20..56f7827ae6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs @@ -18,10 +18,12 @@ Date Author Change using EPPlusImageRenderer.RenderItems; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using OfficeOpenXml.Drawing.Style.Coloring; using OfficeOpenXml.Drawing.Theme; using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; using OfficeOpenXml.Utils.TypeConversion; +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -29,19 +31,14 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal abstract class ChartDrawingObject : DrawingObject + internal abstract class ChartDrawingObjectWithBackground : ChartDrawingObject { - internal ChartRenderer ChartRenderer; - internal ExcelChart Chart => (ExcelChart)ChartRenderer.Drawing; - - internal RenderContext RenderContext => ChartRenderer.RenderContext; - internal ChartDrawingObject(ChartRenderer chart) + protected ChartDrawingObjectWithBackground(ChartRenderer chart) : base(chart) { - ChartRenderer = chart; //Fixes null ref but might be inaccurate for some objects... Rectangle = new RectRenderItem(chart.Bounds); - //InitStyleColors(); } + internal virtual RectRenderItem Rectangle { get; set; } internal void SetMargins(ExcelTextBody tb) { tb.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); @@ -54,60 +51,372 @@ internal void SetMargins(ExcelTextBody tb) internal double RightMargin { get; set; } internal double TopMargin { get; set; } internal double BottomMargin { get; set; } - internal virtual RectRenderItem Rectangle { get; set; } - internal virtual Color? DefaultFillColor { get; } - internal virtual Color? DefaultBorderColor { get; } - protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelLayout layout, BoundingBox parent=null) + + } + internal abstract class ChartDrawingObject : DrawingObject + { + internal ChartRenderer ChartRenderer; + internal ExcelChart Chart => (ExcelChart)ChartRenderer.Drawing; + + internal RenderContext RenderContext => ChartRenderer.RenderContext; + internal ChartDrawingObject(ChartRenderer chart) + { + ChartRenderer = chart; + //InitStyleColors(); + } + /// + /// Get the X serie values. If the values are not numeric, return a serie with the index values (1,2,3,...). Trendline calculation requires numeric X values, but Excel allows non-numeric X values for trendlines, in which case it uses the index values as X for calculation. + /// + /// Input values + /// Output doubles + internal List GetXSerie(List xSerie) + { + var l = new List(); + for (int i = 0; i < xSerie.Count; i++) + { + if (ConvertUtil.IsExcelNumeric(xSerie[i])) + { + l.Add(ConvertUtil.GetValueDouble(xSerie[i])); + } + else + { + return xSerie.Select((x, index) => (double)(index + 1)).ToList(); + } + } + return l; + } + + private Color GetSchemeColorTint(eSchemeColor sColor, double tint = 0.0d) + { + if (tint < 0) + { + tint = 1 + tint; + } + else if (tint > 0) + { + tint = 1 - tint; + } + var schemeClr = tc.ColorConverter.GetSchemeColor(ChartRenderer.Theme, sColor); + var tintedSchemeColor = tc.ColorConverter.ApplyTintDrawing(schemeClr, tint); + return tintedSchemeColor; + } + + private Color GetThemeColorTint(eThemeSchemeColor themeColor, double tint = 0.0d) + { + var schemeClr = tc.ColorConverter.GetThemeColor(ChartRenderer.Theme, themeColor); + var tintedSchemeColor = tc.ColorConverter.ApplyTintDrawing(schemeClr, tint); + return tintedSchemeColor; + } + protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelLayout layout, BoundingBox parent = null) { var bounds = parent ?? sc.Bounds; - var rect = new RectRenderItem(sc.ChartArea.Rectangle.Bounds); + var rect = new RectRenderItem(parent); var ml = layout.ManualLayout; if (ml.LeftMode == eLayoutMode.Edge) { - rect.Left = bounds.Width * (float)(layout.ManualLayout.Left ?? 0D) / 100; + rect.Left = bounds.Width * (float)(layout.ManualLayout.Left ?? 0d) / 100d; } else { - rect.Left = bounds.Width * (float)(ml.Left ?? 0D) / 100; + rect.Left = bounds.Width * (float)(ml.Left ?? 0d) / 100d; //TODO:Add factor from default position } //Width is always factor. - rect.Width = bounds.Width * ml.GetWidth() / 100; + rect.Width = bounds.Width * ml.GetWidth() / 100d; if (ml.LeftMode == eLayoutMode.Edge) { - rect.Top = bounds.Height * (float)(layout.ManualLayout.Top ?? 0D) / 100; + rect.Top = bounds.Height * (float)(layout.ManualLayout.Top ?? 0d) / 100d; } else { - rect.Top = bounds.Height * (float)(ml.Top ?? 0D) / 100; + rect.Top = bounds.Height * (float)(ml.Top ?? 0d) / 100d; //TODO:Add factor from default position } //Height is always factor. - rect.Height = bounds.Height * ml.GetHeight() / 100; + rect.Height = bounds.Height * ml.GetHeight() / 100d; return rect; } + + internal Color? GetStyleColorOrDefault(int styleId, Color col1, Color col2, Color col3, Color col4) + { + Color? themeColor = null; + //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 + //Alternatively it's an unkown or unset style which should also default to style2 + styleId = styleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : styleId; + + if (styleId == 0) + { + //Set to default instead for export + //Otherwise epplus generated get weird. + styleId = 2; + //return Color.Empty; + } + + if (styleId <= 32) + { + themeColor = col1; + } + else if (styleId <= 34) + { + themeColor = col2; + } + else if (styleId <= 40) + { + themeColor = col3; + } + else if (styleId <= 48) + { + themeColor = col4; + } + + return themeColor; + } + /// - /// Get the X serie values. If the values are not numeric, return a serie with the index values (1,2,3,...). Trendline calculation requires numeric X values, but Excel allows non-numeric X values for trendlines, in which case it uses the index values as X for calculation. + /// /// - /// Input values - /// Output doubles - internal List GetXSerie(List xSerie) + /// + /// + /// The line color with fill styles etc applied + /// + /// + protected ExcelThemeLine GetThemedLine(ChartElement element, int ChartStyleId, bool nodeIsEmpty, out Color? lineColor) { - var l = new List(); - for (int i = 0; i < xSerie.Count; i++) + //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 + //Alternatively it's an unkown or unset style which should also default to style2 + var styleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; + + //Everything defaults to subtle style except Area and Floor at certain styles + var AreaOrFloorOrOther = (ChartElement.ChartArea | ChartElement.Floor | ChartElement.OtherLines); + if (AreaOrFloorOrOther.HasFlag(element)) { - if (ConvertUtil.IsExcelNumeric(xSerie[i])) + var themedLine = ChartRenderer.Theme.FormatScheme.BorderStyle[0]; + + //When the node exists but is empty Excel does not apply default styles + //It directly applies the themedLineColor + if (nodeIsEmpty) { - l.Add(ConvertUtil.GetValueDouble(xSerie[i])); + //Is node empty inside the theme + if (themedLine.HasFill == false) + { + lineColor = Color.Transparent; + return themedLine; + } + + bool isSchemeColor = themedLine.Fill.SolidFill.Color.ColorType == eDrawingColorType.Scheme && themedLine.Fill.SolidFill.Color.SchemeColor.Color == eSchemeColor.Style; + + if (isSchemeColor) + { + lineColor = GetDefaultBorderColorForElement(element, styleId); + + if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0 && lineColor.HasValue) + { + //var schemeClr = tc.ColorConverter.GetSchemeColor(ChartRenderer.Theme, eSchemeColor.Dark1); + //var tint = GetSchemeColorTint(eSchemeColor.Dark1, 0.45d); + lineColor = tc.ColorConverter.ApplyTransforms(lineColor.Value, themedLine.Fill.SolidFill.Color.Transforms); + } + + return themedLine; + } + else + { + lineColor = themedLine.Fill.Color; + } + return themedLine; + } + + lineColor = GetDefaultBorderColorForElement(element, styleId); + + var themeColor = tc.ColorConverter.GetThemeColor(ChartRenderer.Theme, themedLine.Fill.SolidFill.Color); + if (themedLine.HasFill == false) + { + //Node exists but has no fill. Excel considers this the same as transparent/noFill + lineColor = Color.Transparent; + return themedLine; + } + + if (styleId < 41 || element == ChartElement.OtherLines) + { + if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0) + { + //themeColor = tc.ColorConverter.ApplyTintDrawing(themeColor.Value, 0.15d); + //but even in this case if there is no ln node found in style it appears to default to 75% despite a scheme color existing in the theme + lineColor = tc.ColorConverter.ApplyTransforms(lineColor.Value, themedLine.Fill.SolidFill.Color.Transforms); + } + else + { + //Default value Should arguably be 75% tint themeColor but something is strange... + //It appears closer to 50% in this specific case + //It also appears to be tx1 (black) and apply color and tint 0.25 in vba + var newTheme = tc.ColorConverter.ApplyTintDrawing(lineColor.Value, 0.25d); + lineColor = newTheme; + } } else { - return xSerie.Select((x, index) => (double)(index + 1)).ToList(); + //No Line + lineColor = Color.Transparent; + return null; } + + return themedLine; + } + else + { + throw new InvalidOperationException( + $"The enum option: '{Enum.GetName(typeof(ChartElement), element)}' is invalid. " + + $"Only ChartArea or Floor has a default themed line"); } - return l; } + + /// + /// + /// + /// + /// + /// The fill color with fill styles etc applied + /// + /// + protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, out Color? fillColor) + { + //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 + //Alternatively it's an unkown or unset style which should also default to style2 + var styleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; + + var bg = ChartRenderer.Theme.FormatScheme.BackgroundFillStyle[0]; + + if ((ChartElement.Floor | ChartElement.Walls).HasFlag(element)) + { + fillColor = GetDefaultFillColorForElement(element, styleId); + var themedFill = ChartRenderer.Theme.FormatScheme.BackgroundFillStyle[0]; + + if (styleId > 32) + { + if (themedFill.SolidFill.Color.Transforms.Count > 0) + { + fillColor = tc.ColorConverter.ApplyTransforms(fillColor.Value, themedFill.SolidFill.Color.Transforms); + } + else + { + //Hardcoded default for fills without any actual info in excel + var newTheme = tc.ColorConverter.ApplyTintDrawing(fillColor.Value, 0.75d); + fillColor = newTheme; + } + } + else + { + //No Fill + fillColor = Color.Transparent; + return null; + } + + return themedFill; + } + else + { + throw new InvalidOperationException( + $"The enum option: '{Enum.GetName(typeof(ChartElement), element)}' is invalid. " + + $"Only Walls or Floor has a default themed fill"); + } + } + + protected Color? GetDefaultBorderColorForElement(ChartElement element, int ChartStyleId) + { + //return Color.Empty; + + if ((ChartElement.Axis | ChartElement.MajorGridLines).HasFlag(element)) + { + //There's only really two options in this particular case + if (ChartStyleId <= 32) + { + return GetSchemeColorTint(eSchemeColor.Text1, 0.75d); + } + else + { + return GetSchemeColorTint(eSchemeColor.Dark1, 0.75d); + } + } + else if (element.HasFlag(ChartElement.MinorGridLines)) + { + var retCol = GetSchemeColorTint(eSchemeColor.Text1, 0.5d); + var retCol2and3 = GetSchemeColorTint(eSchemeColor.Dark1, 0.5d); + var retCol4 = GetSchemeColorTint(eSchemeColor.Dark1, 0.9d); + + return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); + } + else if ((ChartElement.ChartArea | ChartElement.DataTable | ChartElement.Floor).HasFlag(element)) + { + var retCol = GetSchemeColorTint(eSchemeColor.Text1, 0.75d); + var retCol2and3 = GetSchemeColorTint(eSchemeColor.Dark1, 0.75d); + var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); + + return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); + } + else + { + //Other lines should technically always be the enum here but keep it as Else just in case + var retCol = GetSchemeColorTint(eSchemeColor.Text1, 1d); + var retCol2and3 = GetSchemeColorTint(eSchemeColor.Dark1, 1d); + var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); + + return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); + } + } + + + private Color? GetDefaultAccent(int ChartStyleId) + { + if (ChartStyleId < 35 || ChartStyleId > 40) + { + throw new InvalidOperationException($"Invalid ChartStyleId '{ChartStyleId}'" + + $"Default Accent tint must be between 35 and 40"); + } + //35 == accent1, 36 == accent2 etc. + var accentColor = (eSchemeColor.Accent1 + (ChartStyleId) - 35); + return GetSchemeColorTint(accentColor, 0.2d); + } + + protected Color? GetDefaultFillColorForElement(ChartElement element, int ChartStyleId) + { + ChartStyleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; + + if (element.HasFlag(ChartElement.ChartArea)) + { + var retCol = GetSchemeColorTint(eSchemeColor.Background1); + var retCol2And3 = GetSchemeColorTint(eSchemeColor.Text1); + var retCol4 = GetSchemeColorTint(eSchemeColor.Background1); + + return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2And3, retCol2And3, retCol4); + } + else if ((ChartElement.Floor | ChartElement.Walls | ChartElement.PlotArea2d).HasFlag(element)) + { + var retCol = GetSchemeColorTint(eSchemeColor.Background1); + var retCol2 = GetSchemeColorTint(eSchemeColor.Background1, 0.2d); + Color? retCol3 = Color.Empty; + if (ChartStyleId > 35 && ChartStyleId < 40) + { + retCol3 = GetDefaultAccent(ChartStyleId); + } + var retCol4 = GetSchemeColorTint(eSchemeColor.Background1, 0.95d); + + return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2, retCol3.Value, retCol4); + } + else + { + return null; + } + } + + protected Color GetEffectForChartElement(ChartElement element, int ChartStyleId) + { + throw new NotImplementedException("This method has not been implmented yet"); + } + internal abstract Color? DefaultFillColor { get; } + internal abstract Color? DefaultBorderColor { get; } + abstract internal Color? GetDefaultFillColor(); + abstract internal Color? GetDefaultBorderColor(); + } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index cf394b1dbf..a92496f82f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -17,10 +17,12 @@ Date Author Change using EPPlus.Fonts.OpenType.Integration; using EPPlus.Graphics; using EPPlusImageRenderer.RenderItems; +using OfficeOpenXml.Core; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Renderer.Chart; using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Statistical; using OfficeOpenXml.Interfaces.Drawing.Text; using OfficeOpenXml.Style; using System; @@ -29,7 +31,18 @@ Date Author Change using System.Linq; namespace EPPlusImageRenderer.Svg { - internal class ChartLegendRenderer : ChartDrawingObject + internal interface ILegendKeyContainer + { + float MarginItemsWidth { get; } + double MaxWidth { get; } + double MaxHeight { get; } + List SeriesHeadersMeasure { get; } + List SeriesIcon { get; } + eLegendPosition Position { get; } + EPPlusReadOnlyList Entries { get; } + ExcelTextBody TextBody { get; } + } + internal class ChartLegendRenderer : ChartDrawingObjectWithBackground, ILegendKeyContainer { List _seriesHeadersMeasure = new List(); @@ -40,9 +53,6 @@ internal class ChartLegendRenderer : ChartDrawingObject const float MinBarLength = 4; float MinPieLength = 5.25f; float _marginItemsWidth; - - eLegendPosition Position { get; } - double _maxWidth, _maxHeight; internal ChartLegendRenderer(ChartRenderer sc) : base(sc) { @@ -62,8 +72,6 @@ internal ChartLegendRenderer(ChartRenderer sc) : base(sc) TopMargin = BottomMargin = 3; //4px _marginItemsWidth = mf.Size / 2; //We use half the size of the font as margin between items. - Position = l.Position; - switch (l.Position) { case eLegendPosition.Top: @@ -92,8 +100,8 @@ internal ChartLegendRenderer(ChartRenderer sc) : base(sc) Rectangle = GetRectFromManualLayout(sc, l.Layout); } - Rectangle.SetDrawingPropertiesFill(sc.Theme, l.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); - Rectangle.SetDrawingPropertiesBorder(sc.Theme, l.Border, sc.Chart.StyleManager.Style?.Legend.BorderReference.Color, l.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); + Rectangle.Style.SetDrawingPropertiesFill(sc.Theme, l.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); + Rectangle.Style.SetDrawingPropertiesBorder(sc.Theme, l.Border, sc.Chart.StyleManager.Style?.Legend.BorderReference.Color, l.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); var pSls = SetLegendSeries(entryWidth, entryHeight); SetLegendTrendlines(entryWidth, entryHeight, pSls); @@ -322,40 +330,6 @@ private Color GetAccentBasedOnPos(int pos) return ChartRenderer.Theme.ColorScheme.GetColorByEnum(eSchemeColor.Accent1 + mod6).GetColor(); } - private ExcelChartDataPointCollection SetDataPointColors(ExcelChartDataPointCollection dataPoints, bool varyColors, bool hasStyle, ExcelChartStandardSerie serie, Color? overrideColor = null) - { - if (serie.NumberOfItems > dataPoints.Count) - { - var prevPointCount = dataPoints.Count; - dataPoints.ClearDataPoints(); - - for (int i = 0; i < serie.NumberOfItems; i++) - { - dataPoints.Add(i); - var defaultFill = DefaultFillColor; - if(overrideColor != null) - { - defaultFill = overrideColor; - } - - if (varyColors) - { - if (hasStyle == false) - { - defaultFill = GetAccentBasedOnPos(i); - } - } - //Ensures we don't over-write user set style data - if (defaultFill != null && i > prevPointCount - 1) - { - dataPoints[i].Fill.Color = defaultFill.Value; - } - } - } - - return dataPoints; - } - private void GetSerieSize(ExcelChartLegend l, int index, string text, ref double widest, ref double highest) { var entry = l.Entries.FirstOrDefault(x => x.Index == index); @@ -407,7 +381,7 @@ private double GetMaxIconLength(ExcelChart ct, double highestText) var maxIconLength = 0D; foreach(var c in ct.PlotArea.ChartTypes) { - var il = GetIconLength(c, highestText); + var il = LegendIconRenderer.GetIconLength(c, highestText); if (il > maxIconLength) { maxIconLength = il; @@ -419,10 +393,6 @@ private double GetMaxIconLength(ExcelChart ct, double highestText) } return maxIconLength; } - private double GetIconLength(ExcelChart c, double highestText) - { - return c.IsTypeLine() ? LineLength : Math.Max(MinBarLength, highestText * 0.4); - } internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeight) @@ -433,7 +403,7 @@ internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeigh var maxIconLength = GetMaxIconLength(Chart, entryHeight); if ((Chart.IsTypeBar() || Chart.IsTypeColumn()) && Chart.PlotArea.ChartTypes.Count == 1 && Chart.PlotArea.ChartTypes[0].Series.Count == 1) { - SetBarLegendSingle(entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetBarLegendSingle(ChartRenderer, this, entryWidth, entryHeight, maxIconLength); } else { @@ -464,7 +434,7 @@ internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeigh case eChartType.LineMarkersStacked100: case eChartType.LineStacked: case eChartType.LineStacked100: - SetLineLegend(ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetLineLegend(ChartRenderer,this, ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); break; case eChartType.ColumnClustered: case eChartType.ColumnStacked: @@ -472,13 +442,13 @@ internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeigh case eChartType.BarClustered: case eChartType.BarStacked: case eChartType.BarStacked100: - SetBarLegend(ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetBarLegend(ChartRenderer, this, ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); break; case eChartType.Pie: case eChartType.PieExploded: if (ix == 0) { - SetPieLegend(ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetPieLegend(ChartRenderer, this, ct, index, pSls, s, sls, entryWidth, entryHeight, maxIconLength); pSls = null; sls = null; } @@ -489,14 +459,14 @@ internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeigh if (Chart.Legend.Position == eLegendPosition.Top || Chart.Legend.Position == eLegendPosition.Bottom) { - //if (sls.Textbox.Bounds.Bottom > Rectangle.Bottom) + //if (sls.Textbox.Bottom > Rectangle.Bottom) //{ // break; //} } else { - if (sls != null && sls.Textbox.Bounds.Bottom > Rectangle.Height) + if (sls != null && sls.Textbox.Bottom > Rectangle.Height) { break; } @@ -524,92 +494,6 @@ internal DrawingLegendSerie SetLegendSeries(double entryWidth, double entryHeigh } } return pSls; - // if (Chart.Legend.Position == eLegendPosition.Top || - // Chart.Legend.Position == eLegendPosition.Bottom) - // { - // if (sls.Textbox.Bounds.Bottom > Rectangle.Bottom) - // { - // break; - // } - // } - // else - // { - // if (sls.Textbox.Bounds.Bottom > Rectangle.Height) - // { - // break; - // } - // } - // SeriesIcon.Add(sls); - // pSls = sls; - // index++; - // if(ix(); - catValues.Add(""); - Rectangle.Height = entryHeight + MarginHeight; - } - - var index = 0; - DrawingLegendSerie pSls = null; - - foreach(var cv in catValues) - { - var sls=new DrawingLegendSerie(); - var bs = (ExcelBarChartSerie)s; - var tm = _seriesHeadersMeasure[index]; - var si = GetBarSeriesIcon(ct, bs, pSls, entryWidth, entryHeight, 0, index); - sls.SeriesIcon = si; - - var tbLeft = si.Left + maxIconLength + MarginIconText; - var tbTop = si.Top - (entryHeight - si.Height) / 2; - double tbWidth; - - tbWidth = Rectangle.Bounds.Width - tbLeft; - - var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); - //sls.Textbox.Bounds.Left = si.Bottom + MarginIconText; - - var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); - var headerText = cv.ToString(); - if (entry == null || entry.Font.IsEmpty) - { - //sls.Textbox.AddText(s.GetHeaderText(), sc.Chart.Legend.Font); - sls.Textbox.ImportParagraph(Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } - else - { - //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); - sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } - SeriesIcon.Add(sls); - pSls = sls; - index++; - } } private void SetLegendTrendlines(double entryWidth, double entryHeight, DrawingLegendSerie pSls) @@ -639,7 +523,7 @@ private void SetLegendTrendlines(double entryWidth, double entryHeight, DrawingL SetTrendlineLegend(ct, ix, index, pSls, pos, tl, sls, entryWidth, entryHeight); - if (sls.Textbox.Bounds.Bottom > Rectangle.Height) + if (sls.Textbox.Bottom > Rectangle.Height) { return; } @@ -663,16 +547,16 @@ private void SetLegendTrendlines(double entryWidth, double entryHeight, DrawingL private void SetTrendlineLegend(ExcelChart ct, int serieIndex, int entryIndex, DrawingLegendSerie pSls, eLegendPosition pos, ExcelChartTrendline tl, DrawingLegendSerie sls, double entryWidth, double entryHeight) { - var si = GetTrendLineSeriesIcon(ct, tl, pSls, entryWidth, entryHeight); + var si = LegendIconRenderer.GetTrendLineSeriesIcon(ChartRenderer, ct, this, tl, pSls, entryWidth, entryHeight); sls.SeriesIcon = si; var tbLeft = si.X1 + LineLength + MarginIconText; var tbTop = si.Y2 - entryHeight * 0.5; //TODO:Should probably be font ascent double tbWidth; - tbWidth = Rectangle.Bounds.Width - tbLeft; + tbWidth = Rectangle.Width - tbLeft; var tbHeight = entryHeight; - sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == entryIndex); var headerText = tl.GetName(serieIndex); @@ -685,402 +569,69 @@ private void SetTrendlineLegend(ExcelChart ct, int serieIndex, int entryIndex, D sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); } } - - - private void SetPieLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) - { - var ps = (ExcelPieChartSerie)s; - pSls = null; - - //Pie chart only cares about series 0 - var series = ct.Series[0]; - var catSeries = series.XSeries; - var catValues = DrawingExtensions.LoadSeriesValues(ct, catSeries, series.NumberLiteralsX, series.StringLiteralsX); - - //Excel fallsback to index + 1 if no literals and no series - if (catValues == null) - { - catValues = new List(); - foreach (var dp in ps.DataPoints) - { - catValues.Add($"{dp.Index + 1}"); - } - } - - - - Chart.Legend.TextBody.GetInsetsOrDefaults(out double lDefMargin, out double tDefMarg, out double rDefMargin, out double bDefMarg); - - var widestEntry = entryWidth; - - double lastWidth = 0d; - double totalWidth = 0d; - - double firstIconWidth = 0d; - - for (int i = 0; i < catValues.Count; i++) - { - var tm = _seriesHeadersMeasure[index + i]; - //Step 1: Retrieve Icon - - var si = GetPieSeriesIcon(ct, ps, pSls, lastWidth, entryHeight, i); - //The si-width is used as left margin for each entry seemingly - //si.Left += (si.BorderWidth ?? 0d)*2d; - if(si.Left < 4.5d) - { - si.Left = 4.5d; - } - - if (i == 0) - { - firstIconWidth = si.Width; - } - sls = new DrawingLegendSerie(); - var tbLeft = si.Left + si.Width + MarginIconText + (si.BorderWidth ?? 0d); - var tbTop = si.Top - ((entryHeight + MarginIconText) / 2); - - double tbWidth; - - if(i != catValues.Count -1) - { - tbWidth = Rectangle.Bounds.Width - tbLeft; - } - else - { - tbWidth = Rectangle.Bounds.Width; - } - - var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); - //var para = sc.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(); - //Chart.Legend.TextBody.GetInsetsOrDefaults(out double lMarg, out double tMarg, out double rMarg, out double bMarg); - - //if (sls.Textbox.LeftMargin == 0) - //{ - // sls.Textbox.LeftMargin = lMarg; - //} - //if (sls.Textbox.RightMargin == 0) - //{ - // sls.Textbox.RightMargin = rMarg; - //} - - sls.Textbox.ImportParagraph(Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, catValues[i].ToString()); - - //si.Left += sls.Textbox.LeftMargin; - - sls.SeriesIcon = si; - sls.Textbox.RecalculateParagraphs(); - - tbWidth = sls.Textbox.Width + rDefMargin; /*+ lDefMargin + rDefMargin;*/ - - lastWidth = tbWidth + si.Width - (si.BorderWidth ?? 0d); - - totalWidth += tbWidth + si.Width + (si.BorderWidth ?? 0d) + MarginIconText; - - if (i >= 0 && ps.DataPoints.ContainsKey(i)) - { - var dp = ps.DataPoints[i]; - ChartTypeDrawer.SetFillDataPoint(Chart, ps, i, sls.SeriesIcon, dp, Chart.StyleManager.Style?.SeriesLine); - } - else - { - ChartTypeDrawer.SetFillSerie(Chart, ct, ps, 0, i, sls.SeriesIcon); - } - - SeriesIcon.Add(sls); - pSls = sls; - } - - foreach(var icon in SeriesIcon) - { - icon.SeriesIcon.Bounds.Top = icon.SeriesIcon.Bounds.Top - ((entryHeight) / 4); - } - - if(Position == eLegendPosition.Top || Position == eLegendPosition.Bottom) - { - //Rectangle.Bounds.Width = totalWidth; - Rectangle.Bounds.Width = SeriesIcon.Last().Textbox.Bounds.GetGlobalBoundingbox().Right - SeriesIcon[0].SeriesIcon.Bounds.GlobalLeft + 4d + firstIconWidth * 2; - Rectangle.Bounds.Left = ((ChartRenderer.Bounds.Width) / 2d) - (totalWidth / 2d) + 1.5d; - - if(Rectangle.Bounds.Width < _maxWidth) - { - Rectangle.Bounds.Height = entryHeight + TopMargin + BottomMargin; - Rectangle.Bounds.Top = ChartRenderer.ChartArea.Rectangle.Height - Rectangle.Height - BottomMargin - TopMargin; - } - } - pSls = null; - sls = null; - } - - private void SetLineLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) + public override void AppendRenderItems(List renderItems) { - var ls = (ExcelLineChartSerie)s; - - var si = GetLineSeriesIcon(ct, ls, pSls, entryWidth, entryHeight); - sls.SeriesIcon = si; - - var tbLeft = si.X1 + maxIconLength + MarginIconText; - var tbTop = si.Y2 - entryHeight * 0.5; - var tbWidth = Rectangle.Bounds.Width - tbLeft; - - var tbHeight = entryHeight; - sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + var groupItem = new GroupRenderItem(ChartRenderer.Bounds); + groupItem.Name = "Legend"; + groupItem.Top = Rectangle.Top; + groupItem.Left = Rectangle.Left; + renderItems.Add(groupItem); - var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); - var headerText = s.GetHeaderText(index); - if (entry == null || entry.Font.IsEmpty) - { - sls.Textbox.ImportParagraph(Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } - else - { - //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); - sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } + //The rectangle is position using the group transform, so we need to set the rectangle position to 0,0 + Rectangle.Top = 0; + Rectangle.Left = 0; - if (ls.HasMarker() && ls.Marker.Style != eMarkerStyle.None) + groupItem.ChildObjects.Add(Rectangle); + foreach(var s in SeriesIcon) { - var l = sls.SeriesIcon as LineRenderItem; - var x = l.X1 + (l.X2 - l.X1) / 2; - var y = l.Y1; - sls.MarkerIcon = LineMarkerHelper.GetMarkerItem(ChartRenderer, ls, ls.Marker, x, y, true); - if ((ls.Marker.Style == eMarkerStyle.Plus || ls.Marker.Style == eMarkerStyle.X || ls.Marker.Style == eMarkerStyle.Star) && - ls.Marker.Fill.IsEmpty == false) - { - sls.MarkerBackground = LineMarkerHelper.GetMarkerBackground(ChartRenderer, ls, x, y, true); - } - else - { - sls.MarkerBackground = null; - } + if(s.SeriesIcon != null) s.SeriesIcon.Parent = groupItem; + if(s.MarkerBackground != null) s.MarkerBackground.Parent = groupItem; + if (s.MarkerIcon != null) s.MarkerIcon.Parent = groupItem; + //renderItems.Add(s.Textbox); + if(s.Textbox != null) s.Textbox.Parent = groupItem; } } + internal override Color? DefaultFillColor => Color.Transparent; - private void SetBarLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) - { - var bs = (ExcelBarChartSerie)s; - var tm = _seriesHeadersMeasure[index]; - var si = GetBarSeriesIcon(ct, bs, pSls, entryWidth, entryHeight, index, -1); - sls.SeriesIcon = si; - - var tbLeft = si.Left + maxIconLength + MarginIconText; - var tbTop = si.Top - (entryHeight - si.Height) / 2; - double tbWidth; - - tbWidth = Rectangle.Bounds.Width - tbLeft; + internal override Color? DefaultBorderColor => Color.Transparent; - var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); - //sls.Textbox.Bounds.Left = si.Bottom + MarginIconText; + public float MarginItemsWidth => _marginItemsWidth; - var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); - var headerText = s.GetHeaderText(index); - if (entry == null || entry.Font.IsEmpty) - { - //sls.Textbox.AddText(s.GetHeaderText(), sc.Chart.Legend.Font); - sls.Textbox.ImportParagraph(Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } - else - { - //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); - sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); - } - } - - private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie cStandardSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight) + public double MaxWidth => _maxWidth; + internal override Color? GetDefaultFillColor() { - var line = new LineRenderItem(Rectangle.Bounds); - //line.SetDrawingPropertiesFill(ChartRenderer.Theme, cStandardSerie.Fill, Chart.StyleManager.Style?.SeriesLine.FillReference.Color, false, ChartRenderer.Theme.ColorScheme.Accent1.GetColor()); - //Default style is NoLine NoFill - line.SetDrawingPropertiesBorder(ChartRenderer.Theme, cStandardSerie.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.IsEmpty || cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Empty, 3); - double iconTop = 0, iconLeft = 0; - pSls?.GetIconTopLeft(out iconTop, out iconLeft); - - GetItemPosition(pSls, entryWidth, entryHeight, iconLeft, iconTop, out double x, out double y); - - line.X1 = x; - line.X2 = x + LineLength; - line.Y1 = y; - line.Y2 = y; - line.LineCap = LineCap.Round; - - return line; + return DefaultFillColor; } - private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline tl, DrawingLegendSerie pSls, double entryWidth, double entryHeight) - { - var line = new LineRenderItem(Rectangle.Bounds); - line.SetDrawingPropertiesFill(ChartRenderer.Theme, tl.Fill, Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); - //Default is actually NoLine - line.SetDrawingPropertiesBorder(ChartRenderer.Theme, tl.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, tl.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); - double iconTop = 0, iconLeft = 0; - pSls?.GetIconTopLeft(out iconTop, out iconLeft); - - GetItemPosition(pSls, entryWidth, entryHeight, iconLeft, iconTop, out double x, out double y); - - line.X1 = x; - line.Y1 = y; - line.X2 = x + LineLength; - line.Y2 = y; - line.LineCap = LineCap.Round; - - return line; - } - - private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int i) - { - var item = new RectRenderItem(Rectangle.Bounds); - var iconHeight = GetIconLength(ct, entryHeight); - var icon = pSls?.SeriesIcon as RectRenderItem; + public double MaxHeight => _maxHeight; - GetItemPosition(pSls, entryWidth, entryHeight, icon?.Left ?? 0D, icon?.Top ?? 0D, out double x, out double y); + public List SeriesHeadersMeasure => _seriesHeadersMeasure; - item.LineCap = LineCap.Round; - item.Left = x; - if (pSls != null && (Chart.Legend.Position == eLegendPosition.Left || Chart.Legend.Position == eLegendPosition.Right)) - { - item.Top = y - (iconHeight / 2d); - } - else - { - item.Top = y; - } - - double borderWidth = pcS.Border.Width; - - if (pcS.DataPoints != null && pcS.DataPoints.Count != 0 && i < pcS.DataPoints.Count) - { - if(borderWidth < pcS.DataPoints[i].Border.Width) - { - borderWidth = pcS.DataPoints[i].Border.Width; - } - } - - //item.Top = y; - item.Width = iconHeight; - item.Height = iconHeight; - - item.SetDrawingPropertiesFill(ChartRenderer.Theme, pcS.Fill, Chart.StyleManager.Style?.SeriesLine.FillReference.Color); - item.SetDrawingPropertiesBorder(ChartRenderer.Theme, pcS.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 1.5d); - - return item; + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; } - - private RectRenderItem GetBarSeriesIcon(ExcelChart ct, ExcelBarChartSerie chartSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int serieIndex, int index) - { - var item = new RectRenderItem(Rectangle.Bounds); - var iconHeight = GetIconLength(ct, entryHeight); - //var icon = pSls?.SeriesIcon as RectRenderItem; - double iconTop = 0, iconLeft = 0; - pSls?.GetIconTopLeft(out iconTop, out iconLeft); - - GetItemPosition(pSls, entryWidth, entryHeight, iconLeft, iconTop + (iconHeight / 2), out double x, out double y); - - item.LineCap = LineCap.Round; - item.Left = x; - if(pSls !=null && (Chart.Legend.Position == eLegendPosition.Left || Chart.Legend.Position == eLegendPosition.Right)) - { - item.Top = y - iconHeight / 2d; - } - else - { - item.Top = y - iconHeight / 2d; - } - //item.Top = y; - item.Width = iconHeight; - item.Height = iconHeight; - - if (index>=0 && chartSerie.DataPoints.ContainsKey(index)) - { - var dp = chartSerie.DataPoints[index]; - ChartTypeDrawer.SetFillDataPoint(Chart, chartSerie, index, item, dp, Chart.StyleManager.Style?.SeriesLine); - } - else - { - ChartTypeDrawer.SetFillSerie(Chart, ct, chartSerie, serieIndex, index, item); - } - - return item; - } - private double GetItemPosition(DrawingLegendSerie pSls, double entryWidth, double entryHeight, double iconLeft, double iconCenter, out double x, out double y) + internal double GetBottomOverlay(double overlayValue) { - var topOffset = 0D; - if (Chart.Legend.Position == eLegendPosition.Top || - Chart.Legend.Position == eLegendPosition.Bottom) + if (Chart.Legend.Overlay) { - if (pSls != null && iconLeft + entryWidth * 2 + _marginItemsWidth + RightMargin > _maxWidth) - { - topOffset += entryHeight * 1.25; - x = LeftMargin; - } - else - { - if (pSls == null) - { - x = (float)LeftMargin; - } - else - { - x = iconLeft + entryWidth + _marginItemsWidth; - } - } - - if (pSls == null) - { - y = TopMargin + entryHeight / 2; - } - else - { - y = iconCenter + topOffset; - } - - + return overlayValue; } else { - if (pSls == null) - { - y = TopMargin + entryHeight / 2; - } - else - { - y = iconCenter + entryHeight * 1.5; - } - x = LeftMargin; - + return Rectangle.GlobalBottom; } - - return topOffset; } - public override void AppendRenderItems(List renderItems) - { - var groupItem = new GroupRenderItem(ChartRenderer.Bounds); - groupItem.Bounds.Name = "Legend"; - groupItem.Top = Rectangle.Bounds.Top; - groupItem.Left = Rectangle.Bounds.Left; - renderItems.Add(groupItem); + public List SeriesIcon { get; } = new List(); - //The rectangle is position using the group transform, so we need to set the rectangle position to 0,0 - Rectangle.Bounds.Top = 0; - Rectangle.Bounds.Left = 0; + public eLegendPosition Position => ChartRenderer.Chart.Legend?.Position ?? eLegendPosition.Right; - groupItem.RenderItems.Add(Rectangle); - foreach(var s in SeriesIcon) - { - if(s.SeriesIcon != null) groupItem.RenderItems.Add(s.SeriesIcon); - if(s.MarkerBackground != null) groupItem.RenderItems.Add(s.MarkerBackground); - if (s.MarkerIcon != null) groupItem.RenderItems.Add(s.MarkerIcon); - //renderItems.Add(s.Textbox); - if(s.Textbox != null) s.Textbox.AppendRenderItems(groupItem.RenderItems); - } - } - - public List SeriesIcon { get; } = new List(); + public EPPlusReadOnlyList Entries => ChartRenderer.Chart.Legend.Entries; - internal override Color? DefaultFillColor => Color.Transparent; + public ExcelTextBody TextBody => ChartRenderer.Chart.Legend.TextBody; - internal override Color? DefaultBorderColor => Color.Transparent; + public bool Overlay => ChartRenderer.Chart.Legend.Overlay; } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index 9197fdf35b..91aebce171 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -22,10 +22,11 @@ Date Author Change using System.Collections.Generic; using System.Drawing; using System.Linq; +using EPPlus.Graphics; namespace EPPlusImageRenderer.Svg { - internal class ChartPlotareaRenderer : ChartDrawingDefaultObject + internal class ChartPlotareaRenderer : ChartDrawingObjectWithBackground { public ChartPlotareaRenderer(ChartRenderer sc) : base(sc) { @@ -41,8 +42,8 @@ internal void SetPlotAreaRectangle() _pa = Chart.PlotArea; TopMargin = BottomMargin = LeftMargin = RightMargin = 10.5; //14px Group = new GroupRenderItem(ChartRenderer.Bounds); - Group.Bounds.Name = "PlotArea"; - var rect = new RectRenderItem(Group.Bounds); + Group.Name = "PlotArea"; + var rect = new RectRenderItem(Group); if (_pa.Layout.HasLayout) { rect = GetRectFromManualLayout(ChartRenderer, _pa.Layout); @@ -55,10 +56,10 @@ internal void SetPlotAreaRectangle() rect.Height = GetPlotAreaHeight(rect); } - Group.Bounds.Top = rect.Top; - Group.Bounds.Left = rect.Left; + Group.Top = rect.Top; + Group.Left = rect.Left; rect.Top = rect.Left = 0; - Group.RenderItems.Add(rect); + //Group.ChildObjects.Add(rect); //Dupl? if(ChartRenderer.Legend!=null && Chart.Legend.Position == eLegendPosition.Right || Chart.Legend.Position == eLegendPosition.Left) @@ -66,12 +67,13 @@ internal void SetPlotAreaRectangle() ChartRenderer.Legend.Rectangle.Top = Group.Top + rect.Height / 2 - ChartRenderer.Legend.Rectangle.Height / 2; } - rect.SetDrawingPropertiesFill(ChartRenderer.Theme, _pa.Fill, ChartRenderer.Chart.StyleManager.Style?.PlotArea.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - rect.SetDrawingPropertiesBorder(ChartRenderer.Theme, _pa.Border, ChartRenderer.Chart.StyleManager.Style?.PlotArea.BorderReference.Color, _pa.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); + rect.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, _pa.Fill, ChartRenderer.Chart.StyleManager.Style?.PlotArea.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); + rect.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _pa.Border, ChartRenderer.Chart.StyleManager.Style?.PlotArea.BorderReference.Color, _pa.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); Rectangle = rect; + Rectangle.Name = "PlotArea_Rectangle"; } - private double GetPlotAreaHeight(RectRenderItem rect) + internal double GetPlotAreaHeight(RectRenderItem rect) { var bottomAxis = GetAxisActualByPosition(eActualAxisPosition.Bottom); double vaHeight = 0; @@ -86,30 +88,35 @@ private double GetPlotAreaHeight(RectRenderItem rect) vaHeight = secAxis.Title?.Rectangle?.Height??0D; } } - vaHeight += (bottomAxis.Rectangle?.Height ?? 0D) + (bottomAxis.Title?.TextBox?.GetActualHeight() ?? 0D) + (bottomSecondAxis?.Rectangle?.Height ?? 0D); + + vaHeight += (bottomAxis.Rectangle?.Height ?? 0D) + (bottomAxis.Title?.GetHeightOverlay(0) ?? 0D) + (bottomSecondAxis?.Rectangle?.Height ?? 0D); } else { var bottomAx = GetAxisByPosition(eAxisPosition.Bottom); if(bottomAx!=null) //Title is always placed on bottom. { - vaHeight = bottomAx.Title?.TextBox?.GetActualHeight() ?? 0D; + vaHeight = bottomAx.Title?.GetHeightOverlay(0) ?? 0D; } } - if (Chart.Legend?.Position == eLegendPosition.Bottom) + if (Chart.Legend?.Position == eLegendPosition.Bottom && Chart.Legend.Overlay==false) { vaHeight += ChartRenderer.Legend.Rectangle.Height + ChartRenderer.Legend.TopMargin; } + if(ChartRenderer.HasDataTable) + { + vaHeight += ChartRenderer.DataTable.Rectangle.Height + ChartRenderer.DataTable.TopMargin; + } return ChartRenderer.Bounds.Height - rect.GlobalTop - vaHeight - BottomMargin; } - private double GetPlotAreaWidth(RectRenderItem rect) + internal double GetPlotAreaWidth(RectRenderItem rect) { var rightActualAxis = GetAxisActualByPosition(eActualAxisPosition.Right); var rightSecondAxis = GetAxisActualByPosition(eActualAxisPosition.RightSecond); var lp = ChartRenderer.Chart.Legend?.Position; - var right = ((lp == eLegendPosition.Right || lp == eLegendPosition.TopRight) && ChartRenderer.Legend != null ? - ChartRenderer.Legend.Rectangle.Bounds.GlobalLeft - RightMargin : + var right = ((lp == eLegendPosition.Right || lp == eLegendPosition.TopRight) && ChartRenderer.Legend != null && ChartRenderer.Legend.Overlay==false ? + ChartRenderer.Legend.Rectangle.GlobalLeft - RightMargin : ChartRenderer.ChartArea.Rectangle.Width - RightMargin); @@ -150,14 +157,15 @@ private double GetPlotAreaWidth(RectRenderItem rect) } } - return right - rightAxisWidth-rect.GlobalLeft; + return right - rightAxisWidth - rect.GlobalLeft; } private double GetPlotAreaLeft() { var left = LeftMargin; - if(ChartRenderer.Chart.Legend?.Position == eLegendPosition.Left) + var legend = ChartRenderer.Chart.Legend; + if (legend?.Position == eLegendPosition.Left && legend.Overlay == false) { - left += ChartRenderer.Legend.Rectangle.Bounds.Width + ChartRenderer.Legend.RightMargin; + left += ChartRenderer.Legend.Rectangle.Width + ChartRenderer.Legend.RightMargin; } var leftAxis = GetAxisActualByPosition(eActualAxisPosition.Left); @@ -169,13 +177,13 @@ private double GetPlotAreaLeft() } else { - if (leftAxis.Title!=null) + if (leftAxis.Title!=null && leftAxis.Title.Overlay==false) { left += leftAxis.Title.TextBox.GetActualWidth(); } if (leftAxis.Rectangle != null) { - left += leftAxis.Rectangle.Width + 1.5; + left += (leftAxis.Rectangle?.Width ?? 0) + 1.5; } if(leftSecondAxis!=null) { @@ -188,19 +196,19 @@ private double GetPlotAreaTop() { double haHeight = 0; var topAxis = GetAxisActualByPosition(eActualAxisPosition.Top); - var topSecondAxis = GetAxisActualByPosition(eActualAxisPosition.TopSecond); + var topSecondAxis = GetAxisActualByPosition(eActualAxisPosition.TopSecond); if (topAxis == null) { //If the axis is not on the top, we should check if there is an axis that has the position on the top. If there is, we should reserve space for the title of the axis. This can happen when LabelPosition is set to Low and the axis is on the bottom, but the position of the axis is set to top. topAxis = GetAxisByPosition(eAxisPosition.Top); - haHeight = (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis?.Title?.Rectangle.Height ?? 0D); + haHeight = (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis?.Title?.GetHeightOverlay() ?? 0D); } else { - haHeight = (topAxis.Rectangle?.Height ?? 0D) + (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis.Title?.TextBox?.GetActualHeight() ?? 0D); + haHeight = (topAxis.Rectangle?.Height ?? 0D) + (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis.Title?.GetHeightOverlay() ?? 0D); } - return (Chart.Legend?.Position == eLegendPosition.Top ? ChartRenderer.Legend.Rectangle.Bounds.Bottom : ChartRenderer.Title?.Rectangle?.GlobalBottom ?? TopMargin) + haHeight; + return (Chart.Legend?.Position == eLegendPosition.Top ? ChartRenderer.Legend.GetBottomOverlay(0) : ChartRenderer.Title?.GetBottomOverlay(TopMargin) ?? TopMargin) + haHeight; } private ChartAxisRenderer GetAxisActualByPosition(eActualAxisPosition pos) @@ -244,7 +252,7 @@ private ChartAxisRenderer GetAxisByPosition(eAxisPosition pos) return null; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { renderItems.Add(Group); } @@ -266,7 +274,7 @@ internal void DrawSeries() { return null; } - + internal override Color? DefaultBorderColor => null; internal override Color? DefaultFillColor { get => GetDefaultFillColor(); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index 1907ab5c33..f6f47ea6a3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -37,11 +37,13 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal class ChartTitleRenderer : ChartDrawingObject + internal class ChartTitleRenderer : ChartDrawingObjectWithBackground { ExcelChartTitleStandard _title; string _titleText; ChartRenderer _svgChart; + internal bool Overlay { get { return _title.Overlay; } } + /// /// /// @@ -105,8 +107,8 @@ internal ChartTitleRenderer(ChartRenderer sc, ExcelChartTitleStandard t, string } } //Default NoFill for title and axis titles if not set - Rectangle.SetDrawingPropertiesFill(sc.Theme, t.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, null); - Rectangle.SetDrawingPropertiesBorder(sc.Theme, t.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, t.Border.Fill.Style != eFillStyle.NoFill, () => null, 0.75); + Rectangle.Style.SetDrawingPropertiesFill(sc.Theme, t.Fill, sc.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, null); + Rectangle.Style.SetDrawingPropertiesBorder(sc.Theme, t.Border, sc.Chart.StyleManager.Style?.Title.BorderReference.Color, t.Border.Fill.Style != eFillStyle.NoFill, () => null, 0.75); } private void SetAxisTitleRect(ChartRenderer sc, ChartAxisRenderer axis) @@ -193,7 +195,7 @@ private static string GetDefaultChartTitleText(ChartRenderer sc, ExcelChartTitle internal void InitTextBox(double maxWidth, double maxHeight) { - TextBox = new DrawingTextBox(ChartRenderer.RenderContext, _svgChart.Drawing, _svgChart.ChartArea.Rectangle.Bounds, maxWidth, maxHeight); + TextBox = new DrawingTextBox(ChartRenderer.RenderContext, _svgChart.Drawing, _svgChart.ChartArea.Rectangle, maxWidth, maxHeight); if (_title.Rotation != 0) { TextBox.Rotation = _title.Rotation; @@ -221,19 +223,57 @@ public DrawingTextBox TextBox get; private set; } internal override RectRenderItem Rectangle { get => TextBox.Rectangle; set => base.Rectangle = value; } - public override void AppendRenderItems(List renderItems) + + internal override Color? DefaultFillColor => null; + + internal override Color? DefaultBorderColor => null; + + public override void AppendRenderItems(List renderItems) { TextBox.Name = $"{Chart.Name}_Title_Rect"; var p = _title.DefaultTextBody.Paragraphs.FirstOrDefault(); if (p != null) { TextBox.TextBody.FontColorString = "#" + p.DefaultRunProperties.Fill.Color.ToColorString(); - TextBox.Rectangle.SetDrawingPropertiesFill(_svgChart.Theme, _title.Fill, _svgChart.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); + TextBox.Rectangle.Style.SetDrawingPropertiesFill(_svgChart.Theme, _title.Fill, _svgChart.Chart.StyleManager.Style?.Title.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); //Default is actually NoLine - TextBox.Rectangle.SetDrawingPropertiesBorder(_svgChart.Theme, _title.Border, _svgChart.Chart.StyleManager.Style?.Title.BorderReference.Color, _title.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); + TextBox.Rectangle.Style.SetDrawingPropertiesBorder(_svgChart.Theme, _title.Border, _svgChart.Chart.StyleManager.Style?.Title.BorderReference.Color, _title.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 0.75); } TextBox.AppendRenderItems(renderItems); } + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } + + internal double GetBottomOverlay(double overlayValue=0) + { + if(_title.Overlay) + { + return overlayValue; + } + else + { + return Rectangle.GlobalBottom; + } + } + + internal double GetHeightOverlay(double overlayValue=0) + { + if (_title.Overlay) + { + return overlayValue; + } + else + { + return TextBox?.GetActualHeight() ?? overlayValue; + } + } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs index 5b1d6292a5..debe68bd8c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs @@ -258,7 +258,7 @@ private void AddBar(ExcelBarChart chartType, ExcelBarChartSerie serie, List= 0 && serie.DataPoints.ContainsKey(i)) { var dp = serie.DataPoints[i]; - SetFillDataPoint(Chart, serie, i, rect, dp, Chart.StyleManager.Style?.SeriesLine); + SetFillDataPoint(Chart, serie, i, rect.Style, dp, Chart.StyleManager.Style?.SeriesLine); } else { - SetFillSerie(Chart, chartType, serie, position, i, rect); + SetFillSerie(Chart, chartType, serie, position, i, rect.Style); } - rect.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); + rect.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); - dataPoints.Add(rect.Bounds); + dataPoints.Add(rect); SeriesRenderItems.Add(rect); @@ -390,12 +390,22 @@ private void GetAxis(ExcelBarChart chartType, out ChartAxisRenderer yAxis, out C } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { renderItems.AddRange(ChartAreaRenderItems); SeriesRenderItems.ForEach(x => ChartRenderer.Plotarea.Group.AddChildItem(x)); } internal override Color? DefaultFillColor => ChartRenderer.Theme.ColorScheme.Accent1.GetColor(); internal override Color? DefaultBorderColor => null; + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs index 0c524ca674..89bdedbc32 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs @@ -18,7 +18,7 @@ namespace OfficeOpenXml.Drawing.Renderer.Chart.ChartTypeDrawers { - internal class ChartErrorBarRenderer : ChartDrawingDefaultObject + internal class ChartErrorBarRenderer : ChartDrawingObject { internal ExcelChartErrorBars _errorbars; private double[] _ySerie; @@ -227,23 +227,24 @@ internal List GetErrorBarRenderItem(int index, ChartAxisRenderer xAx } foreach (var ri in l) { + var style = ri.Style; if (_errorbars.Border.LineElement == null) { - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultFillColor, 0.75); - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultBorderColor, 0.75d); + style.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultFillColor, 0.75); + style.SetDrawingPropertiesBorder(ChartRenderer.Theme, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, true, GetDefaultBorderColor, 0.75d); } else { - ri.SetDrawingPropertiesBorder(ChartRenderer.Theme, _errorbars.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, _errorbars.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); + style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _errorbars.Border, ChartRenderer.Chart.StyleManager.Style?.ErrorBar.BorderReference.Color, _errorbars.Border.Fill.Style != eFillStyle.NoFill, GetDefaultBorderColor, 0.75); } - ri.SetDrawingPropertiesEffects(ChartRenderer.Theme, _errorbars.Effect); + style.SetDrawingPropertiesEffects(ChartRenderer.Theme, _errorbars.Effect); } return l; } internal override Color? GetDefaultFillColor() { - return GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + return DefaultFillColor; } internal override Color? GetDefaultBorderColor() @@ -271,5 +272,7 @@ internal override Color? DefaultBorderColor //} } } + + internal override Color? DefaultFillColor => GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs index d1879b7cb6..1bece4f366 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs @@ -2,6 +2,7 @@ using EPPlus.DrawingRenderer.RenderItems; using EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers; using EPPlus.Export.ImageRenderer.Utils; +using EPPlus.Graphics; using EPPlusImageRenderer; using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Svg; @@ -14,8 +15,6 @@ using OfficeOpenXml.Drawing.Style.Coloring; using OfficeOpenXml.Drawing.Theme; using OfficeOpenXml.ExternalReferences; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; @@ -24,7 +23,7 @@ namespace EPPlus.Export.ImageRenderer.Svg.Chart { - internal abstract class ChartTypeDrawer : ChartDrawingObject + internal abstract class ChartTypeDrawer : ChartDrawingObjectWithBackground { internal protected ExcelChart _chartType; internal List Trendlines { get; } = new List(); @@ -149,8 +148,8 @@ private void FillInternalValues(ExcelWorksheet ws, ExcelAddressBase address, ref } } - public List SeriesRenderItems { get; } = new List(); - public List ChartAreaRenderItems { get; } = new List(); + public List SeriesRenderItems { get; } = new List(); + public List ChartAreaRenderItems { get; } = new List(); internal static List Create(ChartRenderer svgChart) { var drawers = new List(); @@ -233,30 +232,30 @@ internal bool IsOnAxis(ExcelChartAxisStandard ax) { return _chartType.YAxis==ax || _chartType.XAxis==ax; } - internal static void SetFillDataPoint(ExcelChart chart, ExcelChartStandardSerie cStandardSerie, int index, RenderItem item, ExcelChartDataPoint dp, ExcelChartStyleEntry entry, UserSpaceSettings spaceSettings = UserSpaceSettings.UserSpaceOnUse_Object) + internal static void SetFillDataPoint(ExcelChart chart, ExcelChartStandardSerie cStandardSerie, int index, RenderItemStyle style, ExcelChartDataPoint dp, ExcelChartStyleEntry entry, UserSpaceSettings spaceSettings = UserSpaceSettings.UserSpaceOnUse_Object) { var theme = chart.WorkSheet.Workbook.ThemeManager.GetOrCreateTheme(); var color = GetVaryColor(theme, chart.StyleManager?.ColorsManager, index); - item.SetDrawingPropertiesFill(theme, dp.Fill.IsEmpty ? cStandardSerie.Fill : dp.Fill, entry?.FillReference.Color, spaceSettings, color); - item.SetDrawingPropertiesBorder(theme, dp.Border.IsEmpty ? cStandardSerie.Border : dp.Border, entry?.BorderReference.Color, dp.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); + style.SetDrawingPropertiesFill(theme, dp.Fill.IsEmpty ? cStandardSerie.Fill : dp.Fill, entry?.FillReference.Color, spaceSettings, color); + style.SetDrawingPropertiesBorder(theme, dp.Border.IsEmpty ? cStandardSerie.Border : dp.Border, entry?.BorderReference.Color, dp.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); } - internal static void SetFillSerie(ExcelChart chart, ExcelChart ct, ExcelChartStandardSerie cStandardSerie, int serieIndex, int index, RenderItem item) + internal static void SetFillSerie(ExcelChart chart, ExcelChart ct, ExcelChartStandardSerie cStandardSerie, int serieIndex, int index, RenderItemStyle style) { var theme = chart.WorkSheet.Workbook.ThemeManager.GetOrCreateTheme(); if (ct.VaryColors) { //Get the color based on the index, if no style is set. Accent1, Accent2, Accent3... var color = GetVaryColor(theme, chart.StyleManager.ColorsManager, index); - item.SetDrawingPropertiesFill(theme, cStandardSerie.Fill, chart.StyleManager.Style?.SeriesLine.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Parent, color); + style.SetDrawingPropertiesFill(theme, cStandardSerie.Fill, chart.StyleManager.Style?.SeriesLine.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Parent, color); } else { var color = GetVaryColor(theme, chart.StyleManager?.ColorsManager, serieIndex); - item.SetDrawingPropertiesFill(theme, cStandardSerie.Fill, chart.StyleManager.Style?.SeriesLine.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Parent, color); + style.SetDrawingPropertiesFill(theme, cStandardSerie.Fill, chart.StyleManager.Style?.SeriesLine.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Parent, color); } - item.SetDrawingPropertiesBorder(theme, cStandardSerie.Border, chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); + style.SetDrawingPropertiesBorder(theme, cStandardSerie.Border, chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Transparent, 0.75); } private static Color? GetVaryColor(ExcelTheme theme, ExcelChartColorsManager colorsManager, int index) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index 1efd74be73..10ddfb5cbb 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -73,7 +73,7 @@ private void CreateDropLine(ExcelLineChart chartType, List coords) var catAxis = chartType.UseSecondaryAxis ? ChartRenderer.SecondHorizontalAxis : ChartRenderer.HorizontalAxis; var valAxis = chartType.UseSecondaryAxis ? ChartRenderer.SecondVerticalAxis : ChartRenderer.VerticalAxis; var yBottom = GetAxisBaseY(catAxis, valAxis); - var bb = ChartRenderer.Plotarea.Group.Bounds; + var bb = ChartRenderer.Plotarea.Group; var dl = new LineRenderItem(bb) { X1 = x, @@ -81,10 +81,10 @@ private void CreateDropLine(ExcelLineChart chartType, List coords) Y1 = yTop, Y2 = yBottom, }; - dl.Bounds.Name = $"DropLine {i/2 + 1}"; + dl.Name = $"DropLine {i/2 + 1}"; //TODO: DropLines should actually use the "Other Lines" DefaultDrawingObject - dl.SetDrawingPropertiesBorder(ChartRenderer.Theme, chartType.DropLine.Border, chartType.StyleManager.Style?.DropLine.BorderReference.Color, true, () => DefaultBorderColor, 1.5,DrawingRenderer.UserSpaceSettings.UserSpaceOnUse_Parent); - dl.SetDrawingPropertiesEffects(ChartRenderer.Theme, chartType.DropLine.Effect); + dl.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, chartType.DropLine.Border, chartType.StyleManager.Style?.DropLine.BorderReference.Color, true, () => DefaultBorderColor, 1.5,DrawingRenderer.UserSpaceSettings.UserSpaceOnUse_Parent); + dl.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, chartType.DropLine.Effect); _dropLines.Add(dl); } @@ -172,7 +172,7 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List(); var coords = new List(); var markerItems = new List(); @@ -205,7 +205,7 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List 0 && serie.DataPoints.ContainsKey(i)) { var dp = serie.DataPoints[i]; - var lineDp = new LineRenderItem(ChartRenderer.Plotarea.Rectangle.Bounds); + var lineDp = new LineRenderItem(ChartRenderer.Plotarea.Rectangle); lineDp.X1 = coords[coords.Count - 4]; lineDp.Y1 = coords[coords.Count - 3]; lineDp.X2 = xPos; @@ -245,26 +246,26 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List ChartRenderer.Theme.FormatScheme.FillStyle[0].Color); } } - lineDp.SetDrawingPropertiesBorder(ChartRenderer.Theme, dp.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, () => DefaultBorderColor, 3); - lineDp.SetDrawingPropertiesEffects(ChartRenderer.Theme, dp.Effect); + lineDp.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, dp.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, () => DefaultBorderColor, 3); + lineDp.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, dp.Effect); dataPointOverrides.Add(lineDp); } } CreateDropLine(chartType, coords); - linePath.Commands.Add(new PathCommands(PathCommandType.Move, coords.ToArray())); - linePath.SetDrawingPropertiesBorder(ChartRenderer.Theme, serie.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, () => DefaultBorderColor, 3); - linePath.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); - linePath.FillColor = "none"; //No fill for line - linePath.StrokeMiterLimit = 4; //A much higher value of the miter limit, might cause the "spike" to get beyond the data point on the vertical scale.. - linePath.LineJoin = LineJoin.Round; + linePath.Commands.Add(new PathCommand(PathCommandType.Move, coords.ToArray())); + linePath.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, serie.Border, chartType.StyleManager.Style?.SeriesLine.BorderReference.Color, true, () => DefaultBorderColor, 3); + linePath.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); + linePath.Style.FillColor = "none"; //No fill for line + linePath.Style.StrokeMiterLimit = 4; //A much higher value of the miter limit, might cause the "spike" to get beyond the data point on the vertical scale.. + linePath.Style.LineJoin = LineJoin.Round; SeriesRenderItems.Add(linePath); SeriesRenderItems.AddRange(dataPointOverrides); SeriesRenderItems.AddRange(markerItems); @@ -286,27 +287,35 @@ private void SetMarker(ExcelLineChartSerie serie, ExcelChartMarker marker, List< if (pt != null) { - pt.Width = ls.Bounds.Width; - pt.Height = ls.Bounds.Height; + pt.Width = ls.Width; + pt.Height = ls.Height; dataPoints.Add(pt); } } internal override Color? DefaultBorderColor => ChartRenderer.Theme.ColorScheme.Accent1.GetColor(); - public override void AppendRenderItems(List renderItems) + + internal override Color? DefaultFillColor => null; + + public override void AppendRenderItems(List renderItems) { renderItems.AddRange(ChartAreaRenderItems); - //SeriesRenderItems.ForEach(x => ChartRenderer.Plotarea.Group.AddChildItem(x)); - //ChartRenderer.Plotarea.Group.AddSubGroupingOfRenderItems("Linechart_SeriesItems", SeriesRenderItems); - GroupRenderItem SerieGroup = new GroupRenderItem(ChartRenderer.Plotarea.Group.Bounds); - SerieGroup.Bounds.Name = "LineChart_SeriesItems"; + GroupRenderItem SerieGroup = new GroupRenderItem(ChartRenderer.Plotarea.Group); + SerieGroup.Name = "LineChart_SeriesItems"; SeriesRenderItems.ForEach(x => SerieGroup.AddChildItem(x)); - ChartRenderer.Plotarea.Group.AddChildItem(SerieGroup); - ////ChartRenderer.Plotarea.Group.AddChildItem(SerieGroup); - //SeriesRenderItems.ForEach(x=> ChartRenderer.Plotarea.Group.AddChildItem(x)); + //ChartRenderer.Plotarea.Group.AddChildItem(SerieGroup); } - } + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } + } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index bd9dd7f195..5ceb20cbf6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -15,7 +15,9 @@ using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; +using System.Drawing; using System.Runtime.InteropServices; +using EPPlus.Graphics.TransformPrimitives; namespace EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers { @@ -43,7 +45,13 @@ internal class PieChartTypeDrawer : ChartTypeDrawer /// double _radius; - Point _circleCenter; + TranformPoint _circleCenter; + + internal override Color? DefaultFillColor => null; + + internal override Color? DefaultBorderColor => null; + + PieTransform pieTransform; public PieChartTypeDrawer(ChartRenderer chart, ExcelPieChart chartType) : base(chart, chartType) { @@ -52,10 +60,10 @@ public PieChartTypeDrawer(ChartRenderer chart, ExcelPieChart chartType) : base(c void RenderDebugEllipse() { - var circ = new EllipseRenderItem(ChartRenderer.Plotarea.Rectangle.Bounds); + var circ = new EllipseRenderItem(ChartRenderer.Plotarea.Rectangle); - circ.Bounds.Left = _circleCenter.Left; - circ.Bounds.Top = _circleCenter.Top; + circ.Left = _circleCenter.Left; + circ.Top = _circleCenter.Top; circ.Rx = _radius; circ.Ry = _radius; @@ -63,12 +71,12 @@ void RenderDebugEllipse() circ.Cx = _circleCenter.Left; circ.Cy = _circleCenter.Top; - circ.FillColor = "transparent"; - circ.FillOpacity = 0.3d; - circ.BorderColor = "purple"; - circ.BorderWidth = 10; + circ.Style.FillColor = "transparent"; + circ.Style.FillOpacity = 0.3d; + circ.Style.BorderColor = "purple"; + circ.Style.BorderWidth = 10; - _groupItem.RenderItems.Add(circ); + _groupItem.ChildObjects.Add(circ); } Coordinate CalculateLocalPointOnCircle(double degrees) @@ -91,7 +99,7 @@ void InitializeSlices() { //Calculate how many percent of the pie this slice is var valPercent = _serieValuesAsDoubles[i] / _totalOfSerieValues; //Create and add slice - PieSliceRenderItem slice = new PieSliceRenderItem(ChartRenderer, _groupItem.Bounds, _circleCenter, _radius, valPercent, prevDegrees); + PieSliceRenderItem slice = new PieSliceRenderItem(ChartRenderer, _groupItem, _circleCenter, _radius, valPercent, prevDegrees); Slices.Add(slice); //Next slice will need to be calculated starting from the degrees of this slice @@ -101,10 +109,12 @@ void InitializeSlices() void CalculateLocalCenterAndRadius() { - _circleCenter = new Point(); - _circleCenter.Parent = _groupItem.TranslationOffset; - _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Bounds.Width / 2; - _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Bounds.Height / 2; + _circleCenter = new TranformPoint(); + _circleCenter.Parent = _groupItem.Parent; + _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Width / 2d; + _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Height / 2d; + + _groupItem.RotationPoint = _circleCenter; @@ -174,7 +184,7 @@ private void UpdateSlice(ExcelPieChart chartType, ExcelPieChartSerie serie, int } Slices[position].ImportPathData( - ChartRenderer.Plotarea.Rectangle.Bounds, ChartRenderer.Bounds, + ChartRenderer.Plotarea.Rectangle, ChartRenderer.Bounds, _sliceScaleFactor, explosion, _pieExplosionPercent, position); Slices[position].ImportStlyeInfo(serie, chartType, position); @@ -183,17 +193,17 @@ private void UpdateSlice(ExcelPieChart chartType, ExcelPieChartSerie serie, int internal override void DrawSeries() { - _groupItem = new GroupRenderItem(ChartRenderer.Plotarea.Group.Bounds); + _groupItem = new GroupRenderItem(ChartRenderer.Plotarea.Group); - Rectangle.Bounds.Name = "ChartDrawer"; - _groupItem.Bounds.Name = "OuterGroupChartDrawer"; + Rectangle.Name = "ChartDrawer"; + _groupItem.Name = "OuterGroupChartDrawer"; var chartType = (ExcelPieChart)_chartType; //Read and set Starting angle offset as a rotation on the container //This way no rotation messes with the other calculations var angleOffset = double.IsNaN(chartType.FirstSliceAngle) ? 0 : chartType.FirstSliceAngle; - _groupItem.Rotation = angleOffset; + _groupItem.GroupRotation = angleOffset; LoadSeriesValues(chartType); CalculateLocalCenterAndRadius(); @@ -257,7 +267,7 @@ internal override void DrawSeries() { var innerGroup = Slices[j].GetInnerGroupWithTransformOriginTranslated(); //Get the global position of the inner items (innerGroup the parent of itemGroup has already had its position set correctly) - var dlblBounds = new BoundingBox(innerGroup.LocalPosition.X, innerGroup.LocalPosition.Y, Rectangle.Bounds.Width, Rectangle.Bounds.Height); + var dlblBounds = new BoundingBox(innerGroup.LocalPosition.X, innerGroup.LocalPosition.Y, Rectangle.Width, Rectangle.Height); var ctrToMid = Slices[j].GetWholeVectorCenterToMid(); var startPt = new Transform(); @@ -297,21 +307,41 @@ internal override void DrawSeries() //RenderDebugEllipse(); ChartAreaRenderItems.Add(_groupItem); - //Series Labels - foreach (var dataLabel in serieDataLabels) - { - dataLabel.AppendRenderItems(SeriesRenderItems); - } + + ////This works but does not put them last in the render order in svg + ////Pie charts only use the first serie + //if (serieDataLabels != null && serieDataLabels.Count > 0) + //{ + // serieDataLabels[0].AppendRenderItems(ChartRenderer.RenderItems); + //} + + //////Series Labels + ////foreach (var dataLabel in serieDataLabels) + ////{ + //// //dataLabel.AppendRenderItems(SeriesRenderItems); + //// //if(serieDataLabels.Count =) + //// ChartRenderer.RenderItems.Add(SeriesRenderItems[0]); + ////} } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { //renderItems.AddRange(ChartAreaRenderItems); - ChartRenderer.Plotarea.Group.AddChildItem(_groupItem); - if (SeriesRenderItems != null && SeriesRenderItems.Count > 0) + //ChartRenderer.Plotarea.Group.AddChildItem(_groupItem); + if (serieDataLabels != null && serieDataLabels.Count > 0) { - ChartRenderer.RenderItems.Add(SeriesRenderItems[0]); + serieDataLabels[0].AppendRenderItems(ChartRenderer.RenderItems); } } + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs index 17812b6a1e..2d5392609c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs @@ -14,6 +14,7 @@ Date Author Change using EPPlus.DrawingRenderer.RenderItems; using EPPlus.DrawingRenderer.RenderItems.Textbox; using EPPlus.Export.ImageRenderer.RenderItems.Shared; +using EPPlus.Graphics; using EPPlusImageRenderer; using EPPlusImageRenderer.RenderItems; using EPPlusImageRenderer.Svg; @@ -30,7 +31,7 @@ Date Author Change using System.Text; namespace EPPlus.Export.ImageRenderer.Svg.Chart { - internal class ChartTrendlineRenderer : ChartDrawingDefaultObject + internal class ChartTrendlineRenderer : ChartDrawingObject { private ExcelChartTrendline _trendline; private double[] _ySerie; @@ -124,7 +125,7 @@ private void CreateDatalabel() if (width > 0 && height > 0) { - DataLabel = new DrawingTextBox(ChartRenderer.RenderContext, Chart, ChartRenderer.ChartArea.Rectangle.Bounds, x, y, width, height); + DataLabel = new DrawingTextBox(ChartRenderer.RenderContext, Chart, ChartRenderer.ChartArea.Rectangle, x, y, width, height); DataLabel.TextBody.AutoSize = false; } else @@ -191,9 +192,9 @@ private void CreateDatalabel() } - DataLabel.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, _trendline.Label.Fill, Chart.StyleManager.Style.TrendlineLabel.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); - DataLabel.Rectangle.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Label.Border, Chart.StyleManager.Style.TrendlineLabel.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Label.Border.Width); - DataLabel.Rectangle.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Label.Effect); + DataLabel.Rectangle.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, _trendline.Label.Fill, Chart.StyleManager.Style.TrendlineLabel.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); + DataLabel.Rectangle.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Label.Border, Chart.StyleManager.Style.TrendlineLabel.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Label.Border.Width); + DataLabel.Rectangle.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Label.Effect); } private void AddLblText(DrawingTextBox lbl, string labelText) @@ -661,13 +662,13 @@ internal void CreateRenderCoordinatesAndDatalabel() CreateRenderCoordinates(); CreateDatalabel(); } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { - var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle.Bounds); - pathItem.Commands.Add(new EPPlusImageRenderer.PathCommands(PathCommandType.Move, RenderCoordinates)); - pathItem.FillColor = "none"; - pathItem.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Border.Width); - pathItem.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Effect); + var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle); + pathItem.Commands.Add(new EPPlusImageRenderer.PathCommand(PathCommandType.Move, RenderCoordinates)); + pathItem.Style.FillColor = "none"; + pathItem.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, _trendline.Border, Chart.StyleManager.Style?.Trendline.BorderReference.Color, true, GetDefaultBorderColor, _trendline.Border.Width); + pathItem.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, _trendline.Effect); renderItems.Add(pathItem); } @@ -785,34 +786,24 @@ private double GetLinearValueAtPosition(double x) { return Coefficients[1] + Coefficients[0] * x; } - - internal override Color? GetDefaultFillColor() + internal override Color? DefaultFillColor => GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + internal override Color? GetDefaultBorderColor() { - return GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + return DefaultBorderColor; } - - internal override Color? GetDefaultBorderColor() + internal override Color? GetDefaultFillColor() { - //We only get here if the node is null or empty - var themedLine = GetThemedLine(ChartElement.OtherLines, (int)Chart.Style, _trendline.Border.Fill != null && _trendline.Border.Fill.IsEmpty, out Color? lineColor); - ////Kept here in case needed in future for effect etc. - //var themedLine = GetThemedLine(ChartElement.ChartArea, (int)Chart.Style, out Color? lineCol); - return lineColor; + return DefaultFillColor; } + internal override Color? DefaultBorderColor { get { - return GetDefaultBorderColor(); - //var borderStyleFill = ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill; - //if (borderStyleFill.IsEmpty == false && borderStyleFill.SolidFill != null && borderStyleFill.SolidFill.Color.ColorType != eDrawingColorType.Scheme) - //{ - // return ChartRenderer.Theme.FormatScheme.BorderStyle[0].Fill?.Color; - //} - //else - //{ - // return null; - //} + var themedLine = GetThemedLine(ChartElement.OtherLines, (int)Chart.Style, _trendline.Border.Fill != null && _trendline.Border.Fill.IsEmpty, out Color? lineColor); + ////Kept here in case needed in future for effect etc. + //var themedLine = GetThemedLine(ChartElement.ChartArea, (int)Chart.Style, out Color? lineCol); + return lineColor; } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index abcded2544..b38398bb75 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -6,13 +6,14 @@ using EPPlusImageRenderer.Svg; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.Chart; using OfficeOpenXml.Utils.TypeConversion; using System.Collections.Generic; using System.Drawing; namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem { - internal class ChartSerieDataLabelRenderer : ChartDrawingObject + internal class ChartSerieDataLabelRenderer : ChartDrawingObjectWithBackground { //positioning is handled by parent item via these private List dataLabels = new List(); @@ -26,21 +27,30 @@ internal class ChartSerieDataLabelRenderer : ChartDrawingObject ExcelChartDataLabel _dlbl; internal double rotation = double.NaN; - internal Graphics.Point rotationPoint = null; + internal Graphics.TranformPoint rotationPoint = null; internal override Color? DefaultFillColor { get; } + internal override Color? DefaultBorderColor => null; + double? SummedSeries = null; + GroupRenderItem DatalabelsGroup; + public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl, BoundingBox maxBounds, ExcelChartStandardSerie serie, List xValues, List yValues, int index) : base(chart) { + DatalabelsGroup = new GroupRenderItem(ChartRenderer.Bounds); + DatalabelsGroup.Name = "Dlbl_SeriesGroup"; + _serieIndex = index; _origIndex = index; _dlbl = dlbl; - plotAreaBounds = chart.Plotarea.Group.Bounds; + plotAreaBounds = chart.Plotarea.Group; DefaultFillColor = dlbl.Fill != null && dlbl.Fill.Color.IsEmpty == false ? dlbl.Fill.Color : Color.Transparent; + Rectangle.Name = "SerieDataLabel"; + if(yValues != null && yValues.Count != 0) { @@ -73,7 +83,14 @@ public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl { var yVal = yValues == null ? null : yValues[i]; var xVal = xValues == null ? null : xValues[i]; - AddDatalabel(serie, dlbl, xVal, yValues[i], maxBounds); + if (dlblSerie == null) + { + AddDatalabel(serie, dlbl, xVal, yValues[i], maxBounds); + } + else + { + AddDatalabel(serie, dlblSerie, xVal, yValues[i], maxBounds); + } //Bit strange but in e.g. pie charts each datapoint counts as a new series for the purposes of legendIcons etc. _serieIndex++; } @@ -107,11 +124,11 @@ public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl } } - private void CreateSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBounds) + private void CreateSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBounds, double entryHeight) { if (ChartRenderer.Legend == null) { - seriesIcon = ChartRenderer.GetSeriesIcon(serie, _serieIndex, maxBounds); + seriesIcon = LegendIconRenderer.GetSeriesIcon(ChartRenderer, maxBounds, serie, _serieIndex, entryHeight); } else { @@ -120,9 +137,9 @@ private void CreateSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBoun var seriesIconOrig = legendItem.SeriesIcon[_serieIndex].SeriesIcon; var clonedIcon = seriesIconOrig.Clone(); - if (seriesIconOrig.FillColor == null && seriesIconOrig.GradientFill != null) + if (seriesIconOrig.Style.FillColor == null && seriesIconOrig.Style.GradientFill != null) { - clonedIcon.GradientFill = seriesIconOrig.GradientFill; + clonedIcon.Style.GradientFill = seriesIconOrig.Style.GradientFill; } if (clonedIcon is LineRenderItem lineIcon) @@ -141,26 +158,32 @@ private void CreateSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBoun } } - private RenderItem GetSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBounds) + private RenderItem GetSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxBounds, double entryHeight) { //We MUST create a new icon per series. For pie chart each data point is a new series //Therefore check if _origIndex matches if (seriesIcon == null || _origIndex != _serieIndex) { - CreateSeriesIcon(serie, maxBounds); + CreateSeriesIcon(serie, maxBounds, entryHeight); + return seriesIcon; + } + else + { + return seriesIcon.Clone(); } - - return seriesIcon; } private void AddDatalabel(ExcelChartStandardSerie serie, ExcelChartDataLabel dataLabel, object xValue, object yValue, BoundingBox maxBounds) { var newDataLabel = new SvgDataLabelPoint(ChartRenderer, dataLabel, DefaultFillColor); + + var dlblName = $"DatalabelPoint_{dataLabels.Count}"; newDataLabel.ImportDataLabel(serie, dataLabel, xValue, yValue, defaultParagraph, maxBounds, _defaultMargins, SummedSeries); + newDataLabel.DataLabelPointContainer.Name = dlblName; - if(dataLabel.ShowLegendKey) + if (dataLabel.ShowLegendKey) { - newDataLabel.AddSeriesIcon(GetSeriesIcon(serie, maxBounds)); + newDataLabel.AddSeriesIcon(GetSeriesIcon(serie, maxBounds, newDataLabel.Rectangle.Height)); //TODO: Check if Rectangle.Height matches entry height } dataLabels.Add(newDataLabel); @@ -182,37 +205,46 @@ internal void SetParentPoint(BoundingBox parent, int index) } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { - var plotAreaGroup = new GroupRenderItem(plotAreaBounds); + DatalabelsGroup.Left = ChartRenderer.Plotarea.Group.Left; + DatalabelsGroup.Top = ChartRenderer.Plotarea.Group.Top; - plotAreaGroup.Left = plotAreaBounds.Position.X; - plotAreaGroup.Top = plotAreaBounds.Position.Y; - - if(rotation != double.NaN) + if (rotation != double.NaN) { - if(rotationPoint != null) + if (rotationPoint != null) { - plotAreaGroup.RotationPoint = rotationPoint; + DatalabelsGroup.RotationPoint = rotationPoint; } - plotAreaGroup.Rotation = rotation; + DatalabelsGroup.Rotation = rotation; } if (_dlbl.Fill.IsEmpty == false) { - Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); - plotAreaGroup.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); + Rectangle.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); + DatalabelsGroup.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); } - renderItems.Add(plotAreaGroup); - for(int i = 0; i< dataLabels.Count; i++) + for (int i = 0; i < dataLabels.Count; i++) { - if(rotation != double.NaN) + if (rotation != double.NaN) { dataLabels[i].CounterRotation = -rotation; } - dataLabels[i].AppendRenderItems(plotAreaGroup.RenderItems); + dataLabels[i].PrepareForRenderAndAddToParent(DatalabelsGroup); } + + renderItems.Add(DatalabelsGroup); + } + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs index a4767f8a12..9282c2510e 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs @@ -3,10 +3,11 @@ using EPPlusImageRenderer; using EPPlusImageRenderer.Svg; using System.Collections.Generic; +using System.Drawing; namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem { - internal class PointLines : ChartDrawingObject + internal class PointLines : ChartDrawingObjectWithBackground { internal List RenderLines = new List(); @@ -16,6 +17,10 @@ internal class PointLines : ChartDrawingObject private BoundingBox parentBounds; + internal override Color? DefaultFillColor => Color.Black; + + internal override Color? DefaultBorderColor => Color.Black; + private PointLines(ChartRenderer cr) : base(cr) { Rectangle = new RectRenderItem(cr.Bounds); @@ -25,7 +30,7 @@ internal PointLines(ChartRenderer cr, BoundingBox parent, ConnectionPointsMiddle { parentBounds = parent; - Rectangle.Bounds.Parent = parent; + Rectangle.Parent = parent; ConnectionPoints = connectionPoints; UpdateLines(); @@ -38,26 +43,36 @@ internal void UpdateLines() for (int i = 0; i < ConnectionPoints.Points.Count; i++) { var cPoint = ConnectionPoints.Points[i]; - var cPointLine = new LineRenderItem(Rectangle.Bounds); + var cPointLine = new LineRenderItem(Rectangle); cPointLine.X1 = 0; cPointLine.Y1 = 0; cPointLine.X2 = cPoint.X; cPointLine.Y2 = cPoint.Y; - cPointLine.BorderWidth = 1; - cPointLine.BorderColor = ptColors[i]; + cPointLine.Style.BorderWidth = 1; + cPointLine.Style.BorderColor = ptColors[i]; RenderLines.Add(cPointLine); } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { - GroupRenderItem gItem = new GroupRenderItem(Rectangle.Bounds); + GroupRenderItem gItem = new GroupRenderItem(Rectangle); renderItems.Add(gItem); foreach (var line in RenderLines) { - gItem.RenderItems.Add(line); + gItem.ChildObjects.Add(line); } } + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; + } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index a20d5d34b5..5e0b67239d 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -11,6 +11,7 @@ using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Renderer.TextBox; using OfficeOpenXml.FormulaParsing.Utilities; +using OfficeOpenXml.Style; using OfficeOpenXml.Utils.EnumUtils; using OfficeOpenXml.Utils.TypeConversion; using System; @@ -20,7 +21,7 @@ namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem { - internal class SvgDataLabelPoint : ChartDrawingObject + internal class SvgDataLabelPoint : ChartDrawingObjectWithBackground { bool _hasManualLayout = false; bool _hasLeaderLines = false; @@ -37,6 +38,10 @@ internal class SvgDataLabelPoint : ChartDrawingObject internal override Color? DefaultFillColor { get; } + internal override Color? DefaultBorderColor => null; + + public GroupRenderItem DataLabelPointContainer { get; private set; } + //public SvgChartDataLabelStandard(DrawingChart chart, string dataLabelText) : base(chart) //{ // var txtBox = new SvgTextBox(chart, chart.Bounds, chart.Bounds); @@ -53,7 +58,10 @@ public SvgDataLabelPoint(ChartRenderer chart, ExcelChartDataLabel standard, Colo { DefaultFillColor = defaultFillColor.HasValue ? defaultFillColor : Color.Transparent; _labelPosition = GetDefaultPositionBasedOnChartType(standard); - Rectangle = new RectRenderItem(chart.Bounds); + DataLabelPointContainer = new GroupRenderItem(chart.Bounds); + Rectangle = new RectRenderItem(DataLabelPointContainer); + Rectangle.Name = "Datapoint_Rect"; + Rectangle.Style.FillColor = "none"; } eLabelPosition GetDefaultPositionBasedOnChartType(ExcelChartDataLabel standardDatalabel) @@ -100,30 +108,34 @@ eLabelPosition GetDefaultPositionBasedOnChartType(ExcelChartDataLabel standardDa internal void AddSeriesIcon(RenderItem seriesIcon) { - var iconWidth = seriesIcon.Bounds.Width; - var iconHeight = seriesIcon.Bounds.Height; + var iconWidth = seriesIcon.Width; + var iconHeight = seriesIcon.Height; _seriesIcon = seriesIcon; - _seriesIcon.Bounds.Parent = Rectangle.Bounds; + _seriesIcon.Name = "seriesIcon_" + DataLabelPointContainer.Name; + _seriesIcon.Parent = DataLabelPointContainer; if (_haveAdjustedForIcon == false) { _txtBox.Left += iconWidth; - _seriesIcon.Bounds.Left -= 0.75d; - Rectangle.Bounds.Width += iconWidth + 0.75d; - + _seriesIcon.Left -= 0.75d; + Rectangle.Width += iconWidth + 0.75d; + LeftMargin += 4.5d; + //Rectangle.Left -= iconWidth + 0.75d; //It seems there is a hard-coded margin in excel of about 4.5pt (6px) //Increase width by iconWidth + right margin of icon - //Rectangle.Bounds.Width += iconWidth + 2.25d; + //Rectangle.Width += iconWidth + 2.25d; //Move the starting bounds to origin point since width increased by that much - //LeftMargin -= 2.25; + //LeftMargin = 2.25d; + //DataLabelPointContainer.Left += 4.5d; + //Excel appears to simply add icon width rightwards after applying everything else instead of truly considering the icon //it only considers the new location of the Textbox with Its margins for bestfit //LeftMargin += 2.25d + iconWidth; ////Hardcoded top margin - //Rectangle.Bounds.Height += iconHeight + 1.4173228346d; + //Rectangle.Height += iconHeight + 1.4173228346d; //TopMargin -= 1.4173228346d + 3d; //_txtBox.Top += iconHeight; @@ -181,11 +193,12 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel } } - var txtBox = new DrawingTextBox(ChartRenderer.RenderContext, Chart, Rectangle.Bounds, maxBounds.Width, maxBounds.Height); - + var txtBox = new DrawingTextBox(ChartRenderer.RenderContext, Chart, Rectangle, maxBounds.Width, maxBounds.Height); + //txtBox.Left += Rectangle.Left; + //txtBox.Top += Rectangle.Top; txtBox.ImportTextBodyAndParagraphs(dataLabel.TextBody, false); - txtBox.TextBody.Bounds.Top = 0; + txtBox.TextBody.Top = 0; txtBox.TextBody.AutoSize = true; if (txtBox.TextBody.Paragraphs.Count == 0) @@ -233,22 +246,22 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel //txtBox.Top += txtBox.TopMargin; //Center the textbox at the origin point - Rectangle.Bounds.Left -= txtBox.Rectangle.Bounds.Width / 2; - Rectangle.Bounds.Top -= txtBox.Rectangle.Bounds.Height / 2; + Rectangle.Left -= txtBox.Rectangle.Width / 2; + Rectangle.Top -= txtBox.Rectangle.Height / 2; //Set initial width and height to content - Rectangle.Bounds.Width = txtBox.Rectangle.Bounds.Width; - Rectangle.Bounds.Height = txtBox.Rectangle.Bounds.Height; + Rectangle.Width = txtBox.Rectangle.Width; + Rectangle.Height = txtBox.Rectangle.Height; _txtBox = txtBox; - _txtBox.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, dataLabel.Fill, null, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); + _txtBox.Rectangle.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, dataLabel.Fill, null, UserSpaceSettings.ObjectBoundingBox, DefaultFillColor); //Border should be decided by series if it has no specified color of its own //Therefore it is not set here unless specified as we set a fill color on the group it belongs to //If there is no series to inherit from we should apply the default by setting it here if(dataLabel.Border.Fill.Color.IsEmpty == false || serie == null) { - _txtBox.Rectangle.SetDrawingPropertiesBorder(ChartRenderer.Theme, dataLabel.Border, ChartRenderer.Chart.StyleManager.Style?.DataLabel.BorderReference.Color, dataLabel.Border.IsEmpty, () => Color.Transparent, 0.75); + _txtBox.Rectangle.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, dataLabel.Border, ChartRenderer.Chart.StyleManager.Style?.DataLabel.BorderReference.Color, dataLabel.Border.IsEmpty, () => Color.Transparent, 0.75); } if (dataLabel.Font.IsEmpty == false) { @@ -263,7 +276,7 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel if (individualLabel.Fill.IsEmpty == false) { - _txtBox.Rectangle.FillColor = "#" + individualLabel.Fill.Color.ToColorString(); + _txtBox.Rectangle.Style.FillColor = "#" + individualLabel.Fill.Color.ToColorString(); } if (individualLabel.Layout != null && individualLabel.Layout.HasLayout) @@ -271,18 +284,18 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel _hasManualLayout = true; var rect = GetRectFromManualLayout(ChartRenderer, individualLabel.Layout); - Rectangle.Bounds.Left += rect.Left; - Rectangle.Bounds.Top += rect.Top; + Rectangle.Left += rect.Left; + Rectangle.Top += rect.Top; _manualLayoutOffset = new Coordinate(rect.Left, rect.Top); - if (rect.Bounds.Width != 0) + if (rect.Width != 0) { - Rectangle.Bounds.Width = rect.Bounds.Width; + Rectangle.Width = rect.Width; } - if (rect.Bounds.Height != 0) + if (rect.Height != 0) { - Rectangle.Bounds.Height = rect.Bounds.Height; + Rectangle.Height = rect.Height; } if (dataLabel.ShowLeaderLines) @@ -330,17 +343,17 @@ private void SetPositionBasic(BoundingBox point, eLabelPosition basicPosition) // Bounds.Top = dataLabelCenter.Y; // break; case eLabelPosition.Left: - Rectangle.Bounds.Left -= (_txtBox.Width/2) + (point.Width / 2d); + Rectangle.Left -= (_txtBox.Width/2) + (point.Width / 2d) - LeftMargin; break; case eLabelPosition.Right: case eLabelPosition.BestFit: - Rectangle.Bounds.Left += (_txtBox.Width / 2d) + point.Width; + Rectangle.Left += (_txtBox.Width / 2d) + point.Width + LeftMargin; break; case eLabelPosition.Top: - Rectangle.Bounds.Top -= (point.Height + _txtBox.Height) / 2d; + Rectangle.Top -= (point.Height + _txtBox.Height) / 2d; break; case eLabelPosition.Bottom: - Rectangle.Bounds.Top += (point.Height + _txtBox.Height) / 2d; + Rectangle.Top += (point.Height + _txtBox.Height) / 2d; break; default: throw new InvalidOperationException($"The datalabel position entered in SetPositionBasic: '{basicPosition}' is not a basic position"); @@ -361,7 +374,7 @@ private RectRenderItem GenerateDebugRenderItem(BoundingBox parent, string fillCo var pointRect = new RectRenderItem(parent); pointRect.Width = 10d; pointRect.Height = 10d; - pointRect.FillColor = fillColor; + pointRect.Style.FillColor = fillColor; pointRect.Left = -5d; pointRect.Top = -5d; return pointRect; @@ -377,8 +390,8 @@ private void CreateDebugPoints(Transform basePoint, Transform endPoint, Transfor endPositionRect = GenerateDebugRenderItem(_parentPoint, "darkBlue"); endPositionRect.Left += endPoint.LocalPosition.X; endPositionRect.Top += endPoint.LocalPosition.Y; - endPositionRect.BorderWidth = 2d; - endPositionRect.BorderColor = "cyan"; + endPositionRect.Style.BorderWidth = 2d; + endPositionRect.Style.BorderColor = "cyan"; centerPositionRect = GenerateDebugRenderItem(_parentPoint, "Purple"); centerPositionRect.Left += centerPoint.LocalPosition.X; centerPositionRect.Top += centerPoint.LocalPosition.Y; @@ -404,7 +417,7 @@ private void ApplyBestFitExtraMargin(Vector2 direction, bool reverseDirection, d //Ensure vector is normalized var directionOnly = direction / direction.Length; - Rectangle.Bounds.Position += directionOnly * margin; + Rectangle.Position += directionOnly * margin; } @@ -445,13 +458,13 @@ private void SetAdjustedTextBoxPosition(Vector2 direction, bool reverseDirection //Calculate diagonal between two sides using basic trig actualDistance = Math.Sqrt(Math.Pow(triangleWidth, 2d) + Math.Pow(triangleHeight, 2d)); - Rectangle.Bounds.Position += directionOnly * actualDistance; + Rectangle.Position += directionOnly * actualDistance; } private void SetInOut(Vector2 direction, Vector2 translation, bool reverseDirection) { //Translate to the translation point - Rectangle.Bounds.Position += translation; + Rectangle.Position += translation; SetAdjustedTextBoxPosition(direction, reverseDirection); } @@ -460,22 +473,22 @@ private void AdjustPositionIfOutsideChartAndNotManualLayout() //If there is a manual layout assume this was intentional if(_hasManualLayout == false) { - var gLeft = Rectangle.Bounds.GlobalLeft; - var gTop = Rectangle.Bounds.GlobalTop; + var gLeft = Rectangle.GlobalLeft; + var gTop = Rectangle.GlobalTop; - var chartBounds = ChartRenderer.ChartArea.Rectangle.Bounds; + var chartBounds = ChartRenderer.ChartArea.Rectangle; var plotBounds = ChartRenderer.GetPlotAreaTop(); - var chartMinY = ChartRenderer.Bounds.GlobalTop; + var chartMinY = ChartRenderer.ChartArea.Rectangle.GlobalTop; var chartMinX = chartBounds.Position.X - ChartRenderer.Plotarea.LeftMargin; if (gTop < chartMinY) { - Rectangle.Bounds.Position = new Vector2(Rectangle.Bounds.Position.X - (ChartRenderer.Plotarea.LeftMargin/2d), chartMinY); + Rectangle.Position = new Vector2(Rectangle.Position.X - (ChartRenderer.Plotarea.LeftMargin/2d), chartMinY); } if (gLeft < chartMinX) { - Rectangle.Bounds.Position = new Vector2(chartMinX, Rectangle.Bounds.Position.Y); + Rectangle.Position = new Vector2(chartMinX, Rectangle.Position.Y); } } } @@ -487,17 +500,17 @@ private void AdjustLeaderLinesForManualLayout() if (_hasLeaderLines) { //With origin in top left of current bounds get the connection points - var cPoints = new ConnectionPointsMiddle(0, 0, Rectangle.Bounds.Width, Rectangle.Bounds.Height); + var cPoints = new ConnectionPointsMiddle(0, 0, Rectangle.Width, Rectangle.Height); //Ready to draw the lines so that we can visualize the distances to each point - _connectionPointLines = new PointLines(ChartRenderer, Rectangle.Bounds, cPoints); + _connectionPointLines = new PointLines(ChartRenderer, Rectangle, cPoints); //Adjust if there is a margin - _connectionPointLines.Rectangle.Bounds.Left += LeftMargin; + _connectionPointLines.Rectangle.Left += LeftMargin; _connectionPointLines.UpdateLines(); //Get the offset between those points and the origin point - var offsetToParentPoint = new Coordinate(-(Rectangle.Bounds.Left + LeftMargin), -(Rectangle.Bounds.Top + TopMargin)); + var offsetToParentPoint = new Coordinate(-(Rectangle.Left + LeftMargin), -(Rectangle.Top + TopMargin)); //Calculate closest point var index = GetClosestConnectionPointCoordinateIndex(offsetToParentPoint); @@ -518,10 +531,14 @@ private void AdjustLeaderLinesForManualLayout() extraLine.Y2 = _connectionPointLines.ConnectionPoints.Points[index].Y; extraLine.X2 = extraLine.X1 + xOffset; - extraLine.BorderColor = "gray"; - extraLine.BorderWidth = 0.5; + extraLine.Style.BorderColor = "gray"; + extraLine.Style.BorderWidth = 0.5; _leaderLines.Add(extraLine); + + + _seriesIcon?.Left += -xOffset + LeftMargin; + _txtBox.Left += -xOffset + LeftMargin; } var mainLine = new LineRenderItem(ChartRenderer.Bounds); mainLine.X1 = _connectionPointLines.ConnectionPoints.Points[index].X + xOffset + LeftMargin; @@ -529,8 +546,8 @@ private void AdjustLeaderLinesForManualLayout() mainLine.X2 = offsetToParentPoint.X + LeftMargin; mainLine.Y2 = offsetToParentPoint.Y; - mainLine.BorderColor = "gray"; - mainLine.BorderWidth = 0.5; + mainLine.Style.BorderColor = "gray"; + mainLine.Style.BorderWidth = 0.5; _leaderLines.Add(mainLine); } } @@ -553,7 +570,7 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi _parentPoint.Parent = basePoint.Parent.Parent; _parentPoint.Left = basePoint.Parent.LocalPosition.X; _parentPoint.Top = basePoint.Parent.LocalPosition.Y; - Rectangle.Bounds.Parent = _parentPoint; + DataLabelPointContainer.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); //--- //--- Calculate vectors and center point --- @@ -578,8 +595,8 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi switch (_labelPosition) { case eLabelPosition.Center: - Rectangle.Bounds.Left += centerPoint.LocalPosition.X; - Rectangle.Bounds.Top += centerPoint.LocalPosition.Y; + Rectangle.Left += centerPoint.LocalPosition.X; + Rectangle.Top += centerPoint.LocalPosition.Y; break; case eLabelPosition.Left: SetPositionBasic(_parentPoint, _labelPosition); @@ -628,21 +645,21 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi bool canFitInCenter = false; if (canFitInCenter) { - Rectangle.Bounds.Left += centerPoint.LocalPosition.X; - Rectangle.Bounds.Top += centerPoint.LocalPosition.Y; + Rectangle.Left += centerPoint.LocalPosition.X; + Rectangle.Top += centerPoint.LocalPosition.Y; } else { //Set inside End SetInOut(endToBaseVector, endPoint.LocalPosition, false); - ApplyBestFitExtraMargin(endToBaseVector, false, 5d); + //ApplyBestFitExtraMargin(endToBaseVector, false, 5d); } } else { //Set outside end SetInOut(endToBaseVector, endPoint.LocalPosition, true); - ApplyBestFitExtraMargin(endToBaseVector, true, 5d); + //ApplyBestFitExtraMargin(endToBaseVector, true, 5d); } break; default: @@ -655,16 +672,16 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi internal void SetParentPoint(BoundingBox parentPoint) { - Rectangle.Bounds.Parent = parentPoint; _parentPoint = parentPoint; + DataLabelPointContainer.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); - var dataLabelCenter = new Vector2(Rectangle.Bounds.Left, Rectangle.Bounds.Top); + var dataLabelCenter = new Vector2(DataLabelPointContainer.Left, DataLabelPointContainer.Top); switch (_labelPosition) { case eLabelPosition.Center: - Rectangle.Bounds.Left += Rectangle.Bounds.Left; - Rectangle.Bounds.Top += Rectangle.Bounds.Top; + Rectangle.Left += Rectangle.Left; + Rectangle.Top += Rectangle.Top; break; case eLabelPosition.Left: SetPositionBasic(parentPoint, _labelPosition); @@ -687,99 +704,203 @@ internal void SetParentPoint(BoundingBox parentPoint) AdjustPositionIfOutsideChartAndNotManualLayout(); } - private void AppendDebugBounds(List renderItems) + private void AppendDebugBounds(List renderItems) { - var rect = new RectRenderItem(Rectangle.Bounds); - rect.Bounds.Left = LeftMargin; - rect.Bounds.Top = 0; - rect.Bounds.Width = Rectangle.Bounds.Width; - rect.Bounds.Height = Rectangle.Bounds.Height; - - rect.FillColor = "red"; - rect.FillOpacity = 0.2; + var rect = new RectRenderItem(Rectangle); + rect.Left = LeftMargin; + rect.Top = 0; + rect.Width = Rectangle.Width; + rect.Height = Rectangle.Height; + + rect.Style.FillColor = "red"; + rect.Style.FillOpacity = 0.2; renderItems.Add(rect); } - public override void AppendRenderItems(List renderItems) + public void PrepareForRenderAndAddToParent(Transform parent) { - var parentPointGroup = new GroupRenderItem(_parentPoint); - parentPointGroup.Left = _parentPoint.Left; - parentPointGroup.Top = _parentPoint.Top; + var titleItemOrigin = new TitleRenderItem("DataLabel originpoint/Data point position"); + titleItemOrigin.Parent = DataLabelPointContainer; + //parentPointGroup.AddChildItem(titleItemOrigin); + + if (originPointRect != null) + { + DataLabelPointContainer.AddChildItem(originPointRect); + } + if (basePositionRect != null) + { + DataLabelPointContainer.AddChildItem(basePositionRect); + } + if (endPositionRect != null) + { + DataLabelPointContainer.AddChildItem(endPositionRect); + } + if (centerPositionRect != null) + { + DataLabelPointContainer.AddChildItem(centerPositionRect); + } + if (maxBoundsCircle != null) + { + DataLabelPointContainer.AddChildItem(maxBoundsCircle); + } + if (endPointCircle != null) + { + DataLabelPointContainer.AddChildItem(endPointCircle); + } + + //renderItems.Add(parentPointGroup); + + var group = new GroupRenderItem(DataLabelPointContainer); + group.Left = Rectangle.Left; + group.Top = Rectangle.Top; + + var titleItem = new TitleRenderItem("DataLabel size adjustment + margin"); + group.AddChildItem(titleItem); + + //parentPointGroup.ChildObjects.Add(group); + + group.RotationPoint = new Graphics.TranformPoint(_txtBox.Left + (_txtBox.Width / 2), _txtBox.Top + (_txtBox.Height / 2)); + group.Rotation = CounterRotation; + //_txtBox.Rectangle.Parent = group; + //_txtBox.TextBody.Parent = group; + _txtBox.AppendRenderItems(group.ChildObjects); + //_txtBox.Rectangle.Parent = group; + //group.AddChild(_txtBox.Rectangle); + //_txtBox.TextBody.Parent = group; + //_txtBox.AppendRenderItems(group.ChildObjects); + + if (_renderConnectionPointLines) + { + if (_connectionPointLines != null) + { + _connectionPointLines.AppendRenderItems(group.ChildObjects); + } + } + + if (_seriesIcon != null) + { + var height = Rectangle.Height; + if (height == 0) + { + height = _txtBox.Height; + } + //Currently series icon always has a y1 y2 of 2 + var iconGrp = new GroupRenderItem(group); + iconGrp.Name = "iconGrp_" + DataLabelPointContainer.Name; + iconGrp.Left = _seriesIcon.Left; + iconGrp.Top = (height / 2) - 2; - var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); - parentPointGroup.AddChildItem(titleItemOrigin); + _seriesIcon.Parent = iconGrp; + } - if(originPointRect != null) + if (_leaderLines != null && _leaderLines.Count > 0) { - parentPointGroup.AddChildItem(originPointRect); + foreach (var line in _leaderLines) + { + line.Parent = group; + //group.ChildObjects.Add(line); + } } - if(basePositionRect != null) + + parent.AddChild(DataLabelPointContainer); + } + + public override void AppendRenderItems(List renderItems) + { + var titleItemOrigin = new TitleRenderItem("DataLabel originpoint/Data point position"); + titleItemOrigin.Parent = DataLabelPointContainer; + //parentPointGroup.AddChildItem(titleItemOrigin); + + if (originPointRect != null) + { + DataLabelPointContainer.AddChildItem(originPointRect); + } + if (basePositionRect != null) { - parentPointGroup.AddChildItem(basePositionRect); + DataLabelPointContainer.AddChildItem(basePositionRect); } - if(endPositionRect != null) + if (endPositionRect != null) { - parentPointGroup.AddChildItem(endPositionRect); + DataLabelPointContainer.AddChildItem(endPositionRect); } - if(centerPositionRect != null) + if (centerPositionRect != null) { - parentPointGroup.AddChildItem(centerPositionRect); + DataLabelPointContainer.AddChildItem(centerPositionRect); } - if(maxBoundsCircle != null) + if (maxBoundsCircle != null) { - parentPointGroup.AddChildItem(maxBoundsCircle); + DataLabelPointContainer.AddChildItem(maxBoundsCircle); } if (endPointCircle != null) { - parentPointGroup.AddChildItem(endPointCircle); + DataLabelPointContainer.AddChildItem(endPointCircle); } - renderItems.Add(parentPointGroup); + //renderItems.Add(parentPointGroup); - var group = new GroupRenderItem(Rectangle.Bounds); - group.Left = Rectangle.Bounds.Left; - group.Top = Rectangle.Bounds.Top; + var group = new GroupRenderItem(DataLabelPointContainer); + group.Left = Rectangle.Left; + group.Top = Rectangle.Top; - var titleItem = new TitleRenderItem("DataLabel size adjustment"); + var titleItem = new TitleRenderItem("DataLabel size adjustment + margin"); group.AddChildItem(titleItem); - parentPointGroup.RenderItems.Add(group); + //parentPointGroup.ChildObjects.Add(group); - group.RotationPoint = new Graphics.Point(_txtBox.Left + (_txtBox.Width / 2), _txtBox.Top + (_txtBox.Height / 2)); + group.RotationPoint = new Graphics.TranformPoint(_txtBox.Left + (_txtBox.Width / 2), _txtBox.Top + (_txtBox.Height / 2)); group.Rotation = CounterRotation; + //_txtBox.Rectangle.Parent = group; + //_txtBox.TextBody.Parent = group; + _txtBox.AppendRenderItems(group.ChildObjects); + //_txtBox.Rectangle.Parent = group; + //group.AddChild(_txtBox.Rectangle); + //_txtBox.TextBody.Parent = group; + //_txtBox.AppendRenderItems(group.ChildObjects); - _txtBox.AppendRenderItems(group.RenderItems); - - if(_renderConnectionPointLines) + if (_renderConnectionPointLines) { if (_connectionPointLines != null) { - _connectionPointLines.AppendRenderItems(group.RenderItems); + _connectionPointLines.AppendRenderItems(group.ChildObjects); } } if (_seriesIcon != null) { - var height = Rectangle.Bounds.Height; + var height = Rectangle.Height; if (height == 0) { height = _txtBox.Height; } //Currently series icon always has a y1 y2 of 2 - var iconGrp = new GroupRenderItem(new BoundingBox(_seriesIcon.Bounds.Left, height / 2)); - iconGrp.Left = _seriesIcon.Bounds.Left; + var iconGrp = new GroupRenderItem(group); + iconGrp.Name = "iconGrp_" + DataLabelPointContainer.Name; + iconGrp.Left = _seriesIcon.Left; iconGrp.Top = (height / 2) - 2; - group.RenderItems.Add(iconGrp); - iconGrp.RenderItems.Add(_seriesIcon); + + _seriesIcon.Parent = iconGrp; } if (_leaderLines != null && _leaderLines.Count > 0) { foreach (var line in _leaderLines) { - group.RenderItems.Add(line); + line.Parent = group; + //group.ChildObjects.Add(line); } } + + renderItems.Add(DataLabelPointContainer); + } + + internal override Color? GetDefaultFillColor() + { + return DefaultFillColor; + } + + internal override Color? GetDefaultBorderColor() + { + return DefaultBorderColor; } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs b/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs index cc3f01dc4c..ad118180ac 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Defaults/ChartDrawingDefaultObject.cs @@ -24,313 +24,4 @@ enum ChartElement Walls = 256, OtherLines = 512, } - - internal abstract class ChartDrawingDefaultObject : ChartDrawingObject - { - public ChartDrawingDefaultObject(ChartRenderer chart) : base(chart) - { - - } - - private Color GetSchemeColorTint(eSchemeColor sColor, double tint = 0.0d) - { - if(tint < 0) - { - tint = 1 + tint; - } - else if(tint > 0) - { - tint = 1 - tint; - } - var schemeClr = tc.ColorConverter.GetSchemeColor(ChartRenderer.Theme, sColor); - var tintedSchemeColor = tc.ColorConverter.ApplyTintDrawing(schemeClr, tint); - return tintedSchemeColor; - } - - private Color GetThemeColorTint(eThemeSchemeColor themeColor, double tint = 0.0d) - { - var schemeClr = tc.ColorConverter.GetThemeColor(ChartRenderer.Theme, themeColor); - var tintedSchemeColor = tc.ColorConverter.ApplyTintDrawing(schemeClr, tint); - return tintedSchemeColor; - } - - internal Color? GetStyleColorOrDefault(int styleId, Color col1, Color col2, Color col3, Color col4) - { - Color? themeColor = null; - //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 - //Alternatively it's an unkown or unset style which should also default to style2 - styleId = styleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : styleId; - - if (styleId == 0) - { - //Set to default instead for export - //Otherwise epplus generated get weird. - styleId = 2; - //return Color.Empty; - } - - if (styleId <= 32) - { - themeColor = col1; - } - else if (styleId <= 34) - { - themeColor = col2; - } - else if (styleId <= 40) - { - themeColor = col3; - } - else if (styleId <= 48) - { - themeColor = col4; - } - - return themeColor; - } - - /// - /// - /// - /// - /// - /// The line color with fill styles etc applied - /// - /// - protected ExcelThemeLine GetThemedLine(ChartElement element, int ChartStyleId, bool nodeIsEmpty , out Color? lineColor) - { - //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 - //Alternatively it's an unkown or unset style which should also default to style2 - var styleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; - - //Everything defaults to subtle style except Area and Floor at certain styles - var AreaOrFloorOrOther = (ChartElement.ChartArea | ChartElement.Floor | ChartElement.OtherLines); - if (AreaOrFloorOrOther.HasFlag(element)) - { - var themedLine = ChartRenderer.Theme.FormatScheme.BorderStyle[0]; - - //When the node exists but is empty Excel does not apply default styles - //It directly applies the themedLineColor - if (nodeIsEmpty) - { - //Is node empty inside the theme - if(themedLine.HasFill == false) - { - lineColor = Color.Transparent; - return themedLine; - } - - bool isSchemeColor = themedLine.Fill.SolidFill.Color.ColorType == eDrawingColorType.Scheme && themedLine.Fill.SolidFill.Color.SchemeColor.Color == eSchemeColor.Style; - - if (isSchemeColor) - { - lineColor = GetDefaultBorderColorForElement(element, styleId); - - if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0 && lineColor.HasValue) - { - //var schemeClr = tc.ColorConverter.GetSchemeColor(ChartRenderer.Theme, eSchemeColor.Dark1); - //var tint = GetSchemeColorTint(eSchemeColor.Dark1, 0.45d); - lineColor = tc.ColorConverter.ApplyTransforms(lineColor.Value, themedLine.Fill.SolidFill.Color.Transforms); - } - - return themedLine; - } - else - { - lineColor = themedLine.Fill.Color; - } - return themedLine; - } - - lineColor = GetDefaultBorderColorForElement(element, styleId); - - var themeColor = tc.ColorConverter.GetThemeColor(ChartRenderer.Theme, themedLine.Fill.SolidFill.Color); - if (themedLine.HasFill == false) - { - //Node exists but has no fill. Excel considers this the same as transparent/noFill - lineColor = Color.Transparent; - return themedLine; - } - - if (styleId < 41 || element == ChartElement.OtherLines) - { - if (themedLine.Fill.SolidFill.Color.Transforms.Count > 0) - { - //themeColor = tc.ColorConverter.ApplyTintDrawing(themeColor.Value, 0.15d); - //but even in this case if there is no ln node found in style it appears to default to 75% despite a scheme color existing in the theme - lineColor = tc.ColorConverter.ApplyTransforms(lineColor.Value, themedLine.Fill.SolidFill.Color.Transforms); - } - else - { - //Default value Should arguably be 75% tint themeColor but something is strange... - //It appears closer to 50% in this specific case - //It also appears to be tx1 (black) and apply color and tint 0.25 in vba - var newTheme = tc.ColorConverter.ApplyTintDrawing(lineColor.Value, 0.25d); - lineColor = newTheme; - } - } - else - { - //No Line - lineColor = Color.Transparent; - return null; - } - - return themedLine; - } - else - { - throw new InvalidOperationException( - $"The enum option: '{Enum.GetName(typeof(ChartElement), element)}' is invalid. " + - $"Only ChartArea or Floor has a default themed line"); - } - } - - /// - /// - /// - /// - /// - /// The fill color with fill styles etc applied - /// - /// - protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, out Color? fillColor) - { - //Chart style can only be above 48 if it is Style102 which in this case should be equivalent with style2 - //Alternatively it's an unkown or unset style which should also default to style2 - var styleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; - - var bg = ChartRenderer.Theme.FormatScheme.BackgroundFillStyle[0]; - - if ((ChartElement.Floor | ChartElement.Walls).HasFlag(element)) - { - fillColor = GetDefaultFillColorForElement(element, styleId); - var themedFill = ChartRenderer.Theme.FormatScheme.BackgroundFillStyle[0]; - - if (styleId > 32) - { - if (themedFill.SolidFill.Color.Transforms.Count > 0) - { - fillColor = tc.ColorConverter.ApplyTransforms(fillColor.Value, themedFill.SolidFill.Color.Transforms); - } - else - { - //Hardcoded default for fills without any actual info in excel - var newTheme = tc.ColorConverter.ApplyTintDrawing(fillColor.Value, 0.75d); - fillColor = newTheme; - } - } - else - { - //No Fill - fillColor = Color.Transparent; - return null; - } - - return themedFill; - } - else - { - throw new InvalidOperationException( - $"The enum option: '{Enum.GetName(typeof(ChartElement), element)}' is invalid. " + - $"Only Walls or Floor has a default themed fill"); - } - } - - protected Color? GetDefaultBorderColorForElement(ChartElement element, int ChartStyleId) - { - //return Color.Empty; - - if((ChartElement.Axis | ChartElement.MajorGridLines).HasFlag(element)) - { - //There's only really two options in this particular case - if(ChartStyleId <= 32) - { - return GetSchemeColorTint(eSchemeColor.Text1, 0.75d); - } - else - { - return GetSchemeColorTint(eSchemeColor.Background1, 0.75d); - } - } - else if(element.HasFlag(ChartElement.MinorGridLines)) - { - var retCol = GetSchemeColorTint(eSchemeColor.Text1, 0.5d); - var retCol2and3 = GetSchemeColorTint(eSchemeColor.Background1, 0.5d); - var retCol4 = GetSchemeColorTint(eSchemeColor.Background1, 0.9d); - - return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); - } - else if ((ChartElement.ChartArea | ChartElement.DataTable | ChartElement.Floor).HasFlag(element)) - { - var retCol = GetSchemeColorTint(eSchemeColor.Text1, 0.75d); - var retCol2and3 = GetSchemeColorTint(eSchemeColor.Background1, 0.75d); - var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); - - return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); - } - else - { - //Other lines should technically always be the enum here but keep it as Else just in case - var retCol = GetSchemeColorTint(eSchemeColor.Text1, 1d); - var retCol2and3 = GetSchemeColorTint(eSchemeColor.Background1, 1d); - var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); - - return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); - } - } - - - private Color? GetDefaultAccent(int ChartStyleId) - { - if(ChartStyleId < 35 || ChartStyleId > 40) - { - throw new InvalidOperationException($"Invalid ChartStyleId '{ChartStyleId}'" + - $"Default Accent tint must be between 35 and 40"); - } - //35 == accent1, 36 == accent2 etc. - var accentColor = (eSchemeColor.Accent1 + (ChartStyleId) - 35); - return GetSchemeColorTint(accentColor, 0.2d); - } - - protected Color? GetDefaultFillColorForElement(ChartElement element, int ChartStyleId) - { - ChartStyleId = ChartStyleId > (int)eChartStyle.Style48 ? (int)eChartStyle.Style2 : ChartStyleId; - - if (element.HasFlag(ChartElement.ChartArea)) - { - var retCol = GetSchemeColorTint(eSchemeColor.Background1); - var retCol2And3 = GetSchemeColorTint(eSchemeColor.Text1); - var retCol4 = GetSchemeColorTint(eSchemeColor.Background1); - - return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2And3, retCol2And3, retCol4); - } - else if((ChartElement.Floor | ChartElement.Walls | ChartElement.PlotArea2d).HasFlag(element)) - { - var retCol = GetSchemeColorTint(eSchemeColor.Background1); - var retCol2 = GetSchemeColorTint(eSchemeColor.Background1, 0.2d); - Color? retCol3 = Color.Empty; - if(ChartStyleId > 35 && ChartStyleId < 40) - { - retCol3 = GetDefaultAccent(ChartStyleId); - } - var retCol4 = GetSchemeColorTint(eSchemeColor.Background1, 0.95d); - - return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2, retCol3.Value, retCol4); - } - else - { - return null; - } - } - - protected Color GetEffectForChartElement(ChartElement element, int ChartStyleId) - { - throw new NotImplementedException("This method has not been implmented yet"); - } - - - abstract internal Color? GetDefaultFillColor(); - abstract internal Color? GetDefaultBorderColor(); - } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs b/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs index f1aea585d8..b7d96fc668 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs @@ -36,8 +36,8 @@ internal void GetIconTopLeft(out double top, out double left) } else { - top = SeriesIcon.Bounds.Top; - left = SeriesIcon.Bounds.Left; + top = SeriesIcon.Top; + left = SeriesIcon.Left; } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs new file mode 100644 index 0000000000..1e0da0e1d4 --- /dev/null +++ b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs @@ -0,0 +1,504 @@ +using EPPlus.DrawingRenderer; +using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.Export.ImageRenderer.Svg.Chart; +using EPPlus.Export.Utils; +using EPPlus.Graphics; +using EPPlusImageRenderer; +using EPPlusImageRenderer.RenderItems; +using EPPlusImageRenderer.Svg; +using OfficeOpenXml.Drawing.Chart; +using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OfficeOpenXml.Drawing.Renderer.Chart +{ + internal static class LegendIconRenderer + { + const float MarginIconText = 1.5f; + const float MarginHeight = 7.5f; + const float LineLength = 21; + const float MinBarLength = 4; + const float MinPieLength = 5.25f; + + internal static void SetBarLegendSingle(ChartRenderer chart, T parent, double entryWidth, double entryHeight, double maxIconLength) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var ct = chart.Chart.PlotArea.ChartTypes[0]; + var s = ct.Series[0]; + var series = ct.Series[0]; + var catSeries = series.XSeries; + var catValues = DrawingExtensions.LoadSeriesValues(ct, catSeries, series.NumberLiteralsX, series.StringLiteralsX); + + var valValues = DrawingExtensions.LoadSeriesValues(ct, s.Series, s.NumberLiteralsY, s.StringLiteralsY); + + if (catValues == null || catValues.Count == 0) + { + //Blank cat series. Add blank cat + catValues = new List(); + catValues.Add(""); + parent.Rectangle.Height = entryHeight + MarginHeight; + } + + var index = 0; + DrawingLegendSerie pSls = null; + + foreach (var cv in catValues) + { + var sls = new DrawingLegendSerie(); + var bs = (ExcelBarChartSerie)s; + var tm = parent.SeriesHeadersMeasure[index]; + var si = LegendIconRenderer.GetBarSeriesIcon(chart, ct, parent, bs, pSls, entryWidth, entryHeight, 0, index); + sls.SeriesIcon = si; + + var tbLeft = si.Left + maxIconLength + MarginIconText; + var tbTop = si.Top - (entryHeight - si.Height) / 2; + double tbWidth; + + tbWidth = parent.Rectangle.Width - tbLeft; + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + //sls.Textbox.Bounds.Left = si.Bottom + MarginIconText; + + var headerText = cv.ToString(); + + if (parent is ChartLegendRenderer) + { + var entry = chart.Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); + if (entry == null || entry.Font.IsEmpty) + { + //sls.Textbox.AddText(s.GetHeaderText(), sc.Chart.Legend.Font); + sls.Textbox.ImportParagraph(chart.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + else + { + //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); + sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + } + else if (parent is ChartDataTableRenderer) + { + if(chart.Chart.PlotArea.DataTable.TextBody.Paragraphs.Any()) + { + sls.Textbox.ImportParagraph(chart.Chart.PlotArea.DataTable.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + else + { + sls.Textbox.AddParagraph(headerText); + } + } + parent.SeriesIcon.Add(sls); + pSls = sls; + index++; + } + } + + internal static void SetBarLegend(ChartRenderer chart, T parent, ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var bs = (ExcelBarChartSerie)s; + var tm = parent.SeriesHeadersMeasure[index]; + var si = GetBarSeriesIcon(chart, ct, parent, bs, pSls, entryWidth, entryHeight, index, -1); + sls.SeriesIcon = si; + + var tbLeft = si.Left + maxIconLength + MarginIconText; + var tbTop = si.Top - (entryHeight - si.Height) / 2; + double tbWidth; + + tbWidth = parent.Rectangle.Width - tbLeft; + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + //sls.Textbox.Bounds.Left = si.Bottom + MarginIconText; + + var entry = parent.Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); + var headerText = s.GetHeaderText(index); + if (entry == null || entry.Font.IsEmpty) + { + //sls.Textbox.AddText(s.GetHeaderText(), sc.Chart.Legend.Font); + sls.Textbox.ImportParagraph(chart.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + else + { + //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); + sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + } + + internal static void SetLineLegend(ChartRenderer chart, T parent, ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var ls = (ExcelLineChartSerie)s; + + var si = GetLineSeriesIcon(chart, parent, ls, pSls, entryWidth, entryHeight); + sls.SeriesIcon = si; + + var tbLeft = si.X1 + maxIconLength + MarginIconText; + var tbTop = si.Y2 - entryHeight * 0.5; + var tbWidth = parent.Rectangle.Width - tbLeft; + + var tbHeight = entryHeight; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + + var headerText = s.GetHeaderText(index); + var entry = parent.Entries?.FirstOrDefault(x => x.Index == index); + if (entry == null || entry.Font.IsEmpty) + { + sls.Textbox.ImportParagraph(parent.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + else + { + //sls.Textbox.AddText(s.GetHeaderText(), entry.Font); + sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + } + + if (ls.HasMarker() && ls.Marker.Style != eMarkerStyle.None) + { + var l = sls.SeriesIcon as LineRenderItem; + var x = l.X1 + (l.X2 - l.X1) / 2; + var y = l.Y1; + sls.MarkerIcon = LineMarkerHelper.GetMarkerItem(chart, ls, ls.Marker, x, y, true); + if ((ls.Marker.Style == eMarkerStyle.Plus || ls.Marker.Style == eMarkerStyle.X || ls.Marker.Style == eMarkerStyle.Star) && + ls.Marker.Fill.IsEmpty == false) + { + sls.MarkerBackground = LineMarkerHelper.GetMarkerBackground(chart, ls, x, y, true); + } + else + { + sls.MarkerBackground = null; + } + } + } + internal static void SetPieLegend(ChartRenderer chart, T parent, ExcelChart ct, int index, DrawingLegendSerie pSls, ExcelChartSerie s, DrawingLegendSerie sls, double entryWidth, double entryHeight, double maxIconLength) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var ps = (ExcelPieChartSerie)s; + pSls = null; + + //Pie chart only cares about series 0 + var series = ct.Series[0]; + var catSeries = series.XSeries; + var catValues = DrawingExtensions.LoadSeriesValues(ct, catSeries, series.NumberLiteralsX, series.StringLiteralsX); + + //Excel fallsback to index + 1 if no literals and no series + if (catValues == null) + { + catValues = new List(); + foreach (var dp in ps.DataPoints) + { + catValues.Add($"{dp.Index + 1}"); + } + } + + chart.Chart.Legend.TextBody.GetInsetsOrDefaults(out double lDefMargin, out double tDefMarg, out double rDefMargin, out double bDefMarg); + + var widestEntry = entryWidth; + + double lastWidth = 0d; + double totalWidth = 0d; + + double firstIconWidth = 0d; + + for (int i = 0; i < catValues.Count; i++) + { + var tm = parent.SeriesHeadersMeasure[index + i]; + //Step 1: Retrieve Icon + + var si = GetPieSeriesIcon(chart, ct, parent, ps, pSls, lastWidth, entryHeight, i); + //The si-width is used as left margin for each entry seemingly + //si.Left += (si.BorderWidth ?? 0d)*2d; + if (si.Left < 4.5d) + { + si.Left = 4.5d; + } + + if (i == 0) + { + firstIconWidth = si.Width; + } + sls = new DrawingLegendSerie(); + var tbLeft = si.Left + si.Width + MarginIconText + (si.Style.BorderWidth ?? 0d); + var tbTop = si.Top - ((entryHeight + MarginIconText) / 2); + + double tbWidth; + + if (i != catValues.Count - 1) + { + tbWidth = parent.Rectangle.Width - tbLeft; + } + else + { + tbWidth = parent.Rectangle.Width; + } + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + sls.Textbox.ImportParagraph(chart.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, catValues[i].ToString()); + + //si.Left += sls.Textbox.LeftMargin; + + sls.SeriesIcon = si; + sls.Textbox.RecalculateParagraphs(); + + tbWidth = sls.Textbox.Width + rDefMargin; /*+ lDefMargin + rDefMargin;*/ + + lastWidth = tbWidth + si.Width - (si.Style.BorderWidth ?? 0d); + + totalWidth += tbWidth + si.Width + (si.Style.BorderWidth ?? 0d) + MarginIconText; + + if (i >= 0 && ps.DataPoints.ContainsKey(i)) + { + var dp = ps.DataPoints[i]; + ChartTypeDrawer.SetFillDataPoint(chart.Chart, ps, i, sls.SeriesIcon.Style, dp, chart.Chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart.Chart, ct, ps, 0, i, sls.SeriesIcon.Style); + } + + parent.SeriesIcon.Add(sls); + pSls = sls; + } + + foreach (var icon in parent.SeriesIcon) + { + icon.SeriesIcon.Top = icon.SeriesIcon.Top - ((entryHeight) / 4); + } + var position = chart.Chart.Legend.Position; + if (position == eLegendPosition.Top || position == eLegendPosition.Bottom) + { + //Rectangle.Bounds.Width = totalWidth; + parent.Rectangle.Width = parent.SeriesIcon.Last().Textbox.GetGlobalBoundingbox().Right - parent.SeriesIcon[0].SeriesIcon.GlobalLeft + 4d + firstIconWidth * 2; + parent.Rectangle.Left = ((chart.Bounds.Width) / 2d) - (totalWidth / 2d) + 1.5d; + + if (parent.Rectangle.Width < parent.MaxWidth) + { + parent.Rectangle.Height = entryHeight + parent.TopMargin + parent.BottomMargin; + parent.Rectangle.Top = chart.ChartArea.Rectangle.Height - parent.Rectangle.Height - parent.BottomMargin - parent.TopMargin; + } + } + pSls = null; + sls = null; + } + + internal static LineRenderItem GetLineSeriesIcon(ChartRenderer chart, T parent, ExcelChartStandardSerie cStandardSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var line = new LineRenderItem(parent.Rectangle); + //Default style is NoLine NoFill + line.Style.SetDrawingPropertiesBorder(chart.Theme, cStandardSerie.Border, chart.Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.IsEmpty || cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Empty, 3); + double iconTop = 0, iconLeft = 0; + pSls?.GetIconTopLeft(out iconTop, out iconLeft); + + GetItemPosition(chart, parent, pSls, entryWidth, entryHeight, iconLeft, iconTop, out double x, out double y); + + line.X1 = x; + line.X2 = x + LineLength; + line.Y1 = y; + line.Y2 = y; + line.Style.LineCap = LineCap.Round; + + return line; + } + internal static RectRenderItem GetBarSeriesIcon(ChartRenderer chart, ExcelChart ct, T parent, ExcelBarChartSerie chartSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int serieIndex, int index) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var item = new RectRenderItem(parent.Rectangle); + var iconHeight = GetIconLength(ct, entryHeight); + //var icon = pSls?.SeriesIcon as RectRenderItem; + double iconTop = 0, iconLeft = 0; + pSls?.GetIconTopLeft(out iconTop, out iconLeft); + + GetItemPosition(chart, parent, pSls, entryWidth, entryHeight, iconLeft, iconTop + (iconHeight / 2), out double x, out double y); + + item.Style.LineCap = LineCap.Round; + item.Left = x; + if (pSls != null && (chart.Chart.Legend.Position == eLegendPosition.Left || chart.Chart.Legend.Position == eLegendPosition.Right)) + { + item.Top = y - iconHeight / 2d; + } + else + { + item.Top = y - iconHeight / 2d; + } + //item.Top = y; + item.Width = iconHeight; + item.Height = iconHeight; + + if (index >= 0 && chartSerie.DataPoints.ContainsKey(index)) + { + var dp = chartSerie.DataPoints[index]; + ChartTypeDrawer.SetFillDataPoint(chart.Chart, chartSerie, index, item.Style, dp, chart.Chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart.Chart, ct, chartSerie, serieIndex, index, item.Style); + } + + return item; + } + internal static LineRenderItem GetTrendLineSeriesIcon(ChartRenderer chart, ExcelChart ct, T parent, ExcelChartTrendline tl, DrawingLegendSerie pSls, double entryWidth, double entryHeight) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var line = new LineRenderItem(parent.Rectangle); + line.Style.SetDrawingPropertiesFill(chart.Theme, tl.Fill, chart.Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, parent.DefaultFillColor); + //Default is actually NoLine + line.Style.SetDrawingPropertiesBorder(chart.Theme, tl.Border, chart.Chart.StyleManager.Style?.Trendline.BorderReference.Color, tl.Border.Fill.Style != eFillStyle.NoFill, () => parent.DefaultBorderColor, 0.75); + double iconTop = 0, iconLeft = 0; + pSls?.GetIconTopLeft(out iconTop, out iconLeft); + + GetItemPosition(chart, parent, pSls, entryWidth, entryHeight, iconLeft, iconTop, out double x, out double y); + + line.X1 = x; + line.Y1 = y; + line.X2 = x + LineLength; + line.Y2 = y; + line.Style.LineCap = LineCap.Round; + + return line; + } + + internal static RectRenderItem GetPieSeriesIcon(ChartRenderer chart, ExcelChart ct, T parent, ExcelPieChartSerie pcS, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int i) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var item = new RectRenderItem(parent.Rectangle); + + var iconHeight = GetIconLength(ct, entryHeight); + var icon = pSls?.SeriesIcon as RectRenderItem; + + GetItemPosition(chart, parent, pSls, entryWidth, entryHeight, icon?.Left ?? 0D, icon?.Top ?? 0D, out double x, out double y); + + item.Style.LineCap = LineCap.Round; + item.Left = x; + if (pSls != null && (chart.Chart.Legend.Position == eLegendPosition.Left || chart.Chart.Legend.Position == eLegendPosition.Right)) + { + item.Top = y - (iconHeight / 2d); + } + else + { + item.Top = y; + } + + double borderWidth = pcS.Border.Width; + + if (pcS.DataPoints != null && pcS.DataPoints.Count != 0 && i < pcS.DataPoints.Count) + { + if (borderWidth < pcS.DataPoints[i].Border.Width) + { + borderWidth = pcS.DataPoints[i].Border.Width; + } + } + + //item.Top = y; + item.Width = iconHeight; + item.Height = iconHeight; + + item.Style.SetDrawingPropertiesFill(chart.Theme, pcS.Fill, chart.Chart.StyleManager.Style?.SeriesLine.FillReference.Color); + item.Style.SetDrawingPropertiesBorder(chart.Theme, pcS.Border, chart.Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, () => parent.DefaultBorderColor, 1.5d); + + return item; + } + + internal static double GetIconLength(ExcelChart c, double highestText) + { + return c.IsTypeLine() ? LineLength : Math.Max(MinBarLength, highestText * 0.4); + } + + private static double GetItemPosition(ChartRenderer chart, T parent, DrawingLegendSerie pSls, double entryWidth, double entryHeight, double iconLeft, double iconCenter, out double x, out double y) where T : ChartDrawingObjectWithBackground, ILegendKeyContainer + { + var topOffset = 0D; + if (parent.Position == eLegendPosition.Top || + parent.Position == eLegendPosition.Bottom) + { + if (pSls != null && iconLeft + entryWidth * 2 + parent.MarginItemsWidth + parent.RightMargin > parent.MaxWidth) + { + topOffset += entryHeight * 1.25; + x = parent.LeftMargin; + } + else + { + if (pSls == null) + { + x = (float)parent.LeftMargin; + } + else + { + x = iconLeft + entryWidth + parent.MarginItemsWidth; + } + } + + if (pSls == null) + { + y = parent.TopMargin + entryHeight / 2; + } + else + { + y = iconCenter + topOffset; + } + + + } + else + { + if (pSls == null) + { + y = parent.TopMargin + entryHeight / 2; + } + else + { + y = iconCenter + entryHeight * 1.5; + } + x = parent.LeftMargin; + + } + + return topOffset; + } + + internal static RenderItem GetSeriesIcon(ChartRenderer chartRenderer, BoundingBox parentItem, ExcelChartStandardSerie s, int index, double entryHeight) + { + const float MarginExtra = 1.5f; + const float DefaultStrokeWidth = 0.75f; + + var theme = s._chart.WorkSheet.Workbook.ThemeManager.GetOrCreateTheme(); + + if(s._chart.IsTypeLine()) + { + var item = new LineRenderItem(parentItem); + item.Style.SetDrawingPropertiesFill(theme, s.Fill, chartRenderer.Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + item.Style.SetDrawingPropertiesBorder(theme, s.Border, chartRenderer.Chart.StyleManager.Style.SeriesLine.BorderReference.Color, s.Border.Fill.Style != eFillStyle.NoFill, null, DefaultStrokeWidth, UserSpaceSettings.ObjectBoundingBox); + + float y = (float)parentItem.Top + MarginExtra; + float x = 0; + item.X1 = x; + item.Y1 = y; + item.X2 = x + (LineLength - (float)item.Style.BorderWidth); + item.Y2 = y; + item.Style.LineCap = LineCap.Round; + return item; + } + else + { + var item = new RectRenderItem(parentItem); + var iconHeight = GetIconLength(chartRenderer.Chart, entryHeight); + item.Width = iconHeight; + item.Height = iconHeight; + var chart = chartRenderer.Chart; + var bs = (ExcelBarChartSerie)s; + if (index >= 0 && bs.DataPoints.ContainsKey(index)) + { + var dp = bs.DataPoints[index]; + ChartTypeDrawer.SetFillDataPoint(chart, s, index, item.Style, dp, chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart, s._chart, s, index, index, item.Style); + } + + return item; + } + + + } + + } +} diff --git a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs index b3ae77ca89..b059565c6a 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs @@ -18,30 +18,34 @@ internal class LineMarkerHelper internal static RenderItem GetMarkerItem(ChartRenderer sc, ExcelLineChartSerie ls, ExcelChartMarker marker, double x, double y, bool isLegend) { RenderItem item = GetMarkerRenderItem(sc, x, y, isLegend, marker); - if (marker.Fill.IsEmpty == false) - { - item?.SetDrawingPropertiesFill(sc.Theme, marker.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); - } - else if (ls.Fill.IsEmpty) - { - item?.SetDrawingPropertiesFillBasic(sc.Theme, ls.Border.Fill, sc.Chart.StyleManager.Style?.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, sc.Theme.ColorScheme.Accent1.GetColor()); - } - else - { - item?.SetDrawingPropertiesFill(sc.Theme, ls.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); - } - - if (marker.Border.Width > 0) - { - if (marker.Border.Fill.IsEmpty) + var style = item?.Style; + if (style != null) + { + if (marker.Fill.IsEmpty == false) + { + style.SetDrawingPropertiesFill(sc.Theme, marker.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + } + else if (ls.Fill.IsEmpty) { - //Datapoints including markers actually have a way more complex fallback TODO: Handle later. - item?.SetDrawingPropertiesBorder(sc.Theme, ls.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + style.SetDrawingPropertiesFillBasic(sc.Theme, ls.Border.Fill, sc.Chart.StyleManager.Style?.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, sc.Theme.ColorScheme.Accent1.GetColor()); } else { - //Datapoints including markers actually have a way more complex fallback TODO: Handle later. - item?.SetDrawingPropertiesBorder(sc.Theme, marker.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Marker.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + style?.SetDrawingPropertiesFill(sc.Theme, ls.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + } + + if (marker.Border.Width > 0) + { + if (marker.Border.Fill.IsEmpty) + { + //Datapoints including markers actually have a way more complex fallback TODO: Handle later. + style.SetDrawingPropertiesBorder(sc.Theme, ls.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + } + else + { + //Datapoints including markers actually have a way more complex fallback TODO: Handle later. + style.SetDrawingPropertiesBorder(sc.Theme, marker.Border, sc.Chart.StyleManager.Style.DataPointMarker.BorderReference.Color, ls.Marker.Border.Fill.Style != eFillStyle.NoFill, () => sc.Theme.FormatScheme.BorderStyle[0].Fill.Color, 0.75d); + } } } return item; @@ -71,15 +75,15 @@ private static RenderItem GetMarkerRenderItem(ChartRenderer sc, double x, double break; case eMarkerStyle.Triangle: item = new PathRenderItem(sc.Bounds); - var cmd = new PathCommands(PathCommandType.Move, new double[] { xPath + halfSize, yPath + halfSize, xPath, yPath - halfSize, xPath - halfSize, yPath + halfSize }); + var cmd = new PathCommand(PathCommandType.Move, new double[] { xPath + halfSize, yPath + halfSize, xPath, yPath - halfSize, xPath - halfSize, yPath + halfSize }); ((PathRenderItem)item).Commands.Add(cmd); - ((PathRenderItem)item).Commands.Add(new PathCommands(PathCommandType.End)); + ((PathRenderItem)item).Commands.Add(new PathCommand(PathCommandType.End)); break; case eMarkerStyle.Diamond: - item = new PathRenderItem(sc.ChartArea.Rectangle.Bounds); - cmd = new PathCommands(PathCommandType.Move, new double[] { (xPath - halfSize), yPath, xPath, yPath + halfSize, xPath + halfSize, yPath, xPath, yPath - halfSize }); + item = new PathRenderItem(sc.ChartArea.Rectangle); + cmd = new PathCommand(PathCommandType.Move, new double[] { (xPath - halfSize), yPath, xPath, yPath + halfSize, xPath + halfSize, yPath, xPath, yPath - halfSize }); ((PathRenderItem)item).Commands.Add(cmd); - ((PathRenderItem)item).Commands.Add(new PathCommands(PathCommandType.End)); + ((PathRenderItem)item).Commands.Add(new PathCommand(PathCommandType.End)); break; case eMarkerStyle.Dot: case eMarkerStyle.Dash: @@ -126,29 +130,29 @@ private static RenderItem GetMarkerRenderItem(ChartRenderer sc, double x, double var pathItem = new PathRenderItem(sc.Bounds); if (m.Style == eMarkerStyle.Star) { - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath - halfSize, yPath - halfSize, xPath + halfSize, yPath + halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath - halfSize, yPath - halfSize, xPath + halfSize, yPath + halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath, yPath + halfSize, xPath, yPath - halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath, yPath + halfSize, xPath, yPath - halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath + halfSize, yPath - halfSize, xPath - halfSize, yPath + halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.End)); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath + halfSize, yPath - halfSize, xPath - halfSize, yPath + halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.End)); } else if (m.Style == eMarkerStyle.X) { - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath - halfSize, yPath - halfSize, xPath + halfSize, yPath + halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.End)); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath - halfSize, yPath - halfSize, xPath + halfSize, yPath + halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.End)); - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath - halfSize, yPath + halfSize, xPath + halfSize, yPath - halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.End)); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath - halfSize, yPath + halfSize, xPath + halfSize, yPath - halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.End)); } else { - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath, yPath - halfSize, xPath, yPath + halfSize })); - pathItem.Commands.Add(new PathCommands(PathCommandType.End)); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath, yPath - halfSize, xPath, yPath + halfSize })); + pathItem.Commands.Add(new PathCommand(PathCommandType.End)); - pathItem.Commands.Add(new PathCommands(PathCommandType.Move, new double[] { xPath - halfSize, yPath, xPath + halfSize, yPath })); - pathItem.Commands.Add(new PathCommands(PathCommandType.End)); + pathItem.Commands.Add(new PathCommand(PathCommandType.Move, new double[] { xPath - halfSize, yPath, xPath + halfSize, yPath })); + pathItem.Commands.Add(new PathCommand(PathCommandType.End)); } item = pathItem; break; @@ -173,7 +177,7 @@ internal static RenderItem GetMarkerBackground(ChartRenderer sc, ExcelLineChartS Width = size, Height = size }; - item?.SetDrawingPropertiesFill(sc.Theme, ls.Marker.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + item.Style.SetDrawingPropertiesFill(sc.Theme, ls.Marker.Fill, sc.Chart.StyleManager.Style.DataPointMarker.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); return item; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index f638800802..b45888b7ae 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -8,13 +8,15 @@ using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +using OfficeOpenXml.Style; using OfficeOpenXml.Utils.Drawing; using System; using System.Collections.Generic; +using System.Drawing; namespace EPPlus.Export.ImageRenderer.Svg.Chart { - internal class PieSliceRenderItem : ChartDrawingObject + internal class PieSliceRenderItem : ChartDrawingObjectWithBackground { double _radius; /// @@ -25,7 +27,7 @@ internal class PieSliceRenderItem : ChartDrawingObject /// The holder of the actual items, AFTER origin/translations /// GroupRenderItem _innerItems; - Point _circleCenter; + TranformPoint _circleCenter; /// /// How many percent of the pie this represents @@ -34,11 +36,11 @@ internal class PieSliceRenderItem : ChartDrawingObject internal double Degrees { get; private set; } - Point _startPoint; - Point _startPointHalf; - Point _midPoint; - Point _endPoint; - Point _endPointHalf; + TranformPoint _startPoint; + TranformPoint _startPointHalf; + TranformPoint _midPoint; + TranformPoint _endPoint; + TranformPoint _endPointHalf; internal override System.Drawing.Color? DefaultFillColor { get; } @@ -159,7 +161,7 @@ void CalculateWidthHeight(double prevSliceDegrees) ExtremePoints = new BoundingBox(minX, minY, maxX - minX, maxY - minY); - ExtremePoints.Parent = _innerGroup.Bounds; + ExtremePoints.Parent = _innerGroup; } private double _sliceScaleFactor = 1d; @@ -180,19 +182,19 @@ private void CalculateExplosionDir() CtrToOuterMidDir = new Vector2(pieDirection.X, pieDirection.Y); } - public PieSliceRenderItem(ChartRenderer renderer, BoundingBox parent, Point circleCenter, double radius, double percentOfPie, double prevSliceDegrees) : base(renderer) + public PieSliceRenderItem(ChartRenderer renderer, BoundingBox parent, TranformPoint circleCenter, double radius, double percentOfPie, double prevSliceDegrees) : base(renderer) { _prevSliceDegrees = prevSliceDegrees; DefaultFillColor = renderer.Theme.ColorScheme.Accent1.GetColor(); - Rectangle.Bounds.Parent = parent; + Rectangle.Parent = parent; _radius = radius; _percent = percentOfPie; //How many degrees that percentage is out of 360 Degrees = _percent * 360d; _innerGroup = new GroupRenderItem(parent, 0, circleCenter); - _innerGroup.Bounds.Parent = _innerGroup.TranslationOffset; - _innerGroup.Bounds.Name = "InnerGroupChartDrawer"; + _innerGroup.Parent = _innerGroup.TranslationOffset; + _innerGroup.Name = "InnerGroupChartDrawer"; _circleCenter = circleCenter; @@ -215,22 +217,22 @@ public PieSliceRenderItem(ChartRenderer renderer, BoundingBox parent, Point circ CalculateExplosionDir(); CalculateWidthHeight(prevSliceDegrees); - - _innerItems = new GroupRenderItem(_innerGroup.Bounds, 0); + _innerItems = new GroupRenderItem(_innerGroup, 0); + _innerItems.Name = "Inner_Items"; } internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaBounds, double sliceScaleFactor, double explosionOfPoint, double pieExplosion, int position) { _slicePath = new PathRenderItem(plotAreaBounds); - _slicePath.BorderWidth = 5d; + _slicePath.Style.BorderWidth = 5d; //Calculate path commands - var moveCenter = new PathCommands(PathCommandType.Move, _circleCenter.Left, _circleCenter.Top); - var lineToStart = new PathCommands(PathCommandType.Line, _startPoint.Left, _startPoint.Top); + var moveCenter = new PathCommand(PathCommandType.Move, _circleCenter.Left, _circleCenter.Top); + var lineToStart = new PathCommand(PathCommandType.Line, _startPoint.Left, _startPoint.Top); - var arcCommand = new PathCommands(PathCommandType.Arc, new double[] { _radius, _radius, 0, Degrees > 180 ? 1 : 0, 1, _endPoint.Left, _endPoint.Top }); - var end = new PathCommands(PathCommandType.End, _endPoint.Left, _endPoint.Top); + var arcCommand = new PathCommand(PathCommandType.Arc, new double[] { _radius, _radius, 0, Degrees > 180 ? 1 : 0, 1, _endPoint.Left, _endPoint.Top }); + var end = new PathCommand(PathCommandType.End, _endPoint.Left, _endPoint.Top); //Get max and min values var localMax = GetTranslationMaxLocal(globalAreaBounds.Width, globalAreaBounds.Height); @@ -238,7 +240,7 @@ internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaB _sliceScaleFactor = sliceScaleFactor; //Translate and scale path - _innerGroup.Scale = new Coordinate(_sliceScaleFactor, _sliceScaleFactor); + _innerGroup.GroupScale = new Coordinate(_sliceScaleFactor, _sliceScaleFactor); CalculatePointExplosion(explosionOfPoint, pieExplosion, localMax, localMin); CalculateLargestRectWithinCircleSegment(); @@ -263,25 +265,25 @@ internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaB /// AKA line along scale/explosion vector /// /// - private void AddDebugLines(PathCommands moveCenter, BoundingBox bounds) + private void AddDebugLines(PathCommand moveCenter, BoundingBox bounds) { DebugItems = new List(); //Render bounds for slice _debugBoundsPath = new PathRenderItem(bounds); - _debugBoundsPath.BorderColor = "red"; - _debugBoundsPath.FillColor = "transparent"; - _debugBoundsPath.BorderWidth = 3; - var moveCenterDebug = new PathCommands(PathCommandType.Move, ExtremePoints.Left, ExtremePoints.Top); + _debugBoundsPath.Style.BorderColor = "red"; + _debugBoundsPath.Style.FillColor = "transparent"; + _debugBoundsPath.Style.BorderWidth = 3; + var moveCenterDebug = new PathCommand(PathCommandType.Move, ExtremePoints.Left, ExtremePoints.Top); _debugBoundsPath.Commands.Add(moveCenterDebug); //Draw extremes/bounds //var lineToTopLeft = new PathCommands(PathCommandType.Line, ExtremePoints.Left, ExtremePoints.Top); - var lineToTopRight = new PathCommands(PathCommandType.Line, ExtremePoints.Right, ExtremePoints.Top); + var lineToTopRight = new PathCommand(PathCommandType.Line, ExtremePoints.Right, ExtremePoints.Top); - var lineToBottomRight = new PathCommands(PathCommandType.Line, ExtremePoints.Right, ExtremePoints.Bottom); - var lineToBottomLeft = new PathCommands(PathCommandType.Line, ExtremePoints.Left, ExtremePoints.Bottom); - var end = new PathCommands(PathCommandType.End, ExtremePoints.Left, ExtremePoints.Top); + var lineToBottomRight = new PathCommand(PathCommandType.Line, ExtremePoints.Right, ExtremePoints.Bottom); + var lineToBottomLeft = new PathCommand(PathCommandType.Line, ExtremePoints.Left, ExtremePoints.Bottom); + var end = new PathCommand(PathCommandType.End, ExtremePoints.Left, ExtremePoints.Top); _debugBoundsPath.Commands.Add(lineToTopRight); _debugBoundsPath.Commands.Add(lineToBottomRight); @@ -314,7 +316,9 @@ private RectRenderItem GenerateDebugPoint(BoundingBox parent, Coordinate point, double w = 5d; double h = 5d; - return new RectRenderItem(parent) { Left = l + point.X, Top = t + point.Y, Width = w, Height = h, FillColor = fillColor }; + var rect = new RectRenderItem(parent) { Left = l + point.X, Top = t + point.Y, Width = w, Height = h }; + rect.Style.FillColor = fillColor; + return rect; } @@ -326,11 +330,11 @@ internal void ImportStlyeInfo(ExcelPieChartSerie serie, ExcelPieChart chartType, if (position >= 0 && serie.DataPoints.ContainsKey(position)) { var dp = serie.DataPoints[position]; - ChartTypeDrawer.SetFillDataPoint(Chart, serie, position, _slicePath, dp, Chart.StyleManager.Style?.SeriesLine, UserSpaceSettings.ObjectBoundingBox); + ChartTypeDrawer.SetFillDataPoint(Chart, serie, position, _slicePath.Style, dp, Chart.StyleManager.Style?.SeriesLine, UserSpaceSettings.ObjectBoundingBox); } else { - ChartTypeDrawer.SetFillSerie(Chart, chartType, serie, 0, position, _slicePath); + ChartTypeDrawer.SetFillSerie(Chart, chartType, serie, 0, position, _slicePath.Style); } //if(chartType.VaryColors) //{ @@ -352,10 +356,10 @@ internal void AppendGroupItem(GroupRenderItem group) _innerGroup.Left += _innerGroup.TranslationOffset.Left; _innerGroup.Top += _innerGroup.TranslationOffset.Top; - //The slice items post transform operations + ////The slice items post transform operations _innerItems.AddChildItem(_slicePath); - //The bounds and translations of the slice - _innerGroup.AddChildItem(_innerItems); + ////The bounds and translations of the slice + //_innerGroup.AddChildItem(_innerItems); if (DebugItems != null && DebugItems.Count > 0) { @@ -374,7 +378,7 @@ Vector2 GetTranslationMaxLocal(double globalWidth, double globalHeight) var worldPositionTransformOrigin = _midPoint.Position; //Calculate extremes - Point localMax = new Point( + TranformPoint localMax = new TranformPoint( globalWidth - worldPositionTransformOrigin.X, globalHeight - worldPositionTransformOrigin.Y); @@ -385,7 +389,7 @@ Vector2 GetTranslationMinLocal(double globalWidth, double globalHeight) { var worldPositionTransformOrigin = _midPoint.Position; //Calculate extremes - Point worldMin = new Point(-worldPositionTransformOrigin.X, -worldPositionTransformOrigin.Y); + TranformPoint worldMin = new TranformPoint(-worldPositionTransformOrigin.X, -worldPositionTransformOrigin.Y); //var localMin = _innerGroup.Position.Parent.TransformPointToLocal(worldMin.Position); return worldMin.LocalPosition; @@ -411,11 +415,11 @@ internal Vector2 GetWholeVectorCenterToMid() /// /// /// - internal Point GetSliceShapeCenterLocal() + internal TranformPoint GetSliceShapeCenterLocal() { var translationVector = GetLocalTranslationVector(50); var pt = _circleCenter.LocalPosition + translationVector; - var SliceCenterLocal = new Point(pt.X, pt.Y); + var SliceCenterLocal = new TranformPoint(pt.X, pt.Y); return SliceCenterLocal; } @@ -540,6 +544,8 @@ void CalculatePointExplosion(double explosionOfPoint, double pieExplosion, Vecto internal double LargestWidthRectangle { get; private set; } internal double LargestHeightRectangle { get; private set; } + internal override Color? DefaultBorderColor => throw new NotImplementedException(); + void CalculateLargestRectWithinCircleSegment() { //The degrees of the slice @@ -602,31 +608,31 @@ void CalculateLargestRectWithinCircleSegment() } } - Point CalculateLocalPointOnCircle(double degrees) + TranformPoint CalculateLocalPointOnCircle(double degrees) { var angleRadians = MConverter.DegreesToRadians(degrees); var xPoint = _circleCenter.Left + (_radius * Math.Cos(angleRadians)); var yPoint = _circleCenter.Top + (_radius * Math.Sin(angleRadians)); - var point = new Point(); + var point = new TranformPoint(); //Ensure the cx/cy offset - point.Parent = _innerGroup.TranslationOffset.Parent; + point.Parent = _innerGroup; point.Left = xPoint; point.Top = yPoint; return point; } - Point CalculateLocalPointOnCircleHalfRadius(double degrees) + TranformPoint CalculateLocalPointOnCircleHalfRadius(double degrees) { var angleRadians = MConverter.DegreesToRadians(degrees); var xPoint = _circleCenter.Left + (_radius/2d * Math.Cos(angleRadians)); var yPoint = _circleCenter.Top + (_radius/2d * Math.Sin(angleRadians)); - var point = new Point(); + var point = new TranformPoint(); //Ensure the cx/cy offset point.Parent = _innerGroup.TranslationOffset.Parent; @@ -681,7 +687,7 @@ internal Coordinate GetInnerGroupTransformOriginTranslated() internal Transform GetInnerGroupWithTransformOriginTranslated() { Transform transform = new Transform(); - transform.Parent = _innerGroup.Bounds.Parent; + transform.Parent = _innerGroup.Parent; transform.LocalPosition += new Vector2(_innerGroup.TransformOrigin.X + _innerGroup.TranslationOffset.Left - _innerGroup.Left, _innerGroup.TransformOrigin.Y + _innerGroup.TranslationOffset.Top- _innerGroup.Top); return transform; } @@ -735,15 +741,20 @@ internal Transform GetCenterOfEndPointLine() return _endPoint; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { throw new NotImplementedException(); } - //internal BoundingBox GetInnerGroupBounds() - //{ - // return _innerGroup.Bounds; - //} + internal override Color? GetDefaultFillColor() + { + throw new NotImplementedException(); + } + + internal override Color? GetDefaultBorderColor() + { + throw new NotImplementedException(); + } } } diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 6203cded0e..d056dcc6fb 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -93,72 +93,42 @@ public ChartRenderer(ExcelChart chart, SvgRenderOptions options) : base(chart, o //Append all renderitems after everything has been created and positioned, to ensure the correct z-ordering. AppendItems(); - } private void SetAxisPositionsFromPlotarea() { if (VerticalAxis != null) { - PlaceVerticalAxis(VerticalAxis); - //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. - if (VerticalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && HorizontalAxis.Axis.AxisType == eAxisType.Val && HorizontalAxis.Min < 0D) - { - var CrossValue = HorizontalAxis.GetCrossesValue(); - var newRight = Plotarea.Group.Left + HorizontalAxis.GetPositionInPlotarea(CrossValue); - if(newRight > Plotarea.Group.Left) - { - Plotarea.Rectangle.Width += VerticalAxis.Rectangle.Width; - Plotarea.Group.Left = VerticalAxis.Rectangle.Left; - var rightDiff = newRight - VerticalAxis.Rectangle.Width; - VerticalAxis.Rectangle.Left = rightDiff; - VerticalAxis.Line.X1 = VerticalAxis.Line.X2 = newRight; - } - } - VerticalAxis.AddTickmarksAndValues(DefItems); + PlaceVerticalAxis(VerticalAxis, false); } - if (HorizontalAxis != null && HorizontalAxis.Rectangle != null) + if (HorizontalAxis != null) { PlaceHorizontalAxis(HorizontalAxis, false); - - //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. - if (HorizontalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && VerticalAxis.Axis.AxisType == eAxisType.Val && VerticalAxis.Min < 0D && HorizontalAxis.Axis.Crosses == eCrosses.AutoZero) - { - var CrossValue = HorizontalAxis.GetCrossesValue(); - var newtop = VerticalAxis.GetPositionInPlotarea(CrossValue) + Plotarea.Group.Top; - if (newtop > Plotarea.Group.Top) - { - var topDiff = HorizontalAxis.Rectangle.Top - newtop; - HorizontalAxis.Rectangle.Top = newtop; - HorizontalAxis.Rectangle.Height += topDiff; - HorizontalAxis.Line.Y1 = HorizontalAxis.Line.Y2 = newtop; - } - } - - HorizontalAxis.AddTickmarksAndValues(DefItems); } + HorizontalAxis?.AddTickmarksAndValues(DefItems); + VerticalAxis?.AddTickmarksAndValues(DefItems); + if (SecondVerticalAxis != null) { - PlaceVerticalAxis(SecondVerticalAxis); + PlaceVerticalAxis(SecondVerticalAxis, true); SecondVerticalAxis.AddTickmarksAndValues(DefItems); } - if (SecondHorizontalAxis != null && SecondHorizontalAxis.Rectangle != null) + if (SecondHorizontalAxis != null) { PlaceHorizontalAxis(SecondHorizontalAxis, true); SecondHorizontalAxis.AddTickmarksAndValues(DefItems); } } - private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecondary) { - if (horizontalAxis.Axis.Deleted == false) + if (horizontalAxis.Axis.Deleted == false && horizontalAxis.Rectangle != null) { horizontalAxis.Rectangle.Width = Plotarea.Rectangle.Width; horizontalAxis.Rectangle.Left = Plotarea.Group.Left; - horizontalAxis.Line?.X1 = (float)horizontalAxis.Rectangle.Left; - horizontalAxis.Line?.X2 = (float)horizontalAxis.Rectangle.Right; + horizontalAxis.Line?.X1 = (float)0; + horizontalAxis.Line?.X2 = (float)horizontalAxis.Rectangle.Width; if (horizontalAxis.Line != null) { @@ -173,22 +143,40 @@ private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecond { horizontalAxis.Rectangle.Top = Plotarea.Group.Top + Plotarea.Rectangle.Height; } - horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Top; + horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = 0; } else if (axisPos == eActualAxisPosition.BottomSecond) { horizontalAxis.Rectangle.Top = Plotarea.Group.Top + Plotarea.Rectangle.Height + HorizontalAxis.Rectangle.Height; - horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Top; + horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = 0; } else if (axisPos == eActualAxisPosition.Top) { horizontalAxis.Rectangle.Top = Plotarea.Group.Top - horizontalAxis.Rectangle.Height; - horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = (float)Plotarea.Group.Top; + horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Height; } else { horizontalAxis.Rectangle.Top = Plotarea.Group.Top - horizontalAxis.Rectangle.Height - HorizontalAxis.Rectangle.Height; - horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Bottom; + horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Height; + } + } + + if (isSecondary==false && VerticalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && VerticalAxis.Axis.AxisType == eAxisType.Val && VerticalAxis.Min < 0D && HorizontalAxis.Axis.Crosses == eCrosses.AutoZero) + { + //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. + var CrossValue = HorizontalAxis.GetCrossesValue(); + var newtop = VerticalAxis.GetPositionInPlotarea(CrossValue) + Plotarea.Group.Top; + if (newtop > Plotarea.Group.Top) + { + Plotarea.Rectangle.Height += HorizontalAxis.Rectangle.Height; + VerticalAxis.Rectangle.Height = Plotarea.Rectangle.Height; + VerticalAxis.Line.Y2 = (float)VerticalAxis.Rectangle.Bottom; + newtop = VerticalAxis.GetPositionInPlotarea(CrossValue) + Plotarea.Group.Top; //Get the new position after the resize of the plotarea + var topDiff = HorizontalAxis.Rectangle.Top - newtop; + HorizontalAxis.Rectangle.Top = newtop; + HorizontalAxis.Rectangle.Height += topDiff; + //HorizontalAxis.Line?.Y1 = HorizontalAxis.Line.Y2 = topDiff; } } } @@ -200,7 +188,7 @@ private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecond private void PlaceHorizontalAxisTitle(ChartAxisRenderer horizontalAxis) { - horizontalAxis.Title.Rectangle.Height = Bounds.Height / 4; + horizontalAxis.Title.Rectangle.Height = ChartArea.Rectangle.Height / 4; horizontalAxis.Title.Rectangle.Width = horizontalAxis.Rectangle?.Width ?? Plotarea.Rectangle.Width; if (horizontalAxis.Axis.Deleted) { @@ -274,14 +262,14 @@ private void PlaceHorizontalAxisTitle(ChartAxisRenderer horizontalAxis) horizontalAxis.Title.TextBox.Left = Plotarea.Group.Left + (Plotarea.Rectangle.Width / 2) - (horizontalAxis.Title.TextBox.Width / 2); } - private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis) + private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis, bool isSecondary) { if (verticalAxis.Axis.Deleted == false && verticalAxis.Rectangle != null) { verticalAxis.Rectangle.Top = Plotarea.Group.Top; verticalAxis.Rectangle.Height = Plotarea.Rectangle.Height; - verticalAxis.Line?.Y1 = (float)verticalAxis.Rectangle.Top; - verticalAxis.Line?.Y2 = (float)verticalAxis.Rectangle.Bottom; + verticalAxis.Line?.Y1 = (float)0; + verticalAxis.Line?.Y2 = (float)verticalAxis.Rectangle.Height; var axisPos = verticalAxis.Axis.ActualAxisPosition; if(verticalAxis.Line != null) @@ -289,25 +277,39 @@ private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis) if (axisPos == eActualAxisPosition.Left) { verticalAxis.Rectangle.Left = Plotarea.Group.Left - verticalAxis.Rectangle.Width; - verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)Plotarea.Group.Left; + verticalAxis.Line.X1 = verticalAxis.Line.X2 = verticalAxis.Rectangle.Width; } else if (axisPos == eActualAxisPosition.LeftSecond) { verticalAxis.Rectangle.Left = Plotarea.Group.Left - verticalAxis.Rectangle.Width - VerticalAxis.Rectangle.Width; - verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)Plotarea.Group.Left; + verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)verticalAxis.Rectangle.Width; } else if (axisPos == eActualAxisPosition.Right) { verticalAxis.Rectangle.Left = Plotarea.Group.Left + Plotarea.Rectangle.Width; - verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)Plotarea.Group.Left + Plotarea.Rectangle.Width; + verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)0; } else { verticalAxis.Rectangle.Left = Plotarea.Group.Left + Plotarea.Rectangle.Width + VerticalAxis.Rectangle.Width; - verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)Plotarea.Group.Left + Plotarea.Rectangle.Width; + verticalAxis.Line.X1 = verticalAxis.Line.X2 = (float)0; } } } + //Make sure the horizontal axis is moved up if the vertical axis has a negative minimum value, so that the 0 value is at the correct position. + if (isSecondary == false && HorizontalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && HorizontalAxis.Axis.AxisType == eAxisType.Val && HorizontalAxis.Min < 0D) + { + var CrossValue = HorizontalAxis.GetCrossesValue(); + var newLeft = Plotarea.Group.Left + HorizontalAxis.GetPositionInPlotarea(CrossValue); + if (newLeft > Plotarea.Group.Left) + { + Plotarea.Rectangle.Width += VerticalAxis.Rectangle.Width; + Plotarea.Group.Left = VerticalAxis.Rectangle.Left; + var leftDiff = newLeft - VerticalAxis.Rectangle.Width; + VerticalAxis.Rectangle.Left = leftDiff; + //VerticalAxis.Line?.X1 = VerticalAxis.Line.X2 = newLeft; + } + } PlaceVerticalAxisTitle(verticalAxis); } @@ -367,10 +369,9 @@ private void SetChartArea(SvgRenderOptions options) { var item = new ChartAreaRenderer(this, options); item.Rectangle.Width = Bounds.Width; - item.Rectangle.Height = Bounds.Height; - - item.Rectangle.SetDrawingPropertiesFill(Theme, Chart.Fill, Chart.StyleManager.Style?.ChartArea.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, item.GetDefaultFillColor()); - + item.Rectangle.Height = Bounds.Height; + item.Rectangle.Style.SetDrawingPropertiesFill(Theme, Chart.Fill, Chart.StyleManager.Style?.ChartArea.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, item.GetDefaultFillColor()); + item.Rectangle.Name = "Plotarea"; var borderstyle = Theme.FormatScheme.BorderStyle[0]; //First check @@ -389,7 +390,7 @@ private void SetChartArea(SvgRenderOptions options) //var chartStyleId = Chart.StyleManager.Style.Id; - item.Rectangle.SetDrawingPropertiesBorder( + item.Rectangle.Style.SetDrawingPropertiesBorder( Theme, Chart.Border, reference?.Color, @@ -424,7 +425,7 @@ public ExcelChart Chart { get =>(ExcelChart)Drawing; } - internal ChartDrawingObject ChartArea { get; set; } + internal ChartDrawingObjectWithBackground ChartArea { get; set; } internal ChartLegendRenderer Legend { get; set; } internal ChartTitleRenderer Title { get; set; } internal ChartPlotareaRenderer Plotarea { get; set; } @@ -432,7 +433,7 @@ public ExcelChart Chart internal ChartAxisRenderer HorizontalAxis { get; set; } internal ChartAxisRenderer SecondVerticalAxis { get; set; } internal ChartAxisRenderer SecondHorizontalAxis { get; set; } - + internal List DefItems { get; } = new List(); public bool HasDataTable { get => Chart.PlotArea.DataTable != null; } public ChartDataTableRenderer DataTable { get; private set; } @@ -465,7 +466,10 @@ public bool AppendItems() VerticalAxis?.Textboxes?.AppendRenderItems(RenderItems); SecondHorizontalAxis?.Textboxes?.AppendRenderItems(RenderItems); SecondVerticalAxis?.Textboxes?.AppendRenderItems(RenderItems); + + + DataTable?.AppendRenderItems(RenderItems); Title?.AppendRenderItems(RenderItems); Legend?.AppendRenderItems(RenderItems); @@ -488,26 +492,5 @@ internal double GetPlotAreaTop() } } - internal LineRenderItem GetSeriesIcon(ExcelChartStandardSerie s, int index, BoundingBox parentItem) - { - const float MarginExtra = 1.5f; - const float DefaultStrokeWidth = 0.75f; - const float LineLength = 21.0f; - - var item = new LineRenderItem(parentItem); - item.SetDrawingPropertiesFill(Theme, s.Fill, Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); - item.SetDrawingPropertiesBorder(Theme, s.Border, Chart.StyleManager.Style.SeriesLine.BorderReference.Color, s.Border.Fill.Style != eFillStyle.NoFill, null, DefaultStrokeWidth, UserSpaceSettings.ObjectBoundingBox); - - float y = (float)parentItem.Top + MarginExtra; - float x = 0; - item.X1 = x; - item.Y1 = y; - item.X2 = x + (LineLength - (float)item.BorderWidth); - item.Y2 = y; - item.LineCap = LineCap.Round; - - return item; - } - } } diff --git a/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs b/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs index 1e77cb12ef..c554877bf5 100644 --- a/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs @@ -54,7 +54,7 @@ internal DrawingRenderer() public ExcelWorkbook Workbook => Drawing._drawings.Worksheet.Workbook; //internal ITextMeasurer TextMeasurer { get; } internal RenderContext RenderContext { get; } - public List RenderItems { get; } = new List(); + public List RenderItems { get; } = new List(); internal BoundingBox Bounds = new BoundingBox(); } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs index bab68463a0..8ef167db9a 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs @@ -38,42 +38,42 @@ internal enum SvgFillType } internal static class DrawingRenderItemExtentions { - internal static void SetDrawingPropertiesFill(this RenderItem item, ExcelTheme theme, ExcelDrawingFill fill, ExcelDrawingColorManager color, UserSpaceSettings gradientUserSpace = UserSpaceSettings.ObjectBoundingBox, Color? nullColor = null) + internal static void SetDrawingPropertiesFill(this RenderItemStyle style, ExcelTheme theme, ExcelDrawingFill fill, ExcelDrawingColorManager color, UserSpaceSettings gradientUserSpace = UserSpaceSettings.ObjectBoundingBox, Color? nullColor = null) { switch (fill.Style) { case eFillStyle.PatternFill: - item.PatternFill = new DrawingRenderPatternFill(theme, fill.PatternFill, item.FillColorSource); + style.PatternFill = new DrawingRenderPatternFill(theme, fill.PatternFill, style.FillColorSource); break; case eFillStyle.BlipFill: - item.BlipFill = new DrawingRenderBlipFill(fill.BlipFill); + style.BlipFill = new DrawingRenderBlipFill(fill.BlipFill); break; default: - SetDrawingPropertiesFillBasic(item, theme, fill, color, gradientUserSpace, nullColor); + SetDrawingPropertiesFillBasic(style, theme, fill, color, gradientUserSpace, nullColor); break; } } - internal static void SetDrawingPropertiesFillBasic(this RenderItem item, ExcelTheme theme, ExcelDrawingFillBasic fill, ExcelDrawingColorManager color, UserSpaceSettings gradientUserSpaceOnUse, Color? nullColor) + internal static void SetDrawingPropertiesFillBasic(this RenderItemStyle style, ExcelTheme theme, ExcelDrawingFillBasic fill, ExcelDrawingColorManager color, UserSpaceSettings gradientUserSpaceOnUse, Color? nullColor) { double opacity = double.NaN; - var fillNew = GetFillNew(fill, theme, color, item.FillColorSource, out opacity, () => { return nullColor; }, out DrawingRenderGradientFill gradFill, gradientUserSpaceOnUse); + var fillNew = GetFillNew(fill, theme, color, style.FillColorSource, out opacity, () => { return nullColor; }, out DrawingRenderGradientFill gradFill, gradientUserSpaceOnUse); if(gradFill != null) { //Special case for gradFIll as it does not return string - item.GradientFill = gradFill; - item.FillType = FillType.GradientFill; - item.FillColor = null; + style.GradientFill = gradFill; + style.FillType = FillType.GradientFill; + style.FillColor = null; } else { - item.FillColor = fillNew; + style.FillColor = fillNew; } if (opacity != double.NaN) { - item.FillOpacity = opacity; + style.FillOpacity = opacity; } } @@ -184,7 +184,7 @@ internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, { case eFillStyle.NoFill: //Either transparent or Fallback to style hierarhy (options 2, 3 or 4) - fillStr = GetFallbackFill(theme, fill, reference, fillMode, out opacity, GetHardCodedDefaultForItem); + fillStr = GetFallbackFill(theme, fill, reference, fillMode, out opacity, GetHardCodedDefaultForItem); break; case eFillStyle.SolidFill: //1. Standard case. There is a fill color to apply. @@ -201,7 +201,7 @@ internal static string GetFillNew(ExcelDrawingFillBasic fill, ExcelTheme theme, return fillStr; } - internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme theme, ExcelDrawingBorder border, ExcelChartStyleColorManager reference, bool hasBorder, Func GetStyleDefaultColor, double defaultWidth = 1.5d, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global) + internal static void SetDrawingPropertiesBorder(this RenderItemStyle style, ExcelTheme theme, ExcelDrawingBorder border, ExcelChartStyleColorManager reference, bool hasBorder, Func GetStyleDefaultColor, double defaultWidth = 1.5d, UserSpaceSettings gradientUserSpaceOnUse = UserSpaceSettings.UserSpaceOnUse_Global) { string fillColorStr = null; DrawingRenderGradientFill gradFill = null; @@ -211,38 +211,38 @@ internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme { if (hasBorder) { - fillColorStr = GetFillNew(null, theme, reference, item.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); + fillColorStr = GetFillNew(null, theme, reference, style.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); } } else { - fillColorStr = GetFillNew(border.Fill, theme, reference, item.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); + fillColorStr = GetFillNew(border.Fill, theme, reference, style.BorderColorSource, out opacity, GetStyleDefaultColor, out gradFill); } if(gradFill != null) { //Special case as gradfill does not return a string - item.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, gradientUserSpaceOnUse); - item.BorderColor = null; + style.BorderGradientFill = new DrawingRenderGradientFill(theme, border.Fill.GradientFill, gradientUserSpaceOnUse); + style.BorderColor = null; } else { - item.BorderColor = fillColorStr; - item.BorderGradientFill = null; + style.BorderColor = fillColorStr; + style.BorderGradientFill = null; } - item.BorderOpacity = opacity; + style.BorderOpacity = opacity; - if (item.BorderColorSource != PathFillMode.None) + if (style.BorderColorSource != PathFillMode.None) { - item.BorderWidth = (border?.Width ?? 0D) == 0D ? defaultWidth : border.Width; + style.BorderWidth = (border?.Width ?? 0D) == 0D ? defaultWidth : border.Width; if (border != null && border.LineStyle.HasValue && border.LineStyle != eLineStyle.Solid) { - item.BorderDashArray = GetDashArray(border, item.BorderWidth.Value); + style.BorderDashArray = GetDashArray(border, style.BorderWidth.Value); } if (border != null && border.CompoundLineStyle != eCompoundLineStyle.Single) { - item.CompoundLineStyle = (CompoundLineStyle)border.CompoundLineStyle; + style.CompoundLineStyle = (CompoundLineStyle)border.CompoundLineStyle; //TODO:Add support double compound borders. } } @@ -305,25 +305,25 @@ internal static void SetDrawingPropertiesBorder(this RenderItem item, ExcelTheme // // } // //} //} - internal static void SetDrawingPropertiesEffects(this RenderItem item, ExcelTheme theme, ExcelDrawingEffectStyle effect) + internal static void SetDrawingPropertiesEffects(this RenderItemStyle style, ExcelTheme theme, ExcelDrawingEffectStyle effect) { if (effect.HasGlow) { - item.GlowRadius = effect.Glow.Radius; + style.GlowRadius = effect.Glow.Radius; var gc = tc.ColorConverter.GetThemeColor(theme, effect.Glow.Color); if(gc.A>0) { - item.GlowOpacity = Math.Round(gc.A / 255D * 100); + style.GlowOpacity = Math.Round(gc.A / 255D * 100); } - item.GlowColor = "#" + gc.ToArgb().ToString("x8").Substring(2); + style.GlowColor = "#" + gc.ToArgb().ToString("x8").Substring(2); } if (effect.HasOuterShadow) { - item.OuterShadowEffect = new RenderShadowEffect(); - item.OuterShadowEffect.OuterShadowEffectColor = tc.ColorConverter.GetThemeColor(theme, effect.OuterShadow.Color); - item.OuterShadowEffect.Direction = effect.OuterShadow.Direction; - item.OuterShadowEffect.BlurRadius = effect.OuterShadow.BlurRadius; - item.OuterShadowEffect.Distance = effect.OuterShadow.Distance; + style.OuterShadowEffect = new RenderShadowEffect(); + style.OuterShadowEffect.OuterShadowEffectColor = tc.ColorConverter.GetThemeColor(theme, effect.OuterShadow.Color); + style.OuterShadowEffect.Direction = effect.OuterShadow.Direction; + style.OuterShadowEffect.BlurRadius = effect.OuterShadow.BlurRadius; + style.OuterShadowEffect.Distance = effect.OuterShadow.Distance; } } diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs index c955307d2f..e8a92ba373 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs @@ -156,7 +156,7 @@ private void ImportStyleInfo(DrawingTextBody textBody, ExcelDrawingParagraph p) { if (p.DefaultRunProperties.Fill != null) { - this.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } } else @@ -164,11 +164,11 @@ private void ImportStyleInfo(DrawingTextBody textBody, ExcelDrawingParagraph p) //Drawingproperties has fallback to firstDefault but excel does not display it so we should not either. if (p.DefaultRunProperties != p._paragraphs.FirstDefaultRunProperties) { - this.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } else { - this.SetDrawingPropertiesFill(textBody.Theme, p._paragraphs.FirstDefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p._paragraphs.FirstDefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } } } @@ -190,16 +190,16 @@ private void ImportStyleInfo(DrawingTextBody textBody, ExcelDrawingParagraph p) //Otherwise use the default fill of the first paragraph fill = p._paragraphs[0].DefaultRunProperties.Fill; } - this.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } else if(p.DefaultRunProperties.Fill != null) { - this.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } else { //Should never happen. - this.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); + Style.SetDrawingPropertiesFill(textBody.Theme, p.DefaultRunProperties.Fill, colorManager, UserSpaceSettings.ObjectBoundingBox, defaultTextColor); } } } @@ -287,7 +287,7 @@ void ImportStyles(ExcelDrawingTextRunCollection textRuns, ExcelTextFont font) } protected override TextRunRenderItem CreateTextRun(BoundingBox parent, string displayText, int origRtIdx) { - return new DrawingTextRunRenderItem(Bounds, displayText, origRtIdx); + return new DrawingTextRunRenderItem(this, displayText, origRtIdx); } public override RenderItem Clone() diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBody.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBody.cs index 3abcadfc53..30a56094ae 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBody.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBody.cs @@ -38,10 +38,10 @@ public DrawingTextBody(RenderContext renderContext, ExcelDrawing drawing, Boundi { _drawing = drawing; Theme = drawing._drawings.Worksheet.Workbook.ThemeManager.GetOrCreateTheme(); - Bounds.Left = left; - Bounds.Top = top; - Bounds.Width = maxWidth; - Bounds.Height = maxHeight; + Left = left; + Top = top; + Width = maxWidth; + Height = maxHeight; MaxWidth = maxWidth; MaxHeight = maxHeight; } @@ -51,28 +51,28 @@ public void ImportParagraph(ExcelDrawingParagraph item, double startingY, string bool isFirst = Paragraphs.Count == 0; Text = text; - var paragraph = CreateParagraph(this, item, Bounds, text); - paragraph.Bounds.Name = $"Paragraph{Paragraphs.Count}"; - paragraph.Bounds.Top = startingY; + var paragraph = CreateParagraph(this, item, this, text); + paragraph.Name = $"Paragraph{Paragraphs.Count}"; + paragraph.Top = startingY; if (AutoSize) { if (Paragraphs.Count == 0) { - Bounds.Height = paragraph.Bounds.Height; - if(Bounds.Height == 0) + Height = paragraph.Height; + if(Height == 0) { - Bounds.Height = 3d; + Height = 3d; } } else { - Bounds.Height += paragraph.Bounds.Height; + Height += paragraph.Height; } - if (Bounds.Width < paragraph.Bounds.Width || (Bounds.Width == MaxWidth && Paragraphs.Count == 0)) + if (Width < paragraph.Width || (Width == MaxWidth && Paragraphs.Count == 0)) { - Bounds.Width = paragraph.Bounds.Width; + Width = paragraph.Width; } } Paragraphs.Add(paragraph); @@ -91,19 +91,19 @@ internal void SetHorizontalAlignmentPosition() switch (p.HorizontalAlignment) { case TextAlignment.Left: - p.Bounds.Left = 0; + p.Left = 0; break; case TextAlignment.Center: - p.Bounds.Left = (Bounds.Width / 2) - (p.Bounds.Width / 2); + p.Left = (Width / 2) - (p.Width / 2); break; case TextAlignment.Right: - p.Bounds.Left = Bounds.Right - p.Bounds.Width; + p.Left = Right - p.Width; break; case TextAlignment.Distributed: case TextAlignment.Justified: case TextAlignment.JustifiedLow: case TextAlignment.ThaiDistributed: - p.Bounds.Left = 0; //TODO: Set left for now as we do not support distributed spacing yet + p.Left = 0; //TODO: Set left for now as we do not support distributed spacing yet break; } } @@ -160,14 +160,14 @@ internal virtual void ImportTextBodyAndParagraphs(ExcelTextBody body, ExcelHoriz var addedPara = Paragraphs.Last(); //addedPara.HorizontalAlignment = defaultAlignment; - currentHeight = addedPara.Bounds.Bottom; - largestWidth = Math.Max(largestWidth, addedPara.Bounds.Width); + currentHeight = addedPara.Bottom; + largestWidth = Math.Max(largestWidth, addedPara.Width); } if (Paragraphs != null && Paragraphs.Count() > 0 && AutoSize) { - Bounds.Height = currentHeight; + Height = currentHeight; } //Ensure contentBounds are calculated and paragraphs don't overlap @@ -180,15 +180,15 @@ internal virtual void ImportTextBodyAndParagraphs(ExcelTextBody body, ExcelHoriz SetHorizontalAlignmentPosition(); } - Bounds.Top = GetAlignmentVertical(); + Top = GetAlignmentVertical(); } //internal override void AppendRenderItems(List renderItems) //{ // SvgGroupItem groupItem; - // if (Bounds.Parent.Rotation == 0) //If the parent is rotated, we should not apply rotation again. This is usually when the parent is a textbox. + // if (Parent.Rotation == 0) //If the parent is rotated, we should not apply rotation again. This is usually when the parent is a textbox. // { - // groupItem = new SvgGroupItem(DrawingRenderer, Bounds, Bounds.Rotation); + // groupItem = new SvgGroupItem(DrawingRenderer, Bounds, Rotation); // } // else // { diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBox.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBox.cs index c0aa7737a0..55143d9f49 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBox.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextBox.cs @@ -41,7 +41,7 @@ private void Init(RenderContext renderContext, ExcelDrawing drawing, BoundingBox Parent = parent; _drawing = drawing; //The context must come from the renderer, not the workbook, so the render target is preserved. - TextBody = new DrawingTextBody(renderContext, drawing, _marginGroup.Bounds, true); + TextBody = new DrawingTextBody(renderContext, drawing, _marginGroup, true); TextBody.MaxWidth = maxWidth; TextBody.MaxHeight = maxHeight; } diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs index 829fae6713..db0098e59a 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs @@ -39,7 +39,7 @@ internal void ImportTextRunBase(ExcelParagraphTextRunBase run, IFontFormatBase m _isFirstInParagraph = run.IsFirstInParagraph; _baseline = run.Baseline; ImportExcelStyleInfo(run.Fill, run.FontItalic, run.FontBold, run.FontUnderLine, run.UnderLineColor, run.FontStrike); - SetClippingHeightToCurrentTextBoxBottom((BoundingBox)Bounds.Parent); + SetClippingHeightToCurrentTextBoxBottom(Parent); } /// @@ -62,7 +62,7 @@ internal void ImportExcelTextFont(ExcelTextFont font, IFontFormatBase baseFont) //Parent and clipping height must be calculated dependent on content ImportExcelStyleInfo(font.Fill, font.Italic, font.Bold, font.UnderLine, font.UnderLineColor, font.Strike); //Assumes texbox uses auto-size? - AdjustParentAndSetClippingHeight((BoundingBox)Bounds.Parent); + AdjustParentAndSetClippingHeight((BoundingBox)Parent); } /// @@ -149,13 +149,13 @@ void ImportDrawingFill(ExcelDrawingFill fill) { if (fill.IsEmpty == false && fill.Style == eFillStyle.SolidFill) { - FillColor = "#" + fill.Color.To6CharHexString(); + Style.FillColor = "#" + fill.Color.To6CharHexString(); } //Backup? Should probably be removed or fallback if (fill.Style == eFillStyle.SolidFill) { - FillColor = "#" + fill.Color.To6CharHexString(); + Style.FillColor = "#" + fill.Color.To6CharHexString(); } } @@ -168,7 +168,7 @@ void ImportRichTextInfo(bool italic, bool bold, eUnderLineType uType, Color uCol _strikeType = (eDrawingStrikeType)strikeType; } - void SetClippingHeightToCurrentTextBoxBottom(BoundingBox parent) + void SetClippingHeightToCurrentTextBoxBottom(Transform parent) { //To get clipping height we need to get the textbody bounds if (parent != null && parent.Parent != null && parent.Parent.Parent != null) diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index 92078702c5..35c991c541 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -59,7 +59,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta var parentBounds = shape.GetBoundingBox(); var shapeGroup = new GroupRenderItem(parentBounds, shape.Rotation); - shapeGroup.Bounds.Name = "ShapeGroup"; + shapeGroup.Name = "ShapeGroup"; RenderItems.Add(shapeGroup); @@ -67,7 +67,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta { foreach (var path in shape.CustomGeom.DrawingPaths) { - shapeGroup.RenderItems.Add(AddFromPaths(Bounds, path)); + shapeGroup.ChildObjects.Add(AddFromPaths(Bounds, path)); } } else @@ -87,7 +87,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta { if (path.Fill != PathFillMode.None) { - shapeGroup.RenderItems.Add(AddFromPaths(parentBounds, path, true, false)); + shapeGroup.ChildObjects.Add(AddFromPaths(parentBounds, path, true, false)); } } @@ -96,7 +96,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta { if (path.Stroke) { - shapeGroup.RenderItems.Add(AddFromPaths(parentBounds, path, false, true)); + shapeGroup.ChildObjects.Add(AddFromPaths(parentBounds, path, false, true)); } } @@ -105,9 +105,9 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta if (shapeDef.TextBoxRect != null) { InsetTextBox = new RectRenderItem(Bounds); - InsetTextBox.Bounds.Left = (float)shapeDef.TextBoxRect.LeftValue.PixelToPoint(); - InsetTextBox.Bounds.Top = (float)shapeDef.TextBoxRect.TopValue.PixelToPoint(); - InsetTextBox.FillOpacity = 0.3d; + InsetTextBox.Left = (float)shapeDef.TextBoxRect.LeftValue.PixelToPoint(); + InsetTextBox.Top = (float)shapeDef.TextBoxRect.TopValue.PixelToPoint(); + InsetTextBox.Style.FillOpacity = 0.3d; if (shape.TextBody.TextAutofit != eTextAutofit.ShapeAutofit) { @@ -128,7 +128,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta if (InsetTextBox != null) { - InsetTextBox.FillOpacity = 0.3d; + InsetTextBox.Style.FillOpacity = 0.3d; } TextBody = CreateTextBodyItem(shape.TextBody); @@ -140,7 +140,7 @@ protected RenderItem AddFromPaths(BoundingBox parent, DrawingPath path, bool dra { var pi = new PathRenderItem(parent); var coordinates = new List(); - PathCommands cmd = null; + PathCommand cmd = null; PathsBase pCmd = null; double cx = 0, cy = 0; foreach (var p in path.Paths) @@ -170,7 +170,7 @@ protected RenderItem AddFromPaths(BoundingBox parent, DrawingPath path, bool dra pi.Commands[pi.Commands.Count - 1].Coordinates = coordinates.ToArray(); coordinates.Clear(); } - pi.Commands.Add(new PathCommands(PathCommandType.End)); + pi.Commands.Add(new PathCommand(PathCommandType.End)); cmd = null; break; } @@ -183,26 +183,27 @@ protected RenderItem AddFromPaths(BoundingBox parent, DrawingPath path, bool dra } var shape = (ExcelShape)Drawing; var shapeDefaultStyle = Theme.ObjectDefaults.ShapeDefinition.Style; + var style = pi.Style; if (drawFill) { - pi.FillColorSource = path.Fill; - pi.SetDrawingPropertiesFill(Theme, shape.Fill, shape.ThemeStyles.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.FillReference.ShapeColor)); + style.FillColorSource = path.Fill; + style.SetDrawingPropertiesFill(Theme, shape.Fill, shape.ThemeStyles.FillReference.Color, UserSpaceSettings.ObjectBoundingBox, ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.FillReference.ShapeColor)); } else { - pi.FillColorSource = PathFillMode.None; - pi.FillColor = "none"; + style.FillColorSource = PathFillMode.None; + style.FillColor = "none"; } if (drawBorder) { - pi.BorderColorSource = path.Stroke ? PathFillMode.Norm : PathFillMode.None; - pi.SetDrawingPropertiesBorder(Theme, shape.Border, shape.ThemeStyles.BorderReference.Color, path.Stroke, ()=> ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.BorderReference.ShapeColor), 0.75d); + style.BorderColorSource = path.Stroke ? PathFillMode.Norm : PathFillMode.None; + style.SetDrawingPropertiesBorder(Theme, shape.Border, shape.ThemeStyles.BorderReference.Color, path.Stroke, ()=> ColorConverter.GetThemeColor(Theme, shapeDefaultStyle.BorderReference.ShapeColor), 0.75d); } else { - pi.BorderColorSource = PathFillMode.None; - pi.BorderColor = "none"; + style.BorderColorSource = PathFillMode.None; + style.BorderColor = "none"; } return pi; @@ -214,22 +215,24 @@ public string ViewBox double l=0, t=0, r=1, b=1; foreach(var item in RenderItems) { - item.GetBounds(out var il, out var it, out var ir, out var ib); - if(ilr) - { - r = ir; - } - if(ib>b) - { - b = ib; + if(ri.GlobalLeft r) + { + r = ri.GlobalRight; + } + if(ri.GlobalBottom > b) + { + b = ri.GlobalBottom; + } } } return $"{(Bounds.Left).PointToPixelString()},{Bounds.Top.PointToPixelString()},{Bounds.Right.PointToPixelString()},{Bounds.Bottom.PointToPixelString()}"; @@ -241,8 +244,8 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) { GetShapeInnerBound(out double x, out double y, out double width, out double height); InsetTextBox = new RectRenderItem(Bounds); - InsetTextBox.Bounds.Left = x.PixelToPoint(); - InsetTextBox.Bounds.Top = y.PixelToPoint(); + InsetTextBox.Left = x.PixelToPoint(); + InsetTextBox.Top = y.PixelToPoint(); InsetTextBox.Width = width.PixelToPoint(); InsetTextBox.Height = height.PixelToPoint(); //InsetTextBox.Bounds.Parent = RenderTextbox.Parent; //TODO:Check that textBody is correct. @@ -251,7 +254,7 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) double l, r, t, b; bodyOrig.GetInsetsOrDefaults(out l, out t, out r, out b); - MarginTextBox = new RectRenderItem(InsetTextBox.Bounds); + MarginTextBox = new RectRenderItem(InsetTextBox); MarginTextBox.Top = t; MarginTextBox.Left = l; @@ -259,42 +262,45 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) MarginTextBox.Height = InsetTextBox.Height - b - t; var insetGrp = new GroupRenderItem(this.Bounds); - insetGrp.Bounds.Left = InsetTextBox.Left; - insetGrp.Bounds.Top = InsetTextBox.Top; - insetGrp.Bounds.Name = $"{Drawing.Name}_InsetTextBox_Pos"; + insetGrp.Left = InsetTextBox.Left; + insetGrp.Top = InsetTextBox.Top; + insetGrp.Name = $"{Drawing.Name}_InsetTextBox_Pos"; - var marginGroup = new GroupRenderItem(insetGrp.Bounds); + var marginGroup = new GroupRenderItem(insetGrp); marginGroup.Left = MarginTextBox.Left; marginGroup.Top = MarginTextBox.Top; - marginGroup.Bounds.Name = $"{Drawing.Name}_MarginTextbox_Pos"; - insetGrp.AddChildItem(marginGroup); + marginGroup.Name = $"{Drawing.Name}_MarginTextbox_Pos"; + //insetGrp.AddChildItem(marginGroup); //grp.Bounds.Position = MarginTextBox.Bounds.Position; //grp.TranslationOffset = new Point(InsetTextBox.Left, InsetTextBox.Top); //grp.Bounds.Position = new EPPlus.Graphics.Geometry.Vector2(InsetTextBox.GlobalLeft, InsetTextBox.GlobalRight); //grp.AddChildItem(InsetTextBox); //grp.AddChildItem(MarginTextBox); - RenderItems.Add(insetGrp); + //RenderItems.Add(insetGrp); //RenderItems.Add(InsetTextBox); //RenderItems.Add(MarginTextBox); //Left and Top are already set by MarginTextBox we need not set them again - var txtBodyItem = new DrawingTextBody(RenderContext, Drawing, MarginTextBox.Bounds, 0, 0, MarginTextBox.Width, MarginTextBox.Height); - txtBodyItem.Bounds.Name = $"{Drawing.Name}_TxtBodyItem"; + var txtBodyItem = new DrawingTextBody(RenderContext, Drawing, MarginTextBox, 0, 0, MarginTextBox.Width, MarginTextBox.Height); + txtBodyItem.Name = $"{Drawing.Name}_TxtBodyItem"; txtBodyItem.ImportTextBodyAndParagraphs(bodyOrig); - foreach(var paragraph in txtBodyItem.Paragraphs) + txtBodyItem.Parent = marginGroup; + + foreach (var paragraph in txtBodyItem.Paragraphs) { - paragraph.Bounds.Name = $"{Drawing.Name}_{paragraph.Bounds.Name}"; + paragraph.Name = $"{Drawing.Name}_{paragraph.Name}"; } - - txtBodyItem.AppendRenderItems(marginGroup.RenderItems); + //txtBodyItem.AppendRenderItems(marginGroup.ChildObjects); //txtBodyItem.Left += InsetTextBox.Left; //txtBodyItem.Top += InsetTextBox.Top; + RenderItems.Add(insetGrp); + //ChartAreaRenderItems.Add(new SvgEndGroupItem(this, Bounds)); - + return txtBodyItem; } @@ -341,75 +347,78 @@ private void GetShapeInnerBound(out double x, out double y, out double width, ou x = y = 0; width = xe = Bounds.Width; height = ye = Bounds.Height; - foreach (var ri in RenderItems) + foreach (var item in RenderItems) { - switch (ri.Type) + if (item is RenderItem ri) { - case RenderItemType.Rect: - var rectItem = (RectRenderItem)ri; - x = rectItem.Left; - y = rectItem.Top; - width = rectItem.Width; - height = rectItem.Height; - break; - case RenderItemType.Path: - var pathItem = (PathRenderItem)ri; - foreach (var cmd in pathItem.Commands) - { - var cmdCoordinates = new List(); - for (int i = 0; i < cmd.Coordinates.Length; i++) + switch (ri.Type) + { + case RenderItemType.Rect: + var rectItem = (RectRenderItem)ri; + x = rectItem.Left; + y = rectItem.Top; + width = rectItem.Width; + height = rectItem.Height; + break; + case RenderItemType.Path: + var pathItem = (PathRenderItem)ri; + foreach (var cmd in pathItem.Commands) { - switch (cmd.Type) + var cmdCoordinates = new List(); + for (int i = 0; i < cmd.Coordinates.Length; i++) { - case PathCommandType.Move: - if (i == 0) - { - currentX = cmd.Coordinates[i]; - currentY = cmd.Coordinates[++i]; - } - else - { + switch (cmd.Type) + { + case PathCommandType.Move: + if (i == 0) + { + currentX = cmd.Coordinates[i]; + currentY = cmd.Coordinates[++i]; + } + else + { + HandleLine(ref currentX, ref currentY, ref xe, ref ye, cmd, cmdCoordinates, ref i); + } + break; + case PathCommandType.VerticalLine: + HandleVertical(y, ref currentY, ref xe, cmd.Coordinates[i]); + break; + case PathCommandType.HorizontalLine: + HandleHorizontal(x, ref currentX, ref xe, cmd.Coordinates[i]); + break; + case PathCommandType.Line: HandleLine(ref currentX, ref currentY, ref xe, ref ye, cmd, cmdCoordinates, ref i); - } - break; - case PathCommandType.VerticalLine: - HandleVertical(y, ref currentY, ref xe, cmd.Coordinates[i]); - break; - case PathCommandType.HorizontalLine: - HandleHorizontal(x, ref currentX, ref xe, cmd.Coordinates[i]); - break; - case PathCommandType.Line: - HandleLine(ref currentX, ref currentY, ref xe, ref ye, cmd, cmdCoordinates, ref i); - break; - case PathCommandType.CubicBézier: - if (currentX > x) - { - x = currentX; - } - if (currentY > y) - { - y = currentY; - } - i += 4; - break; - + break; + case PathCommandType.CubicBézier: + if (currentX > x) + { + x = currentX; + } + if (currentY > y) + { + y = currentY; + } + i += 4; + break; + + } } } - } - break; + break; + } + } + if (xe != double.MinValue) + { + width = xe - x; + } + if (ye != double.MinValue) + { + height = ye - y; } - } - if (xe != double.MinValue) - { - width = xe - x; - } - if (ye != double.MinValue) - { - height = ye - y; } } - private static void HandleLine(ref double currentX, ref double currentY, ref double xe, ref double ye, PathCommands cmd, List cmdCoordinates, ref int i) + private static void HandleLine(ref double currentX, ref double currentY, ref double xe, ref double ye, PathCommand cmd, List cmdCoordinates, ref int i) { xe = cmd.Coordinates[i]; ye = cmd.Coordinates[++i]; @@ -454,12 +463,12 @@ private static void HandleHorizontal(double x, ref double currentX, ref double x xe = xec; } } - protected static void AddCmd(PathRenderItem pi, DrawingPath path, List coordinates, ref PathCommands cmd, PathsBase pp, PathsBase p, PathCommandType commandType) + protected static void AddCmd(PathRenderItem pi, DrawingPath path, List coordinates, ref PathCommand cmd, PathsBase pp, PathsBase p, PathCommandType commandType) { if (pp == null || pp.Type != p.Type) { SetCmdCoordinats(cmd, p, coordinates); - cmd = new PathCommands(commandType); + cmd = new PathCommand(commandType); pi.Commands.Add(cmd); } AddToCoordinates(path, coordinates, p); @@ -469,7 +478,7 @@ protected static void AddArc(PathRenderItem pi, DrawingPath path, List c //var width = ((double)path.Width.Value / ExcelDrawing.EMU_PER_PIXEL); //var height = ((double)path.Height.Value / ExcelDrawing.EMU_PER_PIXEL); var arc = (ArcTo)p; - PathCommands c = null; + PathCommand c = null; startPointX = pCmd.EndX; startPointY = pCmd.EndY; if (startPointX != 0) startPointX /= ExcelDrawing.EMU_PER_POINT; @@ -512,7 +521,7 @@ protected static void AddArc(PathRenderItem pi, DrawingPath path, List c var centerY = startPointY - (hR * Math.Sin(angleT)); var endX = (double)centerX + (wR * Math.Cos(angleTEnd)); var endY = (double)centerY + (hR * Math.Sin(angleTEnd)); - c = new PathCommands(PathCommandType.Arc, wR, hR, 0, 0, swA < 0 ? 0 : 1, endX, endY); + c = new PathCommand(PathCommandType.Arc, wR, hR, 0, 0, swA < 0 ? 0 : 1, endX, endY); pi.Commands.Add(c); stA += aAdd; swA -= aAdd; @@ -532,7 +541,7 @@ protected static double AngleToRadians(double angle) { return MConverter.DegreesToRadians(angle); } - protected static void SetCmdCoordinats(PathCommands cmd, PathsBase p, List coordinates) + protected static void SetCmdCoordinats(PathCommand cmd, PathsBase p, List coordinates) { if (cmd != null) { diff --git a/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs b/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs index c3c7b92ee7..7ad8940d80 100644 --- a/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs +++ b/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs @@ -161,7 +161,6 @@ public void DataLabelsMultipleOneSeriesExport() var dlbl = sChart.Series[2].DataLabel.DataLabels.Add(2); sChart.Series[2].DataLabel.DataLabels.Add(1); - dlbl.ShowSeriesName = true; dlbl.Fill.Color = Color.Red; diff --git a/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs b/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs index 785081dc4d..ba997a96da 100644 --- a/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs +++ b/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs @@ -27,8 +27,6 @@ X509Certificate2 GetSelfCert() var newCert = new X509Certificate2(certPrivate, "", X509KeyStorageFlags.Exportable); return newCert; } - - [ClassInitialize] public static void Init(TestContext context) {