Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/MiniPdf/ExcelReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4214,13 +4214,20 @@ private static List<ExcelChartInfo> ReadSheetCharts(ZipArchive archive, string w
XElement? overlayChartTypeEl = null;
var overlayChartType = "";
var overlaySeries = new List<ExcelChartSeries>();
var legendPosition = "";
var chartStyle = 0;
int? gapWidthPercent = null;

if (chartEntry != null)
{
using var cStream = chartEntry.Open();
var cDoc = XDocument.Load(cStream);
var cns = XNamespace.Get("http://schemas.openxmlformats.org/drawingml/2006/chart");

int.TryParse(cDoc.Root?.Element(cns + "style")?.Attribute("val")?.Value, out chartStyle);
var legend = cDoc.Descendants(cns + "legend").FirstOrDefault();
legendPosition = legend?.Element(cns + "legendPos")?.Attribute("val")?.Value ?? "";

// Extract chart title from <c:chart><c:title><c:tx><c:rich><a:r><a:t>
var titleEl = cDoc.Descendants(cns + "title").FirstOrDefault();
if (titleEl != null)
Expand Down Expand Up @@ -4261,6 +4268,10 @@ private static List<ExcelChartInfo> ReadSheetCharts(ZipArchive archive, string w
chartType = groupingVal.Contains("percent", StringComparison.OrdinalIgnoreCase)
? "percentStacked_" + chartType
: "stacked_" + chartType;

if (int.TryParse(chartTypeEl.Element(cns + "gapWidth")?.Attribute("val")?.Value,
out var parsedGapWidth))
gapWidthPercent = Math.Max(0, parsedGapWidth);
}

// Extract axis titles
Expand Down Expand Up @@ -4456,7 +4467,10 @@ private static List<ExcelChartInfo> ReadSheetCharts(ZipArchive archive, string w
showDataLabelVal, dataLabelFmtCode, valAxisFmtCode, isTwoCellAnchor)
{
OverlaySeries = overlaySeries,
OverlayChartType = overlayChartType
OverlayChartType = overlayChartType,
LegendPosition = legendPosition,
ChartStyle = chartStyle,
GapWidthPercent = gapWidthPercent
};
charts.Add(chartInfo);
}
Expand Down Expand Up @@ -4716,6 +4730,12 @@ internal sealed record ExcelChartInfo(
public List<ExcelChartSeries> OverlaySeries { get; init; } = new();
/// <summary>Chart type for overlay series (e.g., "lineChart" when primary is "barChart").</summary>
public string OverlayChartType { get; init; } = "";
/// <summary>Legend position from chart XML (r, l, t, b, tr); empty when hidden.</summary>
public string LegendPosition { get; init; } = "";
/// <summary>Built-in chart style identifier; zero when unspecified.</summary>
public int ChartStyle { get; init; }
/// <summary>Gap between bar clusters as a percentage of one bar width.</summary>
public int? GapWidthPercent { get; init; }
};

/// <summary>
Expand Down
63 changes: 49 additions & 14 deletions src/MiniPdf/ExcelToPdfConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2765,15 +2765,26 @@ float RenderPrintTitleRows()
private static void RenderChart(PdfPage page, ExcelChartInfo chart,
float x, float top, float width, float height, float baseFontSize)
{
var chartFitsPage = x >= 0 && x + width <= page.Width;
var useStyle10Layout = chart.ChartStyle == 10 && chartFitsPage;
var titleFontSize = baseFontSize + 2;
var labelFontSize = baseFontSize - 1;
var axisFontSize = baseFontSize - 2;
var padding = 8f;
var chartColor = chart.ChartTextColor;
var chartFont = chart.ChartFontName;

if (useStyle10Layout)
{
var borderColor = new PdfColor(0.82f, 0.82f, 0.82f);
page.AddLine(x, top, x + width, top, borderColor, 0.5f);
page.AddLine(x + width, top, x + width, top - height, borderColor, 0.5f);
page.AddLine(x + width, top - height, x, top - height, borderColor, 0.5f);
page.AddLine(x, top - height, x, top, borderColor, 0.5f);
}

// Draw chart title (clipped to chart width)
var titleY = top;
var titleY = top - (useStyle10Layout ? 12f : 0f);
if (!string.IsNullOrEmpty(chart.Title))
{
var titleAvailWidth = width - padding * 2; // use nearly full chart width
Expand All @@ -2782,15 +2793,19 @@ private static void RenderChart(PdfPage page, ExcelChartInfo chart,
// Center the title horizontally
var titleTextWidth = (float)MeasureHelveticaWidth(clippedTitle, titleFontSize);
var titleX = x + (width - titleTextWidth) / 2f;
page.AddText(clippedTitle, titleX, titleY - titleFontSize, titleFontSize, chartColor, preferredFontName: chartFont);
page.AddText(clippedTitle, titleX, titleY - titleFontSize, titleFontSize, chartColor, bold: useStyle10Layout, preferredFontName: chartFont);
titleY -= titleFontSize * 2.2f;
}

// Plot area bounds
var plotLeft = x + padding + 40f; // leave room for Y-axis labels
var plotRight = x + width - padding - 10f;
var showRightLegend = chart.LegendPosition is "r" or "tr"
&& chart.Series.Any(series => !string.IsNullOrEmpty(series.Name))
&& chartFitsPage;
Comment on lines +2801 to +2803
var legendWidth = showRightLegend ? Math.Min(32f, width * 0.1f) : 0f;
var plotLeft = x + padding + (useStyle10Layout ? 47f : 40f);
var plotRight = x + width - padding - 10f - legendWidth;
var plotTop = titleY - padding;
var plotBottom = top - height + padding + 30f; // leave room for X-axis labels
var plotBottom = top - height + padding + (useStyle10Layout ? 37f : 30f);
var plotWidth = plotRight - plotLeft;
var plotHeight = plotTop - plotBottom;

Expand Down Expand Up @@ -2866,6 +2881,20 @@ private static void RenderChart(PdfPage page, ExcelChartInfo chart,
{
page.AddText(chart.CategoryAxisTitle, plotLeft + plotWidth * 0.35f, plotBottom - 22f, axisFontSize, chartColor, preferredFontName: chartFont);
}

if (showRightLegend)
{
var legendX = plotRight + 10f;
var legendY = plotBottom + plotHeight * 0.5f;
for (var index = 0; index < chart.Series.Count; index++)
{
var seriesName = chart.Series[index].Name;
if (string.IsNullOrEmpty(seriesName)) continue;
var entryY = legendY - index * (labelFontSize + 6f);
page.AddRectangle(legendX, entryY, 7f, 7f, ChartColors[index % ChartColors.Length]);
page.AddText(seriesName, legendX + 11f, entryY - 1f, labelFontSize, chartColor, preferredFontName: chartFont);
}
}
}

/// <summary>Renders a bar/column chart.</summary>
Expand Down Expand Up @@ -2917,11 +2946,11 @@ private static void RenderBarChart(PdfPage page, ExcelChartInfo chart,
dataMax = allValues.Max();
var rawMin = allValues.Min();
// Zero-base the axis for typical positive data, but when values are
// tightly clustered far above zero (e.g. stock prices around 150),
// LibreOffice's auto-scale keeps the range close to the data instead
// of compressing it against a distant zero baseline.
dataMin = rawMin > 0 && (dataMax - rawMin) < rawMin * 0.5
? rawMin
// tightly clustered far above zero, LibreOffice expands the lower
// bound by half the data range before rounding to a nice interval.
var dataRange = dataMax - rawMin;
dataMin = rawMin > 0 && rawMin / dataMax > 5.0 / 6.0
? rawMin - dataRange / 2.0
: Math.Min(0, rawMin);
}

Expand All @@ -2934,8 +2963,11 @@ private static void RenderBarChart(PdfPage page, ExcelChartInfo chart,

var numSeries = series.Count;
var groupWidth = plotWidth / numCats;
var barWidth = isStacked ? groupWidth * 0.7f : groupWidth * 0.7f / numSeries;
var groupPadding = groupWidth * 0.15f;
var barsPerGroup = isStacked ? 1 : numSeries;
var barWidth = chart.GapWidthPercent.HasValue
? groupWidth / (barsPerGroup + chart.GapWidthPercent.Value / 100f)
: groupWidth * 0.7f / barsPerGroup;
var groupPadding = (groupWidth - barWidth * barsPerGroup) / 2f;

// Y-axis baseline: where value=0 sits, clamped into the visible axis
// range. When the axis is auto-scaled away from zero (data clustered
Expand Down Expand Up @@ -3120,8 +3152,11 @@ private static void RenderHorizontalBarChart(PdfPage page, ExcelChartInfo chart,
if (range <= 0) range = 1;

var groupHeight = plotHeight / numCats;
var barHeight = isStacked ? groupHeight * 0.7f : groupHeight * 0.7f / numSeries;
var groupPadding = groupHeight * 0.15f;
var barsPerGroup = isStacked ? 1 : numSeries;
var barHeight = chart.GapWidthPercent.HasValue
? groupHeight / (barsPerGroup + chart.GapWidthPercent.Value / 100f)
: groupHeight * 0.7f / barsPerGroup;
var groupPadding = (groupHeight - barHeight * barsPerGroup) / 2f;

var baselineX = plotLeft + (float)((0 - niceMin) / range) * plotWidth;

Expand Down
Binary file not shown.
Loading
Loading