From e4ab5663fba18336101e4431beb8442bd984c984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 24 Aug 2026 15:43:07 +0200 Subject: [PATCH 01/44] Started work on Chart Data Tables --- .../Renderer/Chart/ChartDataTableRenderer.cs | 75 ++++++++++++++++++- .../Renderer/Chart/ChartPlotareaRenderer.cs | 4 +- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 068447bdb..d9251dfbf 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -12,8 +12,12 @@ Date Author Change *************************************************************************************************/ using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.DrawingRenderer.RenderItems.SvgItem; +using EPPlus.DrawingRenderer.ShapeDefinitions; using EPPlusImageRenderer; using EPPlusImageRenderer.Svg; +using OfficeOpenXml.Drawing.Renderer.TextBox; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; using System; using System.Collections.Generic; @@ -23,13 +27,76 @@ internal class ChartDataTableRenderer : ChartDrawingObject { internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) { - var chartDataTable = svgChart.Chart.PlotArea.DataTable; - + var chartDataTable = svgChart.Chart.PlotArea.DataTable; + Rectangle = new RectRenderItem(svgChart.Plotarea.Rectangle.Bounds); + var maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); + var maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); + var items = new List>(); + var headers = new List(); + items.Add(headers); + foreach (var v in svgChart.HorizontalAxis.AxisValues) + { + var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); + tb.AddText(v); + headers.Add(tb); + } + //Create the source data for the data table from the series. + foreach (var ct in svgChart.Chart.PlotArea.ChartTypes) + { + foreach (var serie in ct.Series) + { + bool flowControl = AddSerieValues(svgChart, maxWidth, maxHeight, serie); + if (!flowControl) + { + return; + } + } + } } + + private bool AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, Drawing.Chart.ExcelChartSerie serie) + { + 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 DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); + tb.AddText(cell.Text); + } + } + } + else if (serie.StringLiteralsY != null && serie.StringLiteralsY.Length > 0) + { + foreach (var se in serie.StringLiteralsY) + { + var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); + tb.AddText(se); + } + } + else if (serie.NumberLiteralsY != null && serie.NumberLiteralsY.Length > 0) + { + foreach (var nl in serie.NumberLiteralsY) + { + var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); + tb.AddText(nl.ToString()); + } + } + else + { + return false; + } + + return true; + } + public override void AppendRenderItems(List renderItems) { base.AppendRenderItems(renderItems); - } - + } } } \ 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 cfabf254d..beeaa44f6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -66,7 +66,7 @@ internal void SetPlotAreaRectangle() Rectangle = rect; } - private double GetPlotAreaHeight(RectRenderItem rect) + internal double GetPlotAreaHeight(RectRenderItem rect) { var bottomAxis = GetAxisActualByPosition(eActualAxisPosition.Bottom); double vaHeight = 0; @@ -98,7 +98,7 @@ private double GetPlotAreaHeight(RectRenderItem rect) 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); From 6a6b01cf5faf2cf1f9c47843b86272b8e172017d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 22 Sep 2026 07:38:15 +0200 Subject: [PATCH 02/44] Adde tests for data tables --- .../Chart/DataTableTests.cs | 31 +++++++++ .../PresetShapeDefinition.Sync.cs | 66 +++---------------- .../PresetShapeDefiniton.Async.cs | 17 ++--- 3 files changed, 47 insertions(+), 67 deletions(-) create mode 100644 src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/DataTableTests.cs new file mode 100644 index 000000000..fa0eaab6a --- /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/ShapeDefinitions/PresetShapeDefinition.Sync.cs b/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefinition.Sync.cs index 2ec6e76a3..2d6e347b0 100644 --- a/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefinition.Sync.cs +++ b/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefinition.Sync.cs @@ -25,8 +25,9 @@ namespace EPPlus.DrawingRenderer.ShapeDefinitions { public partial class PresetShapeDefinitions { - public static void LoadPresetShapeDefinitionFromXml() + public static Dictionary LoadPresetShapeDefinitionFromXml() { + var shapeDefinitions = new Dictionary(); var assembly = Assembly.GetExecutingAssembly(); using Stream stream = assembly.GetManifestResourceStream("EPPlus.DrawingRenderer.resource.psd.zip"); @@ -39,31 +40,24 @@ public static void LoadPresetShapeDefinitionFromXml() { var br = new BinaryReader(zipStream); var bytes = br.ReadBytes((int)entry.UncompressedSize); - Read(bytes); + Read(shapeDefinitions, bytes); } } } + return shapeDefinitions; } - private static void Read(byte[] bytes) + private static void Read(Dictionary shapeDefinitions, byte[] bytes) { try { var ms = new MemoryStream(bytes); -#if NET35 - var xr = XmlReader.Create(ms, new XmlReaderSettings() - { - ProhibitDtd=true, - IgnoreWhitespace = true - }); -#else var xr = XmlReader.Create(ms, new XmlReaderSettings() { DtdProcessing = DtdProcessing.Prohibit, IgnoreWhitespace = true, Async = true }); -#endif while (xr.Read()) { @@ -72,7 +66,7 @@ private static void Read(byte[] bytes) if (xr.LocalName != "presetShapeDefinitons") { var item = LoadPresetShapeDefinition(xr); - _shapeDefinitions.Add(item.Style, item); + shapeDefinitions.Add(item.Style, item); } } } @@ -82,25 +76,18 @@ private static void Read(byte[] bytes) throw (new IOException("Cannot preset shape definitions file:presetShapeDefinitions.xml", ex)); } } - private static async Task ReadAsync(byte[] bytes) + //We do currently not use this method as it is called from the constructor that cant handle async in a good way. + private static async Task> ReadAsync(Dictionary shapeDefinitions, byte[] bytes) { try { var ms = new MemoryStream(bytes); -#if NET35 - var xr = XmlReader.Create(ms, new XmlReaderSettings() - { - ProhibitDtd=true, - IgnoreWhitespace = true - }); -#else var xr = XmlReader.Create(ms, new XmlReaderSettings() { DtdProcessing = DtdProcessing.Prohibit, IgnoreWhitespace = true, Async = true }); -#endif while (await xr.ReadAsync()) { @@ -109,10 +96,11 @@ private static async Task ReadAsync(byte[] bytes) if (xr.LocalName != "presetShapeDefinitons") { var item = await LoadPresetShapeDefinitionAsync(xr); - _shapeDefinitions.Add(item.Style, item); + shapeDefinitions.Add(item.Style, item); } } } + return shapeDefinitions; } catch (Exception ex) { @@ -120,40 +108,6 @@ private static async Task ReadAsync(byte[] bytes) } } - public static bool LoadPresetShapeDefinitionFromXmlTriangle() - { - _shapeDefinitions = new Dictionary(); - - var xmlFile = Directory.GetCurrentDirectory() + "\\resource\\triangleOnly.xml"; - var fs = new FileStream(xmlFile, FileMode.Open, FileAccess.Read); -#if NET35 - var xr = XmlReader.Create(fs, new XmlReaderSettings() - { - ProhibitDtd=true, - IgnoreWhitespace = true - }); -#else - var xr = XmlReader.Create(fs, new XmlReaderSettings() - { - DtdProcessing = DtdProcessing.Prohibit, - IgnoreWhitespace = true, - Async = true - }); -#endif - while (xr.Read()) - { - if (xr.NodeType == XmlNodeType.Element) - { - if (xr.LocalName == "triangle" && xr.NodeType != XmlNodeType.EndElement) - { - var item = LoadPresetShapeDefinition(xr); - _shapeDefinitions.Add(item.Style, item); - } - } - } - return true; - } - private static ShapeDefinition LoadPresetShapeDefinition(XmlReader xr) { var style = xr.LocalName.ToEnum(); diff --git a/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefiniton.Async.cs b/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefiniton.Async.cs index d39b287c2..3ef6f5a56 100644 --- a/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefiniton.Async.cs +++ b/src/EPPlus.DrawingRenderer/ShapeDefinitions/PresetShapeDefiniton.Async.cs @@ -40,21 +40,16 @@ public static Dictionary ShapeDefinitions { if (_shapeDefinitions == null) { - _shapeDefinitions = new Dictionary(); -#if NET35 - LoadPresetShapeDefinitionFromXml(); -#else - Task.Run(() => LoadPresetShapeDefinitionFromXmlAsync()).Wait(); -#endif - + _shapeDefinitions = LoadPresetShapeDefinitionFromXml(); } return _shapeDefinitions; } } } -#if !NET35 - public static async Task LoadPresetShapeDefinitionFromXmlAsync() + public static async Task> LoadPresetShapeDefinitionFromXmlAsync() { + var shapeDefinitions = new Dictionary(); + var assembly = Assembly.GetExecutingAssembly(); using Stream stream = assembly.GetManifestResourceStream("EPPlus.DrawingRenderer.resource.psd.zip"); @@ -67,10 +62,11 @@ public static async Task LoadPresetShapeDefinitionFromXmlAsync() { var br = new BinaryReader(zipStream); var bytes = br.ReadBytes((int)entry.UncompressedSize); - Read(bytes); + Read(shapeDefinitions, bytes); } } } + return shapeDefinitions; } //var xmlFile = Directory.GetCurrentDirectory() + "\\resource\\presetShapeDefinitions.xml"; @@ -225,6 +221,5 @@ private static async Task> LoadShapeGuidesAsync(XmlReader xr) } return l; } -#endif } } \ No newline at end of file From e5f4303bae2c547a288041ec700ae4dedfee67c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 25 Sep 2026 16:11:05 +0200 Subject: [PATCH 03/44] More work on data tables --- .../Drawing/Chart/ExcelChartAxisStandard.cs | 154 +---- .../Drawing/Chart/ExcelChartDataTable.cs | 11 +- src/EPPlus/Drawing/Chart/ExcelChartSerie.cs | 163 ++++++ .../Drawing/Chart/ExcelChartStandardSerie.cs | 1 + .../Renderer/Chart/ChartAxisRenderer.cs | 14 +- .../Renderer/Chart/ChartDataTableRenderer.cs | 232 +++++++- .../Renderer/Chart/ChartLegendRenderer.cs | 535 +----------------- .../DataLabels/ChartSerieDataLabelRenderer.cs | 11 +- .../Renderer/Chart/LegendIconRenderer.cs | 504 +++++++++++++++++ src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 21 - .../Drawing/Chart/DataPointsTest.cs | 1 - 11 files changed, 937 insertions(+), 710 deletions(-) create mode 100644 src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs diff --git a/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartAxisStandard.cs index 7de2abf58..7f18a0556 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 9ebe70e0e..522cbcdde 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 253e53a47..3df29a0ae 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 ee3d05f68..f928d21c2 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/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 7bba7b2df..64e07ab79 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -465,15 +465,17 @@ 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) { - //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; + 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); } - tb.ImportParagraph(p, 0, t); - //tb.TextBody.Paragraphs[0].AddText(v, Axis.Font); tb.Rectangle.SetDrawingPropertiesFill(ChartRenderer.Theme, Axis.Fill, axisStyle?.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index d9251dfbf..6a1f3628d 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -14,48 +14,245 @@ Date Author Change using EPPlus.DrawingRenderer.RenderItems; using EPPlus.DrawingRenderer.RenderItems.SvgItem; using EPPlus.DrawingRenderer.ShapeDefinitions; +using EPPlus.Export.ImageRenderer.Svg.Chart.Util; using EPPlusImageRenderer; using EPPlusImageRenderer.Svg; +using OfficeOpenXml.Core.Worksheet.Fonts.GenericFontMetrics; +using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Renderer.TextBox; using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; +using OfficeOpenXml.Interfaces.Drawing.Text; +using OfficeOpenXml.Style; +using OfficeOpenXml.Style.XmlAccess; +using OfficeOpenXml.Utils.String; using System; using System.Collections.Generic; +using System.Linq; namespace OfficeOpenXml.Drawing.Renderer.Chart { - internal class ChartDataTableRenderer : ChartDrawingObject + internal class ChartDataTableRenderer : ChartDrawingObject, 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 LegendColumn = new List(); + public List SeriesIcon { get; }=new List(); + public List> DataTableRenderItems { get; set; } = new List>(); + 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.Bounds); - var maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); - var maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); + + MeasurementFont mf; + if(_dataTable.HasFont) + { + mf = _dataTable.Font.GetMeasureFont(); + } + else + { + mf = Chart.Font.GetMeasureFont(); + } + + _marginItemsWidth = mf.Size / 2; + _maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); + _maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); var items = new List>(); var headers = new List(); items.Add(headers); - foreach (var v in svgChart.HorizontalAxis.AxisValues) + + var tm = svgChart.TextMeasurer; + 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 DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); + var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, MaxWidth, MaxHeight); tb.AddText(v); headers.Add(tb); - } + var size = tm.MeasureText(v, mf); + if (entryWidth(); + foreach (var v in formattedValues) { - return; + var tb = new DrawingTextBody(RenderContext, ChartRenderer.Chart, Rectangle.Bounds, true); + if (paragraph == null) + { + tb.AddParagraph(v.ToString()); + } + else + { + tb.ImportParagraph(paragraph, 0, v.ToString()); + } + l.Add(tb); } + DataTableRenderItems.Add(l); } + seriesIndex++; } + + SetColumnWidth(entryHeight); } - private bool AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, Drawing.Chart.ExcelChartSerie serie) + + private void SetColumnWidth(double entryHeight) { + var ledgedColWidth = GetLegendColWidth(); + foreach(var lc in LegendColumn) + { + if (_dataTable.ShowKeys) + { + lc.MarkerIcon.Bounds.Left += ledgedColWidth; + lc.MarkerBackground.Bounds.Left += ledgedColWidth; + lc.SeriesIcon.Bounds.Left += ledgedColWidth; + lc.Textbox.Bounds.Left += ledgedColWidth; + } + else + { + lc.Textbox.Left = ledgedColWidth; + } + ledgedColWidth += _columnsWidth; + } + + double y = entryHeight; + foreach (var row in DataTableRenderItems) + { + var x = ledgedColWidth; + foreach (var cell in row) + { + cell.Bounds.Left = x; + cell.Bounds.Top = y; + x += _columnsWidth; + } + y += entryHeight; + } + + } + + const float MarginIconText = 1.5f; + private double GetLegendColWidth() + { + if (LegendColumn.Count == 0) return 0; + var w = LegendColumn.Max(x=>x.Textbox.Width); + if(_dataTable.ShowKeys) + { + return w + LegendColumn.First().MarkerIcon.Bounds.Width + MarginIconText; + } + return w; + } + + 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 RenderItem GetIcon(ChartRenderer svgChart, ExcelChart ct, ExcelChartStandardSerie serie, DrawingLegendSerie pSls, int serieIndex, int index, double entryWidth, double entryHeight) + { + if(ct.IsTypeLine()) + { + return LegendIconRenderer.GetLineSeriesIcon(svgChart, this, serie, pSls, entryWidth, entryHeight); + } + else if(ct.IsTypeBar() || ct.IsTypeColumn()) + { + return LegendIconRenderer.GetBarSeriesIcon(svgChart, ct, this, (ExcelBarChartSerie)serie, pSls, entryWidth, entryHeight, serieIndex, index); + } + else + { + return LegendIconRenderer.GetPieSeriesIcon(svgChart, ct, this, (ExcelPieChartSerie)serie, pSls, entryWidth, entryHeight, index); + } + } + + 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); @@ -67,6 +264,7 @@ private bool AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxH { var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); tb.AddText(cell.Text); + ret.Add(tb); } } } @@ -76,6 +274,7 @@ private bool AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxH { var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); tb.AddText(se); + ret.Add(tb); } } else if (serie.NumberLiteralsY != null && serie.NumberLiteralsY.Length > 0) @@ -84,19 +283,16 @@ private bool AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxH { var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); tb.AddText(nl.ToString()); + ret.Add(tb); } } - else - { - return false; - } - return true; + return ret; } public override void AppendRenderItems(List renderItems) { - base.AppendRenderItems(renderItems); + //foreach(var tb in ) } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index 6938cfcf2..72b4e3386 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -21,6 +21,7 @@ Date Author Change 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 +30,15 @@ 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; } + } + internal class ChartLegendRenderer : ChartDrawingObject, ILegendKeyContainer { List _seriesHeadersMeasure = new List(); @@ -40,9 +49,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 +68,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: @@ -322,40 +326,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 +377,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 +389,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 +399,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 +430,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 +438,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; } @@ -524,92 +490,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) @@ -663,7 +543,7 @@ 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; @@ -685,375 +565,6 @@ 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) - { - 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 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); - } - - 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(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; - } - } - } - - 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; - - 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 = 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) - { - 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; - } - 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; - - GetItemPosition(pSls, entryWidth, entryHeight, icon?.Left ?? 0D, icon?.Top ?? 0D, 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; - } - - 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; - } - - - 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) - { - var topOffset = 0D; - if (Chart.Legend.Position == eLegendPosition.Top || - Chart.Legend.Position == eLegendPosition.Bottom) - { - 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; - } - - - } - else - { - if (pSls == null) - { - y = TopMargin + entryHeight / 2; - } - else - { - y = iconCenter + entryHeight * 1.5; - } - x = LeftMargin; - - } - - return topOffset; - } - public override void AppendRenderItems(List renderItems) { var groupItem = new GroupRenderItem(ChartRenderer.Bounds); @@ -1075,11 +586,19 @@ public override void AppendRenderItems(List renderItems) if(s.Textbox != null) s.Textbox.AppendRenderItems(groupItem.RenderItems); } } - - public List SeriesIcon { get; } = new List(); - internal override Color? DefaultFillColor => Color.Transparent; internal override Color? DefaultBorderColor => Color.Transparent; + + public float MarginItemsWidth => _marginItemsWidth; + + public double MaxWidth => _maxWidth; + + public double MaxHeight => _maxHeight; + + public List SeriesHeadersMeasure => _seriesHeadersMeasure; + + public List SeriesIcon { get; } = new List(); + } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index abcded254..011f6e11c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -6,6 +6,7 @@ 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; @@ -107,11 +108,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 { @@ -141,13 +142,13 @@ 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; @@ -160,7 +161,7 @@ private void AddDatalabel(ExcelChartStandardSerie serie, ExcelChartDataLabel dat 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs new file mode 100644 index 000000000..630c7f511 --- /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 : ChartDrawingObject, 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.Bounds.Width - tbLeft; + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, 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 : ChartDrawingObject, 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.Bounds.Width - tbLeft; + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, 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 : ChartDrawingObject, 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.Bounds.Width - tbLeft; + + var tbHeight = entryHeight; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + + var headerText = s.GetHeaderText(index); + var entry = chart.Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); + if (entry == null || entry.Font.IsEmpty) + { + 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); + } + + 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 : ChartDrawingObject, 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.BorderWidth ?? 0d); + var tbTop = si.Top - ((entryHeight + MarginIconText) / 2); + + double tbWidth; + + if (i != catValues.Count - 1) + { + tbWidth = parent.Rectangle.Bounds.Width - tbLeft; + } + else + { + tbWidth = parent.Rectangle.Bounds.Width; + } + + var tbHeight = tm.Height; + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, 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.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.Chart, ps, i, sls.SeriesIcon, dp, chart.Chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart.Chart, ct, ps, 0, i, sls.SeriesIcon); + } + + parent.SeriesIcon.Add(sls); + pSls = sls; + } + + foreach (var icon in parent.SeriesIcon) + { + icon.SeriesIcon.Bounds.Top = icon.SeriesIcon.Bounds.Top - ((entryHeight) / 4); + } + var position = chart.Chart.Legend.Position; + if (position == eLegendPosition.Top || position == eLegendPosition.Bottom) + { + //Rectangle.Bounds.Width = totalWidth; + parent.Rectangle.Bounds.Width = parent.SeriesIcon.Last().Textbox.Bounds.GetGlobalBoundingbox().Right - parent.SeriesIcon[0].SeriesIcon.Bounds.GlobalLeft + 4d + firstIconWidth * 2; + parent.Rectangle.Bounds.Left = ((chart.Bounds.Width) / 2d) - (totalWidth / 2d) + 1.5d; + + if (parent.Rectangle.Bounds.Width < parent.MaxWidth) + { + parent.Rectangle.Bounds.Height = entryHeight + parent.TopMargin + parent.BottomMargin; + parent.Rectangle.Bounds.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 : ChartDrawingObject, ILegendKeyContainer + { + var line = new LineRenderItem(parent.Rectangle.Bounds); + //Default style is NoLine NoFill + line.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.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 : ChartDrawingObject, ILegendKeyContainer + { + var item = new RectRenderItem(parent.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(chart, parent, pSls, entryWidth, entryHeight, iconLeft, iconTop + (iconHeight / 2), out double x, out double y); + + item.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, dp, chart.Chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart.Chart, ct, chartSerie, serieIndex, index, item); + } + + return item; + } + internal static LineRenderItem GetTrendLineSeriesIcon(ChartRenderer chart, ExcelChart ct, T parent, ExcelChartTrendline tl, DrawingLegendSerie pSls, double entryWidth, double entryHeight) where T : ChartDrawingObject, ILegendKeyContainer + { + var line = new LineRenderItem(parent.Rectangle.Bounds); + line.SetDrawingPropertiesFill(chart.Theme, tl.Fill, chart.Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, parent.DefaultFillColor); + //Default is actually NoLine + line.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.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 : ChartDrawingObject, ILegendKeyContainer + { + var item = new RectRenderItem(parent.Rectangle.Bounds); + + 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.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.SetDrawingPropertiesFill(chart.Theme, pcS.Fill, chart.Chart.StyleManager.Style?.SeriesLine.FillReference.Color); + item.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 : ChartDrawingObject, ILegendKeyContainer + { + var topOffset = 0D; + if (chart.Chart.Legend.Position == eLegendPosition.Top || + chart.Chart.Legend.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.SetDrawingPropertiesFill(theme, s.Fill, chartRenderer.Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + item.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.BorderWidth); + item.Y2 = y; + item.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, dp, chart.StyleManager.Style?.SeriesLine); + } + else + { + ChartTypeDrawer.SetFillSerie(chart, s._chart, s, index, index, item); + } + + return item; + } + + + } + + } +} diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 131d6ae4d..483aca4e5 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -485,26 +485,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/EPPlusTest/Drawing/Chart/DataPointsTest.cs b/src/EPPlusTest/Drawing/Chart/DataPointsTest.cs index 9052f0405..d686a60aa 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; From cbec7e10e5e16026bb752809bb0daff4f7a255e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 28 Sep 2026 09:16:08 +0200 Subject: [PATCH 04/44] WIP:Svg Chart Data tables --- .../Renderer/Chart/ChartDataTableRenderer.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 6a1f3628d..e73e4bb9c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -155,27 +155,34 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) private void SetColumnWidth(double entryHeight) { - var ledgedColWidth = GetLegendColWidth(); + var height = 0D; + var r = 0; foreach(var lc in LegendColumn) { if (_dataTable.ShowKeys) { - lc.MarkerIcon.Bounds.Left += ledgedColWidth; - lc.MarkerBackground.Bounds.Left += ledgedColWidth; - lc.SeriesIcon.Bounds.Left += ledgedColWidth; - lc.Textbox.Bounds.Left += ledgedColWidth; + if (lc.MarkerIcon != null) + { + lc.MarkerIcon.Bounds.Height += height; + if (lc.MarkerBackground != null) + { + lc.MarkerBackground.Bounds.Height += height; + } + } + lc.SeriesIcon.Bounds.Top += height; + lc.Textbox.Bounds.Top += height; } else { - lc.Textbox.Left = ledgedColWidth; + lc.Textbox.Left = height; } - ledgedColWidth += _columnsWidth; + height += GetLegendColHeight(r++); } double y = entryHeight; foreach (var row in DataTableRenderItems) { - var x = ledgedColWidth; + var x = height; foreach (var cell in row) { cell.Bounds.Left = x; @@ -184,7 +191,6 @@ private void SetColumnWidth(double entryHeight) } y += entryHeight; } - } const float MarginIconText = 1.5f; @@ -194,10 +200,21 @@ private double GetLegendColWidth() var w = LegendColumn.Max(x=>x.Textbox.Width); if(_dataTable.ShowKeys) { - return w + LegendColumn.First().MarkerIcon.Bounds.Width + MarginIconText; + return w + LegendColumn.Max(x=>x.SeriesIcon?.Bounds.Width??0) + MarginIconText; } return w; } + private double GetLegendColHeight(int row) + { + var h = LegendColumn[row].Textbox.Height; + if (_dataTable.ShowKeys) + { + var sih = LegendColumn[row].SeriesIcon.Bounds.Height; + var mih = LegendColumn[x].MarkerIcon?.Bounds.Height ?? 0D; + return Math.Max(mih, Math.Max(h, sih)); + } + return h; + } private double GetDataTableWidth(ChartRenderer svgChart, ExcelChartDataTable chartDataTable) { From 20de56b2acc1435cf8a59f673889db4fa1476840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 28 Sep 2026 14:44:13 +0200 Subject: [PATCH 05/44] WIP:Svg data tables --- .../Drawing/Renderer/Chart/ChartDataTableRenderer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index e73e4bb9c..2054034e7 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -153,7 +153,7 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) } - private void SetColumnWidth(double entryHeight) + private void SetColumnWidth(double entryWidth) { var height = 0D; var r = 0; @@ -179,17 +179,19 @@ private void SetColumnWidth(double entryHeight) height += GetLegendColHeight(r++); } - double y = entryHeight; + double y = 0; + r = 0; + var lcw = GetLegendColWidth(); foreach (var row in DataTableRenderItems) { - var x = height; + var x = lcw; foreach (var cell in row) { cell.Bounds.Left = x; cell.Bounds.Top = y; x += _columnsWidth; } - y += entryHeight; + y += GetLegendColHeight(r++); } } @@ -210,7 +212,7 @@ private double GetLegendColHeight(int row) if (_dataTable.ShowKeys) { var sih = LegendColumn[row].SeriesIcon.Bounds.Height; - var mih = LegendColumn[x].MarkerIcon?.Bounds.Height ?? 0D; + var mih = LegendColumn[row].MarkerIcon?.Bounds.Height ?? 0D; return Math.Max(mih, Math.Max(h, sih)); } return h; From ce0d1c0613db958717b7fd3856f47bf4af9adb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 29 Sep 2026 08:38:21 +0200 Subject: [PATCH 06/44] WIP:SVG Data Tables --- .../Drawing/Chart/ExcelBarChartSerie.cs | 2 +- .../Renderer/Chart/ChartDataTableRenderer.cs | 53 +++++++++++++------ src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 3 +- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs b/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs index ce0cf5296..2a73a9315 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/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 2054034e7..6f7e68b92 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -43,7 +43,6 @@ internal class ChartDataTableRenderer : ChartDrawingObject, ILegendKeyContainer public double MaxHeight => _maxHeight; public List SeriesHeadersMeasure => _seriesHeadersMeasure; - public List LegendColumn = new List(); public List SeriesIcon { get; }=new List(); public List> DataTableRenderItems { get; set; } = new List>(); double _dataTableWidth, _columnsWidth; @@ -65,6 +64,8 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) _marginItemsWidth = mf.Size / 2; _maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); _maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); + Rectangle.Bounds.Width = _maxWidth; //Set to max. Adjust later to actual width + Rectangle.Bounds.Height = _maxHeight; //Set to max. Adjust later to actual height var items = new List>(); var headers = new List(); items.Add(headers); @@ -117,17 +118,18 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) if(ct.IsTypeLine()) { LegendIconRenderer.SetLineLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + SeriesIcon.Add(sls); } - else if(ct.IsTypePie()) + else if (ct.IsTypeColumn() || ct.IsTypeBar()) { - LegendIconRenderer.SetPieLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetBarLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + SeriesIcon.Add(sls); } - else if(ct.IsTypeColumn() || ct.IsTypeBar()) + else if (ct.IsTypePie()) { - LegendIconRenderer.SetBarLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); + LegendIconRenderer.SetPieLegend(ChartRenderer, this, ct, index, pSls, serie, sls, entryWidth, entryHeight, maxIconLength); } var rows = AddSerieValues(svgChart, _maxWidth, _maxHeight, serie); - LegendColumn.Add(sls); pSls = sls; var formattedValues = serie.GetValues(false, true); var l=new List(); @@ -157,7 +159,7 @@ private void SetColumnWidth(double entryWidth) { var height = 0D; var r = 0; - foreach(var lc in LegendColumn) + foreach(var lc in SeriesIcon) { if (_dataTable.ShowKeys) { @@ -187,8 +189,8 @@ private void SetColumnWidth(double entryWidth) var x = lcw; foreach (var cell in row) { - cell.Bounds.Left = x; - cell.Bounds.Top = y; + cell.Bounds.Left += x; + cell.Bounds.Top += y; x += _columnsWidth; } y += GetLegendColHeight(r++); @@ -198,21 +200,21 @@ private void SetColumnWidth(double entryWidth) const float MarginIconText = 1.5f; private double GetLegendColWidth() { - if (LegendColumn.Count == 0) return 0; - var w = LegendColumn.Max(x=>x.Textbox.Width); + if (SeriesIcon.Count == 0) return 0; + var w = SeriesIcon.Max(x=>x.Textbox.Width); if(_dataTable.ShowKeys) { - return w + LegendColumn.Max(x=>x.SeriesIcon?.Bounds.Width??0) + MarginIconText; + return w + SeriesIcon.Max(x=>x.SeriesIcon?.Bounds.Width??0) + MarginIconText; } return w; } private double GetLegendColHeight(int row) { - var h = LegendColumn[row].Textbox.Height; + var h = SeriesIcon[row].Textbox.Height; if (_dataTable.ShowKeys) { - var sih = LegendColumn[row].SeriesIcon.Bounds.Height; - var mih = LegendColumn[row].MarkerIcon?.Bounds.Height ?? 0D; + var sih = SeriesIcon[row].SeriesIcon.Bounds.Height; + var mih = SeriesIcon[row].MarkerIcon?.Bounds.Height ?? 0D; return Math.Max(mih, Math.Max(h, sih)); } return h; @@ -230,7 +232,6 @@ private double GetDataTableWidth(ChartRenderer svgChart, ExcelChartDataTable cha return width - svgChart.Legend.Rectangle.Width - svgChart.Legend.RightMargin; } } - private List GetFormattedValues(ChartRenderer svgChart, List values) { var format = svgChart.HorizontalAxis.Axis.FormatOrFirstValueFormat; @@ -311,7 +312,25 @@ private List AddSerieValues(ChartRenderer svgChart, double maxWi public override void AppendRenderItems(List renderItems) { - //foreach(var tb in ) + //Render series header column. + foreach(var li in SeriesIcon) + { + if (_dataTable.ShowKeys) + { + renderItems.Add(li.SeriesIcon); + if (li.MarkerBackground != null) renderItems.Add(li.MarkerBackground); + if (li.MarkerIcon != null) renderItems.Add(li.MarkerIcon); + } + renderItems.Add(li.Textbox); + } + + foreach(var row in DataTableRenderItems) + { + foreach (var cell in row) + { + renderItems.Add(cell); + } + } } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 483aca4e5..0d82a5ba7 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -462,7 +462,8 @@ public bool AppendItems() VerticalAxis?.Textboxes?.AppendRenderItems(RenderItems); SecondHorizontalAxis?.Textboxes?.AppendRenderItems(RenderItems); SecondVerticalAxis?.Textboxes?.AppendRenderItems(RenderItems); - + + DataTable?.AppendRenderItems(RenderItems); Title?.AppendRenderItems(RenderItems); Legend?.AppendRenderItems(RenderItems); From 6b685b0499e42b5f365eb564a8edf6966046a553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Thu, 1 Oct 2026 14:42:13 +0200 Subject: [PATCH 07/44] Removed BoundingBox property and inherited RenderItem from the Transform class instead. --- .../SvgStandAloneTests.cs | 100 +++++----- .../RenderItems/RenderItem.cs | 122 +++++------- .../Textbox/ParagraphRenderItem.cs | 34 ++-- .../RenderItems/Textbox/RenderTextBody.cs | 84 ++++---- .../RenderItems/Textbox/RenderTextbox.cs | 52 ++--- .../RenderItems/Textbox/TextRunRenderItem.cs | 14 +- .../Svg/Core/Interfaces/IShapeRenderer.cs | 4 +- .../Svg/Core/SvgBaseRenderer.cs | 4 +- .../Svg/Core/SvgShapeRenderer.cs | 29 ++- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 11 +- .../Svg/ItemRenderers/SvgParagraphRenderer.cs | 2 +- .../Svg/ItemRenderers/SvgTextRunRenderer.cs | 4 +- src/EPPlus.Graphics/BoundingBox.cs | 3 + .../{Point.cs => TranformPoint.cs} | 6 +- .../Chart/Axis/CategoryAxisScaleCalculator.cs | 4 +- .../Chart/Axis/DateAxisScaleCalculator.cs | 2 +- .../Renderer/Chart/ChartAreaRenderer.cs | 3 +- .../Renderer/Chart/ChartAxisRenderer.cs | 37 ++-- .../Renderer/Chart/ChartAxisTextBoxes.cs | 7 +- .../Renderer/Chart/ChartDataTableRenderer.cs | 3 +- .../Renderer/Chart/ChartDrawingObject.cs | 2 +- .../Renderer/Chart/ChartLegendRenderer.cs | 80 ++++---- .../Renderer/Chart/ChartPlotareaRenderer.cs | 19 +- .../Renderer/Chart/ChartTitleRenderer.cs | 4 +- .../BarColumnChartTypeDrawer.cs | 6 +- .../Chart/ChartTypeDrawers/ChartTypeDrawer.cs | 7 +- .../ChartTypeDrawers/LineChartTypeDrawer.cs | 20 +- .../ChartTypeDrawers/PieChartTypeDrawer.cs | 30 +-- .../Trendlines/ChartTrendlineRenderer.cs | 7 +- .../DataLabels/ChartSerieDataLabelRenderer.cs | 8 +- .../Renderer/Chart/DataLabels/PointLines.cs | 10 +- .../Chart/DataLabels/SvgDataLabelPoint.cs | 130 ++++++------ .../Renderer/Chart/DrawingLegendSerie.cs | 4 +- .../Renderer/Chart/LineMarkerHelper.cs | 2 +- .../Renderer/Chart/PieSliceRenderItem.cs | 44 ++--- .../Drawing/Renderer/DrawingRenderer.cs | 2 +- .../Textbox/DrawingParagraphRenderItem.cs | 2 +- .../RenderItems/Textbox/DrawingTextBody.cs | 46 ++--- .../RenderItems/Textbox/DrawingTextBox.cs | 2 +- .../RenderItems/Textbox/DrawingTextRunItem.cs | 6 +- src/EPPlus/Drawing/Renderer/ShapeRenderer.cs | 186 +++++++++--------- 41 files changed, 570 insertions(+), 572 deletions(-) rename src/EPPlus.Graphics/{Point.cs => TranformPoint.cs} (84%) diff --git a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs index 3fa92152d..3b5319bed 100644 --- a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs @@ -25,7 +25,7 @@ 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; @@ -41,7 +41,7 @@ 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; @@ -51,13 +51,13 @@ private GroupRenderItem GenerateGroupRenderItem() 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 +69,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 +85,32 @@ 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.Width = baseGroup.Width; + background.Height = baseGroup.Height; background.FillColor = "aliceBlue"; baseGroup.AddChildItem(textBody); baseGroup.AddChildItem(background); - List items = new List() { baseGroup }; + var items = new List() { baseGroup }; svgShapeRenderer.Render(items); @@ -133,7 +133,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"); @@ -157,7 +157,7 @@ public void SvgTextBodyTest() { var baseGroup = GenerateGroupRenderItem(); var textBody = GenerateTextBody(baseGroup); - GenerateSvgFile("standAloneTextBody", baseGroup.Bounds, baseGroup); + GenerateSvgFile("standAloneTextBody", baseGroup, baseGroup); } [TestMethod] @@ -181,17 +181,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 +214,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 +242,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 +278,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 +301,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"); @@ -320,14 +320,14 @@ private RenderTextbox GenerateTextBox(out GroupRenderItem group) public void BasicTextBox() { var textbox = GenerateTextBox(out GroupRenderItem group); - textbox.AppendRenderItems(group.RenderItems); + textbox.AppendRenderItems(group.ChildObjects); 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 +340,22 @@ public void TextBoxWithMargins() textbox.LeftMargin = 10d; textbox.TopMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.AppendRenderItems(group.ChildObjects); //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 +371,13 @@ public void TextBoxWithAllMargins() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.AppendRenderItems(group.ChildObjects); //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 +404,13 @@ public void TextBoxWithAllMarginsANDTextbodyChanged() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.RenderItems); + textbox.AppendRenderItems(group.ChildObjects); //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/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 04672d758..05052c01c 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; @@ -160,12 +150,12 @@ public GroupRenderItem(BoundingBox parent, double rotation) : base(parent) 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); } @@ -175,32 +165,24 @@ public GroupRenderItem(BoundingBox parent, double rotation, Transform rotationPo Rotation = 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(); + //public List RenderItems { get; } = new List(); - Graphics.Point _altRotationPoint = null; + 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.Point TranslationOffset = new Graphics.Point(0, 0); - public Graphics.Point RotationPoint + public Graphics.TranformPoint TranslationOffset = new Graphics.TranformPoint(0, 0); + public Graphics.TranformPoint RotationPoint { get { @@ -220,7 +202,7 @@ public Graphics.Point RotationPoint internal void SetRotationPointToCenterOfGroup(double rotation = double.NaN) { - RotationPoint = new Graphics.Point(Bounds.Width / 2, Bounds.Height / 2); + RotationPoint = new Graphics.TranformPoint(Width / 2, Height / 2); if (double.IsNaN(rotation) == false) { @@ -228,13 +210,16 @@ internal void SetRotationPointToCenterOfGroup(double rotation = double.NaN) } } - 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); + ChildObjects.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; + if (item is BoundingBox bb) + { + Width = bb.Right > Width ? bb.Right : Width; + Height = bb.Bottom > Height ? bb.Bottom : Height; + } } /// @@ -250,24 +235,24 @@ public void AddSubGroupingOfRenderItems(string subGroupName, List Items) w if (Items != null) { //Create subGroup - var subGroup = new GroupRenderItem(this.Bounds); - subGroup.Bounds.Name = subGroupName; + var subGroup = new GroupRenderItem(this); + 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 - this.RenderItems.Add(subGroup); + this.ChildObjects.Add(subGroup); } } public override RenderItem Clone() { - var item = new GroupRenderItem(Bounds) + var item = new GroupRenderItem(this) { Rotation = Rotation, TextAnchor = TextAnchor, @@ -277,9 +262,9 @@ public override RenderItem Clone() Scale = Scale, }; 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 +274,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 override RenderItem Clone() { - var clone = new PathRenderItem(Bounds); + var clone = new PathRenderItem(Parent); CloneBase(clone); foreach(var cmd in Commands) { @@ -307,7 +292,7 @@ public override RenderItem Clone() } public class EllipseRenderItem : RenderItem { - public EllipseRenderItem(BoundingBox parent) : base(parent) + public EllipseRenderItem(Transform parent) : base(parent) { } @@ -318,7 +303,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 +316,7 @@ public override RenderItem Clone() } public class LineRenderItem : RenderItem { - public LineRenderItem(BoundingBox parent) : base(parent) + public LineRenderItem(Transform parent) : base(parent) { } @@ -391,12 +376,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,24 +394,24 @@ 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 { protected RenderItem() { } - protected RenderItem(BoundingBox parent) + protected RenderItem(Transform parent) { - Bounds.Parent = parent; + 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; + il = Left; + it = Top; + ir = Right; + ib = Bottom; } public string DefId { get; set; } //internal bool IsEndOfGroup { get; set; } = false; @@ -501,20 +486,21 @@ internal string GetFilterKey() { return $"{GlowColor} {GlowRadius} { OuterShadowEffect?.GetKey()}"; } + } /// /// Base class for any item rendered. /// - public abstract class RenderItemBase + public abstract class RenderItemBase : BoundingBox { - public BoundingBox Bounds = new BoundingBox(); + //public BoundingBox Bounds = new BoundingBox(); public abstract RenderItemType Type { get; } 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; + il = Left; + it = Top; + ir = Right; + ib = Bottom; } } } \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs index 09e18a74f..d32d150b1 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs @@ -140,7 +140,7 @@ 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 }; @@ -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 182917b2d..18b2f42f8 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; } @@ -62,14 +62,14 @@ public RenderTextBody(RenderContext renderContext, BoundingBox parent, double le public double MaxWidth { get; set; } public double MaxHeight { get; set; } /// - /// Shorthand for Bounds.Width + /// Shorthand for Width /// - public double Width { get { return Bounds.Width; } set { Bounds.Width = value; } } + public double Width { get { return Width; } set { Width = value; } } /// - /// Shorthand for Bounds.Height + /// Shorthand for Height /// - public double Height { get { return Bounds.Height; } set { Bounds.Height = value; } } + public double Height { get { return Height; } set { Height = value; } } public bool AutoSize { get; set; } public double TopMargin { get; set; } @@ -79,16 +79,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 +104,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 +119,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 +140,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 +161,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 +169,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 +197,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 +226,7 @@ private double GetTopToAddNextParagraphAt() if (Paragraphs.Count != 0) { - paragraphTop = Paragraphs.Last().Bounds.Bottom; + paragraphTop = Paragraphs.Last().Bottom; } return paragraphTop; } @@ -243,18 +243,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 1e1fa4dd3..6aec06668 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -16,8 +16,8 @@ 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); + _rectangle = new RectRenderItem(_group); + _marginGroup = new GroupRenderItem(_group); //TextBody = new RenderTextBody(Rectangle.Bounds, true); //TextBody.MaxWidth = maxWidth; //TextBody.MaxHeight = maxHeight; @@ -30,8 +30,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 +45,8 @@ public RectRenderItem Rectangle { get { - _rectangle.Bounds.Width = Width; - _rectangle.Bounds.Height = Height; + _rectangle.Width = Width; + _rectangle.Height = Height; return _rectangle; } set @@ -63,37 +63,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 +110,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,15 +188,15 @@ public double GetActualBottom() { return Top + GetActualHeight(); } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { 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.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); @@ -212,27 +212,27 @@ public override void AppendRenderItems(List renderItems) rect.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 += -(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.Bounds.Left += -(rect.Bounds.Width * COS45); - _group.Bounds.Top += (rect.Bounds.Width * COS45); + _group.Left += -(rect.Width * COS45); + _group.Top += (rect.Width * COS45); } else { - _group.Bounds.Left += rect.Bounds.Height / 2; - _group.Bounds.Top += (rect.Bounds.Width); + _group.Left += rect.Height / 2; + _group.Top += (rect.Width); } } - _group.RenderItems.Add(rect); + _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 @@ -244,7 +244,7 @@ public override void AppendRenderItems(List renderItems) _marginGroup.AddChildItem(marginTitleItem); _group.AddChildItem(_marginGroup); - TextBody.AppendRenderItems(_marginGroup.RenderItems); + 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 0421f49cf..90b3b1454 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; } @@ -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 c80957f05..019df6749 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 558f6057a..0d1afae44 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs @@ -19,9 +19,9 @@ protected SvgBaseRenderer(StringBuilder outputStream) : base(outputStream) /// protected void RenderBaseToSpecified(T item, StringBuilder sb) { - if (item.Bounds.Name != null) + if (item.Name != null) { - sb.Append($" class=\"{item.Bounds.Name}\" "); + sb.Append($" class=\"{item.Name}\" "); } if (string.IsNullOrEmpty(item.DefId) == false) diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs index b81717502..611145b17 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,13 +82,13 @@ 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) { if (item is GroupRenderItem group) { - PreRenderGroup(group.RenderItems, defSb, hs, ref ix); + PreRenderGroup(group.ChildObjects, defSb, hs, ref ix); //foreach(var child in group.RenderItems) //{ // WriteDefsForRenderItem(defSb, hs, ref ix, child); @@ -103,7 +102,7 @@ public void PreRenderGroup(List items, StringBuilder defSb, HashSet< } - public bool PreRender(List items) + public bool PreRender(List items) { var defSb = new StringBuilder(); var hs = new HashSet(); @@ -167,7 +166,7 @@ private void WriteDefsForRenderItem(StringBuilder defSb, HashSet hs, ref } if (item.BorderGradientFill != null) { - var key = item.BorderGradientFill.GetKey() + item.Bounds.UniqueKey; + var key = item.BorderGradientFill.GetKey() + item.UniqueKey; if (_defsCache.TryGetValue(key, out string? name) == false) { name = WriteGradient($"StrokeGradient{ix}", defSb, hs, item, item.BorderGradientFill, item.BorderColorSource); @@ -600,20 +599,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); @@ -632,7 +631,7 @@ private string GetXy(RenderItem item, UserSpaceSettings userSpace, double? angle { //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 8f9fe0170..5289f6d42 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -29,11 +29,14 @@ public override void Render(GroupRenderItem item) fillPropery = $" fill=\"{item.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); + } } OutputStream.Append(""); @@ -50,7 +53,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; } diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgParagraphRenderer.cs index 0c3c9381d..a0ef17791 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/SvgTextRunRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs index d1c9783ae..a40cd6214 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 += $" GlobalLeft + Width; + + public double GlobalBottom => GlobalTop + Height; public string UniqueKey { 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 3e5d03235..6c011e1e5 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/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/CategoryAxisScaleCalculator.cs index 507f55c95..729e5be3b 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 747ad0743..685323868 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs index 9279662b0..4da9ec212 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs @@ -16,6 +16,7 @@ Date Author Change using OfficeOpenXml.Drawing.Renderer.Chart.Defaults; using System.Collections.Generic; using System.Drawing; +using EPPlus.Graphics; namespace EPPlusImageRenderer.Svg { @@ -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 33de52096..02147102f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -124,7 +124,7 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s Rectangle.FillColor = "none"; - Line = new LineRenderItem(Rectangle.Bounds); + Line = new LineRenderItem(Rectangle); 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) { @@ -276,45 +276,42 @@ 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}"; + AxisGroup.Name = $"Axis_{Axis.Index}"; - Title?.AppendRenderItems(AxisGroup.RenderItems); + Title?.AppendRenderItems(AxisGroup.ChildObjects); //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); @@ -475,7 +472,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; @@ -889,7 +886,7 @@ 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) { @@ -900,12 +897,12 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw { 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."); @@ -948,7 +945,7 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw if(id == "xGridLine") { - tm.Bounds.Width = pa.Rectangle.Width; + 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); @@ -1164,7 +1161,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 { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs index 94fa61e83..edeedc4f4 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 { @@ -37,15 +38,15 @@ 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; + AxisTxtBoxGroup.Name = AxisName; foreach (var tb in TextBoxes) { - tb.AppendRenderItems(AxisTxtBoxGroup.RenderItems); + tb.AppendRenderItems(AxisTxtBoxGroup.ChildObjects); } renderItems.Add(AxisTxtBoxGroup); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 068447bdb..0f3dd63ae 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -12,6 +12,7 @@ Date Author Change *************************************************************************************************/ using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.Graphics; using EPPlusImageRenderer; using EPPlusImageRenderer.Svg; using System; @@ -26,7 +27,7 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) var chartDataTable = svgChart.Chart.PlotArea.DataTable; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { base.AppendRenderItems(renderItems); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs index 7f59f29a2..536857bff 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs @@ -60,7 +60,7 @@ internal void SetMargins(ExcelTextBody tb) 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(sc.ChartArea.Rectangle); var ml = layout.ManualLayout; if (ml.LeftMode == eLayoutMode.Edge) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index cf394b1db..fe7cdf191 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -489,14 +489,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; } @@ -527,14 +527,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.Textbox.Bounds.Bottom > Rectangle.Height) + // if (sls.Textbox.Bottom > Rectangle.Height) // { // break; // } @@ -588,11 +588,11 @@ private void SetBarLegendSingle(double entryWidth, double entryHeight, double ma var tbTop = si.Top - (entryHeight - si.Height) / 2; double tbWidth; - tbWidth = Rectangle.Bounds.Width - tbLeft; + tbWidth = Rectangle.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; + sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + //sls.Textbox.Left = si.Bottom + MarginIconText; var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); var headerText = cv.ToString(); @@ -639,7 +639,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; } @@ -669,10 +669,10 @@ private void SetTrendlineLegend(ExcelChart ct, int serieIndex, int entryIndex, D 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); @@ -743,15 +743,15 @@ private void SetPieLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc if(i != catValues.Count -1) { - tbWidth = Rectangle.Bounds.Width - tbLeft; + tbWidth = Rectangle.Width - tbLeft; } else { - tbWidth = Rectangle.Bounds.Width; + tbWidth = Rectangle.Width; } var tbHeight = tm.Height; - 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 para = sc.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(); //Chart.Legend.TextBody.GetInsetsOrDefaults(out double lMarg, out double tMarg, out double rMarg, out double bMarg); @@ -793,19 +793,19 @@ private void SetPieLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc foreach(var icon in SeriesIcon) { - icon.SeriesIcon.Bounds.Top = icon.SeriesIcon.Bounds.Top - ((entryHeight) / 4); + icon.SeriesIcon.Top = icon.SeriesIcon.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; + //Rectangle.Width = totalWidth; + Rectangle.Width = SeriesIcon.Last().Textbox.GetGlobalBoundingbox().Right - SeriesIcon[0].SeriesIcon.GlobalLeft + 4d + firstIconWidth * 2; + Rectangle.Left = ((ChartRenderer.ChartArea.Rectangle.Width) / 2d) - (totalWidth / 2d) + 1.5d; - if(Rectangle.Bounds.Width < _maxWidth) + if(Rectangle.Width < _maxWidth) { - Rectangle.Bounds.Height = entryHeight + TopMargin + BottomMargin; - Rectangle.Bounds.Top = ChartRenderer.ChartArea.Rectangle.Height - Rectangle.Height - BottomMargin - TopMargin; + Rectangle.Height = entryHeight + TopMargin + BottomMargin; + Rectangle.Top = ChartRenderer.ChartArea.Rectangle.Height - Rectangle.Height - BottomMargin - TopMargin; } } pSls = null; @@ -821,10 +821,10 @@ private void SetLineLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Ex var tbLeft = si.X1 + maxIconLength + MarginIconText; var tbTop = si.Y2 - entryHeight * 0.5; - var tbWidth = Rectangle.Bounds.Width - tbLeft; + var 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 == index); var headerText = s.GetHeaderText(index); @@ -867,11 +867,11 @@ private void SetBarLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc var tbTop = si.Top - (entryHeight - si.Height) / 2; double tbWidth; - tbWidth = Rectangle.Bounds.Width - tbLeft; + tbWidth = Rectangle.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; + sls.Textbox = new DrawingTextBody(RenderContext, Chart, Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); + //sls.Textbox.Left = si.Bottom + MarginIconText; var entry = Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); var headerText = s.GetHeaderText(index); @@ -889,7 +889,7 @@ private void SetBarLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie cStandardSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight) { - var line = new LineRenderItem(Rectangle.Bounds); + var line = new LineRenderItem(Rectangle); //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); @@ -908,7 +908,7 @@ private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie } private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline tl, DrawingLegendSerie pSls, double entryWidth, double entryHeight) { - var line = new LineRenderItem(Rectangle.Bounds); + var line = new LineRenderItem(Rectangle); 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); @@ -928,7 +928,7 @@ private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int i) { - var item = new RectRenderItem(Rectangle.Bounds); + var item = new RectRenderItem(Rectangle); var iconHeight = GetIconLength(ct, entryHeight); var icon = pSls?.SeriesIcon as RectRenderItem; @@ -969,7 +969,7 @@ private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, D private RectRenderItem GetBarSeriesIcon(ExcelChart ct, ExcelBarChartSerie chartSerie, DrawingLegendSerie pSls, double entryWidth, double entryHeight, int serieIndex, int index) { - var item = new RectRenderItem(Rectangle.Bounds); + var item = new RectRenderItem(Rectangle); var iconHeight = GetIconLength(ct, entryHeight); //var icon = pSls?.SeriesIcon as RectRenderItem; double iconTop = 0, iconLeft = 0; @@ -1054,26 +1054,26 @@ private double GetItemPosition(DrawingLegendSerie pSls, double entryWidth, doubl return topOffset; } - public override void AppendRenderItems(List renderItems) + 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; + groupItem.Name = "Legend"; + groupItem.Top = Rectangle.Top; + groupItem.Left = Rectangle.Left; renderItems.Add(groupItem); //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; + Rectangle.Top = 0; + Rectangle.Left = 0; - groupItem.RenderItems.Add(Rectangle); + groupItem.ChildObjects.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); + if(s.SeriesIcon != null) groupItem.ChildObjects.Add(s.SeriesIcon); + if(s.MarkerBackground != null) groupItem.ChildObjects.Add(s.MarkerBackground); + if (s.MarkerIcon != null) groupItem.ChildObjects.Add(s.MarkerIcon); //renderItems.Add(s.Textbox); - if(s.Textbox != null) s.Textbox.AppendRenderItems(groupItem.RenderItems); + if(s.Textbox != null) s.Textbox.AppendRenderItems(groupItem.ChildObjects); } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index 9197fdf35..1d9ca69e3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -22,6 +22,7 @@ Date Author Change using System.Collections.Generic; using System.Drawing; using System.Linq; +using EPPlus.Graphics; namespace EPPlusImageRenderer.Svg { @@ -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) @@ -109,7 +110,7 @@ private double GetPlotAreaWidth(RectRenderItem rect) 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 : + ChartRenderer.Legend.Rectangle.GlobalLeft - RightMargin : ChartRenderer.ChartArea.Rectangle.Width - RightMargin); @@ -157,7 +158,7 @@ private double GetPlotAreaLeft() var left = LeftMargin; if(ChartRenderer.Chart.Legend?.Position == eLegendPosition.Left) { - left += ChartRenderer.Legend.Rectangle.Bounds.Width + ChartRenderer.Legend.RightMargin; + left += ChartRenderer.Legend.Rectangle.Width + ChartRenderer.Legend.RightMargin; } var leftAxis = GetAxisActualByPosition(eActualAxisPosition.Left); @@ -200,7 +201,7 @@ private double GetPlotAreaTop() haHeight = (topAxis.Rectangle?.Height ?? 0D) + (topSecondAxis?.Rectangle?.Height ?? 0D) + (topAxis.Title?.TextBox?.GetActualHeight() ?? 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.Rectangle.Bottom : ChartRenderer.Title?.Rectangle?.GlobalBottom ?? TopMargin) + haHeight; } private ChartAxisRenderer GetAxisActualByPosition(eActualAxisPosition pos) @@ -244,7 +245,7 @@ private ChartAxisRenderer GetAxisByPosition(eAxisPosition pos) return null; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { renderItems.Add(Group); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index 1907ab5c3..a4ddaf130 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -193,7 +193,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,7 +221,7 @@ public DrawingTextBox TextBox get; private set; } internal override RectRenderItem Rectangle { get => TextBox.Rectangle; set => base.Rectangle = value; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { TextBox.Name = $"{Chart.Name}_Title_Rect"; var p = _title.DefaultTextBody.Paragraphs.FirstOrDefault(); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs index 5b1d6292a..a9ff2c83e 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 renderItems) + public override void AppendRenderItems(List renderItems) { renderItems.AddRange(ChartAreaRenderItems); SeriesRenderItems.ForEach(x => ChartRenderer.Plotarea.Group.AddChildItem(x)); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs index d1879b7cb..ceafcb179 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; @@ -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(); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index 1efd74be7..d6ea3dd3b 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,7 +81,7 @@ 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); @@ -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; @@ -286,20 +286,20 @@ 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) + 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)); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index bd9dd7f19..f010a16ce 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -43,7 +43,7 @@ internal class PieChartTypeDrawer : ChartTypeDrawer /// double _radius; - Point _circleCenter; + TranformPoint _circleCenter; public PieChartTypeDrawer(ChartRenderer chart, ExcelPieChart chartType) : base(chart, chartType) { @@ -52,10 +52,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; @@ -68,7 +68,7 @@ void RenderDebugEllipse() circ.BorderColor = "purple"; circ.BorderWidth = 10; - _groupItem.RenderItems.Add(circ); + _groupItem.ChildObjects.Add(circ); } Coordinate CalculateLocalPointOnCircle(double degrees) @@ -91,7 +91,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 +101,10 @@ void InitializeSlices() void CalculateLocalCenterAndRadius() { - _circleCenter = new Point(); + _circleCenter = new TranformPoint(); _circleCenter.Parent = _groupItem.TranslationOffset; - _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Bounds.Width / 2; - _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Bounds.Height / 2; + _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Width / 2; + _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Height / 2; _groupItem.RotationPoint = _circleCenter; @@ -174,7 +174,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,10 +183,10 @@ 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; @@ -257,7 +257,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(); @@ -304,7 +304,7 @@ internal override void DrawSeries() } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { //renderItems.AddRange(ChartAreaRenderItems); ChartRenderer.Plotarea.Group.AddChildItem(_groupItem); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs index 17812b6a1..b534535bb 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; @@ -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 @@ -661,9 +662,9 @@ 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); + var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle); 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index abcded254..d8ce0cdc6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -26,7 +26,7 @@ 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; } @@ -37,7 +37,7 @@ public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl _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; @@ -182,7 +182,7 @@ internal void SetParentPoint(BoundingBox parent, int index) } } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { var plotAreaGroup = new GroupRenderItem(plotAreaBounds); @@ -211,7 +211,7 @@ public override void AppendRenderItems(List renderItems) { dataLabels[i].CounterRotation = -rotation; } - dataLabels[i].AppendRenderItems(plotAreaGroup.RenderItems); + dataLabels[i].AppendRenderItems(plotAreaGroup.ChildObjects); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs index a4767f8a1..35d16640b 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs @@ -25,7 +25,7 @@ internal PointLines(ChartRenderer cr, BoundingBox parent, ConnectionPointsMiddle { parentBounds = parent; - Rectangle.Bounds.Parent = parent; + Rectangle.Parent = parent; ConnectionPoints = connectionPoints; UpdateLines(); @@ -38,7 +38,7 @@ 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; @@ -50,13 +50,13 @@ internal void UpdateLines() } } - 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); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index a20d5d34b..e6f6c178f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -100,22 +100,22 @@ 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.Parent = Rectangle; 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; //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; //Excel appears to simply add icon width rightwards after applying everything else instead of truly considering the icon @@ -123,7 +123,7 @@ internal void AddSeriesIcon(RenderItem seriesIcon) //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 +181,11 @@ 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.ImportTextBodyAndParagraphs(dataLabel.TextBody, false); - txtBox.TextBody.Bounds.Top = 0; + txtBox.TextBody.Top = 0; txtBox.TextBody.AutoSize = true; if (txtBox.TextBody.Paragraphs.Count == 0) @@ -233,12 +233,12 @@ 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); @@ -271,18 +271,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 +330,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); break; case eLabelPosition.Right: case eLabelPosition.BestFit: - Rectangle.Bounds.Left += (_txtBox.Width / 2d) + point.Width; + Rectangle.Left += (_txtBox.Width / 2d) + point.Width; 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"); @@ -404,7 +404,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 +445,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 +460,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 +487,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); @@ -553,7 +553,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; + Rectangle.Parent = _parentPoint; //--- //--- Calculate vectors and center point --- @@ -578,8 +578,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,8 +628,8 @@ 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 { @@ -655,16 +655,16 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi internal void SetParentPoint(BoundingBox parentPoint) { - Rectangle.Bounds.Parent = parentPoint; + Rectangle.Parent = parentPoint; _parentPoint = parentPoint; - var dataLabelCenter = new Vector2(Rectangle.Bounds.Left, Rectangle.Bounds.Top); + var dataLabelCenter = new Vector2(Rectangle.Left, Rectangle.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,20 +687,20 @@ 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; + var rect = new RectRenderItem(Rectangle); + rect.Left = LeftMargin; + rect.Top = 0; + rect.Width = Rectangle.Width; + rect.Height = Rectangle.Height; rect.FillColor = "red"; rect.FillOpacity = 0.2; renderItems.Add(rect); } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { var parentPointGroup = new GroupRenderItem(_parentPoint); parentPointGroup.Left = _parentPoint.Left; @@ -736,48 +736,48 @@ public override void AppendRenderItems(List renderItems) renderItems.Add(parentPointGroup); - var group = new GroupRenderItem(Rectangle.Bounds); - group.Left = Rectangle.Bounds.Left; - group.Top = Rectangle.Bounds.Top; + var group = new GroupRenderItem(Rectangle); + group.Left = Rectangle.Left; + group.Top = Rectangle.Top; var titleItem = new TitleRenderItem("DataLabel size adjustment"); 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.AppendRenderItems(group.RenderItems); + _txtBox.AppendRenderItems(group.ChildObjects); 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(new BoundingBox(_seriesIcon.Left, height / 2)); + iconGrp.Left = _seriesIcon.Left; iconGrp.Top = (height / 2) - 2; - group.RenderItems.Add(iconGrp); - iconGrp.RenderItems.Add(_seriesIcon); + group.ChildObjects.Add(iconGrp); + iconGrp.ChildObjects.Add(_seriesIcon); } if (_leaderLines != null && _leaderLines.Count > 0) { foreach (var line in _leaderLines) { - group.RenderItems.Add(line); + group.ChildObjects.Add(line); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs b/src/EPPlus/Drawing/Renderer/Chart/DrawingLegendSerie.cs index f1aea585d..b7d96fc66 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/LineMarkerHelper.cs b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs index b3ae77ca8..18462e4d5 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs @@ -76,7 +76,7 @@ private static RenderItem GetMarkerRenderItem(ChartRenderer sc, double x, double ((PathRenderItem)item).Commands.Add(new PathCommands(PathCommandType.End)); break; case eMarkerStyle.Diamond: - item = new PathRenderItem(sc.ChartArea.Rectangle.Bounds); + item = new PathRenderItem(sc.ChartArea.Rectangle); cmd = new PathCommands(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)); diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index f63880080..6a051e75e 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -25,7 +25,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 +34,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 +159,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 +180,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; @@ -216,7 +216,7 @@ public PieSliceRenderItem(ChartRenderer renderer, BoundingBox parent, Point circ CalculateWidthHeight(prevSliceDegrees); - _innerItems = new GroupRenderItem(_innerGroup.Bounds, 0); + _innerItems = new GroupRenderItem(_innerGroup, 0); } internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaBounds, double sliceScaleFactor, double explosionOfPoint, double pieExplosion, int position) @@ -374,7 +374,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 +385,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 +411,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; } @@ -602,14 +602,14 @@ 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; @@ -619,14 +619,14 @@ Point CalculateLocalPointOnCircle(double degrees) 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 +681,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,7 +735,7 @@ internal Transform GetCenterOfEndPointLine() return _endPoint; } - public override void AppendRenderItems(List renderItems) + public override void AppendRenderItems(List renderItems) { throw new NotImplementedException(); diff --git a/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs b/src/EPPlus/Drawing/Renderer/DrawingRenderer.cs index 1e77cb12e..c554877bf 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/Textbox/DrawingParagraphRenderItem.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs index c955307d2..843bdf518 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingParagraphRenderItem.cs @@ -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 3abcadfc5..30a56094a 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 c0aa7737a..55143d9f4 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 829fae671..819eff822 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); } /// @@ -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 92078702c..03ee30202 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,8 +105,8 @@ 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.Left = (float)shapeDef.TextBoxRect.LeftValue.PixelToPoint(); + InsetTextBox.Top = (float)shapeDef.TextBoxRect.TopValue.PixelToPoint(); InsetTextBox.FillOpacity = 0.3d; if (shape.TextBody.TextAutofit != eTextAutofit.ShapeAutofit) @@ -214,22 +214,25 @@ 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; + ri.GetBounds(out var il, out var it, out var ir, out var ib); + if(ilr) + { + r = ir; + } + if(ib>b) + { + b = ib; + } } } 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,14 +262,14 @@ 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"; + marginGroup.Name = $"{Drawing.Name}_MarginTextbox_Pos"; insetGrp.AddChildItem(marginGroup); //grp.Bounds.Position = MarginTextBox.Bounds.Position; @@ -280,16 +283,16 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) //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) { - 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; @@ -341,71 +344,74 @@ 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; } } From 3f7117119bc16e6977d899502b135206f71d1c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Thu, 1 Oct 2026 15:18:00 +0200 Subject: [PATCH 08/44] Fixed Recursion on Height and Width --- .../RenderItems/Textbox/RenderTextBody.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs index 18b2f42f8..7c8bbc1f3 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextBody.cs @@ -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 Width - /// - public double Width { get { return Width; } set { Width = value; } } - - /// - /// Shorthand for Height - /// - public double Height { get { return Height; } set { Height = value; } } - public bool AutoSize { get; set; } public double TopMargin { get; set; } public double BottomMargin { get; set; } From f4de06a32343c7f2261a72c8de11525588ab9a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Thu, 1 Oct 2026 16:13:50 +0200 Subject: [PATCH 09/44] Fixed RenderItem cast issue --- .../Svg/Core/SvgShapeRenderer.cs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs index 611145b17..78d81e4ee 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs @@ -84,19 +84,22 @@ private string GetViewBoxAttr() 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.ChildObjects, 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); + } } } } @@ -108,10 +111,13 @@ public bool PreRender(List items) 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) { From e2096343d0b5d776cded389c9dcc77bdbb9a28c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 2 Oct 2026 12:29:04 +0200 Subject: [PATCH 10/44] Fixed textbox (partially) --- .../SvgStandAloneTests.cs | 19 ++--- .../RenderItems/RenderItem.cs | 33 +++++++ .../RenderItems/Textbox/RenderTextbox.cs | 85 +++++++++++++++---- 3 files changed, 110 insertions(+), 27 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs index 3b5319bed..32327ed43 100644 --- a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs @@ -31,7 +31,6 @@ private GroupRenderItem GenerateShapeRenderer() background.Height = bounds.Height; background.FillColor = "aliceBlue"; - baseGroup.AddChildItem(background); return baseGroup; } @@ -47,7 +46,7 @@ private GroupRenderItem GenerateGroupRenderItem() background.Height = bounds.Height; background.FillColor = "aliceBlue"; - baseGroup.AddChildItem(background); + //baseGroup.AddChildItem(background); return baseGroup; } @@ -90,8 +89,6 @@ public void SvgTextRun() //Add size of text since svg renders text upwards from the start point. textRun.YPosition = rt.Size; - baseGroup.AddChildItem(textRun); - GenerateSvgFile("textRunStandAlone", baseGroup, baseGroup); } @@ -144,10 +141,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; } @@ -320,7 +317,7 @@ private RenderTextbox GenerateTextBox(out GroupRenderItem group) public void BasicTextBox() { var textbox = GenerateTextBox(out GroupRenderItem group); - textbox.AppendRenderItems(group.ChildObjects); + textbox.PrepareForRender(); double delta = 0.001; @@ -340,7 +337,7 @@ public void TextBoxWithMargins() textbox.LeftMargin = 10d; textbox.TopMargin = 10d; - textbox.AppendRenderItems(group.ChildObjects); + textbox.PrepareForRender(); //Assert local position unchanged Assert.AreEqual(0d, textbox.TextBody.Left); @@ -371,7 +368,7 @@ public void TextBoxWithAllMargins() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.ChildObjects); + textbox.PrepareForRender(); //Assert width and height changed by margins Assert.AreEqual(135.2d, textbox.Width, delta); @@ -404,7 +401,7 @@ public void TextBoxWithAllMarginsANDTextbodyChanged() textbox.RightMargin = 10d; textbox.BottomMargin = 10d; - textbox.AppendRenderItems(group.ChildObjects); + textbox.PrepareForRender(); //Assert width and height changed by margins and textbody Assert.AreEqual(150.2d, textbox.Width, delta); diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 05052c01c..df8e6db05 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -210,6 +210,39 @@ internal void SetRotationPointToCenterOfGroup(double rotation = double.NaN) } } + //Note: This does not take negative child items into acount + //TODO: Fix that + public override double Width + { get + { + foreach(var item in ChildObjects) + { + if (item is BoundingBox bb) + { + Width = bb.Right > base.Width ? bb.Right : base.Width; + } + } + return base.Width; + } + } + + //Note: This does not take negative child items into acount + //TODO: Fix that + public override double Height + { + get + { + foreach (var item in ChildObjects) + { + if (item is BoundingBox bb) + { + base.Height = bb.Bottom > base.Height ? bb.Bottom : base.Height; + } + } + return base.Height; + } + } + public void AddChildItem(Transform item) { //item.Bounds.Parent = TranslationOffset; //This incorrectly sets the parent bounds to zero. Intended? diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs index 6aec06668..e82b5ac99 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -188,10 +188,9 @@ 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.Left = Left; _group.Top = Top; @@ -199,40 +198,38 @@ public override void AppendRenderItems(List renderItems) _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.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); + _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.Left += -(rect.Width * COS45); - _group.Top += (rect.Width * COS45); + _group.Left += -(Rectangle.Width * COS45); + _group.Top += (Rectangle.Width * COS45); } else { - _group.Left += rect.Height / 2; - _group.Top += (rect.Width); + _group.Left += Rectangle.Height / 2; + _group.Top += (Rectangle.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 @@ -242,9 +239,65 @@ public override void AppendRenderItems(List renderItems) var marginTitleItem = new TitleRenderItem("TextBox Margin Group"); _marginGroup.AddChildItem(marginTitleItem); + } + public override void AppendRenderItems(List renderItems) + { + PrepareForRender(); + //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.ChildObjects); + //_group.AddChildItem(_marginGroup); + //TextBody.AppendRenderItems(_marginGroup.ChildObjects); } /// /// How the text is anchored. From 3322337247335e51cf0d573e30e7a76004e14f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 2 Oct 2026 13:48:38 +0200 Subject: [PATCH 11/44] Fixed date axis scaling and positioning of text boxes --- .../Chart/Axis/DateAxisScaleCalculator.cs | 32 +++++++++++++-- .../Renderer/Chart/ChartAxisRenderer.cs | 39 ++++++++++++++++++- .../ChartTypeDrawers/LineChartTypeDrawer.cs | 5 --- src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 5 +-- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs index 685323868..23075419a 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs @@ -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,37 @@ 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 = 1; + else if (fraction <= 2) nice = 2; + else if (fraction <= 5) 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/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 02147102f..61ecd69cc 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -602,6 +602,43 @@ private List GetAxisValueTextBoxes() } } } + else + { + 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; } @@ -890,7 +927,7 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw 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) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index d6ea3dd3b..5025d8c76 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -296,17 +296,12 @@ private void SetMarker(ExcelLineChartSerie serie, ExcelChartMarker marker, List< 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); 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)); } } - } diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 6203cded0..167f1f5f4 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -93,7 +93,6 @@ 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() { @@ -101,7 +100,7 @@ private void SetAxisPositionsFromPlotarea() { 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) + if (HorizontalAxis.Axis.TickLabelPosition == eTickLabelPosition.NextTo && HorizontalAxis.Axis.AxisType == eAxisType.Val && HorizontalAxis.Min < 0D) { var CrossValue = HorizontalAxis.GetCrossesValue(); var newRight = Plotarea.Group.Left + HorizontalAxis.GetPositionInPlotarea(CrossValue); @@ -122,7 +121,7 @@ private void SetAxisPositionsFromPlotarea() 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) + if (VerticalAxis.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; From a42e650b4cabd337b5caf1b454a743043f07a2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 2 Oct 2026 15:01:24 +0200 Subject: [PATCH 12/44] Fixed double-render on shapes Text --- src/EPPlus/Drawing/Renderer/ShapeRenderer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index 03ee30202..d205500d7 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -270,14 +270,14 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) marginGroup.Left = MarginTextBox.Left; marginGroup.Top = MarginTextBox.Top; marginGroup.Name = $"{Drawing.Name}_MarginTextbox_Pos"; - insetGrp.AddChildItem(marginGroup); + //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); @@ -287,17 +287,20 @@ DrawingTextBody CreateTextBodyItem(ExcelTextBody bodyOrig) txtBodyItem.Name = $"{Drawing.Name}_TxtBodyItem"; txtBodyItem.ImportTextBodyAndParagraphs(bodyOrig); - foreach(var paragraph in txtBodyItem.Paragraphs) + txtBodyItem.Parent = marginGroup; + + foreach (var paragraph in txtBodyItem.Paragraphs) { paragraph.Name = $"{Drawing.Name}_{paragraph.Name}"; } - - txtBodyItem.AppendRenderItems(marginGroup.ChildObjects); + //txtBodyItem.AppendRenderItems(marginGroup.ChildObjects); //txtBodyItem.Left += InsetTextBox.Left; //txtBodyItem.Top += InsetTextBox.Top; + RenderItems.Add(insetGrp); + //ChartAreaRenderItems.Add(new SvgEndGroupItem(this, Bounds)); - + return txtBodyItem; } From 2bcf8b9f5f347fbd9788a6c91efedd231b9b3f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 2 Oct 2026 15:07:50 +0200 Subject: [PATCH 13/44] WIP:Fixes Axis and line chart duplication --- .../Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs | 5 ++--- src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs | 2 +- .../Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs index 23075419a..8cf4712a9 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs @@ -539,9 +539,8 @@ private static void AddIntervall(ref int interval, ref eTimeUnit unit) var fraction = interval / magnitude; double nice; - if (fraction <= 1) nice = 1; - else if (fraction <= 2) nice = 2; - else if (fraction <= 5) nice = 5; + if (fraction <= 1) nice = 2; + else if (fraction <= 2) nice = 5; else nice = 10; interval = (int)Math.Round(nice * magnitude); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 61ecd69cc..407e367ce 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -602,7 +602,7 @@ private List GetAxisValueTextBoxes() } } } - else + else if(LabelOrientation==eTextOrientation.Horizontal) { double majorWidth; var lblAlignment = (Axis as ExcelChartAxisStandard)?.LabelAlignment ?? OfficeOpenXml.eAxisLabelAlignment.Center; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index 5025d8c76..d927aadda 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -301,7 +301,7 @@ public override void AppendRenderItems(List renderItems) SeriesRenderItems.ForEach(x => SerieGroup.AddChildItem(x)); - ChartRenderer.Plotarea.Group.AddChildItem(SerieGroup); + //ChartRenderer.Plotarea.Group.AddChildItem(SerieGroup); } } } From e2e3034020963ff13e273b1a50914e498179bd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 2 Oct 2026 16:55:14 +0200 Subject: [PATCH 14/44] Beginning of fixing datalabels --- .../Renderer/Chart/ChartLegendRenderer.cs | 8 +- .../Renderer/Chart/DataLabels/PointLines.cs | 3 +- .../Chart/DataLabels/SvgDataLabelPoint.cs | 167 +++++++++--------- 3 files changed, 94 insertions(+), 84 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index fe7cdf191..d84e8a9e6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -1069,11 +1069,11 @@ public override void AppendRenderItems(List renderItems) groupItem.ChildObjects.Add(Rectangle); foreach(var s in SeriesIcon) { - if(s.SeriesIcon != null) groupItem.ChildObjects.Add(s.SeriesIcon); - if(s.MarkerBackground != null) groupItem.ChildObjects.Add(s.MarkerBackground); - if (s.MarkerIcon != null) groupItem.ChildObjects.Add(s.MarkerIcon); + 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.AppendRenderItems(groupItem.ChildObjects); + if(s.Textbox != null) s.Textbox.Parent = groupItem; } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs index 35d16640b..b500fa972 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs @@ -56,8 +56,9 @@ public override void AppendRenderItems(List renderItems) renderItems.Add(gItem); foreach (var line in RenderLines) { - gItem.ChildObjects.Add(line); + line.Parent = gItem; } + renderItems.Add(gItem); } } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index e6f6c178f..38d4e8d7f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -182,7 +182,8 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel } 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.Top = 0; @@ -702,84 +703,92 @@ private void AppendDebugBounds(List renderItems) public override void AppendRenderItems(List renderItems) { - var parentPointGroup = new GroupRenderItem(_parentPoint); - parentPointGroup.Left = _parentPoint.Left; - parentPointGroup.Top = _parentPoint.Top; - - var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); - parentPointGroup.AddChildItem(titleItemOrigin); - - if(originPointRect != null) - { - parentPointGroup.AddChildItem(originPointRect); - } - if(basePositionRect != null) - { - parentPointGroup.AddChildItem(basePositionRect); - } - if(endPositionRect != null) - { - parentPointGroup.AddChildItem(endPositionRect); - } - if(centerPositionRect != null) - { - parentPointGroup.AddChildItem(centerPositionRect); - } - if(maxBoundsCircle != null) - { - parentPointGroup.AddChildItem(maxBoundsCircle); - } - if (endPointCircle != null) - { - parentPointGroup.AddChildItem(endPointCircle); - } - - renderItems.Add(parentPointGroup); - - var group = new GroupRenderItem(Rectangle); - group.Left = Rectangle.Left; - group.Top = Rectangle.Top; - - var titleItem = new TitleRenderItem("DataLabel size adjustment"); - 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.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(new BoundingBox(_seriesIcon.Left, height / 2)); - iconGrp.Left = _seriesIcon.Left; - iconGrp.Top = (height / 2) - 2; - group.ChildObjects.Add(iconGrp); - iconGrp.ChildObjects.Add(_seriesIcon); - } - - if (_leaderLines != null && _leaderLines.Count > 0) - { - foreach (var line in _leaderLines) - { - group.ChildObjects.Add(line); - } - } + //var parentPointGroup = new GroupRenderItem(_parentPoint); + //parentPointGroup.Left = _parentPoint.Left; + //parentPointGroup.Top = _parentPoint.Top; + + //var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); + //titleItemOrigin.Parent = parentPointGroup; + ////parentPointGroup.AddChildItem(titleItemOrigin); + + //if(originPointRect != null) + //{ + // parentPointGroup.AddChildItem(originPointRect); + //} + //if(basePositionRect != null) + //{ + // parentPointGroup.AddChildItem(basePositionRect); + //} + //if(endPositionRect != null) + //{ + // parentPointGroup.AddChildItem(endPositionRect); + //} + //if(centerPositionRect != null) + //{ + // parentPointGroup.AddChildItem(centerPositionRect); + //} + //if(maxBoundsCircle != null) + //{ + // parentPointGroup.AddChildItem(maxBoundsCircle); + //} + //if (endPointCircle != null) + //{ + // parentPointGroup.AddChildItem(endPointCircle); + //} + + ////renderItems.Add(parentPointGroup); + + //var group = new GroupRenderItem(_parentPoint); + //group.Left = Rectangle.Left; + //group.Top = Rectangle.Top; + + //var titleItem = new TitleRenderItem("DataLabel size adjustment"); + //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.PrepareForRender(); + ////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(new BoundingBox(_seriesIcon.Left, height / 2)); + // iconGrp.Left = _seriesIcon.Left; + // iconGrp.Top = (height / 2) - 2; + // iconGrp.Parent = group; + // _seriesIcon.Parent = iconGrp; + //} + + //if (_leaderLines != null && _leaderLines.Count > 0) + //{ + // foreach (var line in _leaderLines) + // { + // line.Parent = group; + // //group.ChildObjects.Add(line); + // } + //} + + //renderItems.Add(group); } } } From 2c162da3db661bcf595da65fdf86cbb2d20ce976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 2 Oct 2026 17:00:35 +0200 Subject: [PATCH 15/44] Found part of root problem --- .../Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index d8ce0cdc6..d1d1c8046 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -204,15 +204,16 @@ public override void AppendRenderItems(List renderItems) plotAreaGroup.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); } - renderItems.Add(plotAreaGroup); for(int i = 0; i< dataLabels.Count; i++) { if(rotation != double.NaN) { dataLabels[i].CounterRotation = -rotation; } - dataLabels[i].AppendRenderItems(plotAreaGroup.ChildObjects); + //dataLabels[i].AppendRenderItems(plotAreaGroup.ChildObjects); } + + renderItems.Add(plotAreaGroup); } } } From 25ec063c8ef6f16f7be8b6ceeec8a8525df17c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 5 Oct 2026 11:25:58 +0200 Subject: [PATCH 16/44] WIP:RenderItem refactor --- .../RenderItems/RenderItem.cs | 146 ++----- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 22 +- src/EPPlus.Graphics/BoundingBox.cs | 79 ++++ .../Renderer/Chart/ChartAreaRenderer.cs | 2 +- .../Renderer/Chart/ChartAxisRenderer.cs | 4 +- .../Renderer/Chart/ChartAxisTextBoxes.cs | 12 + .../Renderer/Chart/ChartDataTableRenderer.cs | 17 +- .../Renderer/Chart/ChartDrawingObject.cs | 357 ++++++++++++++++-- .../Renderer/Chart/ChartLegendRenderer.cs | 11 +- .../Renderer/Chart/ChartPlotareaRenderer.cs | 4 +- .../Renderer/Chart/ChartTitleRenderer.cs | 16 +- .../BarColumnChartTypeDrawer.cs | 10 + .../ChartTypeDrawers/ChartErrorBarRenderer.cs | 6 +- .../Chart/ChartTypeDrawers/ChartTypeDrawer.cs | 2 +- .../ChartTypeDrawers/LineChartTypeDrawer.cs | 13 + .../ChartTypeDrawers/PieChartTypeDrawer.cs | 15 + .../Trendlines/ChartTrendlineRenderer.cs | 54 ++- .../DataLabels/ChartSerieDataLabelRenderer.cs | 14 +- .../Renderer/Chart/DataLabels/PointLines.cs | 20 +- .../Chart/DataLabels/SvgDataLabelPoint.cs | 14 +- .../Defaults/ChartDrawingDefaultObject.cs | 309 --------------- .../Renderer/Chart/PieSliceRenderItem.cs | 20 +- src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 6 +- src/EPPlus/Drawing/Renderer/ShapeRenderer.cs | 17 +- .../Longrunning Tests/LongRunningTests.cs | 2 - 25 files changed, 645 insertions(+), 527 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index df8e6db05..e13456db9 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -169,79 +169,9 @@ public GroupRenderItem(BoundingBox parent, double rotation, Transform rotationPo } } 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.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; - } - } //Note: This does not take negative child items into acount //TODO: Fix that - public override double Width - { get - { - foreach(var item in ChildObjects) - { - if (item is BoundingBox bb) - { - Width = bb.Right > base.Width ? bb.Right : base.Width; - } - } - return base.Width; - } - } - - //Note: This does not take negative child items into acount - //TODO: Fix that - public override double Height - { - get - { - foreach (var item in ChildObjects) - { - if (item is BoundingBox bb) - { - base.Height = bb.Bottom > base.Height ? bb.Bottom : base.Height; - } - } - return base.Height; - } - } public void AddChildItem(Transform item) { @@ -255,32 +185,32 @@ public void AddChildItem(Transform 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) - { - //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); - } - } + ///// + ///// 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() @@ -290,9 +220,9 @@ public override RenderItem Clone() Rotation = Rotation, TextAnchor = TextAnchor, TransformOrigin = TransformOrigin, - GroupTransform = GroupTransform, + // GroupTransform = GroupTransform, RotationPoint = RotationPoint, - Scale = Scale, + // Scale = Scale, }; CloneBase(item); foreach(var child in ChildObjects) @@ -438,14 +368,6 @@ protected RenderItem(Transform parent) { 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 = Left; - it = Top; - ir = Right; - ib = Bottom; - } public string DefId { get; set; } //internal bool IsEndOfGroup { get; set; } = false; public string FillColor { get; set; } @@ -472,12 +394,12 @@ public virtual void GetBounds(out double il, out double it, out double ir, out d 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; @@ -518,8 +440,7 @@ internal void GetOuterShadowColor(out string shadowColor, out double opacity) internal string GetFilterKey() { return $"{GlowColor} {GlowRadius} { OuterShadowEffect?.GetKey()}"; - } - + } } /// /// Base class for any item rendered. @@ -528,12 +449,5 @@ public abstract class RenderItemBase : BoundingBox { //public BoundingBox Bounds = new BoundingBox(); public abstract RenderItemType Type { get; } - public virtual void GetBounds(out double il, out double it, out double ir, out double ib) - { - il = Left; - it = Top; - ir = Right; - ib = Bottom; - } } } \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 5289f6d42..154115416 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -45,7 +45,7 @@ string GetCombinedTransformString(GroupRenderItem item) { string positionStr = ""; string rotationStr = GetRotationStr(item); - string scalingStr = GetScalingStr(item); + //string scalingStr = GetScalingStr(item); //if (item.TranslationOffset != null && (item.TranslationOffset.Left == 0 && item.TranslationOffset.Top == 0) == false) @@ -55,7 +55,7 @@ string GetCombinedTransformString(GroupRenderItem item) positionStr = string.Format("translate({0}, {1})", item.Left.PointToPixelString(), item.Top.PointToPixelString()) + " "; - return positionStr + rotationStr + scalingStr; + return positionStr + rotationStr; //+ scalingStr; } string GetTransformOrigin(GroupRenderItem item) { @@ -69,17 +69,17 @@ string GetTransformOrigin(GroupRenderItem item) } - string GetScalingStr(GroupRenderItem item) - { - var scaleStr = string.Empty; + //string GetScalingStr(GroupRenderItem item) + //{ + // var scaleStr = string.Empty; - if (item.Scale != null) - { - scaleStr = string.Format(transformScale, item.Scale.X.ToString(CultureInfo.InvariantCulture), item.Scale.Y.ToString(CultureInfo.InvariantCulture)) + " "; - } + // if (item.Scale != null) + // { + // scaleStr = string.Format(transformScale, item.Scale.X.ToString(CultureInfo.InvariantCulture), item.Scale.Y.ToString(CultureInfo.InvariantCulture)) + " "; + // } - return scaleStr; - } + // return scaleStr; + //} string GetRotationStr(GroupRenderItem item) { diff --git a/src/EPPlus.Graphics/BoundingBox.cs b/src/EPPlus.Graphics/BoundingBox.cs index 0f2637695..1357e849a 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 { @@ -128,6 +129,84 @@ public double GlobalTop 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 { get diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs index 4da9ec212..7afb4e8e3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAreaRenderer.cs @@ -20,7 +20,7 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal class ChartAreaRenderer : ChartDrawingDefaultObject + internal class ChartAreaRenderer : ChartDrawingObjectWithBackground { public ChartAreaRenderer(ChartRenderer sc, SvgRenderOptions options) : base(sc) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 407e367ce..913408ccd 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -46,7 +46,7 @@ Date Author Change 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 @@ -263,6 +263,7 @@ public bool IsDateScale private set; } = false; + /// /// Create a subGroup beneath the ParentGroup /// (Or beneath altOverrideBounds but add the renderitems to the parentGroup) This is strange and due to legacy @@ -1321,6 +1322,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 edeedc4f4..320f8565c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs @@ -38,6 +38,7 @@ internal List TextBoxes set; }=new List(); + public override void AppendRenderItems(List renderItems) { if (TextBoxes != null && TextBoxes.Count > 0) @@ -52,5 +53,16 @@ public override void AppendRenderItems(List renderItems) } } + 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 0f3dd63ae..1d2b024f7 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -17,6 +17,7 @@ Date Author Change using EPPlusImageRenderer.Svg; using System; using System.Collections.Generic; +using System.Drawing; namespace OfficeOpenXml.Drawing.Renderer.Chart { @@ -27,10 +28,24 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) var chartDataTable = svgChart.Chart.PlotArea.DataTable; } + + internal override Color? DefaultFillColor => throw new NotImplementedException(); + + internal override Color? DefaultBorderColor => throw new NotImplementedException(); + public override void AppendRenderItems(List renderItems) { base.AppendRenderItems(renderItems); } - + + internal override Color? GetDefaultBorderColor() + { + throw new NotImplementedException(); + } + + internal override Color? GetDefaultFillColor() + { + throw new NotImplementedException(); + } } } \ 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 536857bff..8c2d0a2bb 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,38 +31,18 @@ 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 void SetMargins(ExcelTextBody tb) - { - tb.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); - LeftMargin = l; - RightMargin = r; - TopMargin = t; - BottomMargin = b; - } - internal double LeftMargin { get; set; } - 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) + protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelLayout layout, BoundingBox parent = null) { var bounds = parent ?? sc.Bounds; - var rect = new RectRenderItem(sc.ChartArea.Rectangle); + var rect = new RectRenderItem(parent); var ml = layout.ManualLayout; if (ml.LeftMode == eLayoutMode.Edge) { @@ -88,6 +70,31 @@ protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelL rect.Height = bounds.Height * ml.GetHeight() / 100; return rect; } + internal void SetMargins(ExcelTextBody tb) + { + tb.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); + LeftMargin = l; + RightMargin = r; + TopMargin = t; + BottomMargin = b; + } + internal double LeftMargin { get; set; } + internal double RightMargin { get; set; } + internal double TopMargin { get; set; } + internal double BottomMargin { get; set; } + + } + 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. /// @@ -109,5 +116,307 @@ internal List GetXSerie(List xSerie) } 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; + } + + 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"); + } + 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 d84e8a9e6..bd0946750 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -29,7 +29,7 @@ Date Author Change using System.Linq; namespace EPPlusImageRenderer.Svg { - internal class ChartLegendRenderer : ChartDrawingObject + internal class ChartLegendRenderer : ChartDrawingObjectWithBackground { List _seriesHeadersMeasure = new List(); @@ -1082,5 +1082,14 @@ public override void AppendRenderItems(List renderItems) internal override Color? DefaultFillColor => Color.Transparent; internal override Color? DefaultBorderColor => Color.Transparent; + 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/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index 1d9ca69e3..ecf39c04c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -26,7 +26,7 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal class ChartPlotareaRenderer : ChartDrawingDefaultObject + internal class ChartPlotareaRenderer : ChartDrawingObjectWithBackground { public ChartPlotareaRenderer(ChartRenderer sc) : base(sc) { @@ -267,7 +267,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 a4ddaf130..54dc7554c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -37,7 +37,7 @@ Date Author Change namespace EPPlusImageRenderer.Svg { - internal class ChartTitleRenderer : ChartDrawingObject + internal class ChartTitleRenderer : ChartDrawingObjectWithBackground { ExcelChartTitleStandard _title; string _titleText; @@ -221,6 +221,11 @@ public DrawingTextBox TextBox get; private set; } internal override RectRenderItem Rectangle { get => TextBox.Rectangle; set => base.Rectangle = value; } + + internal override Color? DefaultFillColor => throw new NotImplementedException(); + + internal override Color? DefaultBorderColor => throw new NotImplementedException(); + public override void AppendRenderItems(List renderItems) { TextBox.Name = $"{Chart.Name}_Title_Rect"; @@ -235,5 +240,14 @@ public override void AppendRenderItems(List renderItems) TextBox.AppendRenderItems(renderItems); } + 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/ChartTypeDrawers/BarColumnChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs index a9ff2c83e..4e7fca3a0 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs @@ -397,5 +397,15 @@ public override void AppendRenderItems(List renderItems) } 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 0c524ca67..356a7ae16 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; @@ -243,7 +243,7 @@ internal List GetErrorBarRenderItem(int index, ChartAxisRenderer xAx internal override Color? GetDefaultFillColor() { - return GetDefaultFillColorForElement(ChartElement.OtherLines, (int)Chart.Style); + return DefaultFillColor; } internal override Color? GetDefaultBorderColor() @@ -271,5 +271,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 ceafcb179..260f2a985 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs @@ -23,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(); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index d927aadda..189fa73eb 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -293,6 +293,9 @@ private void SetMarker(ExcelLineChartSerie serie, ExcelChartMarker marker, List< } internal override Color? DefaultBorderColor => ChartRenderer.Theme.ColorScheme.Accent1.GetColor(); + + internal override Color? DefaultFillColor => null; + public override void AppendRenderItems(List renderItems) { renderItems.AddRange(ChartAreaRenderItems); @@ -303,5 +306,15 @@ public override void AppendRenderItems(List renderItems) //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 f010a16ce..998b068e3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -15,6 +15,7 @@ using OfficeOpenXml.Utils.TypeConversion; using System; using System.Collections.Generic; +using System.Drawing; using System.Runtime.InteropServices; namespace EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers @@ -45,6 +46,10 @@ internal class PieChartTypeDrawer : ChartTypeDrawer TranformPoint _circleCenter; + internal override Color? DefaultFillColor => null; + + internal override Color? DefaultBorderColor => null; + public PieChartTypeDrawer(ChartRenderer chart, ExcelPieChart chartType) : base(chart, chartType) { //Moved to draw series @@ -313,5 +318,15 @@ public override void AppendRenderItems(List renderItems) ChartRenderer.RenderItems.Add(SeriesRenderItems[0]); } } + + 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 b534535bb..877353bb4 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs @@ -31,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; @@ -111,17 +111,17 @@ private void CreateDatalabel() var y = ChartRenderer.Plotarea.Group.Top + coord[coord.Length - 1]; double width = 0, height = 0; - if (_trendline.Label.Layout.HasLayout) - { - var mlRect = GetRectFromManualLayout(ChartRenderer, _trendline.Label.Layout); - x += mlRect.Left; - y += mlRect.Top; - if (lbl.Layout.ManualLayout.Width.HasValue && lbl.Layout.ManualLayout.Height.HasValue) - { - width = mlRect.Width; - height = mlRect.Height; - } - } + //if (_trendline.Label.Layout.HasLayout) + //{ + // var mlRect = GetRectFromManualLayout(ChartRenderer, _trendline.Label.Layout); + // x += mlRect.Left; + // y += mlRect.Top; + // if (lbl.Layout.ManualLayout.Width.HasValue && lbl.Layout.ManualLayout.Height.HasValue) + // { + // width = mlRect.Width; + // height = mlRect.Height; + // } + //} if (width > 0 && height > 0) { @@ -786,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 d1d1c8046..c554e50c0 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -12,7 +12,7 @@ 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(); @@ -30,6 +30,8 @@ internal class ChartSerieDataLabelRenderer : ChartDrawingObject internal override Color? DefaultFillColor { get; } + internal override Color? DefaultBorderColor => null; + double? SummedSeries = null; public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl, BoundingBox maxBounds, ExcelChartStandardSerie serie, List xValues, List yValues, int index) : base(chart) @@ -215,5 +217,15 @@ public override void AppendRenderItems(List renderItems) renderItems.Add(plotAreaGroup); } + + 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 b500fa972..78fdeda31 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); @@ -56,9 +61,18 @@ public override void AppendRenderItems(List renderItems) renderItems.Add(gItem); foreach (var line in RenderLines) { - line.Parent = gItem; + gItem.ChildObjects.Add(line); } - renderItems.Add(gItem); + } + + 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 38d4e8d7f..835211fa9 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -20,7 +20,7 @@ namespace EPPlus.Export.ImageRenderer.RenderItems.SvgItem { - internal class SvgDataLabelPoint : ChartDrawingObject + internal class SvgDataLabelPoint : ChartDrawingObjectWithBackground { bool _hasManualLayout = false; bool _hasLeaderLines = false; @@ -37,6 +37,8 @@ internal class SvgDataLabelPoint : ChartDrawingObject internal override Color? DefaultFillColor { get; } + internal override Color? DefaultBorderColor => null; + //public SvgChartDataLabelStandard(DrawingChart chart, string dataLabelText) : base(chart) //{ // var txtBox = new SvgTextBox(chart, chart.Bounds, chart.Bounds); @@ -790,5 +792,15 @@ public override void AppendRenderItems(List renderItems) //renderItems.Add(group); } + + 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 cc3f01dc4..ad118180a 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/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index 6a051e75e..22105a437 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -11,10 +11,11 @@ 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; /// @@ -238,7 +239,7 @@ internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaB _sliceScaleFactor = sliceScaleFactor; //Translate and scale path - _innerGroup.Scale = new Coordinate(_sliceScaleFactor, _sliceScaleFactor); + //_innerGroup.Scale = new Coordinate(_sliceScaleFactor, _sliceScaleFactor); CalculatePointExplosion(explosionOfPoint, pieExplosion, localMax, localMin); CalculateLargestRectWithinCircleSegment(); @@ -540,6 +541,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 @@ -741,9 +744,14 @@ 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 167f1f5f4..5b901a1b6 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -110,7 +110,7 @@ private void SetAxisPositionsFromPlotarea() Plotarea.Group.Left = VerticalAxis.Rectangle.Left; var rightDiff = newRight - VerticalAxis.Rectangle.Width; VerticalAxis.Rectangle.Left = rightDiff; - VerticalAxis.Line.X1 = VerticalAxis.Line.X2 = newRight; + VerticalAxis.Line?.X1 = VerticalAxis.Line.X2 = newRight; } } VerticalAxis.AddTickmarksAndValues(DefItems); @@ -130,7 +130,7 @@ private void SetAxisPositionsFromPlotarea() var topDiff = HorizontalAxis.Rectangle.Top - newtop; HorizontalAxis.Rectangle.Top = newtop; HorizontalAxis.Rectangle.Height += topDiff; - HorizontalAxis.Line.Y1 = HorizontalAxis.Line.Y2 = newtop; + HorizontalAxis.Line?.Y1 = HorizontalAxis.Line.Y2 = newtop; } } @@ -423,7 +423,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; } diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index d205500d7..747906542 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -216,22 +216,21 @@ public string ViewBox { if(item is RenderItem ri) { - ri.GetBounds(out var il, out var it, out var ir, out var ib); - if(ilr) + if(ri.GlobalRight > r) { - r = ir; + r = ri.GlobalRight; } - if(ib>b) + if(ri.GlobalBottom > b) { - b = ib; + b = ri.GlobalBottom; } } } diff --git a/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs b/src/EPPlusTest/Longrunning Tests/LongRunningTests.cs index 785081dc4..ba997a96d 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) { From 2355a3d2fc9f7db5c2804b7c874b49317d8d6440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Mon, 5 Oct 2026 13:01:21 +0200 Subject: [PATCH 17/44] Fixed textbox group --- .../RenderItems/Textbox/RenderTextbox.cs | 1 + .../Renderer/Chart/ChartPlotareaRenderer.cs | 1 + .../Renderer/Chart/ChartTitleRenderer.cs | 4 +- .../DataLabels/ChartSerieDataLabelRenderer.cs | 29 +-- .../Chart/DataLabels/SvgDataLabelPoint.cs | 174 +++++++++--------- 5 files changed, 109 insertions(+), 100 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs index e82b5ac99..8787d32b1 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -243,6 +243,7 @@ public void PrepareForRender() 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. diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index ecf39c04c..0e3b248b6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -70,6 +70,7 @@ internal void SetPlotAreaRectangle() 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); Rectangle = rect; + Rectangle.Name = "PlotArea_Rectangle"; } private double GetPlotAreaHeight(RectRenderItem rect) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index 54dc7554c..e63959d71 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -222,9 +222,9 @@ public DrawingTextBox TextBox } internal override RectRenderItem Rectangle { get => TextBox.Rectangle; set => base.Rectangle = value; } - internal override Color? DefaultFillColor => throw new NotImplementedException(); + internal override Color? DefaultFillColor => null; - internal override Color? DefaultBorderColor => throw new NotImplementedException(); + internal override Color? DefaultBorderColor => null; public override void AppendRenderItems(List renderItems) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index c554e50c0..74c3754f6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -43,6 +43,8 @@ public ChartSerieDataLabelRenderer(ChartRenderer chart, ExcelChartDataLabel dlbl DefaultFillColor = dlbl.Fill != null && dlbl.Fill.Color.IsEmpty == false ? dlbl.Fill.Color : Color.Transparent; + Rectangle.Name = "SerieDataLabel"; + if(yValues != null && yValues.Count != 0) { @@ -181,41 +183,44 @@ internal void SetParentPoint(BoundingBox parent, int index) if (dataLabels.Count > index) { dataLabels[index].SetParentPoint(parent); + dataLabels[index].Rectangle.Name = $"DatalabelPoint_Rect_{index}"; } } public override void AppendRenderItems(List renderItems) { - var plotAreaGroup = new GroupRenderItem(plotAreaBounds); + var DatalabelsGroup = new GroupRenderItem(ChartRenderer.ChartArea.Rectangle); + + DatalabelsGroup.Name = "DlblGrp"; - plotAreaGroup.Left = plotAreaBounds.Position.X; - plotAreaGroup.Top = plotAreaBounds.Position.Y; + DatalabelsGroup.Left = plotAreaBounds.Position.X; + DatalabelsGroup.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); + DatalabelsGroup.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); } - 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.ChildObjects); + dataLabels[i].AppendRenderItems(DatalabelsGroup.ChildObjects); } - renderItems.Add(plotAreaGroup); + renderItems.Add(DatalabelsGroup); } internal override Color? GetDefaultFillColor() diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 835211fa9..a8fc088a3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -56,6 +56,7 @@ public SvgDataLabelPoint(ChartRenderer chart, ExcelChartDataLabel standard, Colo DefaultFillColor = defaultFillColor.HasValue ? defaultFillColor : Color.Transparent; _labelPosition = GetDefaultPositionBasedOnChartType(standard); Rectangle = new RectRenderItem(chart.Bounds); + Rectangle.Name = "Datapoint_Rect"; } eLabelPosition GetDefaultPositionBasedOnChartType(ExcelChartDataLabel standardDatalabel) @@ -705,92 +706,93 @@ private void AppendDebugBounds(List renderItems) public override void AppendRenderItems(List renderItems) { - //var parentPointGroup = new GroupRenderItem(_parentPoint); - //parentPointGroup.Left = _parentPoint.Left; - //parentPointGroup.Top = _parentPoint.Top; - - //var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); - //titleItemOrigin.Parent = parentPointGroup; - ////parentPointGroup.AddChildItem(titleItemOrigin); - - //if(originPointRect != null) - //{ - // parentPointGroup.AddChildItem(originPointRect); - //} - //if(basePositionRect != null) - //{ - // parentPointGroup.AddChildItem(basePositionRect); - //} - //if(endPositionRect != null) - //{ - // parentPointGroup.AddChildItem(endPositionRect); - //} - //if(centerPositionRect != null) - //{ - // parentPointGroup.AddChildItem(centerPositionRect); - //} - //if(maxBoundsCircle != null) - //{ - // parentPointGroup.AddChildItem(maxBoundsCircle); - //} - //if (endPointCircle != null) - //{ - // parentPointGroup.AddChildItem(endPointCircle); - //} - - ////renderItems.Add(parentPointGroup); - - //var group = new GroupRenderItem(_parentPoint); - //group.Left = Rectangle.Left; - //group.Top = Rectangle.Top; - - //var titleItem = new TitleRenderItem("DataLabel size adjustment"); - //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.PrepareForRender(); - ////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(new BoundingBox(_seriesIcon.Left, height / 2)); - // iconGrp.Left = _seriesIcon.Left; - // iconGrp.Top = (height / 2) - 2; - // iconGrp.Parent = group; - // _seriesIcon.Parent = iconGrp; - //} - - //if (_leaderLines != null && _leaderLines.Count > 0) - //{ - // foreach (var line in _leaderLines) - // { - // line.Parent = group; - // //group.ChildObjects.Add(line); - // } - //} - - //renderItems.Add(group); + var parentPointGroup = new GroupRenderItem(_parentPoint); + parentPointGroup.Left = _parentPoint.Left; + parentPointGroup.Top = _parentPoint.Top; + + var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); + titleItemOrigin.Parent = parentPointGroup; + //parentPointGroup.AddChildItem(titleItemOrigin); + + if (originPointRect != null) + { + parentPointGroup.AddChildItem(originPointRect); + } + if (basePositionRect != null) + { + parentPointGroup.AddChildItem(basePositionRect); + } + if (endPositionRect != null) + { + parentPointGroup.AddChildItem(endPositionRect); + } + if (centerPositionRect != null) + { + parentPointGroup.AddChildItem(centerPositionRect); + } + if (maxBoundsCircle != null) + { + parentPointGroup.AddChildItem(maxBoundsCircle); + } + if (endPointCircle != null) + { + parentPointGroup.AddChildItem(endPointCircle); + } + + //renderItems.Add(parentPointGroup); + + var group = new GroupRenderItem(_parentPoint); + group.Left = Rectangle.Left; + group.Top = Rectangle.Top; + + var titleItem = new TitleRenderItem("DataLabel size adjustment"); + 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.PrepareForRender(); + _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(new BoundingBox(_seriesIcon.Left, height / 2)); + iconGrp.Left = _seriesIcon.Left; + iconGrp.Top = (height / 2) - 2; + iconGrp.Parent = group; + _seriesIcon.Parent = iconGrp; + } + + if (_leaderLines != null && _leaderLines.Count > 0) + { + foreach (var line in _leaderLines) + { + line.Parent = group; + //group.ChildObjects.Add(line); + } + } + + renderItems.Add(group); } internal override Color? GetDefaultFillColor() From 3868b7da738cd4fe5f108a79e68f91fd1f620578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Mon, 5 Oct 2026 13:14:16 +0200 Subject: [PATCH 18/44] Fixed datalabel positioning (temp) --- .../RenderItems/Textbox/RenderTextbox.cs | 1 + .../DataLabels/ChartSerieDataLabelRenderer.cs | 5 +---- .../Chart/DataLabels/SvgDataLabelPoint.cs | 15 ++++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs index 8787d32b1..82216b214 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -16,6 +16,7 @@ public void Init(BoundingBox parent, double maxWidth, double maxHeight) { Parent = parent; _group = new GroupRenderItem(Parent); + _group.Name = "Textbox_Group"; _rectangle = new RectRenderItem(_group); _marginGroup = new GroupRenderItem(_group); //TextBody = new RenderTextBody(Rectangle.Bounds, true); diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index 74c3754f6..8803ebe89 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -191,10 +191,7 @@ public override void AppendRenderItems(List renderItems) { var DatalabelsGroup = new GroupRenderItem(ChartRenderer.ChartArea.Rectangle); - DatalabelsGroup.Name = "DlblGrp"; - - DatalabelsGroup.Left = plotAreaBounds.Position.X; - DatalabelsGroup.Top = plotAreaBounds.Position.Y; + DatalabelsGroup.Name = "Dlbl_SeriesGroup"; if (rotation != double.NaN) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index a8fc088a3..f681417c4 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -706,9 +706,10 @@ private void AppendDebugBounds(List renderItems) public override void AppendRenderItems(List renderItems) { - var parentPointGroup = new GroupRenderItem(_parentPoint); - parentPointGroup.Left = _parentPoint.Left; - parentPointGroup.Top = _parentPoint.Top; + var parentPointGroup = new GroupRenderItem(); + parentPointGroup.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); + + parentPointGroup.Name = "Dlbl_Parent_Point_Grp"; var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); titleItemOrigin.Parent = parentPointGroup; @@ -741,7 +742,7 @@ public override void AppendRenderItems(List renderItems) //renderItems.Add(parentPointGroup); - var group = new GroupRenderItem(_parentPoint); + var group = new GroupRenderItem(parentPointGroup); group.Left = Rectangle.Left; group.Top = Rectangle.Top; @@ -754,8 +755,8 @@ public override void AppendRenderItems(List renderItems) group.Rotation = CounterRotation; //_txtBox.Rectangle.Parent = group; //_txtBox.TextBody.Parent = group; - _txtBox.PrepareForRender(); - _txtBox.Rectangle.Parent = group; + _txtBox.AppendRenderItems(group.ChildObjects); + //_txtBox.Rectangle.Parent = group; //group.AddChild(_txtBox.Rectangle); //_txtBox.TextBody.Parent = group; //_txtBox.AppendRenderItems(group.ChildObjects); @@ -792,7 +793,7 @@ public override void AppendRenderItems(List renderItems) } } - renderItems.Add(group); + renderItems.Add(parentPointGroup); } internal override Color? GetDefaultFillColor() From 8b61766df1865fa3706aea5e3bdd9f2f18885e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 5 Oct 2026 13:27:30 +0200 Subject: [PATCH 19/44] Removed RenderItemBase. Moved RenderItem style properties to RenderItemStyle. --- .../SvgStandAloneTests.cs | 8 +- .../RenderItems/RenderItem.cs | 76 ++++++++-------- .../Textbox/ParagraphRenderItem.cs | 2 +- .../RenderItems/Textbox/TextRunRenderItem.cs | 4 +- .../Svg/Core/SvgBaseRenderer.cs | 59 ++++++------ .../Svg/Core/SvgShapeRenderer.cs | 89 ++++++++++--------- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 4 +- .../Svg/ItemRenderers/SvgLineRenderer.cs | 31 +++---- .../Svg/ItemRenderers/SvgPathRenderer.cs | 29 +++--- .../Svg/ItemRenderers/SvgTextRunRenderer.cs | 10 +-- .../Renderer/Chart/ChartAxisRenderer.cs | 20 ++--- .../Renderer/Chart/ChartDrawingObject.cs | 62 ++++++------- .../Renderer/Chart/ChartLegendRenderer.cs | 36 ++++---- .../Renderer/Chart/ChartPlotareaRenderer.cs | 4 +- .../Renderer/Chart/ChartTitleRenderer.cs | 8 +- .../BarColumnChartTypeDrawer.cs | 6 +- .../ChartTypeDrawers/ChartErrorBarRenderer.cs | 9 +- .../Chart/ChartTypeDrawers/ChartTypeDrawer.cs | 14 +-- .../ChartTypeDrawers/LineChartTypeDrawer.cs | 22 ++--- .../ChartTypeDrawers/PieChartTypeDrawer.cs | 8 +- .../Trendlines/ChartTrendlineRenderer.cs | 34 +++---- .../DataLabels/ChartSerieDataLabelRenderer.cs | 8 +- .../Renderer/Chart/DataLabels/PointLines.cs | 4 +- .../Chart/DataLabels/SvgDataLabelPoint.cs | 24 ++--- .../Renderer/Chart/LineMarkerHelper.cs | 46 +++++----- .../Renderer/Chart/PieSliceRenderItem.cs | 17 ++-- src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 12 +-- .../DrawingRenderItemExtentions.cs | 64 ++++++------- .../Textbox/DrawingParagraphRenderItem.cs | 12 +-- .../RenderItems/Textbox/DrawingTextRunItem.cs | 4 +- src/EPPlus/Drawing/Renderer/ShapeRenderer.cs | 21 ++--- 31 files changed, 381 insertions(+), 366 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs index 32327ed43..cb61fb436 100644 --- a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs @@ -29,7 +29,7 @@ private GroupRenderItem GenerateShapeRenderer() background.Width = bounds.Width; background.Height = bounds.Height; - background.FillColor = "aliceBlue"; + background.Style.FillColor = "aliceBlue"; return baseGroup; } @@ -44,7 +44,7 @@ private GroupRenderItem GenerateGroupRenderItem() background.Width = bounds.Width; background.Height = bounds.Height; - background.FillColor = "aliceBlue"; + background.Style.FillColor = "aliceBlue"; //baseGroup.AddChildItem(background); return baseGroup; @@ -102,7 +102,7 @@ private void GenerateTextBodyFile(string fileName, GroupRenderItem baseGroup, Sv background.Width = baseGroup.Width; background.Height = baseGroup.Height; - background.FillColor = "aliceBlue"; + background.Style.FillColor = "aliceBlue"; baseGroup.AddChildItem(textBody); baseGroup.AddChildItem(background); @@ -308,7 +308,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; } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index e13456db9..61ea45725 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -359,15 +359,8 @@ public abstract class DrawingObject { public virtual void AppendRenderItems(List renderItems) { } } - public abstract class RenderItem : RenderItemBase + public class RenderItemStyle { - protected RenderItem() - { - } - protected RenderItem(Transform parent) - { - Parent = parent; - } public string DefId { get; set; } //internal bool IsEndOfGroup { get; set; } = false; public string FillColor { get; set; } @@ -390,30 +383,9 @@ protected RenderItem(Transform parent) 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) @@ -439,15 +411,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 : BoundingBox + public abstract class RenderItem : BoundingBox { - //public BoundingBox Bounds = new BoundingBox(); + protected RenderItem() + { + } + protected RenderItem(Transform parent) + { + Parent = parent; + } public abstract RenderItemType Type { get; } + 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) + { + item.Style = Style.Clone(); + } + public abstract RenderItem Clone(); } } \ No newline at end of file diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs index d32d150b1..38fa4a8cd 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/ParagraphRenderItem.cs @@ -145,7 +145,7 @@ protected ParagraphRenderItem(RenderContext renderContext, BoundingBox parent, b { var defaultFont = new MeasurementFont { FontFamily = "Aptos Narrow", Size = 11, Style = MeasurementFontStyles.Regular }; DefaultParagraphFont = new FontFormatBase(defaultFont); - FillColor = "black"; + Style.FillColor = "black"; } } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs index 90b3b1454..6916f09f6 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/TextRunRenderItem.cs @@ -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; diff --git a/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/Core/SvgBaseRenderer.cs index 0d1afae44..215f866d5 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) { + var style = item.Style; if (item.Name != null) { 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 78d81e4ee..f87c9a6cd 100644 --- a/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/Core/SvgShapeRenderer.cs @@ -134,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.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 += $"" + $"" + - $"" + + $"" + $"" + $""; @@ -202,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 += $""; } } @@ -245,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; } @@ -633,7 +634,7 @@ 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 diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 154115416..69cb893ed 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -24,9 +24,9 @@ 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($""); diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgLineRenderer.cs index edca4f770..c4acda910 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/SvgPathRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs index 9fe9acde3..eac712d05 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; } } @@ -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 a40cd6214..8f67b207e 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgTextRunRenderer.cs @@ -138,14 +138,14 @@ public override void Render(TextRunRenderItem textRun) + $"font-size=\"{fontSize.ToString(CultureInfo.InvariantCulture)}px\" "; } - if(string.IsNullOrEmpty(textRun.FillColor) == false) + if(string.IsNullOrEmpty(textRun.Style.FillColor) == false) { - finalString += $" style=\"fill: {textRun.FillColor};\" "; + finalString += $" style=\"fill: {textRun.Style.FillColor};\" "; } //Avoid rendering fill color as we do so via style - var temp = textRun.FillColor; - textRun.FillColor = null; + var temp = textRun.Style.FillColor; + textRun.Style.FillColor = null; var sb = new StringBuilder(); sb.Append(finalString); @@ -154,7 +154,7 @@ public override void Render(TextRunRenderItem textRun) //Renders up until this point (must be done to end the attribute addings so that text content can then be added) RenderBaseToSpecified(textRun, sb); - textRun.FillColor = temp; + textRun.Style.FillColor = temp; //Since final string has been written in base.render erase it. finalString = ""; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 913408ccd..5f68c62ae 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -122,13 +122,13 @@ internal ChartAxisRenderer(ChartRenderer sc, ExcelChartAxisStandard ax) : base(s } } - Rectangle.FillColor = "none"; + Rectangle.Style.FillColor = "none"; Line = new LineRenderItem(Rectangle); - 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.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; } } } @@ -502,7 +502,7 @@ private List GetAxisValueTextBoxes() 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) { @@ -855,10 +855,10 @@ 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); } @@ -986,9 +986,9 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs index 8c2d0a2bb..d7d9848f1 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs @@ -39,37 +39,6 @@ protected ChartDrawingObjectWithBackground(ChartRenderer chart) : base(chart) Rectangle = new RectRenderItem(chart.Bounds); } internal virtual RectRenderItem Rectangle { get; set; } - protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelLayout layout, BoundingBox parent = null) - { - var bounds = parent ?? sc.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; - } - else - { - rect.Left = bounds.Width * (float)(ml.Left ?? 0D) / 100; - //TODO:Add factor from default position - } - - //Width is always factor. - rect.Width = bounds.Width * ml.GetWidth() / 100; - - if (ml.LeftMode == eLayoutMode.Edge) - { - rect.Top = bounds.Height * (float)(layout.ManualLayout.Top ?? 0D) / 100; - } - else - { - rect.Top = bounds.Height * (float)(ml.Top ?? 0D) / 100; - //TODO:Add factor from default position - } - //Height is always factor. - rect.Height = bounds.Height * ml.GetHeight() / 100; - return rect; - } internal void SetMargins(ExcelTextBody tb) { tb.GetInsetsOrDefaults(out double l, out double r, out double t, out double b); @@ -138,6 +107,37 @@ private Color GetThemeColorTint(eThemeSchemeColor themeColor, double tint = 0.0d 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(parent); + var ml = layout.ManualLayout; + if (ml.LeftMode == eLayoutMode.Edge) + { + rect.Left = bounds.Width * (float)(layout.ManualLayout.Left ?? 0D) / 100; + } + else + { + rect.Left = bounds.Width * (float)(ml.Left ?? 0D) / 100; + //TODO:Add factor from default position + } + + //Width is always factor. + rect.Width = bounds.Width * ml.GetWidth() / 100; + + if (ml.LeftMode == eLayoutMode.Edge) + { + rect.Top = bounds.Height * (float)(layout.ManualLayout.Top ?? 0D) / 100; + } + else + { + rect.Top = bounds.Height * (float)(ml.Top ?? 0D) / 100; + //TODO:Add factor from default position + } + //Height is always factor. + rect.Height = bounds.Height * ml.GetHeight() / 100; + return rect; + } internal Color? GetStyleColorOrDefault(int styleId, Color col1, Color col2, Color col3, Color col4) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index bd0946750..eee392b8c 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -92,8 +92,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); @@ -736,7 +736,7 @@ private void SetPieLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc firstIconWidth = si.Width; } sls = new DrawingLegendSerie(); - var tbLeft = si.Left + si.Width + MarginIconText + (si.BorderWidth ?? 0d); + var tbLeft = si.Left + si.Width + MarginIconText + (si.Style.BorderWidth ?? 0d); var tbTop = si.Top - ((entryHeight + MarginIconText) / 2); double tbWidth; @@ -773,18 +773,18 @@ private void SetPieLegend(ExcelChart ct, int index, DrawingLegendSerie pSls, Exc tbWidth = sls.Textbox.Width + rDefMargin; /*+ lDefMargin + rDefMargin;*/ - lastWidth = tbWidth + si.Width - (si.BorderWidth ?? 0d); + lastWidth = tbWidth + si.Width - (si.Style.BorderWidth ?? 0d); - totalWidth += tbWidth + si.Width + (si.BorderWidth ?? 0d) + MarginIconText; + totalWidth += tbWidth + si.Width + (si.Style.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); + ChartTypeDrawer.SetFillDataPoint(Chart, ps, i, sls.SeriesIcon.Style, dp, Chart.StyleManager.Style?.SeriesLine); } else { - ChartTypeDrawer.SetFillSerie(Chart, ct, ps, 0, i, sls.SeriesIcon); + ChartTypeDrawer.SetFillSerie(Chart, ct, ps, 0, i, sls.SeriesIcon.Style); } SeriesIcon.Add(sls); @@ -892,7 +892,7 @@ private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie var line = new LineRenderItem(Rectangle); //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); + line.Style.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); @@ -902,16 +902,16 @@ private LineRenderItem GetLineSeriesIcon(ExcelChart ct, ExcelChartStandardSerie line.X2 = x + LineLength; line.Y1 = y; line.Y2 = y; - line.LineCap = LineCap.Round; + line.Style.LineCap = LineCap.Round; return line; } private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline tl, DrawingLegendSerie pSls, double entryWidth, double entryHeight) { var line = new LineRenderItem(Rectangle); - line.SetDrawingPropertiesFill(ChartRenderer.Theme, tl.Fill, Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); + line.Style.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); + line.Style.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); @@ -921,7 +921,7 @@ private LineRenderItem GetTrendLineSeriesIcon(ExcelChart ct, ExcelChartTrendline line.Y1 = y; line.X2 = x + LineLength; line.Y2 = y; - line.LineCap = LineCap.Round; + line.Style.LineCap = LineCap.Round; return line; } @@ -935,7 +935,7 @@ private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, D GetItemPosition(pSls, entryWidth, entryHeight, icon?.Left ?? 0D, icon?.Top ?? 0D, out double x, out double y); - item.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; item.Left = x; if (pSls != null && (Chart.Legend.Position == eLegendPosition.Left || Chart.Legend.Position == eLegendPosition.Right)) { @@ -960,8 +960,8 @@ private RectRenderItem GetPieSeriesIcon(ExcelChart ct, ExcelPieChartSerie pcS, D 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); + item.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, pcS.Fill, Chart.StyleManager.Style?.SeriesLine.FillReference.Color); + item.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, pcS.Border, Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, () => DefaultBorderColor, 1.5d); return item; } @@ -977,7 +977,7 @@ private RectRenderItem GetBarSeriesIcon(ExcelChart ct, ExcelBarChartSerie chartS GetItemPosition(pSls, entryWidth, entryHeight, iconLeft, iconTop + (iconHeight / 2), out double x, out double y); - item.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; item.Left = x; if(pSls !=null && (Chart.Legend.Position == eLegendPosition.Left || Chart.Legend.Position == eLegendPosition.Right)) { @@ -994,11 +994,11 @@ private RectRenderItem GetBarSeriesIcon(ExcelChart ct, ExcelBarChartSerie chartS if (index>=0 && chartSerie.DataPoints.ContainsKey(index)) { var dp = chartSerie.DataPoints[index]; - ChartTypeDrawer.SetFillDataPoint(Chart, chartSerie, index, item, dp, Chart.StyleManager.Style?.SeriesLine); + ChartTypeDrawer.SetFillDataPoint(Chart, chartSerie, index, item.Style, dp, Chart.StyleManager.Style?.SeriesLine); } else { - ChartTypeDrawer.SetFillSerie(Chart, ct, chartSerie, serieIndex, index, item); + ChartTypeDrawer.SetFillSerie(Chart, ct, chartSerie, serieIndex, index, item.Style); } return item; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs index ecf39c04c..e5b34c451 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -67,8 +67,8 @@ 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; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index 54dc7554c..1e39ae1ec 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -105,8 +105,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) @@ -233,9 +233,9 @@ public override void AppendRenderItems(List renderItems) 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); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs index 4e7fca3a0..debe68bd8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/BarColumnChartTypeDrawer.cs @@ -342,14 +342,14 @@ 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs index 356a7ae16..89bdedbc3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartErrorBarRenderer.cs @@ -227,16 +227,17 @@ 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; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs index 260f2a985..1bece4f36 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/ChartTypeDrawer.cs @@ -232,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 189fa73eb..a0076b39a 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -83,8 +83,8 @@ private void CreateDropLine(ExcelLineChart chartType, List coords) }; 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); } @@ -245,14 +245,14 @@ 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); } } @@ -260,11 +260,11 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List 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.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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index 998b068e3..d3304a648 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -68,10 +68,10 @@ 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.ChildObjects.Add(circ); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs index 877353bb4..c295ed4ad 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs @@ -111,17 +111,17 @@ private void CreateDatalabel() var y = ChartRenderer.Plotarea.Group.Top + coord[coord.Length - 1]; double width = 0, height = 0; - //if (_trendline.Label.Layout.HasLayout) - //{ - // var mlRect = GetRectFromManualLayout(ChartRenderer, _trendline.Label.Layout); - // x += mlRect.Left; - // y += mlRect.Top; - // if (lbl.Layout.ManualLayout.Width.HasValue && lbl.Layout.ManualLayout.Height.HasValue) - // { - // width = mlRect.Width; - // height = mlRect.Height; - // } - //} + if (_trendline.Label.Layout.HasLayout) + { + var mlRect = GetRectFromManualLayout(ChartRenderer, _trendline.Label.Layout); + x += mlRect.Left; + y += mlRect.Top; + if (lbl.Layout.ManualLayout.Width.HasValue && lbl.Layout.ManualLayout.Height.HasValue) + { + width = mlRect.Width; + height = mlRect.Height; + } + } if (width > 0 && height > 0) { @@ -192,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) @@ -666,9 +666,9 @@ public override void AppendRenderItems(List renderItems) { var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle); 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); + 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); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index c554e50c0..bbca8bfaa 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -122,9 +122,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) @@ -202,8 +202,8 @@ public override void AppendRenderItems(List renderItems) 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); + plotAreaGroup.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, _dlbl.Fill, null); } for(int i = 0; i< dataLabels.Count; i++) diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs index 78fdeda31..9282c2510 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/PointLines.cs @@ -49,8 +49,8 @@ internal void UpdateLines() 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); } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 835211fa9..24aa7c333 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -244,14 +244,14 @@ internal void ImportDataLabel(ExcelChartStandardSerie serie, ExcelChartDataLabel 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) { @@ -266,7 +266,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) @@ -364,7 +364,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; @@ -380,8 +380,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; @@ -521,8 +521,8 @@ 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); } @@ -532,8 +532,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); } } @@ -698,8 +698,8 @@ private void AppendDebugBounds(List renderItems) rect.Width = Rectangle.Width; rect.Height = Rectangle.Height; - rect.FillColor = "red"; - rect.FillOpacity = 0.2; + rect.Style.FillColor = "red"; + rect.Style.FillOpacity = 0.2; renderItems.Add(rect); } diff --git a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs index 18462e4d5..454efba79 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; @@ -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 22105a437..333ccbcdf 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -8,6 +8,7 @@ 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; @@ -224,7 +225,7 @@ internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaB { _slicePath = new PathRenderItem(plotAreaBounds); - _slicePath.BorderWidth = 5d; + _slicePath.Style.BorderWidth = 5d; //Calculate path commands var moveCenter = new PathCommands(PathCommandType.Move, _circleCenter.Left, _circleCenter.Top); @@ -270,9 +271,9 @@ private void AddDebugLines(PathCommands moveCenter, BoundingBox bounds) //Render bounds for slice _debugBoundsPath = new PathRenderItem(bounds); - _debugBoundsPath.BorderColor = "red"; - _debugBoundsPath.FillColor = "transparent"; - _debugBoundsPath.BorderWidth = 3; + _debugBoundsPath.Style.BorderColor = "red"; + _debugBoundsPath.Style.FillColor = "transparent"; + _debugBoundsPath.Style.BorderWidth = 3; var moveCenterDebug = new PathCommands(PathCommandType.Move, ExtremePoints.Left, ExtremePoints.Top); _debugBoundsPath.Commands.Add(moveCenterDebug); @@ -315,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; } @@ -327,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) //{ diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 5b901a1b6..87130ef24 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -368,7 +368,7 @@ private void SetChartArea(SvgRenderOptions 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.Style.SetDrawingPropertiesFill(Theme, Chart.Fill, Chart.StyleManager.Style?.ChartArea.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, item.GetDefaultFillColor()); var borderstyle = Theme.FormatScheme.BorderStyle[0]; @@ -388,7 +388,7 @@ private void SetChartArea(SvgRenderOptions options) //var chartStyleId = Chart.StyleManager.Style.Id; - item.Rectangle.SetDrawingPropertiesBorder( + item.Rectangle.Style.SetDrawingPropertiesBorder( Theme, Chart.Border, reference?.Color, @@ -494,16 +494,16 @@ internal LineRenderItem GetSeriesIcon(ExcelChartStandardSerie s, int index, Boun 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); + item.Style.SetDrawingPropertiesFill(Theme, s.Fill, Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); + item.Style.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.X2 = x + (LineLength - (float)item.Style.BorderWidth); item.Y2 = y; - item.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; return item; } diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs index bab68463a..29c37ade5 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; } } @@ -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 843bdf518..e8a92ba37 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); } } } diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs index 819eff822..db0098e59 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/Textbox/DrawingTextRunItem.cs @@ -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(); } } diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index 747906542..bdbd83542 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -107,7 +107,7 @@ public ShapeRenderer(ExcelShape shape, FontRenderTarget target) : base(shape, ta InsetTextBox = new RectRenderItem(Bounds); InsetTextBox.Left = (float)shapeDef.TextBoxRect.LeftValue.PixelToPoint(); InsetTextBox.Top = (float)shapeDef.TextBoxRect.TopValue.PixelToPoint(); - InsetTextBox.FillOpacity = 0.3d; + 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); @@ -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; From 5be5163d0383a55cce98e5d0f22885b5dc61577e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 5 Oct 2026 13:30:53 +0200 Subject: [PATCH 20/44] Removed commented out code --- src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 61ea45725..5a2a62eb4 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -362,7 +362,6 @@ public virtual void AppendRenderItems(List renderItems) { } public class RenderItemStyle { 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; } From 3054572ffe650a0080c922ca09431921a72899e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Mon, 5 Oct 2026 13:50:43 +0200 Subject: [PATCH 21/44] Quick dlbl fix --- .../Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs | 1 + .../Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs | 4 ++-- src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index a0076b39a..5a811c519 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -223,6 +223,7 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List renderItems) var parentPointGroup = new GroupRenderItem(); parentPointGroup.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); - parentPointGroup.Name = "Dlbl_Parent_Point_Grp"; + parentPointGroup.Name = "Dlbl_DataPoint_Position"; - var titleItemOrigin = new TitleRenderItem("DataLabel originpoint"); + var titleItemOrigin = new TitleRenderItem("DataLabel originpoint/Data point position"); titleItemOrigin.Parent = parentPointGroup; //parentPointGroup.AddChildItem(titleItemOrigin); diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 87130ef24..36f2c322f 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -199,7 +199,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) { From c0c13e44a3be095b79ecb48eabc9534c464f24f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Mon, 5 Oct 2026 14:10:14 +0200 Subject: [PATCH 22/44] Set groups to localrotation --- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 69cb893ed..98b6f66ad 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -83,9 +83,9 @@ string GetTransformOrigin(GroupRenderItem item) string GetRotationStr(GroupRenderItem item) { - if (double.IsNaN(item.Rotation) == false && item.Rotation!=0) + 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) { From c255572a631a9a80ba9d939ddc905fa8b8a7ffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 6 Oct 2026 09:34:39 +0200 Subject: [PATCH 23/44] WIP:DataTables --- .../SvgStandAloneTests.cs | 9 +- .../Renderer/Chart/ChartDataTableRenderer.cs | 130 ++++++++++-------- .../Renderer/Chart/ChartLegendRenderer.cs | 1 - .../Renderer/Chart/LegendIconRenderer.cs | 100 +++++++------- 4 files changed, 130 insertions(+), 110 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs b/src/EPPlus.DrawingRenderer.Tests/DrawingShapeRenderer/SvgStandAloneTests.cs index cb61fb436..a63fa7fbf 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index dbc19518f..34cc39a1f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -19,6 +19,7 @@ Date Author Change using EPPlusImageRenderer.Svg; 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.Interfaces.Drawing.Text; @@ -27,11 +28,12 @@ Date Author Change 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, ILegendKeyContainer + internal class ChartDataTableRenderer : ChartDrawingObjectWithBackground, ILegendKeyContainer { ExcelChartDataTable _dataTable; float _marginItemsWidth; @@ -45,11 +47,16 @@ internal class ChartDataTableRenderer : ChartDrawingObject, ILegendKeyContainer public List SeriesIcon { get; }=new List(); public List> DataTableRenderItems { get; set; } = new List>(); + + internal override Color? DefaultFillColor => GetDefaultFillColor(); + + internal override Color? DefaultBorderColor => GetDefaultBorderColor(); + double _dataTableWidth, _columnsWidth; internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) { _dataTable = svgChart.Chart.PlotArea.DataTable; - Rectangle = new RectRenderItem(svgChart.Plotarea.Rectangle.Bounds); + Rectangle = new RectRenderItem(svgChart.Plotarea.Rectangle); MeasurementFont mf; if(_dataTable.HasFont) @@ -64,31 +71,29 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) _marginItemsWidth = mf.Size / 2; _maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); _maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); - Rectangle.Bounds.Width = _maxWidth; //Set to max. Adjust later to actual width - Rectangle.Bounds.Height = _maxHeight; //Set to max. Adjust later to actual height - var items = new List>(); - var headers = new List(); + 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); - var tm = svgChart.TextMeasurer; 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 DrawingTextBox(svgChart.Chart, Rectangle.Bounds, MaxWidth, MaxHeight); - tb.AddText(v); + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(v); headers.Add(tb); - var size = tm.MeasureText(v, mf); - if (entryWidth(); foreach (var v in formattedValues) { - var tb = new DrawingTextBody(RenderContext, ChartRenderer.Chart, Rectangle.Bounds, true); + var tb = new DrawingTextBody(RenderContext, ChartRenderer.Chart, Rectangle, true); if (paragraph == null) { tb.AddParagraph(v.ToString()); @@ -165,14 +170,14 @@ private void SetColumnWidth(double entryWidth) { if (lc.MarkerIcon != null) { - lc.MarkerIcon.Bounds.Height += height; + lc.MarkerIcon.Height += height; if (lc.MarkerBackground != null) { - lc.MarkerBackground.Bounds.Height += height; + lc.MarkerBackground.Height += height; } } - lc.SeriesIcon.Bounds.Top += height; - lc.Textbox.Bounds.Top += height; + lc.SeriesIcon.Top += height; + lc.Textbox.Top += height; } else { @@ -189,8 +194,8 @@ private void SetColumnWidth(double entryWidth) var x = lcw; foreach (var cell in row) { - cell.Bounds.Left += x; - cell.Bounds.Top += y; + cell.Left += x; + cell.Top += y; x += _columnsWidth; } y += GetLegendColHeight(r++); @@ -204,7 +209,7 @@ private double GetLegendColWidth() var w = SeriesIcon.Max(x=>x.Textbox.Width); if(_dataTable.ShowKeys) { - return w + SeriesIcon.Max(x=>x.SeriesIcon?.Bounds.Width??0) + MarginIconText; + return w + SeriesIcon.Max(x=>x.SeriesIcon?.Width??0) + MarginIconText; } return w; } @@ -213,8 +218,8 @@ private double GetLegendColHeight(int row) var h = SeriesIcon[row].Textbox.Height; if (_dataTable.ShowKeys) { - var sih = SeriesIcon[row].SeriesIcon.Bounds.Height; - var mih = SeriesIcon[row].MarkerIcon?.Bounds.Height ?? 0D; + var sih = SeriesIcon[row].SeriesIcon.Height; + var mih = SeriesIcon[row].MarkerIcon?.Height ?? 0D; return Math.Max(mih, Math.Max(h, sih)); } return h; @@ -270,9 +275,9 @@ private RenderItem GetIcon(ChartRenderer svgChart, ExcelChart ct, ExcelChartStan } } - private List AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, ExcelChartSerie serie) + private List AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, ExcelChartSerie serie) { - var ret = new List(); + var ret = new List(); if (string.IsNullOrEmpty(serie.Series)) { var a = new ExcelAddressBase(serie.Series); @@ -282,8 +287,8 @@ private List AddSerieValues(ChartRenderer svgChart, double maxWi var range = ws.Cells[a.Address]; foreach (var cell in range) { - var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); - tb.AddText(cell.Text); + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(cell.Text); ret.Add(tb); } } @@ -292,8 +297,8 @@ private List AddSerieValues(ChartRenderer svgChart, double maxWi { foreach (var se in serie.StringLiteralsY) { - var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); - tb.AddText(se); + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(se); ret.Add(tb); } } @@ -301,8 +306,8 @@ private List AddSerieValues(ChartRenderer svgChart, double maxWi { foreach (var nl in serie.NumberLiteralsY) { - var tb = new DrawingTextBox(svgChart.Chart, Rectangle.Bounds, maxWidth, maxHeight); - tb.AddText(nl.ToString()); + var tb = new DrawingTextBody(RenderContext, svgChart.Chart, Rectangle, true); + tb.AddParagraph(nl.ToString()); ret.Add(tb); } } @@ -310,35 +315,46 @@ private List AddSerieValues(ChartRenderer svgChart, double maxWi return ret; } + internal override Color? GetDefaultFillColor() { - //Render series header column. - foreach(var li in SeriesIcon) - { - if (_dataTable.ShowKeys) - { - renderItems.Add(li.SeriesIcon); - if (li.MarkerBackground != null) renderItems.Add(li.MarkerBackground); - if (li.MarkerIcon != null) renderItems.Add(li.MarkerIcon); - } - renderItems.Add(li.Textbox); - } + return GetDefaultFillColorForElement(ChartElement.DataTable, (int)Chart.Style); + } - foreach(var row in DataTableRenderItems) - { - foreach (var cell in row) - { - renderItems.Add(cell); - } - } - } internal override Color? GetDefaultBorderColor() { - throw new NotImplementedException(); + return GetDefaultBorderColorForElement(ChartElement.DataTable, (int)Chart.Style); } - internal override Color? GetDefaultFillColor() - { - throw new NotImplementedException(); - } + + //{ + // //Render series header column. + // foreach(var li in SeriesIcon) + // { + // if (_dataTable.ShowKeys) + // { + // renderItems.Add(li.SeriesIcon); + // if (li.MarkerBackground != null) renderItems.Add(li.MarkerBackground); + // if (li.MarkerIcon != null) renderItems.Add(li.MarkerIcon); + // } + // renderItems.Add(li.Textbox); + // } + + // foreach(var row in DataTableRenderItems) + // { + // foreach (var cell in row) + // { + // renderItems.Add(cell); + // } + // } + //} + //internal override Color? GetDefaultBorderColor() + //{ + // throw new NotImplementedException(); + //} + + //internal override Color? GetDefaultFillColor() + //{ + // throw new NotImplementedException(); + //} } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index 015fe22cc..fb7bbc638 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -565,7 +565,6 @@ private void SetTrendlineLegend(ExcelChart ct, int serieIndex, int entryIndex, D sls.Textbox.ImportParagraph(entry.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); } } - var item = new RectRenderItem(Rectangle); public override void AppendRenderItems(List renderItems) { var groupItem = new GroupRenderItem(ChartRenderer.Bounds); diff --git a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs index 630c7f511..f273a64af 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs @@ -27,7 +27,7 @@ internal static class LegendIconRenderer 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 : ChartDrawingObject, ILegendKeyContainer + 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]; @@ -60,10 +60,10 @@ internal static void SetBarLegendSingle(ChartRenderer chart, T parent, double var tbTop = si.Top - (entryHeight - si.Height) / 2; double tbWidth; - tbWidth = parent.Rectangle.Bounds.Width - tbLeft; + tbWidth = parent.Rectangle.Width - tbLeft; var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + 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(); @@ -99,7 +99,7 @@ internal static void SetBarLegendSingle(ChartRenderer chart, T parent, double } } - 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 : ChartDrawingObject, ILegendKeyContainer + 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]; @@ -110,10 +110,10 @@ internal static void SetBarLegend(ChartRenderer chart, T parent, ExcelChart c var tbTop = si.Top - (entryHeight - si.Height) / 2; double tbWidth; - tbWidth = parent.Rectangle.Bounds.Width - tbLeft; + tbWidth = parent.Rectangle.Width - tbLeft; var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + 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); @@ -130,7 +130,7 @@ internal static void SetBarLegend(ChartRenderer chart, T parent, ExcelChart c } } - 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 : ChartDrawingObject, ILegendKeyContainer + 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; @@ -139,10 +139,10 @@ internal static void SetLineLegend(ChartRenderer chart, T parent, ExcelChart var tbLeft = si.X1 + maxIconLength + MarginIconText; var tbTop = si.Y2 - entryHeight * 0.5; - var tbWidth = parent.Rectangle.Bounds.Width - tbLeft; + var tbWidth = parent.Rectangle.Width - tbLeft; var tbHeight = entryHeight; - sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); var headerText = s.GetHeaderText(index); var entry = chart.Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); @@ -173,7 +173,7 @@ internal static void SetLineLegend(ChartRenderer chart, T parent, ExcelChart } } } - 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 : ChartDrawingObject, ILegendKeyContainer + 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; @@ -220,22 +220,22 @@ internal static void SetPieLegend(ChartRenderer chart, T parent, ExcelChart c firstIconWidth = si.Width; } sls = new DrawingLegendSerie(); - var tbLeft = si.Left + si.Width + MarginIconText + (si.BorderWidth ?? 0d); + 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.Bounds.Width - tbLeft; + tbWidth = parent.Rectangle.Width - tbLeft; } else { - tbWidth = parent.Rectangle.Bounds.Width; + tbWidth = parent.Rectangle.Width; } var tbHeight = tm.Height; - sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle.Bounds, tbLeft, tbTop, tbWidth, tbHeight, false, true); + 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; @@ -245,18 +245,18 @@ internal static void SetPieLegend(ChartRenderer chart, T parent, ExcelChart c tbWidth = sls.Textbox.Width + rDefMargin; /*+ lDefMargin + rDefMargin;*/ - lastWidth = tbWidth + si.Width - (si.BorderWidth ?? 0d); + lastWidth = tbWidth + si.Width - (si.Style.BorderWidth ?? 0d); - totalWidth += tbWidth + si.Width + (si.BorderWidth ?? 0d) + MarginIconText; + 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, dp, chart.Chart.StyleManager.Style?.SeriesLine); + 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); + ChartTypeDrawer.SetFillSerie(chart.Chart, ct, ps, 0, i, sls.SeriesIcon.Style); } parent.SeriesIcon.Add(sls); @@ -265,30 +265,30 @@ internal static void SetPieLegend(ChartRenderer chart, T parent, ExcelChart c foreach (var icon in parent.SeriesIcon) { - icon.SeriesIcon.Bounds.Top = icon.SeriesIcon.Bounds.Top - ((entryHeight) / 4); + 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.Bounds.Width = parent.SeriesIcon.Last().Textbox.Bounds.GetGlobalBoundingbox().Right - parent.SeriesIcon[0].SeriesIcon.Bounds.GlobalLeft + 4d + firstIconWidth * 2; - parent.Rectangle.Bounds.Left = ((chart.Bounds.Width) / 2d) - (totalWidth / 2d) + 1.5d; + 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.Bounds.Width < parent.MaxWidth) + if (parent.Rectangle.Width < parent.MaxWidth) { - parent.Rectangle.Bounds.Height = entryHeight + parent.TopMargin + parent.BottomMargin; - parent.Rectangle.Bounds.Top = chart.ChartArea.Rectangle.Height - parent.Rectangle.Height - parent.BottomMargin - parent.TopMargin; + 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 : ChartDrawingObject, ILegendKeyContainer + 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.Bounds); + var line = new LineRenderItem(parent.Rectangle); //Default style is NoLine NoFill - line.SetDrawingPropertiesBorder(chart.Theme, cStandardSerie.Border, chart.Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, cStandardSerie.Border.IsEmpty || cStandardSerie.Border.Fill.Style != eFillStyle.NoFill, () => Color.Empty, 3); + 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); @@ -298,13 +298,13 @@ internal static LineRenderItem GetLineSeriesIcon(ChartRenderer chart, T paren line.X2 = x + LineLength; line.Y1 = y; line.Y2 = y; - line.LineCap = LineCap.Round; + 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 : ChartDrawingObject, ILegendKeyContainer + 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.Bounds); + var item = new RectRenderItem(parent.Rectangle); var iconHeight = GetIconLength(ct, entryHeight); //var icon = pSls?.SeriesIcon as RectRenderItem; double iconTop = 0, iconLeft = 0; @@ -312,7 +312,7 @@ internal static RectRenderItem GetBarSeriesIcon(ChartRenderer chart, ExcelCha GetItemPosition(chart, parent, pSls, entryWidth, entryHeight, iconLeft, iconTop + (iconHeight / 2), out double x, out double y); - item.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; item.Left = x; if (pSls != null && (chart.Chart.Legend.Position == eLegendPosition.Left || chart.Chart.Legend.Position == eLegendPosition.Right)) { @@ -329,21 +329,21 @@ internal static RectRenderItem GetBarSeriesIcon(ChartRenderer chart, ExcelCha if (index >= 0 && chartSerie.DataPoints.ContainsKey(index)) { var dp = chartSerie.DataPoints[index]; - ChartTypeDrawer.SetFillDataPoint(chart.Chart, chartSerie, index, item, dp, chart.Chart.StyleManager.Style?.SeriesLine); + ChartTypeDrawer.SetFillDataPoint(chart.Chart, chartSerie, index, item.Style, dp, chart.Chart.StyleManager.Style?.SeriesLine); } else { - ChartTypeDrawer.SetFillSerie(chart.Chart, ct, chartSerie, serieIndex, index, item); + 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 : ChartDrawingObject, ILegendKeyContainer + 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.Bounds); - line.SetDrawingPropertiesFill(chart.Theme, tl.Fill, chart.Chart.StyleManager.Style?.Trendline.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, parent.DefaultFillColor); + 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.SetDrawingPropertiesBorder(chart.Theme, tl.Border, chart.Chart.StyleManager.Style?.Trendline.BorderReference.Color, tl.Border.Fill.Style != eFillStyle.NoFill, () => parent.DefaultBorderColor, 0.75); + 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); @@ -353,21 +353,21 @@ internal static LineRenderItem GetTrendLineSeriesIcon(ChartRenderer chart, Ex line.Y1 = y; line.X2 = x + LineLength; line.Y2 = y; - line.LineCap = LineCap.Round; + 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 : ChartDrawingObject, ILegendKeyContainer + 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.Bounds); + 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.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; item.Left = x; if (pSls != null && (chart.Chart.Legend.Position == eLegendPosition.Left || chart.Chart.Legend.Position == eLegendPosition.Right)) { @@ -392,8 +392,8 @@ internal static RectRenderItem GetPieSeriesIcon(ChartRenderer chart, ExcelCha item.Width = iconHeight; item.Height = iconHeight; - item.SetDrawingPropertiesFill(chart.Theme, pcS.Fill, chart.Chart.StyleManager.Style?.SeriesLine.FillReference.Color); - item.SetDrawingPropertiesBorder(chart.Theme, pcS.Border, chart.Chart.StyleManager.Style?.SeriesLine.BorderReference.Color, pcS.Border.Fill.Style != eFillStyle.NoFill, () => parent.DefaultBorderColor, 1.5d); + 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; } @@ -403,7 +403,7 @@ 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 : ChartDrawingObject, ILegendKeyContainer + 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 (chart.Chart.Legend.Position == eLegendPosition.Top || @@ -464,16 +464,16 @@ internal static RenderItem GetSeriesIcon(ChartRenderer chartRenderer, BoundingBo if(s._chart.IsTypeLine()) { var item = new LineRenderItem(parentItem); - item.SetDrawingPropertiesFill(theme, s.Fill, chartRenderer.Chart.StyleManager.Style.SeriesLine.FillReference.Color, UserSpaceSettings.ObjectBoundingBox); - item.SetDrawingPropertiesBorder(theme, s.Border, chartRenderer.Chart.StyleManager.Style.SeriesLine.BorderReference.Color, s.Border.Fill.Style != eFillStyle.NoFill, null, DefaultStrokeWidth, UserSpaceSettings.ObjectBoundingBox); + 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.BorderWidth); + item.X2 = x + (LineLength - (float)item.Style.BorderWidth); item.Y2 = y; - item.LineCap = LineCap.Round; + item.Style.LineCap = LineCap.Round; return item; } else @@ -487,11 +487,11 @@ internal static RenderItem GetSeriesIcon(ChartRenderer chartRenderer, BoundingBo if (index >= 0 && bs.DataPoints.ContainsKey(index)) { var dp = bs.DataPoints[index]; - ChartTypeDrawer.SetFillDataPoint(chart, s, index, item, dp, chart.StyleManager.Style?.SeriesLine); + ChartTypeDrawer.SetFillDataPoint(chart, s, index, item.Style, dp, chart.StyleManager.Style?.SeriesLine); } else { - ChartTypeDrawer.SetFillSerie(chart, s._chart, s, index, index, item); + ChartTypeDrawer.SetFillSerie(chart, s._chart, s, index, index, item.Style); } return item; From b746b06d7047da724523687d2f9197d565de7269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 6 Oct 2026 10:10:28 +0200 Subject: [PATCH 24/44] Fixed "most" dlbl issues. DefaultFont remains --- .../SvgItem/SvgTextRunRenderItem.cs | 5 -- .../RenderItems/Textbox/RenderTextbox.cs | 1 + .../Renderer/Chart/ChartDrawingObject.cs | 12 ++-- .../DataLabels/ChartSerieDataLabelRenderer.cs | 22 +++++-- .../Chart/DataLabels/SvgDataLabelPoint.cs | 60 +++++++++++-------- 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/SvgItem/SvgTextRunRenderItem.cs index af92527cf..d40cf02d9 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/RenderTextbox.cs b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs index 82216b214..baf7755d0 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/Textbox/RenderTextbox.cs @@ -18,6 +18,7 @@ public void Init(BoundingBox parent, double maxWidth, double maxHeight) _group = new GroupRenderItem(Parent); _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; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs index d7d9848f1..b501d8899 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs @@ -114,28 +114,28 @@ protected static RectRenderItem GetRectFromManualLayout(ChartRenderer sc, ExcelL 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; } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index 3cdd18bb9..f1936c29e 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -77,7 +77,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++; } @@ -152,17 +159,23 @@ private RenderItem GetSeriesIcon(ExcelChartStandardSerie serie, BoundingBox maxB if (seriesIcon == null || _origIndex != _serieIndex) { CreateSeriesIcon(serie, maxBounds); + 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)); } @@ -183,7 +196,6 @@ internal void SetParentPoint(BoundingBox parent, int index) if (dataLabels.Count > index) { dataLabels[index].SetParentPoint(parent); - dataLabels[index].Rectangle.Name = $"DatalabelPoint_Rect_{index}"; } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 1451fc603..cddb5606d 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; @@ -39,6 +40,8 @@ internal class SvgDataLabelPoint : ChartDrawingObjectWithBackground 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); @@ -55,8 +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) @@ -107,20 +112,24 @@ internal void AddSeriesIcon(RenderItem seriesIcon) var iconHeight = seriesIcon.Height; _seriesIcon = seriesIcon; - _seriesIcon.Parent = Rectangle; + _seriesIcon.Name = "seriesIcon_" + DataLabelPointContainer.Name; + _seriesIcon.Parent = DataLabelPointContainer; if (_haveAdjustedForIcon == false) { _txtBox.Left += iconWidth; _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.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; @@ -334,11 +343,11 @@ private void SetPositionBasic(BoundingBox point, eLabelPosition basicPosition) // Bounds.Top = dataLabelCenter.Y; // break; case eLabelPosition.Left: - Rectangle.Left -= (_txtBox.Width/2) + (point.Width / 2d); + Rectangle.Left -= (_txtBox.Width/2) + (point.Width / 2d) - LeftMargin; break; case eLabelPosition.Right: case eLabelPosition.BestFit: - Rectangle.Left += (_txtBox.Width / 2d) + point.Width; + Rectangle.Left += (_txtBox.Width / 2d) + point.Width + LeftMargin; break; case eLabelPosition.Top: Rectangle.Top -= (point.Height + _txtBox.Height) / 2d; @@ -526,6 +535,9 @@ private void AdjustLeaderLinesForManualLayout() 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; @@ -557,7 +569,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.Parent = _parentPoint; + DataLabelPointContainer.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); //--- //--- Calculate vectors and center point --- @@ -659,10 +671,10 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi internal void SetParentPoint(BoundingBox parentPoint) { - Rectangle.Parent = parentPoint; _parentPoint = parentPoint; + DataLabelPointContainer.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); - var dataLabelCenter = new Vector2(Rectangle.Left, Rectangle.Top); + var dataLabelCenter = new Vector2(DataLabelPointContainer.Left, DataLabelPointContainer.Top); switch (_labelPosition) { @@ -706,47 +718,42 @@ private void AppendDebugBounds(List renderItems) public override void AppendRenderItems(List renderItems) { - var parentPointGroup = new GroupRenderItem(); - parentPointGroup.Position = new Vector2(_parentPoint.GlobalLeft, _parentPoint.GlobalTop); - - parentPointGroup.Name = "Dlbl_DataPoint_Position"; - var titleItemOrigin = new TitleRenderItem("DataLabel originpoint/Data point position"); - titleItemOrigin.Parent = parentPointGroup; + titleItemOrigin.Parent = DataLabelPointContainer; //parentPointGroup.AddChildItem(titleItemOrigin); if (originPointRect != null) { - parentPointGroup.AddChildItem(originPointRect); + DataLabelPointContainer.AddChildItem(originPointRect); } if (basePositionRect != null) { - parentPointGroup.AddChildItem(basePositionRect); + DataLabelPointContainer.AddChildItem(basePositionRect); } if (endPositionRect != null) { - parentPointGroup.AddChildItem(endPositionRect); + DataLabelPointContainer.AddChildItem(endPositionRect); } if (centerPositionRect != null) { - parentPointGroup.AddChildItem(centerPositionRect); + DataLabelPointContainer.AddChildItem(centerPositionRect); } if (maxBoundsCircle != null) { - parentPointGroup.AddChildItem(maxBoundsCircle); + DataLabelPointContainer.AddChildItem(maxBoundsCircle); } if (endPointCircle != null) { - parentPointGroup.AddChildItem(endPointCircle); + DataLabelPointContainer.AddChildItem(endPointCircle); } //renderItems.Add(parentPointGroup); - var group = new GroupRenderItem(parentPointGroup); + 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.ChildObjects.Add(group); @@ -777,10 +784,11 @@ public override void AppendRenderItems(List renderItems) height = _txtBox.Height; } //Currently series icon always has a y1 y2 of 2 - var iconGrp = new GroupRenderItem(new BoundingBox(_seriesIcon.Left, height / 2)); + var iconGrp = new GroupRenderItem(group); + iconGrp.Name = "iconGrp_" + DataLabelPointContainer.Name; iconGrp.Left = _seriesIcon.Left; iconGrp.Top = (height / 2) - 2; - iconGrp.Parent = group; + _seriesIcon.Parent = iconGrp; } @@ -793,7 +801,7 @@ public override void AppendRenderItems(List renderItems) } } - renderItems.Add(parentPointGroup); + renderItems.Add(DataLabelPointContainer); } internal override Color? GetDefaultFillColor() From 27435508bfe0088454fb5fb749aa8de6364582d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 6 Oct 2026 10:31:43 +0200 Subject: [PATCH 25/44] Fix datalabel failing test nullcheck --- .../Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index cddb5606d..682dba3dc 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -536,7 +536,8 @@ private void AdjustLeaderLinesForManualLayout() _leaderLines.Add(extraLine); - _seriesIcon.Left += -xOffset + LeftMargin; + + _seriesIcon?.Left += -xOffset + LeftMargin; _txtBox.Left += -xOffset + LeftMargin; } var mainLine = new LineRenderItem(ChartRenderer.Bounds); From ad693320cdeca2c54a964998bf7eff59cca5edf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 6 Oct 2026 15:43:31 +0200 Subject: [PATCH 26/44] Added start of container item that measures all child content --- .../Shared/MultiContainerTest.cs | 138 ++++++++++++++++++ .../RenderItems/MultiContainerItem.cs | 116 +++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs create mode 100644 src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs diff --git a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs new file mode 100644 index 000000000..33c1342ae --- /dev/null +++ b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs @@ -0,0 +1,138 @@ +using EPPlus.Graphics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using EPPlus.DrawingRenderer.RenderItems; + +namespace EPPlus.DrawingRenderer.Tests.Shared +{ + [TestClass] + public class MultiContainerTest + { + [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); + } + + [TestMethod] + public void VerifyGrandChildContentTopLeftNegative() + { + 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.Left = -5; + grandChild.Top = -9; + + grandChild.Width = 15; + grandChild.Height = 2; + + Assert.AreEqual(-2, container.ContentLeft); + Assert.AreEqual(-6, container.ContentTop); + Assert.AreEqual(30, container.ContentWidth); + Assert.AreEqual(19 + 6, container.ContentHeight); + Assert.AreEqual(28, container.ContentRight); + Assert.AreEqual(19, container.ContentBottom); + } + + [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); + } + } +} diff --git a/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs new file mode 100644 index 000000000..7b67675f9 --- /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 : RenderItem + { + 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(); + } + } +} From be337ae7c0f52a07dae9a86c7bb28030909a0ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 6 Oct 2026 16:54:42 +0200 Subject: [PATCH 27/44] multicontainer test update --- .../Shared/MultiContainerTest.cs | 75 +++++++++++++++++-- .../RenderItems/MultiContainerItem.cs | 2 +- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 5 ++ 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs index 33c1342ae..0f7052f81 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs @@ -1,16 +1,15 @@ -using EPPlus.Graphics; +using EPPlus.DrawingRenderer.RenderItems; +using EPPlus.DrawingRenderer.Svg; +using EPPlus.Graphics; using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; - -using EPPlus.DrawingRenderer.RenderItems; namespace EPPlus.DrawingRenderer.Tests.Shared { [TestClass] - public class MultiContainerTest + public class MultiContainerTest : TestBase { [TestMethod] public void VerifySimpleChildContent() @@ -72,17 +71,33 @@ public void VerifyGrandChildContentWidthHeight() 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 VerifyGrandChildContentTopLeftNegative() { - var world = new BoundingBox(0, 0, 500, 500); + var world = new GroupRenderItem(); + world.Width = 500; + world.Height = 500; var container = new MultiContainerItem(world); container.Left = 5; container.Top = 5; - var child1 = new BoundingBox(); + var child1 = new GroupRenderItem(); child1.Left = 3; child1.Top = 3; @@ -91,10 +106,12 @@ public void VerifyGrandChildContentTopLeftNegative() child1.Parent = container; - var grandChild = new BoundingBox(); + 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; @@ -103,9 +120,51 @@ public void VerifyGrandChildContentTopLeftNegative() 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()); } [TestMethod] diff --git a/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs index 7b67675f9..d95c5f5ba 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/MultiContainerItem.cs @@ -6,7 +6,7 @@ namespace EPPlus.DrawingRenderer.RenderItems { - public class MultiContainerItem : RenderItem + public class MultiContainerItem : GroupRenderItem { public override RenderItemType Type => RenderItemType.Group; diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 98b6f66ad..f26dc68a7 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -37,6 +37,11 @@ public override void Render(GroupRenderItem item) { _shapeRenderer.Render(renderItem); } + else + { + //Is not render item but its children could contain renderItems + //Handle it as if it was a group? + } } OutputStream.Append(""); From 7b0bd04a58ab8ffdbbfd9b6c5b01b861b84da8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 7 Oct 2026 09:49:58 +0200 Subject: [PATCH 28/44] Stopped pie chart duplication. Simplified adding pie chart datalabels --- .../ChartTypeDrawers/PieChartTypeDrawer.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index d3304a648..7b9a5fc32 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -302,20 +302,30 @@ 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) { //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); } } From dd4e723b6e7b341e1034473745ae139e6fd05a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 7 Oct 2026 10:34:59 +0200 Subject: [PATCH 29/44] Fixed pieChart inner positioning. Dlbl remains --- src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs | 2 +- src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 5a2a62eb4..97aacd87b 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -145,7 +145,7 @@ public GroupRenderItem(BoundingBox parent) : base(parent) } public GroupRenderItem(BoundingBox parent, double rotation) : base(parent) { - Rotation = rotation; + LocalRotation = rotation; } public GroupRenderItem() : base() diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index 333ccbcdf..ac5a242a2 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -217,8 +217,8 @@ public PieSliceRenderItem(ChartRenderer renderer, BoundingBox parent, TranformPo CalculateExplosionDir(); CalculateWidthHeight(prevSliceDegrees); - _innerItems = new GroupRenderItem(_innerGroup, 0); + _innerItems.Name = "Inner_Items"; } internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaBounds, double sliceScaleFactor, double explosionOfPoint, double pieExplosion, int position) @@ -356,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) { From 31c8f0ee081c4af5c7d35a600e85f766c0782b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 7 Oct 2026 15:21:23 +0200 Subject: [PATCH 30/44] WIP:Fixed Data table rendering with borders --- .../{PathCommands.cs => PathCommand.cs} | 8 +- .../RenderItems/RenderItem.cs | 2 +- .../Svg/ItemRenderers/SvgPathRenderer.cs | 2 +- src/EPPlus.Graphics/BoundingBox.cs | 28 +- .../Drawing/ImageHandling/ImageReader.cs | 6 +- .../Chart/Axis/DateAxisScaleCalculator.cs | 2 +- .../Renderer/Chart/ChartAxisRenderer.cs | 43 +-- .../Renderer/Chart/ChartDataTableRenderer.cs | 251 +++++++++++++----- .../Renderer/Chart/ChartDrawingObject.cs | 10 +- .../Renderer/Chart/ChartLegendRenderer.cs | 9 + .../Renderer/Chart/ChartPlotareaRenderer.cs | 6 +- .../ChartTypeDrawers/LineChartTypeDrawer.cs | 2 +- .../Trendlines/ChartTrendlineRenderer.cs | 2 +- .../Renderer/Chart/LegendIconRenderer.cs | 8 +- .../Renderer/Chart/LineMarkerHelper.cs | 32 +-- .../Renderer/Chart/PieSliceRenderItem.cs | 20 +- .../DrawingRenderItemExtentions.cs | 2 +- src/EPPlus/Drawing/Renderer/ShapeRenderer.cs | 16 +- 18 files changed, 287 insertions(+), 162 deletions(-) rename src/EPPlus.DrawingRenderer/RenderItems/{PathCommands.cs => PathCommand.cs} (88%) 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 224d0de5a..c83db27b0 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 5a2a62eb4..c8a55c41a 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -241,7 +241,7 @@ 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(Parent); diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs index eac712d05..7b50155b0 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgPathRenderer.cs @@ -62,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++) diff --git a/src/EPPlus.Graphics/BoundingBox.cs b/src/EPPlus.Graphics/BoundingBox.cs index 1357e849a..b3b7c3700 100644 --- a/src/EPPlus.Graphics/BoundingBox.cs +++ b/src/EPPlus.Graphics/BoundingBox.cs @@ -23,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); } } diff --git a/src/EPPlus/Drawing/ImageHandling/ImageReader.cs b/src/EPPlus/Drawing/ImageHandling/ImageReader.cs index a764e5535..2524acac5 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/DateAxisScaleCalculator.cs b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs index 8cf4712a9..b5e052bb5 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/Axis/DateAxisScaleCalculator.cs @@ -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; diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 71780d235..75d196112 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -262,7 +262,7 @@ public bool IsDateScale get; private set; } = false; - + public bool IsCount { get; set; } /// /// Create a subGroup beneath the ParentGroup @@ -503,6 +503,10 @@ private List GetAxisValueTextBoxes() } tb.ImportParagraph(p, 0, t); } + else + { + tb.AddText(t); + } tb.Rectangle.Style.SetDrawingPropertiesFill(ChartRenderer.Theme, Axis.Fill, axisStyle?.FillReference.Color, UserSpaceSettings.UserSpaceOnUse_Global, DefaultFillColor); @@ -767,10 +771,10 @@ 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(double.IsNaN(parentUnit)==false && parentUnit==units) + //{ + // addMinor = parentUnit / 2; + //} if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis==false) { @@ -807,17 +811,18 @@ 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) { @@ -863,7 +868,7 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou tm.Style.BorderWidth = 0.75; } tms.Add(tm); - } + //} if (units == 0) break; switch (dateUnit) { @@ -887,7 +892,7 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou break; } } - return tms; + return tms; } private bool IsMinorDateUnit(eTimeUnit dateUnit, double parentUnit, double d) @@ -983,10 +988,10 @@ private List AddGridlines(double units, double parentUnit, ExcelDraw tm.X2 = x2; tm.Y2 = y2; - if(id == "xGridLine") - { - tm.Width = pa.Rectangle.Width; - } + //if(id == "xGridLine") + //{ + // tm.Width = pa.Rectangle.Width; + //} //var lineWidth = lineItem.Width <= 0 ? 0.75 : lineItem.Width; tm.Style.SetDrawingPropertiesBorder(ChartRenderer.Theme, lineItem, styleEntry?.BorderReference.Color, true, GetDefaultBorderColor, 0.75); @@ -1026,6 +1031,7 @@ private ExcelChartStyleEntry GetAxisStyleEntry() switch (Axis.AxisType) { case eAxisType.Cat: + case eAxisType.Date: axisStyle = Chart.StyleManager.Style?.CategoryAxis; break; case eAxisType.Serie: @@ -1107,6 +1113,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, diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index 34cc39a1f..a0bc88682 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -15,13 +15,17 @@ Date Author Change 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; @@ -52,12 +56,19 @@ internal class ChartDataTableRenderer : ChartDrawingObjectWithBackground, ILegen 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) { _dataTable = svgChart.Chart.PlotArea.DataTable; Rectangle = new RectRenderItem(svgChart.Plotarea.Rectangle); - + LeftMargin = RightMargin = 3; + TopMargin = BottomMargin = 3; MeasurementFont mf; if(_dataTable.HasFont) { @@ -69,21 +80,38 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) } _marginItemsWidth = mf.Size / 2; - _maxWidth = svgChart.Plotarea.GetPlotAreaWidth(Rectangle); - _maxHeight = svgChart.Plotarea.GetPlotAreaHeight(Rectangle); + _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); - tb.AddParagraph(v); + if (paragraph == null) + { + tb.AddParagraph(v); + } + else + { + tb.ImportParagraph(paragraph, 0, v); + } headers.Add(tb); if (entryWidth < tb.Width) { @@ -96,18 +124,9 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) _seriesHeadersMeasure.Add(new TextMeasurement((float)tb.Width, (float)tb.Height)); } - ExcelDrawingParagraph paragraph; - if (_dataTable.HasFont) - { - paragraph = _dataTable.TextBody.Paragraphs.FirstOrDefault(); - } - else - { - paragraph = null; - } + DataTableRenderItems.Add(headers); _dataTableWidth = GetDataTableWidth(svgChart, _dataTable); - _columnsWidth = (_dataTableWidth - entryWidth) / headers.Count; //Create the source data for the data table from the series. DrawingLegendSerie pSls = null; @@ -155,29 +174,53 @@ internal ChartDataTableRenderer(ChartRenderer svgChart) : base(svgChart) } seriesIndex++; } - + _columnsWidth = (_dataTableWidth - (GetLegendColWidth()+LeftMargin+RightMargin)) / headers.Count; SetColumnWidth(entryHeight); } private void SetColumnWidth(double entryWidth) { - var height = 0D; - var r = 0; - foreach(var lc in SeriesIcon) + var height = GetLegendColHeight(1); + var r = 1; + foreach (var lc in SeriesIcon) { - if (_dataTable.ShowKeys) + if (_dataTable.ShowKeys) { - if (lc.MarkerIcon != null) + //if (lc.MarkerIcon != null) + //{ + // lc.MarkerIcon.Height += height; + // if (lc.MarkerBackground != null) + // { + // lc.MarkerBackground.Height += height; + // } + //} + if(lc.SeriesIcon.Top > lc.Textbox.Top) { - lc.MarkerIcon.Height += height; - if (lc.MarkerBackground != null) + var diff = lc.SeriesIcon.Top - lc.Textbox.Top; + if(lc.SeriesIcon is LineRenderItem line) { - lc.MarkerBackground.Height += height; + 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; } - lc.SeriesIcon.Top += height; - lc.Textbox.Top += height; } else { @@ -191,15 +234,17 @@ private void SetColumnWidth(double entryWidth) var lcw = GetLegendColWidth(); foreach (var row in DataTableRenderItems) { - var x = lcw; + var x = lcw + LeftMargin + RightMargin; foreach (var cell in row) { - cell.Left += x; + 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; @@ -215,12 +260,20 @@ private double GetLegendColWidth() } private double GetLegendColHeight(int row) { - var h = SeriesIcon[row].Textbox.Height; - if (_dataTable.ShowKeys) + var h = 0D; + if (row > 0) { - var sih = SeriesIcon[row].SeriesIcon.Height; - var mih = SeriesIcon[row].MarkerIcon?.Height ?? 0D; - return Math.Max(mih, Math.Max(h, sih)); + 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; } @@ -325,36 +378,108 @@ private List AddSerieValues(ChartRenderer svgChart, double maxW 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); + } + + 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; + } - //{ - // //Render series header column. - // foreach(var li in SeriesIcon) - // { - // if (_dataTable.ShowKeys) - // { - // renderItems.Add(li.SeriesIcon); - // if (li.MarkerBackground != null) renderItems.Add(li.MarkerBackground); - // if (li.MarkerIcon != null) renderItems.Add(li.MarkerIcon); - // } - // renderItems.Add(li.Textbox); - // } - - // foreach(var row in DataTableRenderItems) - // { - // foreach (var cell in row) - // { - // renderItems.Add(cell); - // } - // } - //} - //internal override Color? GetDefaultBorderColor() - //{ - // throw new NotImplementedException(); - //} - - //internal override Color? GetDefaultFillColor() - //{ - // throw new NotImplementedException(); - //} + 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() + { + 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 b501d8899..56f7827ae 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDrawingObject.cs @@ -335,21 +335,21 @@ protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, } else { - return GetSchemeColorTint(eSchemeColor.Background1, 0.75d); + return GetSchemeColorTint(eSchemeColor.Dark1, 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); + 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.Background1, 0.75d); + var retCol2and3 = GetSchemeColorTint(eSchemeColor.Dark1, 0.75d); var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); @@ -358,7 +358,7 @@ protected ExcelDrawingFill GetThemedFill(ChartElement element, int ChartStyleId, { //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 retCol2and3 = GetSchemeColorTint(eSchemeColor.Dark1, 1d); var retCol4 = GetSchemeColorTint(eSchemeColor.Text1, 1d); return GetStyleColorOrDefault(ChartStyleId, retCol, retCol2and3, retCol2and3, retCol4); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index fb7bbc638..92b600dd6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -17,6 +17,7 @@ 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; @@ -37,6 +38,9 @@ internal interface ILegendKeyContainer double MaxHeight { get; } List SeriesHeadersMeasure { get; } List SeriesIcon { get; } + eLegendPosition Position { get; } + EPPlusReadOnlyList Entries { get; } + ExcelTextBody TextBody { get; } } internal class ChartLegendRenderer : ChartDrawingObjectWithBackground, ILegendKeyContainer { @@ -609,5 +613,10 @@ public override void AppendRenderItems(List renderItems) } public List SeriesIcon { get; } = new List(); + public eLegendPosition Position => ChartRenderer.Chart.Legend?.Position ?? eLegendPosition.Right; + + public EPPlusReadOnlyList Entries => ChartRenderer.Chart.Legend.Entries; + + public ExcelTextBody TextBody => ChartRenderer.Chart.Legend.TextBody; } } \ 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 7520b736a..d4252ed27 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -102,6 +102,10 @@ internal double GetPlotAreaHeight(RectRenderItem rect) { 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; } @@ -152,7 +156,7 @@ internal double GetPlotAreaWidth(RectRenderItem rect) } } - return right - rightAxisWidth-rect.GlobalLeft; + return right - rightAxisWidth - rect.GlobalLeft; } private double GetPlotAreaLeft() { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs index 5a811c519..10ddfb5cb 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/LineChartTypeDrawer.cs @@ -260,7 +260,7 @@ private void AddLine(ExcelLineChart chartType, ExcelLineChartSerie serie, List DefaultBorderColor, 3); linePath.Style.SetDrawingPropertiesEffects(ChartRenderer.Theme, serie.Effect); linePath.Style.FillColor = "none"; //No fill for line diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs index c295ed4ad..2d5392609 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/Trendlines/ChartTrendlineRenderer.cs @@ -665,7 +665,7 @@ internal void CreateRenderCoordinatesAndDatalabel() public override void AppendRenderItems(List renderItems) { var pathItem = new PathRenderItem(ChartRenderer.Plotarea.Rectangle); - pathItem.Commands.Add(new EPPlusImageRenderer.PathCommands(PathCommandType.Move, RenderCoordinates)); + 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); diff --git a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs index f273a64af..1e0da0e1d 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LegendIconRenderer.cs @@ -145,10 +145,10 @@ internal static void SetLineLegend(ChartRenderer chart, T parent, ExcelChart sls.Textbox = new DrawingTextBody(parent.RenderContext, chart.Chart, parent.Rectangle, tbLeft, tbTop, tbWidth, tbHeight, false, true); var headerText = s.GetHeaderText(index); - var entry = chart.Chart.Legend.Entries.FirstOrDefault(x => x.Index == index); + var entry = parent.Entries?.FirstOrDefault(x => x.Index == index); if (entry == null || entry.Font.IsEmpty) { - sls.Textbox.ImportParagraph(chart.Chart.Legend.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); + sls.Textbox.ImportParagraph(parent.TextBody.Paragraphs.FirstOrDefault(), 0, headerText); } else { @@ -406,8 +406,8 @@ internal static double GetIconLength(ExcelChart c, double highestText) 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 (chart.Chart.Legend.Position == eLegendPosition.Top || - chart.Chart.Legend.Position == eLegendPosition.Bottom) + if (parent.Position == eLegendPosition.Top || + parent.Position == eLegendPosition.Bottom) { if (pSls != null && iconLeft + entryWidth * 2 + parent.MarginItemsWidth + parent.RightMargin > parent.MaxWidth) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs index 454efba79..b059565c6 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/LineMarkerHelper.cs @@ -75,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); - cmd = new PathCommands(PathCommandType.Move, new double[] { (xPath - halfSize), yPath, xPath, yPath + halfSize, xPath + halfSize, yPath, xPath, yPath - halfSize }); + 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: @@ -130,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; diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index 333ccbcdf..5c0160aa3 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -228,11 +228,11 @@ internal void ImportPathData(BoundingBox plotAreaBounds, BoundingBox globalAreaB _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); @@ -265,7 +265,7 @@ 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(); @@ -274,16 +274,16 @@ private void AddDebugLines(PathCommands moveCenter, BoundingBox bounds) _debugBoundsPath.Style.BorderColor = "red"; _debugBoundsPath.Style.FillColor = "transparent"; _debugBoundsPath.Style.BorderWidth = 3; - var moveCenterDebug = new PathCommands(PathCommandType.Move, ExtremePoints.Left, ExtremePoints.Top); + 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); diff --git a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs index 29c37ade5..8ef167db9 100644 --- a/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs +++ b/src/EPPlus/Drawing/Renderer/RenderItems/DrawingRenderItemExtentions.cs @@ -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. diff --git a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs index bdbd83542..35c991c54 100644 --- a/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ShapeRenderer.cs @@ -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; } @@ -418,7 +418,7 @@ private void GetShapeInnerBound(out double x, out double y, out double width, ou } } - 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]; @@ -463,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); @@ -478,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; @@ -521,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; @@ -541,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) { From 13e0d9145abe8890416dc610bf304e2a6f906e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 7 Oct 2026 17:18:51 +0200 Subject: [PATCH 31/44] Started fixing positions of pie items --- src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs | 2 +- src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs | 2 +- .../Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs | 8 +++++--- .../Renderer/Chart/DataLabels/SvgDataLabelPoint.cs | 6 +++--- src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs | 1 + 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs index 3e27f7e21..67d1e5c36 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs @@ -100,7 +100,7 @@ public void GenerateEPPlusPieCharts() for (int i = 0; i < ws.Drawings.Count; i++) { - var c = ws.Drawings[i]; + var c = ws.Drawings[0]; var svg = c.ToSvg(); SaveTextFileToWorkbook($"svg\\Pie{i}.svg", svg); } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index 97aacd87b..10233359c 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -162,7 +162,7 @@ public GroupRenderItem(double localXPos, double localYPos) : this() 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.TranformPoint(rotationPoint.LocalPosition.X, rotationPoint.LocalPosition.Y); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index 7b9a5fc32..8a5951ae2 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -107,9 +107,11 @@ void InitializeSlices() void CalculateLocalCenterAndRadius() { _circleCenter = new TranformPoint(); - _circleCenter.Parent = _groupItem.TranslationOffset; - _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Width / 2; - _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Height / 2; + _circleCenter.Parent = _groupItem.Parent; + _circleCenter.Left = ChartRenderer.Plotarea.Rectangle.Width / 2d; + _circleCenter.Top = ChartRenderer.Plotarea.Rectangle.Height / 2d; + + _groupItem.RotationPoint = _circleCenter; diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 682dba3dc..dcb824cc7 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -589,7 +589,7 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi //And endVector is the top center position. //--- Visualize positions for debugging purposes - //CreateDebugPoints(basePoint, endPoint, centerPoint, maxBoundsPieSlice); + CreateDebugPoints(basePoint, endPoint, centerPoint, maxBoundsPieSlice); //--- switch (_labelPosition) @@ -652,14 +652,14 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi { //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: diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index ac5a242a2..21631b505 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -689,6 +689,7 @@ internal Transform GetInnerGroupWithTransformOriginTranslated() Transform transform = new Transform(); transform.Parent = _innerGroup.Parent; transform.LocalPosition += new Vector2(_innerGroup.TransformOrigin.X + _innerGroup.TranslationOffset.Left - _innerGroup.Left, _innerGroup.TransformOrigin.Y + _innerGroup.TranslationOffset.Top- _innerGroup.Top); + transform.LocalRotation = _innerGroup.LocalRotation; return transform; } From 136e7897094882b24802d08445346b8cfea3a83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 09:56:44 +0200 Subject: [PATCH 32/44] Fixed pieScaling issue --- .../Chart/PieChartTests.cs | 22 ++++++++++++++++--- .../RenderItems/RenderItem.cs | 3 ++- .../Svg/ItemRenderers/SvgGroupRenderer.cs | 22 +++++++++---------- .../Renderer/Chart/PieSliceRenderItem.cs | 4 ++-- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs index 67d1e5c36..c5c77f077 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs @@ -11,15 +11,31 @@ 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() { ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); using (var p = OpenTemplatePackage("PieChartSvgALL.xlsx")) { - var ws = p.Workbook.Worksheets[4]; + var ws = p.Workbook.Worksheets[1]; - var explosion = ws.Drawings["ExplosionAll"]; + var explosion = ws.Drawings["PieExplosion30"]; var explodeSvg = explosion.ToSvg(); @@ -100,7 +116,7 @@ public void GenerateEPPlusPieCharts() for (int i = 0; i < ws.Drawings.Count; i++) { - var c = ws.Drawings[0]; + var c = ws.Drawings[1]; var svg = c.ToSvg(); SaveTextFileToWorkbook($"svg\\Pie{i}.svg", svg); } diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index faa1d3737..b2654fdb8 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -158,6 +158,7 @@ public GroupRenderItem(double localXPos, double localYPos) : this() TranslationOffset = new Graphics.TranformPoint(localXPos, localYPos); } + public Coordinate GroupScale = null; public GroupRenderItem(BoundingBox parent, double rotation, Transform rotationPoint = null) : this(0, 0) { @@ -222,7 +223,7 @@ public override RenderItem Clone() TransformOrigin = TransformOrigin, // GroupTransform = GroupTransform, RotationPoint = RotationPoint, - // Scale = Scale, + GroupScale = GroupScale, }; CloneBase(item); foreach(var child in ChildObjects) diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index f26dc68a7..871f93aa5 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -50,7 +50,7 @@ string GetCombinedTransformString(GroupRenderItem item) { string positionStr = ""; string rotationStr = GetRotationStr(item); - //string scalingStr = GetScalingStr(item); + string scalingStr = GetScalingStr(item); //if (item.TranslationOffset != null && (item.TranslationOffset.Left == 0 && item.TranslationOffset.Top == 0) == false) @@ -60,7 +60,7 @@ string GetCombinedTransformString(GroupRenderItem item) positionStr = string.Format("translate({0}, {1})", item.Left.PointToPixelString(), item.Top.PointToPixelString()) + " "; - return positionStr + rotationStr; //+ scalingStr; + return positionStr + rotationStr + scalingStr; } string GetTransformOrigin(GroupRenderItem item) { @@ -74,17 +74,17 @@ string GetTransformOrigin(GroupRenderItem item) } - //string GetScalingStr(GroupRenderItem item) - //{ - // var scaleStr = string.Empty; + string GetScalingStr(GroupRenderItem item) + { + var scaleStr = string.Empty; - // if (item.Scale != null) - // { - // scaleStr = string.Format(transformScale, item.Scale.X.ToString(CultureInfo.InvariantCulture), item.Scale.Y.ToString(CultureInfo.InvariantCulture)) + " "; - // } + if (item.GroupScale != null) + { + scaleStr = string.Format(transformScale, item.GroupScale.X.ToString(CultureInfo.InvariantCulture), item.GroupScale.Y.ToString(CultureInfo.InvariantCulture)) + " "; + } - // return scaleStr; - //} + return scaleStr; + } string GetRotationStr(GroupRenderItem item) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index 28ad4cff5..2c5db1d8b 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -240,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(); @@ -618,7 +618,7 @@ TranformPoint CalculateLocalPointOnCircle(double degrees) var point = new TranformPoint(); //Ensure the cx/cy offset - point.Parent = _innerGroup.TranslationOffset.Parent; + point.Parent = _innerGroup; point.Left = xPoint; point.Top = yPoint; From 7ce37e4967a4ebb400a927243051ed70a1f42cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 10:18:47 +0200 Subject: [PATCH 33/44] Workaround for deferred rotation GroupRotation --- .../RenderItems/RenderItem.cs | 1 + .../Svg/ItemRenderers/SvgGroupRenderer.cs | 13 ++++++++++++- .../Chart/ChartTypeDrawers/PieChartTypeDrawer.cs | 2 +- .../Drawing/Renderer/Chart/PieSliceRenderItem.cs | 1 - 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs index b2654fdb8..8cc5c065f 100644 --- a/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs +++ b/src/EPPlus.DrawingRenderer/RenderItems/RenderItem.cs @@ -159,6 +159,7 @@ public GroupRenderItem(double localXPos, double localYPos) : this() } public Coordinate GroupScale = null; + public double GroupRotation { get; set;} public GroupRenderItem(BoundingBox parent, double rotation, Transform rotationPoint = null) : this(0, 0) { diff --git a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs index 871f93aa5..447f95def 100644 --- a/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs +++ b/src/EPPlus.DrawingRenderer/Svg/ItemRenderers/SvgGroupRenderer.cs @@ -88,7 +88,18 @@ string GetScalingStr(GroupRenderItem item) string GetRotationStr(GroupRenderItem item) { - if (double.IsNaN(item.LocalRotation) == false && item.LocalRotation != 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.LocalRotation.ToString(CultureInfo.InvariantCulture); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index 8a5951ae2..0d0bb6484 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -200,7 +200,7 @@ internal override void DrawSeries() //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(); diff --git a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs index 2c5db1d8b..b45888b7a 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/PieSliceRenderItem.cs @@ -689,7 +689,6 @@ internal Transform GetInnerGroupWithTransformOriginTranslated() Transform transform = new Transform(); transform.Parent = _innerGroup.Parent; transform.LocalPosition += new Vector2(_innerGroup.TransformOrigin.X + _innerGroup.TranslationOffset.Left - _innerGroup.Left, _innerGroup.TransformOrigin.Y + _innerGroup.TranslationOffset.Top- _innerGroup.Top); - transform.LocalRotation = _innerGroup.LocalRotation; return transform; } From 1d6725ea87bb4529d22981a652cdc430a80fb906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 10:43:16 +0200 Subject: [PATCH 34/44] Dlbl_SeriesGroup now holds start pos of points --- .../Chart/PieChartTests.cs | 6 +- .../DataLabels/ChartSerieDataLabelRenderer.cs | 12 ++- .../Chart/DataLabels/SvgDataLabelPoint.cs | 88 +++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs index c5c77f077..4d500d4ed 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Chart/PieChartTests.cs @@ -33,11 +33,11 @@ public void ReadAndCreateSvgsAll() ExcelPackage.License.SetNonCommercialOrganization("EPPlus Project"); using (var p = OpenTemplatePackage("PieChartSvgALL.xlsx")) { - var ws = p.Workbook.Worksheets[1]; + var ws = p.Workbook.Worksheets[4]; - var explosion = ws.Drawings["PieExplosion30"]; + var manySlices = ws.Drawings["ManySlices_Rot"]; - var explodeSvg = explosion.ToSvg(); + var manySlicesSvg = manySlices.ToSvg(); for (int i = 0; i < p.Workbook.Worksheets.Count; i++) { diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs index 91c42532e..b38398bb7 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/ChartSerieDataLabelRenderer.cs @@ -35,8 +35,13 @@ internal class ChartSerieDataLabelRenderer : ChartDrawingObjectWithBackground 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; @@ -202,9 +207,8 @@ internal void SetParentPoint(BoundingBox parent, int index) public override void AppendRenderItems(List renderItems) { - var DatalabelsGroup = new GroupRenderItem(ChartRenderer.ChartArea.Rectangle); - - DatalabelsGroup.Name = "Dlbl_SeriesGroup"; + DatalabelsGroup.Left = ChartRenderer.Plotarea.Group.Left; + DatalabelsGroup.Top = ChartRenderer.Plotarea.Group.Top; if (rotation != double.NaN) { @@ -227,7 +231,7 @@ public override void AppendRenderItems(List renderItems) { dataLabels[i].CounterRotation = -rotation; } - dataLabels[i].AppendRenderItems(DatalabelsGroup.ChildObjects); + dataLabels[i].PrepareForRenderAndAddToParent(DatalabelsGroup); } renderItems.Add(DatalabelsGroup); diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index dcb824cc7..1571853f8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -717,6 +717,94 @@ private void AppendDebugBounds(List renderItems) renderItems.Add(rect); } + public void PrepareForRenderAndAddToParent(Transform parent) + { + 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; + + _seriesIcon.Parent = iconGrp; + } + + if (_leaderLines != null && _leaderLines.Count > 0) + { + foreach (var line in _leaderLines) + { + line.Parent = group; + //group.ChildObjects.Add(line); + } + } + + parent.AddChild(DataLabelPointContainer); + } + public override void AppendRenderItems(List renderItems) { var titleItemOrigin = new TitleRenderItem("DataLabel originpoint/Data point position"); From 731f37eaf65123a1b68b0120b5753e424cf1856d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 10:45:52 +0200 Subject: [PATCH 35/44] Commented out debug points --- .../Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs index 1571853f8..5e0b67239 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/DataLabels/SvgDataLabelPoint.cs @@ -589,7 +589,7 @@ internal void SetShapeDimensions(Transform basePoint, Transform endPoint, Boundi //And endVector is the top center position. //--- Visualize positions for debugging purposes - CreateDebugPoints(basePoint, endPoint, centerPoint, maxBoundsPieSlice); + //CreateDebugPoints(basePoint, endPoint, centerPoint, maxBoundsPieSlice); //--- switch (_labelPosition) From 4de39fca1a7b2f977944330e650782a18126ba6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 15:57:06 +0200 Subject: [PATCH 36/44] Added base primitives for circles/pie slices --- .../RenderItems/ContainerRenderItem.cs | 22 +++++ src/EPPlus.Graphics/Geometry/GeoMathUtils.cs | 43 ++++++++ src/EPPlus.Graphics/Primitives/CircularArc.cs | 23 +++++ .../Primitives/PieSliceBase.cs | 98 +++++++++++++++++++ .../Primitives/PrimitiveCircle.cs | 22 +++++ .../TransformPrimitives/PieSliceCollection.cs | 44 +++++++++ .../TransformPrimitives/PieTransform.cs | 14 +++ .../TransformPrimitives/TransformCircle.cs | 51 ++++++++++ 8 files changed, 317 insertions(+) create mode 100644 src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs create mode 100644 src/EPPlus.Graphics/Geometry/GeoMathUtils.cs create mode 100644 src/EPPlus.Graphics/Primitives/CircularArc.cs create mode 100644 src/EPPlus.Graphics/Primitives/PieSliceBase.cs create mode 100644 src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs create mode 100644 src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs create mode 100644 src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs create mode 100644 src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs diff --git a/src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs b/src/EPPlus.DrawingRenderer/RenderItems/ContainerRenderItem.cs new file mode 100644 index 000000000..40231a6bb --- /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.Graphics/Geometry/GeoMathUtils.cs b/src/EPPlus.Graphics/Geometry/GeoMathUtils.cs new file mode 100644 index 000000000..3ee57d4b8 --- /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 000000000..4192d2ce6 --- /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 000000000..dff04e662 --- /dev/null +++ b/src/EPPlus.Graphics/Primitives/PieSliceBase.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EPPlus.Graphics.Geometry; +using System.Text; + +namespace EPPlus.Graphics.Primitives +{ + internal class PieSliceBase + { + Vector2 _startPoint; + Vector2 _endPoint; + Vector2 _midPoint; + + internal Vector2 StartPoint + { + get + { + CalculateSliceArcPoints(); + return _startPoint; + } + private set + { + _startPoint = value; + } + } + + internal Vector2 EndPoint + { + get + { + CalculateSliceArcPoints(); + return _endPoint; + } + private set + { + _endPoint = value; + } + } + + internal Vector2 MidPoint + { + get + { + CalculateSliceArcPoints(); + return _midPoint; + } + private set + { + _midPoint = value; + } + } + + 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 000000000..28f601146 --- /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 +{ + internal 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/TransformPrimitives/PieSliceCollection.cs b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs new file mode 100644 index 000000000..c6153018f --- /dev/null +++ b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs @@ -0,0 +1,44 @@ +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 +{ + internal class PieSliceCollection : IEnumerable + { + const double MaximumDegrees = 360; + //double totalDegrees = 0; + + double totalValue = 0; + + List _slices = new List(); + + internal PieSliceCollection() + { + + } + + public void Add(PieSliceBase slice) + { + + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _slices.Count; i++) + { + yield return _slices[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs new file mode 100644 index 000000000..7b477e04c --- /dev/null +++ b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Graphics.TransformPrimitives +{ + internal class PieTransform : TransformCircle + { + internal PieTransform(double radius) : base(radius) + { + } + } +} diff --git a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs new file mode 100644 index 000000000..5550f166c --- /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 + internal class TransformCircle : Transform + { + double Radius = double.NaN; + + PrimitiveCircle circle; + + 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; + //} + } +} From faf387b3960d0eb5444252cb2a723b70136e3e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 16:29:54 +0200 Subject: [PATCH 37/44] Added transform variants --- .../Shared/MultiContainerTest.cs | 54 +++++++++---------- .../TransformPrimitives/PieSliceCollection.cs | 11 +++- .../TransformPrimitives/PieTransform.cs | 19 ++++++- .../TransformPrimitives/TransformCircle.cs | 12 ++--- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs index 0f7052f81..73fd765bc 100644 --- a/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs +++ b/src/EPPlus.DrawingRenderer.Tests/Shared/MultiContainerTest.cs @@ -85,6 +85,33 @@ private void GenerateSvgFile(string fileName, BoundingBox bounds, params Transfo 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() { @@ -166,32 +193,5 @@ public void VerifyGrandChildContentTopLeftNegative() //GenerateSvgFile("VerifyGrandChildContentTopLeftNegative", world, world.ChildObjects.ToArray()); } - - [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); - } } } diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs index c6153018f..7653390f3 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs @@ -16,6 +16,7 @@ internal class PieSliceCollection : IEnumerable double totalValue = 0; + List _percentages = new List(); List _slices = new List(); internal PieSliceCollection() @@ -23,9 +24,10 @@ internal PieSliceCollection() } - public void Add(PieSliceBase slice) + public void Add(PieSliceBase slice, double percentage) { - + _percentages.Add(percentage); + _slices.Add(slice); } public IEnumerator GetEnumerator() @@ -40,5 +42,10 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + public void Clear() + { + + } } } diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs index 7b477e04c..c65b49a36 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs @@ -1,4 +1,5 @@ -using System; +using EPPlus.Graphics.Primitives; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,8 +8,24 @@ namespace EPPlus.Graphics.TransformPrimitives { internal class PieTransform : TransformCircle { + PieSliceCollection Slices = new PieSliceCollection(); + internal PieTransform(double radius) : base(radius) { + + } + + void GenerateSlices(IEnumerable SlicePercentages) + { + + + double startDegrees = 0; + + foreach (var percentage in SlicePercentages) + { + PieSliceBase slice = new PieSliceBase(startDegrees, percentage * 360, Circle); + Slices.Add(slice, percentage); + } } } } diff --git a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs index 5550f166c..e500a34fa 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs @@ -10,29 +10,29 @@ namespace EPPlus.Graphics.TransformPrimitives //Position refers to the center of the circle internal class TransformCircle : Transform { - double Radius = double.NaN; + protected PrimitiveCircle Circle; - PrimitiveCircle circle; + protected double Radius { get { return Circle.Radius; } } internal TransformCircle(double radius) : base() { - circle = new PrimitiveCircle(Vector2.Zero, radius); + Circle = new PrimitiveCircle(Vector2.Zero, radius); } internal TransformCircle(Transform parent, double radius) : base(Vector2.Zero, Vector2.One, parent) { - circle = new PrimitiveCircle(Vector2.Zero, radius); + Circle = new PrimitiveCircle(Vector2.Zero, radius); } internal Vector2 GetPointOnCircleGlobal(double degrees) { - var ptOnCircle = circle.GetPointOnCircle(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); + var ptOnCircle = Circle.GetPointOnCircle(degrees); return new Vector2(LocalPosition.X + ptOnCircle.X, LocalPosition.Y + ptOnCircle.Y); } From 566729be7a7c66317d1316d56a2dc290890237e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Thu, 8 Oct 2026 16:30:45 +0200 Subject: [PATCH 38/44] Fixes for axis and data tables --- .../Chart/LineChartToSvgTests.cs | 22 ++--- .../Renderer/Chart/ChartAxisRenderer.cs | 21 +++-- .../Renderer/Chart/ChartDataTableRenderer.cs | 17 ---- .../Renderer/Chart/ChartLegendRenderer.cs | 15 ++++ .../Renderer/Chart/ChartPlotareaRenderer.cs | 24 +++--- .../Renderer/Chart/ChartTitleRenderer.cs | 26 ++++++ src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 86 ++++++++++--------- 7 files changed, 125 insertions(+), 86 deletions(-) diff --git a/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs b/src/EPPlus.DrawingRenderer.Tests/Chart/LineChartToSvgTests.cs index 8d46b25c7..e685e0d1d 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/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 75d196112..b1816156d 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -235,7 +235,16 @@ private double GetTextWidest(ExcelChartAxisStandard ax) } return widest; } + //internal GetAxisWidthForPlotarea() + //{ + //} + //internal double GetAxisHeightForPlotarea() + //{ + // var pa = ChartRenderer.Plotarea; + + // var + //} public List Values { get; @@ -352,9 +361,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(ChartRenderer) + { + AxisName = $"Axis_{Axis.Index}_Textboxes", + TextBoxes = GetAxisValueTextBoxes() + }; } } @@ -544,7 +555,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 @@ -685,7 +696,7 @@ 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 { diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs index a0bc88682..34046c77f 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartDataTableRenderer.cs @@ -311,23 +311,6 @@ private List GetFormattedValues(ChartRenderer svgChart, List val } return displayValues; } - - private RenderItem GetIcon(ChartRenderer svgChart, ExcelChart ct, ExcelChartStandardSerie serie, DrawingLegendSerie pSls, int serieIndex, int index, double entryWidth, double entryHeight) - { - if(ct.IsTypeLine()) - { - return LegendIconRenderer.GetLineSeriesIcon(svgChart, this, serie, pSls, entryWidth, entryHeight); - } - else if(ct.IsTypeBar() || ct.IsTypeColumn()) - { - return LegendIconRenderer.GetBarSeriesIcon(svgChart, ct, this, (ExcelBarChartSerie)serie, pSls, entryWidth, entryHeight, serieIndex, index); - } - else - { - return LegendIconRenderer.GetPieSeriesIcon(svgChart, ct, this, (ExcelPieChartSerie)serie, pSls, entryWidth, entryHeight, index); - } - } - private List AddSerieValues(ChartRenderer svgChart, double maxWidth, double maxHeight, ExcelChartSerie serie) { var ret = new List(); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs index 92b600dd6..a92496f82 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartLegendRenderer.cs @@ -611,6 +611,19 @@ public override void AppendRenderItems(List renderItems) { return DefaultBorderColor; } + + internal double GetBottomOverlay(double overlayValue) + { + if (Chart.Legend.Overlay) + { + return overlayValue; + } + else + { + return Rectangle.GlobalBottom; + } + } + public List SeriesIcon { get; } = new List(); public eLegendPosition Position => ChartRenderer.Chart.Legend?.Position ?? eLegendPosition.Right; @@ -618,5 +631,7 @@ public override void AppendRenderItems(List renderItems) public EPPlusReadOnlyList Entries => ChartRenderer.Chart.Legend.Entries; public ExcelTextBody TextBody => ChartRenderer.Chart.Legend.TextBody; + + 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 d4252ed27..91aebce17 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartPlotareaRenderer.cs @@ -88,17 +88,18 @@ internal 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; } @@ -114,7 +115,7 @@ 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 ? + 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); @@ -161,7 +162,8 @@ internal double GetPlotAreaWidth(RectRenderItem rect) 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.Width + ChartRenderer.Legend.RightMargin; } @@ -175,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) { @@ -194,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.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) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs index b7fb136dd..f6f47ea6a 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTitleRenderer.cs @@ -42,6 +42,8 @@ internal class ChartTitleRenderer : ChartDrawingObjectWithBackground ExcelChartTitleStandard _title; string _titleText; ChartRenderer _svgChart; + internal bool Overlay { get { return _title.Overlay; } } + /// /// /// @@ -249,5 +251,29 @@ public override void AppendRenderItems(List renderItems) { 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/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index 0b0adcccd..e91a3f7df 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -98,61 +98,32 @@ 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 (HorizontalAxis.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 (VerticalAxis.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; @@ -190,6 +161,24 @@ private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecond horizontalAxis.Line.Y1 = horizontalAxis.Line.Y2 = horizontalAxis.Rectangle.Bottom; } } + + 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 = newtop; + } + } } if (horizontalAxis.Title != null) { @@ -273,7 +262,7 @@ 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) { @@ -307,6 +296,20 @@ private void PlaceVerticalAxis(ChartAxisRenderer 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 (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); } @@ -366,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.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 @@ -431,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; } From 087c1c6c43800e6b00b9fa4aed667f6546d84d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 17:10:33 +0200 Subject: [PATCH 39/44] Added basic test for new pieTransform primitive --- .../Primitives/PieSliceBase.cs | 18 +++++++++----- .../Primitives/PrimitiveCircle.cs | 2 +- .../TransformPrimitives/PieSliceCollection.cs | 18 +++++++++++++- .../TransformPrimitives/PieTransform.cs | 24 +++++++++++-------- .../TransformPrimitives/TransformCircle.cs | 4 ++-- .../ChartTypeDrawers/PieChartTypeDrawer.cs | 3 +++ 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/EPPlus.Graphics/Primitives/PieSliceBase.cs b/src/EPPlus.Graphics/Primitives/PieSliceBase.cs index dff04e662..b07724906 100644 --- a/src/EPPlus.Graphics/Primitives/PieSliceBase.cs +++ b/src/EPPlus.Graphics/Primitives/PieSliceBase.cs @@ -6,15 +6,15 @@ namespace EPPlus.Graphics.Primitives { - internal class PieSliceBase + public class PieSliceBase { Vector2 _startPoint; Vector2 _endPoint; Vector2 _midPoint; - internal Vector2 StartPoint - { - get + public Vector2 StartPoint + { + get { CalculateSliceArcPoints(); return _startPoint; @@ -25,7 +25,7 @@ private set } } - internal Vector2 EndPoint + public Vector2 EndPoint { get { @@ -38,7 +38,7 @@ private set } } - internal Vector2 MidPoint + public Vector2 MidPoint { get { @@ -51,6 +51,12 @@ private set } } + /// + /// 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; diff --git a/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs b/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs index 28f601146..dd8883bd6 100644 --- a/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs +++ b/src/EPPlus.Graphics/Primitives/PrimitiveCircle.cs @@ -3,7 +3,7 @@ namespace EPPlus.Graphics.Primitives { - internal class PrimitiveCircle + public class PrimitiveCircle { internal Vector2 Center; internal double Radius; diff --git a/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs index 7653390f3..3eda7d2df 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/PieSliceCollection.cs @@ -9,7 +9,7 @@ namespace EPPlus.Graphics.TransformPrimitives { - internal class PieSliceCollection : IEnumerable + public class PieSliceCollection : IEnumerable { const double MaximumDegrees = 360; //double totalDegrees = 0; @@ -45,7 +45,23 @@ IEnumerator IEnumerable.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 index c65b49a36..8747819ec 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs @@ -6,25 +6,29 @@ namespace EPPlus.Graphics.TransformPrimitives { - internal class PieTransform : TransformCircle + public class PieTransform : TransformCircle { - PieSliceCollection Slices = new PieSliceCollection(); + public PieSliceCollection Slices = new PieSliceCollection(); - internal PieTransform(double radius) : base(radius) - { + public PieTransform(Transform parent, double radius) : base(parent, radius) { } - } + public PieTransform(double radius) : base(radius) { } - void GenerateSlices(IEnumerable SlicePercentages) + /// + /// + /// + /// Between 0 and 1 + public void GenerateSlicesAndClearPrevious(IEnumerable SlicePercentages) { - - - double startDegrees = 0; + double prevSliceDegrees = 0; + Slices.Clear(); foreach (var percentage in SlicePercentages) { - PieSliceBase slice = new PieSliceBase(startDegrees, percentage * 360, Circle); + var degrees = percentage * 360d; + PieSliceBase slice = new PieSliceBase(prevSliceDegrees, prevSliceDegrees + degrees, Circle); Slices.Add(slice, percentage); + prevSliceDegrees = prevSliceDegrees + slice.EndDegrees; } } } diff --git a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs index e500a34fa..3abd14b03 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/TransformCircle.cs @@ -8,9 +8,9 @@ namespace EPPlus.Graphics.TransformPrimitives { //Position refers to the center of the circle - internal class TransformCircle : Transform + public class TransformCircle : Transform { - protected PrimitiveCircle Circle; + public PrimitiveCircle Circle; protected double Radius { get { return Circle.Radius; } } diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs index 0d0bb6484..5ceb20cbf 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartTypeDrawers/PieChartTypeDrawer.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Drawing; using System.Runtime.InteropServices; +using EPPlus.Graphics.TransformPrimitives; namespace EPPlus.Export.ImageRenderer.Svg.Chart.ChartTypeDrawers { @@ -50,6 +51,8 @@ internal class PieChartTypeDrawer : ChartTypeDrawer internal override Color? DefaultBorderColor => null; + PieTransform pieTransform; + public PieChartTypeDrawer(ChartRenderer chart, ExcelPieChart chartType) : base(chart, chartType) { //Moved to draw series From 4c0db3ea9e31330aee4f736e5352fbf0ed22412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 8 Oct 2026 18:17:09 +0200 Subject: [PATCH 40/44] added actual test --- src/EPPlus.Graphics.Tests/PrimitivesTest.cs | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/EPPlus.Graphics.Tests/PrimitivesTest.cs diff --git a/src/EPPlus.Graphics.Tests/PrimitivesTest.cs b/src/EPPlus.Graphics.Tests/PrimitivesTest.cs new file mode 100644 index 000000000..95de43554 --- /dev/null +++ b/src/EPPlus.Graphics.Tests/PrimitivesTest.cs @@ -0,0 +1,30 @@ +using EPPlus.Graphics.TransformPrimitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace EPPlus.Graphics.Tests +{ + [TestClass] + public class PrimitivesTest + { + + [TestMethod] + public void PieTransformGenerateTwoSlices() + { + PieTransform pie = new PieTransform(10d); + + pie.GenerateSlicesAndClearPrevious(new List() { 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); + + } + } +} From 3789f9c41b7889bc1667d10ac63dcd3b18cc5f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 9 Oct 2026 09:10:23 +0200 Subject: [PATCH 41/44] Fixed minor bug --- src/EPPlus.Graphics.Tests/PrimitivesTest.cs | 21 +++++++++++++++++++ .../TransformPrimitives/PieTransform.cs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/EPPlus.Graphics.Tests/PrimitivesTest.cs b/src/EPPlus.Graphics.Tests/PrimitivesTest.cs index 95de43554..fff8a269b 100644 --- a/src/EPPlus.Graphics.Tests/PrimitivesTest.cs +++ b/src/EPPlus.Graphics.Tests/PrimitivesTest.cs @@ -26,5 +26,26 @@ public void PieTransformGenerateTwoSlices() 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/TransformPrimitives/PieTransform.cs b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs index 8747819ec..00d846ce4 100644 --- a/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs +++ b/src/EPPlus.Graphics/TransformPrimitives/PieTransform.cs @@ -28,7 +28,7 @@ public void GenerateSlicesAndClearPrevious(IEnumerable SlicePercentages) var degrees = percentage * 360d; PieSliceBase slice = new PieSliceBase(prevSliceDegrees, prevSliceDegrees + degrees, Circle); Slices.Add(slice, percentage); - prevSliceDegrees = prevSliceDegrees + slice.EndDegrees; + prevSliceDegrees = slice.EndDegrees; } } } From 543146f765d49ecd76f68788f0487872ce228d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 9 Oct 2026 09:41:38 +0200 Subject: [PATCH 42/44] Fixed cat axes --- .../Renderer/Chart/ChartAxisRenderer.cs | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index b1816156d..2090c01c3 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,21 +25,15 @@ 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 { @@ -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 { @@ -265,7 +260,6 @@ 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; @@ -782,16 +776,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) - { + min = 1; + //if(AxisValues != null) + //{ if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) { max = AxisValues.Count; @@ -800,11 +791,11 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou { max = AxisValues.Count - 1; } - } - else - { - max = 0; - } + //} + //else + //{ + // max = 0; + //} } else { @@ -1060,7 +1051,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) @@ -1087,7 +1078,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) @@ -1275,7 +1266,6 @@ protected List GetAxisValue(ExcelChartAxisStandard ax, RenderItem rect, majorUnit = res.MajorInterval; dateUnit = null; orientation = eTextOrientation.Horizontal; - IsNumericAutoAxis = false; } return l; From ae0841d10e4f42e5d644b14e7bec0a77125a6381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 9 Oct 2026 12:22:39 +0200 Subject: [PATCH 43/44] Tickmarks, lines and axis titles now relate to the axis recangle group instead of the chart area --- .../Renderer/Chart/ChartAxisRenderer.cs | 94 +++++++++---------- .../Renderer/Chart/ChartAxisTextBoxes.cs | 17 ++-- src/EPPlus/Drawing/Renderer/ChartRenderer.cs | 30 +++--- 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 2090c01c3..44ecb1aa8 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -293,19 +293,21 @@ private void AddSubGroupingOfRenderItems(string subGroupName, List Items, public override void AppendRenderItems(List renderItems) { - var AxisGroup = new GroupRenderItem(ChartRenderer.Bounds); - AxisGroup.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.ChildObjects); + Title?.AppendRenderItems(renderItems); //Title?.Render(sb); - if(Rectangle!=null || Rectangle.Width==0 || Rectangle.Height==0) AxisGroup.ChildObjects.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.ChildObjects.Add(Line); + if (Line != null) axisGroup.ChildObjects.Add(Line); var TickMarkGroup = new GroupRenderItem(ChartRenderer.Bounds); TickMarkGroup.Name = $"Axis_{Axis.Index}_TickMarkGroup"; @@ -315,10 +317,10 @@ public override void AppendRenderItems(List renderItems) if(MinorTickMarkPositions != null || MajorTickMarkPositions != null) { - AxisGroup.ChildObjects.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. } @@ -355,7 +357,7 @@ internal void AddTickmarksAndValues(List DefItems) if (AxisValues != null && AxisValues.Count > 0 && Axis.Deleted==false && Axis.LabelPosition != eTickLabelPosition.None) { - Textboxes = new ChartAxisTextBoxes(ChartRenderer) + Textboxes = new ChartAxisTextBoxes(this) { AxisName = $"Axis_{Axis.Index}_Textboxes", TextBoxes = GetAxisValueTextBoxes() @@ -664,7 +666,7 @@ private double GetAxisItemLeft(int i) { if (Axis.IsVertical) { - return Rectangle.Left; + return 0; } else { @@ -679,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 @@ -697,7 +699,7 @@ private double GetAxisItemLeft(int i) majorWidth = Rectangle.Width * (v - Min) / (Max - Min); } - return Rectangle.Left + majorWidth; + return majorWidth; } //} } @@ -711,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) @@ -723,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; } } } @@ -758,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); } } @@ -781,21 +783,15 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis==false) { min = 1; - //if(AxisValues != null) - //{ - if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) - { - max = AxisValues.Count; - } - else - { - max = AxisValues.Count - 1; - } - //} - //else - //{ - // max = 0; - //} + if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) + { + max = AxisValues.Count; + } + else + { + max = AxisValues.Count - 1; + } + } else { @@ -830,31 +826,31 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou { 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"); diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs index 320f8565c..143137789 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisTextBoxes.cs @@ -26,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; } @@ -43,13 +44,17 @@ public override void AppendRenderItems(List renderItems) { if (TextBoxes != null && TextBoxes.Count > 0) { - var AxisTxtBoxGroup = new GroupRenderItem(ChartRenderer.Bounds); - AxisTxtBoxGroup.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.ChildObjects); + tb.AppendRenderItems(axisTxtBoxGroup.ChildObjects); } - renderItems.Add(AxisTxtBoxGroup); + renderItems.Add(axisTxtBoxGroup); } } diff --git a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs index e91a3f7df..d056dcc6f 100644 --- a/src/EPPlus/Drawing/Renderer/ChartRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/ChartRenderer.cs @@ -127,8 +127,8 @@ private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecond { 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) { @@ -143,22 +143,22 @@ 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; } } @@ -176,7 +176,7 @@ private void PlaceHorizontalAxis(ChartAxisRenderer horizontalAxis, bool isSecond var topDiff = HorizontalAxis.Rectangle.Top - newtop; HorizontalAxis.Rectangle.Top = newtop; HorizontalAxis.Rectangle.Height += topDiff; - HorizontalAxis.Line?.Y1 = HorizontalAxis.Line.Y2 = newtop; + //HorizontalAxis.Line?.Y1 = HorizontalAxis.Line.Y2 = topDiff; } } } @@ -268,8 +268,8 @@ private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis, bool isSecondary) { 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) @@ -277,22 +277,22 @@ private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis, bool isSecondary) 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; } } } @@ -307,7 +307,7 @@ private void PlaceVerticalAxis(ChartAxisRenderer verticalAxis, bool isSecondary) Plotarea.Group.Left = VerticalAxis.Rectangle.Left; var leftDiff = newLeft - VerticalAxis.Rectangle.Width; VerticalAxis.Rectangle.Left = leftDiff; - VerticalAxis.Line?.X1 = VerticalAxis.Line.X2 = newLeft; + //VerticalAxis.Line?.X1 = VerticalAxis.Line.X2 = newLeft; } } @@ -467,6 +467,8 @@ public bool AppendItems() SecondHorizontalAxis?.Textboxes?.AppendRenderItems(RenderItems); SecondVerticalAxis?.Textboxes?.AppendRenderItems(RenderItems); + + DataTable?.AppendRenderItems(RenderItems); Title?.AppendRenderItems(RenderItems); Legend?.AppendRenderItems(RenderItems); From 9e7e624b1625238cf76017bfc58dc8fe503698a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Fri, 9 Oct 2026 12:42:28 +0200 Subject: [PATCH 44/44] Fixed failing tests --- .../Drawing/Renderer/Chart/ChartAxisRenderer.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs index 44ecb1aa8..370c56e60 100644 --- a/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs +++ b/src/EPPlus/Drawing/Renderer/Chart/ChartAxisRenderer.cs @@ -782,16 +782,23 @@ private List AddTickmarks(double units, eTimeUnit? dateUnit, dou if (Axis.AxisType == eAxisType.Cat && IsDateAutoAxis==false) { - min = 1; - if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) + if (AxisValues != null) { - max = AxisValues.Count; + min = 1; + if (Axis.CrossingAxis == null || Axis.CrossingAxis.CrossBetween == eCrossBetween.Between) + { + max = AxisValues.Count; + } + else + { + max = AxisValues.Count - 1; + } } else { - max = AxisValues.Count - 1; + min = 0; + max = 0; } - } else {