Skip to content

Commit

Permalink
Review and rework pivot cache API, refactor and document pivot caches.
Browse files Browse the repository at this point in the history
  • Loading branch information
jahav committed Jun 4, 2023
1 parent 0435ce9 commit b21ee00
Show file tree
Hide file tree
Showing 54 changed files with 1,211 additions and 708 deletions.
81 changes: 81 additions & 0 deletions ClosedXML.Tests/Excel/PivotTables/XLPivotCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using ClosedXML.Excel;
using NUnit.Framework;

namespace ClosedXML.Tests.Excel.PivotTables
{
[TestFixture]
public class XLPivotCacheTests
{
[Test]
public void FieldNames_KeepNamesEvenWhenSourceChange()
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet();
var range = ws.FirstCell().InsertData(new[] { "Name", "Pie" });

var pivotCache = wb.PivotCaches.Add(range);
ws.Cell("A1").Value = "Pastry";

Assert.AreEqual(new[] { "Name" }, pivotCache.FieldNames);
}

[Test]
public void Refresh_UpdatesFieldNames()
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet();
var range = ws.FirstCell().InsertData(new[] { "Name", "Pie" });

var pivotCache = wb.PivotCaches.Add(range);
ws.Cell("A1").Value = "Pastry";
pivotCache.Refresh();

Assert.AreEqual(new[] { "Pastry" }, pivotCache.FieldNames);
}

[Test]
public void Refresh_RetainsSetOptions()
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet();
var range = ws.FirstCell().InsertData(new[] { "Name", "Pie" });

var pivotCache = wb.PivotCaches.Add(range);

pivotCache.ItemsToRetainPerField = XLItemsToRetain.None;
pivotCache.SaveSourceData = false;
pivotCache.RefreshDataOnOpen = true;

pivotCache.Refresh();

Assert.AreEqual(XLItemsToRetain.None, pivotCache.ItemsToRetainPerField);
Assert.AreEqual(false, pivotCache.SaveSourceData);
Assert.AreEqual(true, pivotCache.RefreshDataOnOpen);
}

[Test]
public void Refresh_RenamedFieldIsRemovedFromPivotTable()
{
// Pivot table has only field for Pastry, the dough is no longer in the pivot table after refresh
TestHelper.CreateAndCompare(wb =>
{
var ws = wb.AddWorksheet();
var range = ws.FirstCell().InsertData(new object[]
{
("Pastry", "Dough"),
("Waffles", "Puff")
});
var table = range.CreateTable();
var pivotTable = ws.PivotTables.Add("pvt", ws.Cell("D1"), table);
pivotTable.RowLabels.Add("Pastry");
pivotTable.RowLabels.Add("Dough");
pivotTable.Values.Add("Pastry").SetSummaryFormula(XLPivotSummary.Count);
ws.Cell("B1").Value = "Mixture";
pivotTable.PivotCache.Refresh();
}, @"Other\PivotTableReferenceFiles\RenamedFieldIsRemovedFromPivotTable-output.xlsx");
}
}
}
52 changes: 41 additions & 11 deletions ClosedXML.Tests/Excel/PivotTables/XLPivotTableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void PivotTableOptionsSaveTest()
pt.ShowContextualTooltips = false;
pt.DisplayCaptionsAndDropdowns = false;
pt.RepeatRowLabels = true;
pt.Source.SaveSourceData = false;
pt.PivotCache.SaveSourceData = false;
pt.EnableShowDetails = false;
pt.ShowColumnHeaders = false;
pt.ShowRowHeaders = false;
Expand All @@ -103,8 +103,8 @@ public void PivotTableOptionsSaveTest()
pt.PrintExpandCollapsedButtons = true;
pt.PrintTitles = true;

// TODO pt.RefreshDataOnOpen = false;
pt.Source.ItemsToRetainPerField = XLItemsToRetain.Max;
pt.PivotCache.RefreshDataOnOpen = false;
pt.PivotCache.ItemsToRetainPerField = XLItemsToRetain.Max;
pt.EnableCellEditing = true;
pt.ShowValuesRow = true;
pt.ShowRowStripes = true;
Expand Down Expand Up @@ -149,10 +149,10 @@ public void PivotTableOptionsSaveTest()
Assert.AreEqual(true, ptassert.PrintExpandCollapsedButtons, "PrintExpandCollapsedButtons save failure");
Assert.AreEqual(true, ptassert.RepeatRowLabels, "RepeatRowLabels save failure");
Assert.AreEqual(true, ptassert.PrintTitles, "PrintTitles save failure");
Assert.AreEqual(false, ptassert.Source.SaveSourceData, "SaveSourceData save failure");
Assert.AreEqual(false, ptassert.PivotCache.SaveSourceData, "SaveSourceData save failure");
Assert.AreEqual(false, ptassert.EnableShowDetails, "EnableShowDetails save failure");
// TODO Assert.AreEqual(false, ptassert.RefreshDataOnOpen, "RefreshDataOnOpen save failure");
Assert.AreEqual(XLItemsToRetain.Max, ptassert.Source.ItemsToRetainPerField, "ItemsToRetainPerField save failure");
Assert.AreEqual(false, ptassert.PivotCache.RefreshDataOnOpen, "RefreshDataOnOpen save failure");
Assert.AreEqual(XLItemsToRetain.Max, ptassert.PivotCache.ItemsToRetainPerField, "ItemsToRetainPerField save failure");
Assert.AreEqual(true, ptassert.EnableCellEditing, "EnableCellEditing save failure");
Assert.AreEqual(XLPivotTableTheme.PivotStyleDark13, ptassert.Theme, "Theme save failure");
Assert.AreEqual(true, ptassert.ShowValuesRow, "ShowValuesRow save failure");
Expand Down Expand Up @@ -246,7 +246,7 @@ public void PivotTableStyleFormatsTest()
monthPivotField.StyleFormats.Label.Style.Fill.BackgroundColor = XLColor.Amber;
monthPivotField.StyleFormats.Header.Style.Font.FontColor = XLColor.Yellow;
namePivotField.StyleFormats.DataValuesFormat
.AndWith(monthPivotField, v => v.GetText() == "May")
.AndWith(monthPivotField, v => v.IsText && v.GetText() == "May")
.ForValueField(numberOfOrdersPivotValue)
.Style.Font.FontColor = XLColor.Green;

Expand Down Expand Up @@ -508,7 +508,7 @@ public void PivotTableWithNoneTheme()
TestHelper.CreateAndCompare(() =>
{
var wb = new XLWorkbook(stream);
wb.SaveAs(ms, validate: false);
wb.SaveAs(ms);
return wb;
}, @"Other\PivotTableReferenceFiles\PivotTableWithNoneTheme\outputfile.xlsx");
}
Expand Down Expand Up @@ -737,11 +737,11 @@ public void TwoPivotWithOneSourceTest()
var wb = new XLWorkbook(stream);
var srcRange = wb.Range("Sheet1!$B$2:$H$207");
var pivotSource = wb.PivotSources.Add(srcRange);
var pivotSource = wb.PivotCaches.Add(srcRange);
foreach (var pt in wb.Worksheets.SelectMany(ws => ws.PivotTables))
{
pt.Source = pivotSource;
pt.PivotCache = pivotSource;
}
return wb;
Expand All @@ -751,6 +751,8 @@ public void TwoPivotWithOneSourceTest()
[Test]
public void PivotSubtotalsLoadingTest()
{
// Make sure that if the original file has *subtotals*, the subtotals are
// turned on even after loading into ClosedXML and then saving the document.
using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Other\PivotTableReferenceFiles\PivotSubtotalsSource\input.xlsx")))
TestHelper.CreateAndCompare(() =>
{
Expand All @@ -760,7 +762,7 @@ public void PivotSubtotalsLoadingTest()
}

[Test]
public void ClearPivotTableTenderedTange()
public void ClearPivotTableRenderedRange()
{
// https://github.com/ClosedXML/ClosedXML/pull/856
using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Other\PivotTableReferenceFiles\ClearPivotTableRenderedRangeWhenLoading\inputfile.xlsx")))
Expand All @@ -787,6 +789,34 @@ public void ClearPivotTableTenderedTange()
}
}

[Test]
public void Add_TwoPivotTablesWithSameRangeUseSamePivotCache()
{
using var wb = new XLWorkbook();
var ws = wb.AddWorksheet();
var range = ws.FirstCell().InsertData(new object[]
{
("Name", "Count"),
("Pie", 14),
});

var rangePivot1 = ws.PivotTables.Add("rangePivot1", ws.Cell("D1"), range);
var rangePivot2 = ws.PivotTables.Add("rangePivot2", ws.Cell("D20"), range);

Assert.AreNotSame(rangePivot1, rangePivot2);
Assert.AreSame(rangePivot1.PivotCache, rangePivot2.PivotCache);

var table = range.CreateTable();
var tablePivot1 = ws.PivotTables.Add("tablePivot1", ws.Cell("J1"), table);
var tablePivot2 = ws.PivotTables.Add("tablePivot2", ws.Cell("J20"), table);

Assert.AreNotSame(tablePivot1, tablePivot2);
Assert.AreSame(tablePivot1.PivotCache, tablePivot2.PivotCache);

// Table has a different cache, because unlike range, the size of a table can change.
Assert.AreNotSame(rangePivot1.PivotCache, tablePivot2.PivotCache);
}

private static void SetFieldOptions(IXLPivotField field, bool withDefaults)
{
field.SubtotalsAtTop = !withDefaults;
Expand Down
Binary file modified ClosedXML.Tests/Resource/Examples/PivotTables/PivotTables.xlsx
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6 changes: 3 additions & 3 deletions ClosedXML.Tests/Utils/PivotTableComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ public bool Equals(XLPivotTable x, XLPivotTable y)
&& x.PrintExpandCollapsedButtons.Equals(y.PrintExpandCollapsedButtons)
&& x.RepeatRowLabels.Equals(y.RepeatRowLabels)
&& x.PrintTitles.Equals(y.PrintTitles)
&& x.Source.SaveSourceData.Equals(y.Source.SaveSourceData)
&& x.PivotCache.SaveSourceData.Equals(y.PivotCache.SaveSourceData)
&& x.EnableShowDetails.Equals(y.EnableShowDetails)
&& x.Source.RefreshDataOnOpen.Equals(y.Source.RefreshDataOnOpen)
&& x.Source.ItemsToRetainPerField.Equals(y.Source.ItemsToRetainPerField)
&& x.PivotCache.RefreshDataOnOpen.Equals(y.PivotCache.RefreshDataOnOpen)
&& x.PivotCache.ItemsToRetainPerField.Equals(y.PivotCache.ItemsToRetainPerField)
&& x.EnableCellEditing.Equals(y.EnableCellEditing)
&& x.ShowRowHeaders.Equals(y.ShowRowHeaders)
&& x.ShowColumnHeaders.Equals(y.ShowColumnHeaders)
Expand Down
7 changes: 6 additions & 1 deletion ClosedXML/Excel/CalcEngine/XLRangeAddressComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ internal class XLRangeAddressComparer : IEqualityComparer<IXLRangeAddress>
{
private readonly XLAddressComparer _addressComparer;

public XLRangeAddressComparer(bool ignoreFixed)
/// <summary>
/// Comparer of ranges that ignores whether row/column is fixes or not.
/// </summary>
internal static readonly XLRangeAddressComparer IgnoreFixed = new(true);

private XLRangeAddressComparer(bool ignoreFixed)
{
_addressComparer = new XLAddressComparer(ignoreFixed);
}
Expand Down
20 changes: 19 additions & 1 deletion ClosedXML/Excel/IXLWorkbook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,33 @@ public interface IXLWorkbook : IXLProtectable<IXLWorkbookProtection, XLWorkbookP
Double ColumnWidth { get; set; }

IXLCustomProperties CustomProperties { get; }

Boolean DefaultRightToLeft { get; }

Boolean DefaultShowFormulas { get; }

Boolean DefaultShowGridLines { get; }

Boolean DefaultShowOutlineSymbols { get; }

Boolean DefaultShowRowColHeaders { get; }

Boolean DefaultShowRuler { get; }

Boolean DefaultShowWhiteSpace { get; }

Boolean DefaultShowZeros { get; }

IXLFileSharing FileSharing { get; }

Boolean ForceFullCalculation { get; set; }

Boolean FullCalculationOnLoad { get; set; }

Boolean FullPrecision { get; set; }

Boolean LockStructure { get; set; }

Boolean LockWindows { get; set; }

/// <summary>
Expand All @@ -60,7 +74,11 @@ public interface IXLWorkbook : IXLProtectable<IXLWorkbookProtection, XLWorkbookP
/// </summary>
IXLPageSetup PageOptions { get; set; }

IXLPivotSources PivotSources { get; }
/// <summary>
/// Gets all pivot caches in a workbook. A one cache can be
/// used by multiple tables. Unused caches are not saved.
/// </summary>
IXLPivotCaches PivotCaches { get; }

/// <summary>
/// Gets or sets the workbook's properties.
Expand Down
70 changes: 70 additions & 0 deletions ClosedXML/Excel/PivotTables/IXLPivotCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;

namespace ClosedXML.Excel
{
/// <summary>
/// A cache of pivot data - essentially a collection of fields and their values that can be
/// displayed by a <see cref="IXLPivotTable"/>. Data for the cache are retrieved from
/// an area (a table or a range). The pivot cache data are <strong>cached</strong>, i.e.
/// the data in the source are not immediately updated once the data in a worksheet change.
/// </summary>
public interface IXLPivotCache
{
/// <summary>
/// Get names of all fields in the source, in left to right order. Every field name is unique.
/// </summary>
/// <remarks>
/// The field names are case insensitive. The field names of the cached
/// source might differ from actual names of the columns
/// in the data cells.
/// </remarks>
IReadOnlyList<String> FieldNames { get; }

/// <summary>
/// Gets the number of unused items in shared items to allow before discarding unused items.
/// </summary>
/// <remarks>
/// Shared items are distinct values of a source field values. Updating them can be expensive
/// and this controls, when should the cache be updated. Application-dependent attribute.
/// </remarks>
/// <value>Default value is <see cref="XLItemsToRetain.Automatic"/>.</value>
XLItemsToRetain ItemsToRetainPerField { get; set; }

/// <summary>
/// Will Excel refresh the cache when it opens the workbook.
/// </summary>
/// <value>Default value is <c>false</c>.</value>
Boolean RefreshDataOnOpen { get; set; }

/// <summary>
/// Should the cached values of the pivot source be saved into the workbook file?
/// If source data are not saved, they will have to be refreshed from the source
/// reference which might cause a change in the table values.
/// </summary>
/// <value>Default value is <c>true</c>.</value>
Boolean SaveSourceData { get; set; }

/// <summary>
/// Refresh data in the pivot source from the source reference data.
/// </summary>
IXLPivotCache Refresh();

/// <inheritdoc cref="ItemsToRetainPerField"/>
IXLPivotCache SetItemsToRetainPerField(XLItemsToRetain value);

/// <inheritdoc cref="RefreshDataOnOpen"/>
/// <remarks>Sets the value to <c>true</c>.</remarks>
IXLPivotCache SetRefreshDataOnOpen();

/// <inheritdoc cref="RefreshDataOnOpen"/>
IXLPivotCache SetRefreshDataOnOpen(Boolean value);

/// <inheritdoc cref="SaveSourceData"/>
/// <remarks>Sets the value to <c>true</c>.</remarks>
IXLPivotCache SetSaveSourceData();

/// <inheritdoc cref="SaveSourceData"/>
IXLPivotCache SetSaveSourceData(Boolean value);
}
}
18 changes: 18 additions & 0 deletions ClosedXML/Excel/PivotTables/IXLPivotCaches.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;

namespace ClosedXML.Excel
{
/// <summary>
/// A collection of <see cref="IXLPivotCache">pivot caches</see>. Pivot cache
/// can be added from a <see cref="IXLRange"/> or a <see cref="IXLTable"/>.
/// </summary>
public interface IXLPivotCaches : IEnumerable<IXLPivotCache>
{
/// <summary>
/// Add a new pivot cache.
/// </summary>
/// <param name="range">Range for which to create the pivot cache.</param>
/// <returns>The pivot cache for the range.</returns>
IXLPivotCache Add(IXLRange range);
}
}
28 changes: 0 additions & 28 deletions ClosedXML/Excel/PivotTables/IXLPivotSource.cs

This file was deleted.

0 comments on commit b21ee00

Please sign in to comment.