Repository navigation
Add chart sheets to OpenXml export #1029
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
virzak
wants to merge
3
commits into
mini-software:master
Choose a base branch
from
virzak:feature/chart-sheets
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,110
−8
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// A chart made of one or more plots that share primary and, optionally, secondary axes. | ||
| /// </summary> | ||
| public class OpenXmlChart | ||
| { | ||
| public string? Title { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The title font size in points. When null, Excel picks it. | ||
| /// </summary> | ||
| public double? TitleFontSize { get; set; } | ||
|
|
||
| public bool TitleBold { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Where the legend is placed. When null, the chart has no legend. | ||
| /// </summary> | ||
| public OpenXmlChartLegendPosition? LegendPosition { get; set; } = OpenXmlChartLegendPosition.Right; | ||
|
|
||
| /// <summary> | ||
| /// The style of the chart border. When null, Excel picks it. | ||
| /// </summary> | ||
| public OpenXmlChartLine? Border { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The horizontal axis of the primary plots, or the vertical one for bar plots. | ||
| /// </summary> | ||
| public OpenXmlChartAxis XAxis { get; } = new(); | ||
|
|
||
| /// <summary> | ||
| /// The value axis of the primary plots. | ||
| /// </summary> | ||
| public OpenXmlChartAxis YAxis { get; } = new() { MajorGridlines = new OpenXmlChartLine() }; | ||
|
|
||
| /// <summary> | ||
| /// The category or X axis of the plots on the secondary axes. Hidden unless made visible. | ||
| /// </summary> | ||
| public OpenXmlChartAxis SecondaryXAxis { get; } = new() { Visible = false }; | ||
|
|
||
| /// <summary> | ||
| /// The value axis of the plots on the secondary axes, drawn on the opposite side of the primary one. | ||
| /// </summary> | ||
| public OpenXmlChartAxis SecondaryYAxis { get; } = new(); | ||
|
|
||
| public List<OpenXmlChartPlot> Plots { get; } = []; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| public class OpenXmlChartAxis | ||
| { | ||
| /// <summary> | ||
| /// Whether the axis line, tick marks and labels are drawn. A hidden axis still scales its plots. | ||
| /// </summary> | ||
| public bool Visible { get; set; } = true; | ||
|
|
||
| public string? Title { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The lowest value on the axis. When null, Excel picks it. Ignored by category axes. | ||
| /// </summary> | ||
| public double? Minimum { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The highest value on the axis. When null, Excel picks it. Ignored by category axes. | ||
| /// </summary> | ||
| public double? Maximum { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The distance between major tick marks. When null, Excel picks it. Ignored by category axes. | ||
| /// </summary> | ||
| public double? MajorUnit { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The number format of the labels, such as <c>#,##0</c>. When null, the labels follow the source data. | ||
| /// </summary> | ||
| public string? NumberFormat { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The rotation of the labels in degrees from -90 to 90, counterclockwise like in Excel. When null, Excel picks it. | ||
| /// </summary> | ||
| public int? LabelRotation { get; set; } | ||
|
|
||
| public OpenXmlChartTickMark MajorTickMark { get; set; } = OpenXmlChartTickMark.Outside; | ||
|
|
||
| public OpenXmlChartTickMark MinorTickMark { get; set; } = OpenXmlChartTickMark.None; | ||
|
|
||
| /// <summary> | ||
| /// The style of the axis line. When null, Excel picks it. | ||
| /// </summary> | ||
| public OpenXmlChartLine? Line { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The style of the major gridlines. When null, no gridlines are drawn. | ||
| /// </summary> | ||
| public OpenXmlChartLine? MajorGridlines { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// The values or categories of a chart series: either a range of cells or literal values stored in the chart itself. | ||
| /// </summary> | ||
| public sealed class OpenXmlChartData | ||
| { | ||
| private OpenXmlChartData(string? formula, double[]? numbers, string[]? strings) | ||
| { | ||
| Formula = formula; | ||
| Numbers = numbers; | ||
| Strings = strings; | ||
| } | ||
|
|
||
| internal string? Formula { get; } | ||
| internal double[]? Numbers { get; } | ||
| internal string[]? Strings { get; } | ||
|
|
||
| /// <summary> | ||
| /// Data read from a range of cells, such as <c>B2:B100</c>, on a sheet of the same workbook. | ||
| /// </summary> | ||
| public static OpenXmlChartData FromRange(string sheetName, string range) | ||
| { | ||
| if (string.IsNullOrEmpty(range)) | ||
| throw new ArgumentException("The range must not be empty.", nameof(range)); | ||
|
|
||
| var cells = range.Split(':'); | ||
| if (cells.Length > 2) | ||
| throw new ArgumentException($"Value {range} is not a valid cell range.", nameof(range)); | ||
|
|
||
| if (!CellReferenceConverter.TryParseCellReference(cells[0], out var firstColumn, out var firstRow) || | ||
| !CellReferenceConverter.TryParseCellReference(cells[^1], out var lastColumn, out var lastRow)) | ||
| throw new ArgumentException($"Value {range} is not a valid cell range.", nameof(range)); | ||
|
|
||
| return FromRange(sheetName, firstRow, firstColumn, lastRow, lastColumn); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Data read from a range of cells on a sheet of the same workbook. Rows and columns are 1-based. | ||
| /// </summary> | ||
| public static OpenXmlChartData FromRange(string sheetName, int firstRow, int firstColumn, int lastRow, int lastColumn) | ||
| { | ||
| if (string.IsNullOrEmpty(sheetName)) | ||
| throw new ArgumentException("The sheet name must not be empty.", nameof(sheetName)); | ||
| if (firstRow < 1 || firstColumn < 1 || lastRow < firstRow || lastColumn < firstColumn) | ||
| throw new ArgumentOutOfRangeException(nameof(lastRow), "The range must start at row and column 1 or later and must not end before it starts."); | ||
|
|
||
| var first = AbsoluteReference(firstColumn, firstRow); | ||
| var last = AbsoluteReference(lastColumn, lastRow); | ||
| var range = first == last ? first : $"{first}:{last}"; | ||
|
|
||
| return new OpenXmlChartData($"'{sheetName.Replace("'", "''")}'!{range}", null, null); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Numbers stored in the chart itself. | ||
| /// </summary> | ||
| public static OpenXmlChartData FromValues(params double[] values) | ||
| { | ||
| if (values is null or []) | ||
| throw new ArgumentException("At least one value is required.", nameof(values)); | ||
| if (values.Any(v => double.IsNaN(v) || double.IsInfinity(v))) | ||
| throw new ArgumentException("Chart values must be finite numbers.", nameof(values)); | ||
|
|
||
| return new OpenXmlChartData(null, values, null); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Text stored in the chart itself, typically category labels. | ||
| /// </summary> | ||
| public static OpenXmlChartData FromValues(params string[] values) | ||
| { | ||
| if (values is null or []) | ||
| throw new ArgumentException("At least one value is required.", nameof(values)); | ||
|
|
||
| return new OpenXmlChartData(null, null, values); | ||
| } | ||
|
|
||
| private static string AbsoluteReference(int column, int row) | ||
| => $"${CellReferenceConverter.GetAlphabeticalIndex(column - 1)}${row}"; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| public enum OpenXmlChartType | ||
| { | ||
| /// <summary>Points at X and Y values, drawn as markers.</summary> | ||
| Scatter, | ||
| /// <summary>Points at X and Y values, joined by straight lines without markers.</summary> | ||
| ScatterLines, | ||
| /// <summary>Values at evenly spaced categories, joined by straight lines without markers.</summary> | ||
| Line, | ||
| /// <summary>Vertical bars side by side.</summary> | ||
| Column, | ||
| /// <summary>Vertical bars stacked on top of each other.</summary> | ||
| StackedColumn, | ||
| /// <summary>Horizontal bars side by side.</summary> | ||
| Bar, | ||
| /// <summary>Horizontal bars stacked end to end.</summary> | ||
| StackedBar | ||
| } | ||
|
|
||
| public enum OpenXmlChartLegendPosition | ||
| { | ||
| Right, | ||
| Left, | ||
| Top, | ||
| Bottom, | ||
| TopRight | ||
| } | ||
|
|
||
| public enum OpenXmlChartTickMark | ||
| { | ||
| None, | ||
| Inside, | ||
| Outside, | ||
| Cross | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// The style of a line in a chart: an axis, gridlines or the chart border. | ||
| /// </summary> | ||
| public class OpenXmlChartLine | ||
| { | ||
| /// <summary> | ||
| /// The line color. The alpha channel sets the transparency. When empty, Excel picks the color. | ||
| /// </summary> | ||
| public Color Color { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The line width in points. When null, Excel picks the width. | ||
| /// </summary> | ||
| public double? Width { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// A group of series drawn the same way on the same axes. A chart with several plots combines them, for example columns | ||
| /// on the secondary axes behind a line on the primary axes. | ||
| /// </summary> | ||
| public class OpenXmlChartPlot(OpenXmlChartType type) | ||
| { | ||
| public OpenXmlChartType Type { get; } = type; | ||
|
|
||
| /// <summary> | ||
| /// Whether the plot uses the secondary axes of the chart instead of the primary ones. | ||
| /// </summary> | ||
| public bool UseSecondaryAxes { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The gap between column or bar clusters as a percentage of the bar width, from 0 to 500. Ignored by other plots. | ||
| /// </summary> | ||
| public int GapWidth { get; set; } = 150; | ||
|
|
||
| public List<OpenXmlChartSeries> Series { get; } = []; | ||
|
|
||
| internal bool IsScatter => Type is OpenXmlChartType.Scatter or OpenXmlChartType.ScatterLines; | ||
|
|
||
| internal bool IsBar => Type is OpenXmlChartType.Column or OpenXmlChartType.StackedColumn or OpenXmlChartType.Bar or OpenXmlChartType.StackedBar; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| using System.Drawing; | ||
|
|
||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// One series of a chart plot. | ||
| /// </summary> | ||
| /// <param name="values">The values of the series: the Y values of a scatter plot, or the bar heights and line points of the other plots.</param> | ||
| /// <param name="categories">The X values of a scatter plot, or the category labels of the other plots. When omitted, points are numbered from 1.</param> | ||
| public class OpenXmlChartSeries(OpenXmlChartData values, OpenXmlChartData? categories = null) | ||
| { | ||
| public OpenXmlChartData Values { get; } = values ?? throw new ArgumentNullException(nameof(values)); | ||
|
|
||
| public OpenXmlChartData? Categories { get; } = categories; | ||
|
|
||
| /// <summary> | ||
| /// The name shown in the legend. | ||
| /// </summary> | ||
| public string? Name { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The line color of scatter and line series, or the fill color of column and bar series. The alpha channel sets | ||
| /// the transparency. When empty, Excel picks the color. | ||
| /// </summary> | ||
| public Color Color { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The line width in points, for scatter and line series. When null, Excel picks the width. | ||
| /// </summary> | ||
| public double? LineWidth { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Whether the series has an entry in the legend. | ||
| /// </summary> | ||
| public bool ShowInLegend { get; set; } = true; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace MiniExcelLib.OpenXml.Charts; | ||
|
|
||
| /// <summary> | ||
| /// A sheet that holds only a chart. Export it as one of the sheets of a workbook, next to the sheets with its data. | ||
| /// </summary> | ||
| public class OpenXmlChartSheet(OpenXmlChart chart) | ||
| { | ||
| public OpenXmlChart Chart { get; } = chart ?? throw new ArgumentNullException(nameof(chart)); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: mini-software/MiniExcel
Length of output: 15441
Reject chart sheets in
CopyAndAddSheetAsync.An
OpenXmlChartSheetpassed to eitherCopyAndAddSheetAsyncoverload enters the copy writer and can reach worksheet-row serialization. Add the existing guard at the shared stream boundary.The path-based
InsertSheetAsyncbehavior is valid. When the target path does not exist, it callsExportAsync, which is the chart-sheet creation path. Do not reject that case.🤖 Prompt for AI Agents