ClosedXML PivotTable ReportFilter multiple values - c#

I am working on a piece of code to generate a pivot table in Excel.
This is the code:
using (XL.XLWorkbook workbook = new XL.XLWorkbook(sourceFile))
{
var outSheet = workbook.Worksheets.Add("output table");
outSheet.Cell(1, 1).InsertTable(dt, "out table", true);
var datarange = outSheet.RangeUsed();
var pivotSheet = workbook.Worksheets.Add("PivotTable");
var pivotTable = pivotSheet.PivotTables.AddNew("Pivot Table", pivotSheet.Cell(3, 1), datarange);
pivotTable.ReportFilters.Add("Filter1");
pivotTable.ReportFilters.Add("Filter2");
pivotTable.RowLabels.Add("RLabel");
pivotTable.ColumnLabels.Add("CLabel");
pivotTable.Values.Add("Value").SummaryFormula = XL.XLPivotSummary.Sum;
workbook.SaveAs(#"C:\Temp\Test.xlsx");
}
How would I go about to filter the values in "Filter1"?
For example, selecting only the values for "Unknown" and "Gcom".
In Excel the Pivot filter looks like this:
Excel Pivot Table Report Filter
I have checked all the ClosedXML documentation for pivots, the ReportFilters functionality is not mentioned.
Source code wiki example
Please advise, is this functionality even available?
Any advice/help is much appreciated.

Not sure when the functionality was added, but I got it to work with the following additions to your code:
using (XL.XLWorkbook workbook = new XL.XLWorkbook(sourceFile))
{
var outSheet = workbook.Worksheets.Add("output table");
outSheet.Cell(1, 1).InsertTable(dt, "out table", true);
var datarange = outSheet.RangeUsed();
var pivotSheet = workbook.Worksheets.Add("PivotTable");
var pivotTable = pivotSheet.PivotTables.AddNew("Pivot Table", pivotSheet.Cell(3, 1), datarange);
// I was not sure how to retrieve the filter after adding, but found Add() returns it for you.
var filter1 = pivotTable.ReportFilters.Add("Filter1");
// Now add your filter selection.
filter1.AddSelectedValue("Unknown");
filter1.AddSelectedValue("GCom");
pivotTable.ReportFilters.Add("Filter2");
pivotTable.RowLabels.Add("RLabel");
pivotTable.ColumnLabels.Add("CLabel");
pivotTable.Values.Add("Value").SummaryFormula = XL.XLPivotSummary.Sum;
workbook.SaveAs(#"C:\Temp\Test.xlsx");
}

Related

Does EPPlus support ConditionalFormatting for Pivot tables?

I am using EPPlus to create a pivot table and now I am trying to use ConditionalFormatting on the pivot table and it simply doesn't seem to work. I have taken the example from Three color scale example and am trying to apply it on the cells of the pivot table:
var rng = worksheet.Cells["N3:Y88"]; // (This is the output of the pivot table)
var cfRule = rng.Worksheet.ConditionalFormatting.AddThreeColorScale(rng);
cfRule.LowValue.Color = ColorTranslator.FromHtml("#FF63BE7B");
cfRule.MiddleValue.Color = ColorTranslator.FromHtml("#FFFFEB84");
cfRule.MiddleValue.Type = eExcelConditionalFormattingValueObjectType.Percentile;
cfRule.MiddleValue.Value = 50;
cfRule.HighValue.Color = ColorTranslator.FromHtml("#FFF8696B");
And there isn't any impact. I use the same code on a set of regular cells and it works fine. Does EPPlus support the ability to conditionalformat a pivot table?
I found the answer in Pivot Table support in Conditional Formatting #399.
public static void AddConditionalFormattingToPivotTable(ExcelPivotTable pivotTable)
{
var worksheetXml = pivotTable.WorkSheet.WorksheetXml;
var element = worksheetXml.GetElementsByTagName("conditionalFormatting")[0];
((XmlElement)element).SetAttribute("pivot", "1");
}

Multiple Pivot tables ClosedXML

Using latest Closed XML (0.76) on Net 4.5.1
Created a Worksheet with a table by:
DataTable Table = ...
var DataWorkSheet = Workbook.Worksheets.Any(x => x.Name == "Data") ?
Workbook
.Worksheets
.First(x => x.Name == "Data") :
Workbook
.Worksheets
.Add("Data");
int Start = ... // calculate cell start
var Source = DataWorkSheet
.Cell(Start, 1)
.InsertTable(Table, Name, true);
var Range = Source.DataRange;
This is done inside a loop (i.e. multiple tables in the "Data" sheet). A problem arises where the generated Excel document can't be opened if multiple pivot tables are created in a separate sheet.
var PivotWorkSheet = Workbook
.Worksheets
.Add(Name);
var Pivot = PivotWorkSheet
.PivotTables
.AddNew(Name, PivotWorkSheet.Cell(1, 1), DataRange);
Any ideas why and how to debug?
This is the same issue as in ClosedXML - Creating multiple pivot tables.
For the record, it's caused by ClosedXML bug which requires source code modification as in my answer of the linked question.

How to append a new row to an Excel file using C# and ClosedXML?

I should append a new row to an existing Excel file. The task consists of two parts:
Add to non-existing file (works well).
Add to existing file (doesn't work: it doesn't make NEW record, displaying only the old record from "else" body).
Here is my code:
private static void ExportToEXCEL(DataTable dt, string paymentStoryPath)
{
if (File.Exists(paymentStoryPath))
{
XLWorkbook currentWorkbook = new XLWorkbook(paymentStoryPath);
IXLWorksheet currentWsh = currentWorkbook.Worksheet("Payment history");
//IXLCell cellForNewData = index.Cell(index.LastRowUsed().RowNumber() + 1, 1);
IXLRow rowForNewData = currentWsh.Row(currentWsh.LastRowUsed().RowNumber()+1);
rowForNewData.InsertRowsBelow(1);
rowForNewData.Value = dt;
currentWorkbook.Save();
}
else
{
//not exist
XLWorkbook wb = new XLWorkbook();
wb.Worksheets.Add(dt, "Payment history");
wb.SaveAs(paymentStoryPath);
}
}
What is wrong and what should I change in my code?
To add a DataTable use the InsertTable() method:
XLWorkbook currentWorkbook = new XLWorkbook(paymentStoryPath);
IXLWorksheet currentWsh = currentWorkbook.Worksheet("Payment history");
IXLCell cellForNewData = currentWsh.Cell(currentWsh.LastRowUsed().RowNumber() + 1, 1);
cellForNewData.InsertTable(dt);
currentWorkbook.Save();
I've got the following code from one of my projects that inserts a DataTable into Excel.
//insert rows below a range from the cell going table rows down
ws.Range(
cell.Address.RowNumber
, cell.Address.ColumnNumber
, cell.Address.RowNumber + DocDataSet.Tables[tableNo].Rows.Count
, cell.Address.ColumnNumber)
.InsertRowsBelow(DocDataSet.Tables[tableNo].Rows.Count);
//InsertData returns a range covering the inserted data
var ra = ws.Cell(cell.Address.RowNumber, cell.Address.ColumnNumber)
.InsertData(DocDataSet.Tables[tableNo].AsEnumerable());
//apply the style of the table token cell to the whole range
ra.Style = cell.Style;
Its been a while since I wrote it, but as far as I know the idea is, create a range that will cover the rows and columns that will be populated. The Cell object has a InsertData method that can take any IEnumerable source.
You might not need the ws.Range line, I was inserting into a template so I had to create the space first.
I took #raidri example and took it one step further where I have an extension method to handle this.
public static class Extensions
{
public static void ToExcelFile(this DataTable dataTable, string filename, string worksheetName = "Sheet1")
{
using(var workbook = new XLWorkbook())
{
workbook.Worksheets.Add(dataTable, worksheetName);
workbook.SaveAs(filename);
}
}
}
Use
myDataTable.ToExcelFile(#"C:\temp\myFile.xlsx");

How to add values to a spreadsheet from a dictionary?

I have a template spreadsheet document that has two columns, Server Name and IP Address.
How can I populate the spreadsheet so that each dictionary key goes in its own cell in the Server column and the corresponding value goes in the cell next to it in the IP column?
I am using the EPPlus library but couldn't find anything on the topic.
Below is what I found and tried, but its for lists
using (ExcelPackage package = new ExcelPackage(_fileInfo))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
for (int i = 0; i < listOfIPs.Count; i++)
{
worksheet.Cells[i + 2, 1].Value = listOfIPs[i];
}
package.Save();
}
I am not familiar with EPPlus, therefore I am not sure how you get the reference to the active sheet - you need to figure out this bit though once that's done pure C# with a bit of knowledge about VBA model and you can easily avoid any iterations to get contents of your dictionary to a spreadsheet:
// create a sample dictionary and fill it
Dictionary<string, string> myCol = new Dictionary<string, string>();
myCol.Add("server1", "ip1");
myCol.Add("server2", "ip2");
// grab a reference to the active Sheet
// this may vary depending on what framework you are using
Worksheet ws = Globals.ThisWorkbook.ActiveSheet as Worksheet;
// create a Range variable
Range myRange;
// Transpose the keys to column A
myRange = ws.get_Range("A1");
myRange.get_Resize(myCol.Keys.ToArray().Count(),1).Value =
ws.Parent.Parent.Transpose(myCol.Keys.AsEnumerable().ToArray());
// transpose the Values to column B
myRange = ws.get_Range("B1");
myRange.get_Resize(myCol.Values.ToArray().Count(), 1).Value =
ws.Parent.Parent.Transpose(myCol.Values.AsEnumerable().ToArray());
Debugging results as expected
With EPPlus I think you can do it like this (untested)
using (ExcelPackage package = new ExcelPackage(file))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("test");
worksheet.Cells["A1"].LoadFromCollection(myColl, true, OfficeOpenXml.Table.TableStyles.Medium);
package.Save();
}
More details on VBA Collections iterations and printing to Sheet # vba4all.com
You can just access the keys of a dictionary and iterate over them just like you are iterating over the list.
var foo = new Dictionary<string, string>(); // populate your dictionary
foreach(var key in foo.Keys)
{
var value = foo[key];
// do stuff with 'key' and 'value', like put them into the excel doc
}

Using OpenXML, how can I associate a list for data validation

I am processing an .xlsm file and need to know how to use a list on another sheet for data validation using openXML and C#.
To start, I have a .xlsm file with two empty sheets and macros in it. In my program I open the file, Create the column header on Sheet1 then create the validation list on sheet2. So, after I run my program Sheet1 "A1" contains the text "Color" and Sheet2 "A1:A4" contains "Blue","Green","Red","Yellow". I get this far just fine.
I would like to make it so there is a dropdown list in all cells of column "A" on sheet1 that contains each of the 4 colors and enforces them as the only input. In Microsoft Excel this is done by going to the "Data" tab, selecting "Data Validation" selecting "List" and highlighting the cells you want to use. I need to make this association programmatically.
The (Desired) XML that Microsoft Excel creates if I do it manually is this:
<extLst>
<ext uri="{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main">
<x14:dataValidations count="1" xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">
<x14:dataValidation type="list" allowBlank="1" showInputMessage="1" showErrorMessage="1">
<x14:formula1>
<xm:f>'Validation Data'!$A$1:$A$4</xm:f>
</x14:formula1>
<xm:sqref>A1:A1048576</xm:sqref>
</x14:dataValidation>
</x14:dataValidations>
</ext>
</extLst>
The following method and results is something I tried. It may give a better Idea of what I'm trying to do.
Here, I pass in "'Sheet2'!$A$1:$A$4" as the "validationListCells" parameter. This represents the cells in "Sheet2" that, in this example, would contain the color names "Red", "Green"...etc.
I pass in "A2:A1048576" as the "cellsToValidate" parameter. This represents all cells of Sheet1 column "A", on which I want to enforce validation.
I pass "Sheet1" as the worksheetName parameter.
private void InsertValidation(String worksheetName, String validationListCells, String cellsToValidate)
{
DataValidations dataValidations1 = new DataValidations() { Count = (UInt32Value)1U };
DataValidation dataValidation1 = new DataValidation()
{
Formula1 = new Formula1(validationListCells),
Type = DataValidationValues.List,
ShowInputMessage = true,
ShowErrorMessage = true,
SequenceOfReferences = new ListValue<StringValue>() { InnerText = cellsToValidate }
};
dataValidations1.Append(dataValidation1);
using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(_documentPath, true))
{
WorksheetPart worksheetPart = GetWorksheetPartByName(spreadSheet, worksheetName);
worksheetPart.Worksheet.Append(dataValidations1);
worksheetPart.Worksheet.Save();
}
}
It results in this XML in Sheet1.xml. Which causes an error in Excel.
<x:dataValidations count="1">
<x:dataValidation type="list" showInputMessage="1" showErrorMessage="1" sqref="A2: A1048576">
<x:formula1>'Sheet2'!$A$1:$A$5</x:formula1>
</x:dataValidation>
</x:dataValidations>
It looks like I may be on the right track since it is beginning to resemble the xml created by Excel, but I'm completely new to openXML and I'm finding little about this topic on the net.
Thanks in advance!
For anyone else in need of this..the code below worked for me.
I put in there user3251089's variable names.
In general, when I try to programmatically create an excel "feature" I manually make a really basic excel that has in it that feature (delete extra sheets too). Then I reflect the code and try to make it prettier.
hope it serves to someone!
using Excel = DocumentFormat.OpenXml.Office.Excel;
using X14 = DocumentFormat.OpenXml.Office2010.Excel;
.....
Worksheet worksheet = worksheetPart.Worksheet;
WorksheetExtensionList worksheetExtensionList = new WorksheetExtensionList();
WorksheetExtension worksheetExtension = new WorksheetExtension() { Uri = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}" };
worksheetExtension.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main");
X14.DataValidations dataValidations = new X14.DataValidations() { Count = (UInt32Value)3U };
dataValidations.AddNamespaceDeclaration("xm", "http://schemas.microsoft.com/office/excel/2006/main");
//sites validation
dataValidations.Append(new X14.DataValidation()
{
Type = DataValidationValues.List,
AllowBlank = true,
ShowInputMessage = true,
ShowErrorMessage = true,
DataValidationForumla1 = new X14.DataValidationForumla1() { Formula = new Excel.Formula(validationListCells) },
ReferenceSequence = new Excel.ReferenceSequence(cellsToValidate)
});
worksheetExtension.Append(dataValidations);
worksheetExtensionList.Append(worksheetExtension);
worksheet.Append(worksheetExtensionList);
worksheet.Save();

Categories

Resources