I've developed the following code, but is not fully acomplishing what I need.
I would like to have the chance to set some specific datalabels of this serie
public static void main(String[] args) {
Workbook wb = new XSSFWorkbook();
Sheet dataSheet = wb.createSheet("linechart");
final int NUM_OF_ROWS = 10;
final int NUM_OF_COLUMNS = 4;
Row row;
Cell cell;
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = dataSheet.createRow((short) rowIndex);
for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
if (colIndex<3) {
cell = row.createCell((short) colIndex);
cell.setCellValue(rowIndex * ((colIndex + 1) + ((int) (Math.random() * 10))));
}
else{
if (rowIndex == 0){
cell = row.createCell((short) colIndex);
cell.setCellValue("This is the first comment");
}
else if (rowIndex == 3){
cell = row.createCell((short) colIndex);
cell.setCellValue("This is another comment");
}
}
}
}
Drawing drawing = dataSheet.createDrawingPatriarch();
ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, NUM_OF_COLUMNS + 2, 3, NUM_OF_COLUMNS + 15, 20);
//XSSFChart xlsxChart = XSSFChart.createChart();
Chart chart = drawing.createChart(anchor);
ChartLegend legend = chart.getOrCreateLegend();
legend.setPosition(LegendPosition.RIGHT);
LineChartData data = chart.getChartDataFactory().createLineChartData();
ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
ChartDataSource<Number> xs = DataSources.fromNumericCellRange(dataSheet, new CellRangeAddress(0, NUM_OF_ROWS - 1, 0, 0));
ChartDataSource<Number> ys1 = DataSources.fromNumericCellRange(dataSheet, new CellRangeAddress(0, NUM_OF_ROWS - 1, 1, 1));
ChartDataSource<Number> ys2 = DataSources.fromNumericCellRange(dataSheet, new CellRangeAddress(0, NUM_OF_ROWS - 1, 2, 2));
// Not used: ChartDataSource<String> ys3 = DataSources.fromStringCellRange(dataSheet, new CellRangeAddress(0, NUM_OF_ROWS - 1, 3, 3));
LineChartSeries series1 = data.addSeries(xs, ys1);
series1.setTitle("one");
LineChartSeries series2 = data.addSeries(xs, ys2);
series2.setTitle("two");
chart.plot(data, bottomAxis, leftAxis); // creating the basics of the graph
XSSFChart xssfChart = (XSSFChart) chart;
CTPlotArea plotArea = xssfChart.getCTChart().getPlotArea();
CTBoolean ctBool = CTBoolean.Factory.newInstance();
ctBool.setVal(true);
plotArea.getLineChartArray(0).getSerArray(0).addNewDLbls().setShowVal(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().addNewShowLeaderLines();
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowLeaderLines(ctBool);
ctBool.setVal(false);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowSerName(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowPercent(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowLegendKey(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowCatName(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowLeaderLines(ctBool);
plotArea.getLineChartArray(0).getSerArray(0).getDLbls().setShowBubbleSize(ctBool);
// Adding "markers" on each point
CTMarker ctMarker = CTMarker.Factory.newInstance();
ctMarker.setSymbol(CTMarkerStyle.Factory.newInstance());
for (CTLineSer ser : plotArea.getLineChartArray()[0].getSerArray()) {
ser.setMarker(ctMarker);
}
try{
FileOutputStream fileOut = new FileOutputStream("D:" + File.separator + "stack_v2.xlsx");
wb.write(fileOut);
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Generating the following output
Output from the code
But what I want, is having the showLeaderLines option activated inside the CDTLbsls structure
See the following desired output:
Desired output
I need help on:
Clarifying Apache-POI classes involved (CDTLbl, CDTLbls, CTLine, DLbl & DLbls)
How to achieved the ShowLeaderLines functionality
How to set specific content rather than the value
Located them at a specific point of the graph (as you may see, I would like to add it close to the top)
In the end, I find it was not possible to achieve the same functionality as with MS Office 2016 because the libs are not enough developed.
Ways tried between others was modifying directly the XML output... but some tags are not available yet.
Related
Read pptx template then use new data to replace it, there is a scatter chart
associated excel data
xVal and yVal could replace successfully but how to replace C column (extList) ?
xVal and yVal replace by below manner
final CTScatterSer ser = serList.get(0);
final CTAxDataSource xVal = ser.getXVal();
final CTNumDataSource yVal = ser.getYVal();
final CTExtension ctExtension = ser.getExtLst().getExtList().get(0);
final long ptCount = xVal.getNumRef().getNumCache().getPtCount().getVal();
for (int i = 0; i < scData.size(); i++) {
SCNameDouble data = scData.get(i);
CTNumVal xNumVal = ptCount > i ? xVal.getNumRef().getNumCache().getPtArray(i)
: xVal.getNumRef().getNumCache().addNewPt();
xNumVal.setIdx(i);
xNumVal.setV(String.format("%.2f", data.xValue));
CTNumVal yNumVal = ptCount > i ? yVal.getNumRef().getNumCache().getPtArray(i)
: yVal.getNumRef().getNumCache().addNewPt();
yNumVal.setIdx(i);
yNumVal.setV(String.format("%.2f", data.yValue));
}
final int newSize = scData.size();
xVal.getNumRef().setF(
replaceRowEnd(xVal.getNumRef().getF(),
ptCount,
newSize));
yVal.getNumRef().setF(
replaceRowEnd(yVal.getNumRef().getF(),
ptCount,
newSize));
xVal.getNumRef().getNumCache().getPtCount().setVal(newSize);
yVal.getNumRef().getNumCache().getPtCount().setVal(newSize);
Using current apache poi versions one should not trying manipulating charts using the low level CT... classes. There is XDDF for such cases now.
If it comes to PowerPoint charts, then the need is always updating the data in the embedded workbook and updating the data in the chart. See Java edit bar chart in ppt by using poi for an example using bar chart.
Of course a scatter chart is another case then as it not has a category axis but has two value axes. But this also can be updated using XDDF.
The biggest problem you have is the data labels. There is not full support for chart data labels in XDDF upto now. And since you are talkig about extLst and your Excel table shows the data labels in a cell range, I suspect you have set the data labels comming from a cell range. This is a new feature which was not present when Microsoft had published Office Open XML. So not even the low level CT... classes are able to support that feature.
The only way is to manipulate the XML using pure XML manupulating based on org.apache.xmlbeans.XmlObject.
The following shows this on sample of a template you seems to use according to your question.
ScatterChartSample.pptx:
Code:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.AreaReference;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
public class PowerPointChangeScatterChartData {
//patched version of XSSFTable.updateHeaders, see https://stackoverflow.com/questions/55532006/renaming-headers-of-xssftable-with-apache-poi-leads-to-corrupt-xlsx-file/55539181#55539181
static void updateHeaders(XSSFTable table) {
XSSFSheet sheet = (XSSFSheet)table.getParent();
CellReference ref = table.getStartCellReference();
if (ref == null) return;
int headerRow = ref.getRow();
int firstHeaderColumn = ref.getCol();
XSSFRow row = sheet.getRow(headerRow);
DataFormatter formatter = new DataFormatter();
if (row != null /*&& row.getCTRow().validate()*/) {
int cellnum = firstHeaderColumn;
CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
if(ctTableColumns != null) {
for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
XSSFCell cell = row.getCell(cellnum);
if (cell != null) {
col.setName(formatter.formatCellValue(cell));
}
cellnum++;
}
}
}
}
static void updateScatterChart(XSLFChart chart, Object[][] data) throws Exception {
// get chart's data source which is a Excel sheet
XSSFWorkbook chartDataWorkbook = chart.getWorkbook();
String sheetName = chartDataWorkbook.getSheetName(0);
XSSFSheet chartDataSheet = chartDataWorkbook.getSheet(sheetName);
// current Office uses a table as data source
// so get that table if present
XSSFTable chartDataTable = null;
if (chartDataSheet.getTables().size() > 0) {
chartDataTable = chartDataSheet.getTables().get(0);
}
if (chart.getChartSeries().size() == 1) { // we will process only one chart data
XDDFChartData chartData = chart.getChartSeries().get(0);
if (chartData.getSeriesCount() == 1) { // we will process only templates having one series
int rMin = 1; // first row (0) is headers row
int rMax = data.length - 1;
// column 0 is X-Values
int c = 0;
// set new x data
XDDFDataSource xs = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((Double)data[r][c]); // in sheet
}
xs = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart
// set new x-title in sheet
String xTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(xTitle); // in sheet
// column 1 is Y-Values
c = 1;
// set new y data in sheet and in chart
XDDFNumericalDataSource<Double> ys = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((Double)data[r][c]); // in sheet
}
ys = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c));
XDDFChartData.Series series1 = chartData.getSeries(0);
series1.replaceData(xs, ys); // in chart
// set new y-title in sheet and in chart
String yTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(yTitle); // in sheet
series1.setTitle(yTitle, new CellReference(sheetName, 0, c, true, true)); // in chart
series1.plot();
// column 2 is data-labels-range
c = 2;
// set new data labels data in sheet and in chart
XDDFDataSource dataLabelsRangeSource = null;
for (int r = rMin; r <= rMax; r++) {
XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
cell.setCellValue((String)data[r][c]); // in sheet
}
dataLabelsRangeSource = XDDFDataSourcesFactory.fromStringCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart
updateDataLabelsRange(chart, dataLabelsRangeSource); // in chart
// set new data-labels-title in sheet
String descrTitle = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(descrTitle); // in sheet
// update the table if present
if (chartDataTable != null) {
CellReference topLeft = new CellReference(chartDataSheet.getRow(0).getCell(0));
CellReference bottomRight = new CellReference(chartDataSheet.getRow(rMax).getCell(c));
AreaReference tableArea = chartDataWorkbook.getCreationHelper().createAreaReference(topLeft, bottomRight);
chartDataTable.setArea(tableArea);
updateHeaders(chartDataTable);
}
}
}
}
static void updateDataLabelsRange(XDDFChart chart, XDDFDataSource dataLabelsRangeSource) {
String declareNameSpaces = "declare namespace c='http://schemas.openxmlformats.org/drawingml/2006/chart'; "
+ "declare namespace c15='http://schemas.microsoft.com/office/drawing/2012/chart' ";
org.apache.xmlbeans.XmlObject[] selectedObjects = chart.getCTChart().selectPath(
declareNameSpaces
+ ".//c:ext[c15:datalabelsRange]"); // needs net.sf.saxon - Saxon-HE (Saxon-HE-10.6.jar)
if (selectedObjects.length > 0) { // we have at least one ext containing datalabelsRange
org.apache.xmlbeans.XmlObject ext = selectedObjects[0]; // get first ext containing datalabelsRange
// get dataLabelsRange
org.apache.xmlbeans.XmlObject[] datalabelsRanges = ext.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "datalabelsRange", "c15"));
org.apache.xmlbeans.XmlObject dataLabelsRange = datalabelsRanges[0];
// set formula
org.apache.xmlbeans.XmlObject[] formulas = dataLabelsRange.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "f", "c15"));
org.apache.xmlbeans.XmlObject formula = formulas[0];
((org.apache.xmlbeans.impl.values.XmlObjectBase)formula).setStringValue(dataLabelsRangeSource.getFormula());
// get dlblRangeCache
org.apache.xmlbeans.XmlObject[] dlblRangeCaches = dataLabelsRange.selectChildren(new javax.xml.namespace.QName("http://schemas.microsoft.com/office/drawing/2012/chart", "dlblRangeCache", "c15"));
org.apache.xmlbeans.XmlObject dlblRangeCache = dlblRangeCaches[0];
// empty the cache
dlblRangeCache.newCursor().removeXmlContents();
// create new cache from dataLabelsRangeSource
org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData cache = org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData.Factory.newInstance();
dataLabelsRangeSource.fillStringCache(cache);
// set new cache
dlblRangeCache.set(cache);
}
}
public static void main(String[] args) throws Exception {
String filePath = "ScatterChartSample.pptx"; // has template scatter chart
String filePathNew = "ScatterChartSample_New.pptx";
Object[][] data = new Object[][] { // new data 1 series, 6 x-y-values and data labels
{"X-Values", "Y-Values", "DataLabels"}, // series title
{0.7d, 1.7d, "aa"}, // x1
{1.8d, 3.2d, "bb"}, // x2
{2.6d, 2.8d, "cc"}, // x3
{1.7d, 3.7d, "dd"}, // x4
{2.8d, 4.2d, "ee"}, // x5
{3.6d, 1.8d, "ff"} // x6
};
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream(filePath));
XSLFChart chart = slideShow.getCharts().get(0);
updateScatterChart(chart, data);
FileOutputStream out = new FileOutputStream(filePathNew);
slideShow.write(out);
out.close();
slideShow.close();
}
}
Resulting ScatterChartSample_New.pptx:
Note: Tested and works using current apache poi 5.2.0.
To be able to use XPath as .//c:ext[c15:datalabelsRange] it needs net.sf.saxon - Saxon-HE (Saxon-HE-10.6.jar in my case).
And it needs poi-ooxml-full-5.2.0.jar and not only the lite version of ooxml-schemas.
this is how I generated Excel variable:
Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook excelworkBook;
Microsoft.Office.Interop.Excel.Worksheet excelSheet;
Microsoft.Office.Interop.Excel.Range excelCellrange;
excelworkBook = excel.Application.Workbooks.Add(Type.Missing);
excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelworkBook.ActiveSheet;
//proper data from xml data field to store in excel
// ************************
//generate a data table and fill excell cell
excelCellrange = excelSheet.Range[excelSheet.Cells[1, 1],
excelSheet.Cells[theDataSet.Tables[0].Rows.Count+1, theDataSet.Tables[0].Columns.Count]];
excelCellrange.Font.ThemeColor =
Microsoft.Office.Interop.Excel.XlThemeColor.xlThemeColorAccent1;
excelCellrange.EntireColumn.AutoFit();
Microsoft.Office.Interop.Excel.Borders border = excelCellrange.Borders;
border.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
border.Weight = 3d;
excel.Columns.AutoFit();
excel.DisplayAlerts = false;
excel.Visible = true;
now the question is how save this excel variable to real excel file to specific destination ?
best regards.
this is how I figure out this problem that maybe help someone else:
Microsoft.Office.Interop.Excel.Application excel = new
Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook excelworkBook;
Microsoft.Office.Interop.Excel.Worksheet excelSheet;
Microsoft.Office.Interop.Excel.Range excelCellrange;
excelworkBook = excel.Application.Workbooks.Add(Type.Missing);
excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelworkBook.ActiveSheet;
XmlDocument xml = new XmlDocument();
xml.LoadXml(dtAll.Rows[gridEX1.CurrentRow.RowIndex]["Data"].ToString());
StringReader theReader = new StringReader(xml.InnerXml);
DataSet theDataSet = new DataSet();
theDataSet.ReadXml(theReader);
excel.Caption = dtAll.Rows[gridEX1.CurrentRow.RowIndex]["docID"].ToString() + "_" + dtAll.Rows[gridEX1.CurrentRow.RowIndex]["DocTitle"].ToString() + "_" + dtAll.Rows[gridEX1.CurrentRow.RowIndex]["DocDetails"].ToString();
excel.StandardFont = "B Nazanin";
excel.StatusBar = "col count: " + theDataSet.Tables[0].Rows.Count;
for (int i = 0; i < theDataSet.Tables[0].Columns.Count; i++)
{
excel.Cells[1, i + 1] = theDataSet.Tables[0].Columns[i].ColumnName;
excel.Cells[1, i + 1].Font.Color = System.Drawing.Color.Blue;
for (int j = 0; j < theDataSet.Tables[0].Rows.Count; j++)
{
excel.Cells[j + 2, i + 1].Font.Color = System.Drawing.Color.Black;
excel.Cells[j + 2, i + 1] = theDataSet.Tables[0].Rows[j][i].ToString();
}
}
excelCellrange = excelSheet.Range[excelSheet.Cells[1, 1], excelSheet.Cells[theDataSet.Tables[0].Rows.Count + 1, theDataSet.Tables[0].Columns.Count]];
excelCellrange.Font.ThemeColor = Microsoft.Office.Interop.Excel.XlThemeColor.xlThemeColorAccent1;
excelCellrange.EntireColumn.AutoFit();
Microsoft.Office.Interop.Excel.Borders border = excelCellrange.Borders;
border.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
border.Weight = 3d;
byte[] output = Encoding.UTF8.GetBytes(xml.InnerXml);
string strfn = GroupTitle + ".xlsx";
if (File.Exists(strfn))
{
File.Delete(strfn);
}
FileStream fs = new FileStream(strfn, FileMode.CreateNew, FileAccess.Write);
fs.Write(output, 0, output.Length);
fs.Flush();
fs.Close();
string dest = Global.DocOutputPath.Substring(0, Global.DocOutputPath.Length - 1) + GroupTitle + ".xlsx";
if (File.Exists(dest))
{
DialogResult dialogResult = MessageBox.Show("are you sure about rewrite on file?", "warnning", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
File.Copy(strfn, dest);
File.Delete(dest);
}
else if (dialogResult == DialogResult.No)
{
return;
}
}
else
{
File.Copy(strfn, dest);
}
I export excel using Apache POI with drop down and many cell comments in it. When generating less cell comments, drop down's options appear for choosing when clicking drop down arrow, but when generating much cell comments, the drop down's options don't appear.
I do test with latest Apache POI version - 4.1.0.
HSSFWorkbook wb=new HSSFWorkbook();
HSSFSheet sheet=wb.createSheet("my sheet");
// create cell and add comments
int rowNum = 25;
int columnNum = 50;
HSSFPatriarch p=sheet.createDrawingPatriarch();
for (int i=0; i<rowNum; i++) {
HSSFRow row = sheet.createRow(i);
for (int j=0; j<columnNum; j++) {
HSSFCell cell = row.createCell(j);
cell.setCellValue(new HSSFRichTextString((i+1)+","+(j+1)));
if (i != 0 || j != 0) {
HSSFComment comment=p.createComment(new HSSFClientAnchor(0,0,0,0,(short)3,3,(short)5,6));
comment.setString(new HSSFRichTextString("comment for cell: " + (i+1) +","+(j+1)));
cell.setCellComment(comment);
}
}
}
// add drop down
String hiddenSheetName = "hiddenSheet";
HSSFSheet hiddenSheet = wb.createSheet(hiddenSheetName);
wb.setSheetHidden(wb.getSheetIndex(hiddenSheet), true);
HSSFRow hiddenRow = null;
HSSFCell hiddenCell = null;
String[] menuItems = {"Yes", "No"};
for (int i = 0; i < menuItems.length; i++)
{
hiddenRow = hiddenSheet.createRow(i);
hiddenCell = hiddenRow.createCell(0);
hiddenCell.setCellValue(menuItems[i]);
}
HSSFName namedCell = wb.createName();
String formulaId = "formulaId";
namedCell.setNameName(formulaId);
namedCell.setRefersToFormula(hiddenSheetName + "!A$1:A$" + menuItems.length);
HSSFDataValidationHelper dvHelper = new HSSFDataValidationHelper(sheet);
DataValidationConstraint dvConstraint = dvHelper.createFormulaListConstraint(formulaId);
CellRangeAddressList addressList = new CellRangeAddressList(0, 0, 0, 0);
HSSFDataValidation validation = (HSSFDataValidation)dvHelper.createValidation(dvConstraint, addressList);
sheet.addValidationData(validation);
FileOutputStream out = null;
try{
out=new FileOutputStream("exportExcelTest.xls");
wb.write(out);
out.close();
} catch (Exception e) {
}
From my test result, when commenting out cell comments generation part, drop down's options appear for choosing when clicking drop down arrow and when setting variable rowNum to 15, the drop down's options also appear. When setting variable rowNum to 25, then it will generate much more cell comments, then drop down's options don't appear. And if setting rowNum to be greater than 25, options also don't appear. So I infer this problem is related to number of comments generated. I am not sure if this is an Apache POI issue or there is anything I write incorrectly in code. Hope someone can help me to figure out this problem.
These numbers I give to rowNum are just for test, you can give any rowNum to try.
I think it has nothing to do with the comments and the number of comments.
By using new CellRangeAddressList(0, 0, 0, 0) you apply the validation to exactly one cell (A1).
By changing it to new CellRangeAddressList(0, rowNum - 1, 0, columnNum - 1) you apply the validation to all the cells that are created.
The second thing you should change is your formula. Instead of !A$1:A$ you should write !$A$1:$A$.
I'm trying to create an Excel table in a streamed workbook (SXSSFWorkbook). It is not supported directly by the API but I've had some success by accessing the underlying XSSFWorkbook (workbook.getXSSFWorkbook).
When I open the file in Excel (2007), it says "Excel found unreadable content in "test.xlsx". Do you want to recover the contents of this workbook?". Clicking yes successfully repairs the workbook and I get the correct result.
Log says "Repaired Records: Table from /xl/tables/table1.xml part (Table)".
Anyone has an idea on how I could avoid the Excel error?
Below is an example:
public class SXSSFTest {
private static final int NB_ROWS = 5;
private static final int NB_COLS = 5;
public static void main(String[] args) throws Exception {
try (SXSSFWorkbook workbook = new SXSSFWorkbook();
FileOutputStream outputStream = new FileOutputStream("C:\\test.xlsx")) {
SXSSFSheet sheet = workbook.createSheet();
fillSheet(sheet);
String dataRange = new AreaReference(
new CellReference(0, 0),
new CellReference(NB_ROWS - 1, NB_COLS - 1))
.formatAsString();
CTTable cttable = workbook.getXSSFWorkbook()
.getSheetAt(0)
.createTable()
.getCTTable();
CTTableStyleInfo tableStyle = cttable.addNewTableStyleInfo();
tableStyle.setName("TableStyleMedium17");
cttable.setRef(dataRange);
cttable.setDisplayName("TABLE");
cttable.setName("TABLE");
cttable.setId(1L);
CTTableColumns columns = cttable.addNewTableColumns();
columns.setCount(NB_COLS);
for (int c = 0; c < NB_COLS; c++) {
CTTableColumn column = columns.addNewTableColumn();
column.setName("Column" + c);
column.setId(c + 1L);
}
cttable.setAutoFilter(CTAutoFilter.Factory.newInstance());
workbook.write(outputStream);
}
}
private static void fillSheet(SXSSFSheet sheet) {
for (int rowNb = 0; rowNb < NB_ROWS; rowNb++) {
SXSSFRow row = sheet.createRow(rowNb);
for (int colNb = 0; colNb < NB_COLS; colNb++) {
SXSSFCell cell = row.createCell(colNb);
cell.setCellValue("Cell-" + colNb);
}
}
}
}
The cell values in the first row of the table must correspond with the column names.
Your code in main method names the columns Column0 ... Column4 but your code in fillSheet method writes "Cell-0" ... "Cell-4" into the cells of first row. This does not match.
You could change the fillSheet method like this:
...
private static void fillSheet(SXSSFSheet sheet) {
for (int rowNb = 0; rowNb < NB_ROWS; rowNb++) {
SXSSFRow row = sheet.createRow(rowNb);
for (int colNb = 0; colNb < NB_COLS; colNb++) {
SXSSFCell cell = row.createCell(colNb);
if (rowNb==0) cell.setCellValue("Column" + colNb); //first row are column names
else cell.setCellValue("Cell-" + colNb);
}
}
}
...
Here's an updated version that fixes several usages of deprecated methods (tested with POI 4.1.2). Note that it doesn't require creating columns and settings IDs manually anymore, everything is done by createTable(dataRange):
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
import java.io.FileOutputStream;
public class SXSSFTest {
private static final int NB_ROWS = 5;
private static final int NB_COLS = 5;
public static void main(String[] args) throws Exception {
try (SXSSFWorkbook workbook = new SXSSFWorkbook();
FileOutputStream outputStream = new FileOutputStream("C:\\test.xlsx")) {
SXSSFSheet sheet = workbook.createSheet();
fillSheet(sheet);
AreaReference dataRange = new AreaReference(
new CellReference(0, 0),
new CellReference(NB_ROWS - 1, NB_COLS - 1),
SpreadsheetVersion.EXCEL2007
);
CTTable cttable = workbook.getXSSFWorkbook()
.getSheetAt(0)
.createTable(dataRange)
.getCTTable();
CTTableStyleInfo tableStyle = cttable.addNewTableStyleInfo();
tableStyle.setName("TableStyleMedium17");
cttable.setDisplayName("TABLE");
cttable.setName("TABLE");
CTTableColumns columns = cttable.getTableColumns();
for (int c = 0; c < NB_COLS; c++) {
CTTableColumn column = columns.getTableColumnArray(c);
column.setName("Column title " + c);
}
cttable.setAutoFilter(CTAutoFilter.Factory.newInstance());
workbook.write(outputStream);
}
}
private static void fillSheet(SXSSFSheet sheet) {
for (int rowNb = 0; rowNb < NB_ROWS; rowNb++) {
SXSSFRow row = sheet.createRow(rowNb);
for (int colNb = 0; colNb < NB_COLS; colNb++) {
SXSSFCell cell = row.createCell(colNb);
if (rowNb == 0) {
cell.setCellValue("Column title " + colNb); //first row are column names
} else {
cell.setCellValue("Cell-" + colNb);
}
}
}
}
}
When adding a table, make sure that the names of the column headers are not repeated. If you have duplicates column names Excel throw this error and will fix it by renaming the 2nd duplicated column by adding a number a the end. See example below:
| QTY | UNIT # | QTY2 |
See that 2nd QTY was renamed to QTY2 by excel recovery process.
I'm trying to conditionally colorize ranges in a PivotTable like so:
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 7;
int DESCRIPTION_COL = 0;
int ROWS_BETWEEN_DESCRIPTIONS = 4;
var pivot = pivotTableSheet.PivotTables[0];
var dataBodyRange = pivot.DataBodyRange;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
int rowsUsed = dataBodyRange.EndRow;
pivot.RefreshData();
pivot.CalculateData();
// Loop through PivotTable data, colorizing contract items
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
// args are firstRow, firstColumn, totalRows, totalColumns
Range rangeToColorize = pivotTableSheet.Cells.CreateRange(
currentRowBeingExamined, 0,
ROWS_BETWEEN_DESCRIPTIONS, _grandTotalsColumnPivotTable + 2);
Style style = workBook.Styles[workBook.Styles.Add()];
style.BackgroundColor = CONTRACT_ITEM_COLOR;
StyleFlag styleFlag = new StyleFlag();
styleFlag.All = true;
rangeToColorize.ApplyStyle(style, styleFlag);
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
}
...but, although the if block is reached several times (such as where "rangeToColorize" spans A28:E31) it doesn't "take"; what is wrong with my styling or styleflagging or applying those?
UPDATE
Even when I changed it to this:
// Declare a style object.
Style style;
// Create/add the style object.
style = workBook.CreateStyle();
// To Set the fill color of the range, you may use ForegroundColor with
// Solid Pattern setting.
style.BackgroundColor = CONTRACT_ITEM_COLOR;
style.Pattern = BackgroundType.Solid;
// Create a StyleFlag object.
StyleFlag styleFlag = new StyleFlag();
// Make the corresponding attributes ON.
styleFlag.Font = true;
styleFlag.CellShading = true;
// Apply the style to the range.
rangeToColorize.ApplyStyle(style, styleFlag);
...based on the offical docs, it made no difference.
UPDATE 2
Even when I changed some very explicit code about one particular cell to this:
cell = pivotTableSheet.Cells[4, 0];
cell.PutValue(AnnualContractProductsLabel);
style = workBook.CreateStyle(); // cell.GetStyle();
style.HorizontalAlignment = TextAlignmentType.Center;
style.VerticalAlignment = TextAlignmentType.Center;
style.Font.IsBold = true;
pivotTableSheet.Cells.SetRowHeight(4, 25);
//style.BackgroundColor = CONTRACT_ITEM_COLOR;
style.ForegroundColor = CONTRACT_ITEM_COLOR;
pivotTableSheet.Cells[4, 0].SetStyle(style);
...IOW, changing "style = cell.GetStyle()" to "style = workBook.CreateStyle()" and "BackgroundColor" to "ForegroundColor", it does nothing; the cell in question is not colored.
UPDATE 3
Well, the (or an) odd thing is that the only way I've been able to color anything is in a manually generated "Grand Total" column:
As you can see, certain rows have been colorized, based on a condition. But only in that column, not across the entire row, as it should (theoretically, at least) be:
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 7;
int DESCRIPTION_COL = 0;
int ROWS_BETWEEN_DESCRIPTIONS = 4;
var pivot = pivotTableSheet.PivotTables[0];
var dataBodyRange = pivot.DataBodyRange;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
int rowsUsed = dataBodyRange.EndRow;
pivot.RefreshData();
pivot.CalculateData();
// Loop through PivotTable data, colorizing contract items
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
// args are firstRow, firstColumn, totalRows, totalColumns
Range rangeToColorize = pivotTableSheet.Cells.CreateRange(
currentRowBeingExamined, 0,
ROWS_BETWEEN_DESCRIPTIONS, _grandTotalsColumnPivotTable + 2);
// Declare a style object.
Style style;
// Create/add the style object.
style = workBook.CreateStyle();
style.ForegroundColor = CONTRACT_ITEM_COLOR; //Color.Red;
style.Pattern = BackgroundType.Solid;
// Create a StyleFlag object.
StyleFlag styleFlag = new StyleFlag();
// Make the corresponding attributes ON.
styleFlag.Font = true;
styleFlag.CellShading = true;
// Apply the style to the range.
rangeToColorize.ApplyStyle(style, styleFlag);
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
}
So that makes me think that a PivotTable cannot be colorized. Yet, even when I try to color a "plain old" cell like so:
cell = pivotTableSheet.Cells[4, 0];
cell.PutValue(AnnualContractProductsLabel);
style = workBook.CreateStyle();
style.HorizontalAlignment = TextAlignmentType.Center;
style.VerticalAlignment = TextAlignmentType.Center;
style.Font.IsBold = true;
pivotTableSheet.Cells.SetRowHeight(4, 25);
style.ForegroundColor = CONTRACT_ITEM_COLOR;
// Create a StyleFlag object.
StyleFlag styleFlag = new StyleFlag();
// Make the corresponding attributes ON.
styleFlag.Font = true;
styleFlag.CellShading = true;
// Apply the style to the cell
pivotTableSheet.Cells[4, 0].SetStyle(style, styleFlag);
...it doesn't work - the color is not added. Why does colorizing only work in the one circumstance, but not otherwise?
UPDATE 4
With this:
PivotTableCollection pivotTables = pivotTableSheet.PivotTables;
PivotTable pivotTable = pivotTables[0];
pivotTable.Format();
...I get, "No overload for method 'Format' takes 0 arguments."
...and also with simply this:
PivotTable.Format();
(capital "P", no "pivotTable" assigned), I get the same err msg.
UPDATE 5
Even with the following, based on the suggestion of another Aspose support person, it does nothing:
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 7;
int DESCRIPTION_COL = 0;
int ROWS_BETWEEN_DESCRIPTIONS = 4;
var pivot = pivotTableSheet.PivotTables[0];
var dataBodyRange = pivot.DataBodyRange;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
int rowsUsed = dataBodyRange.EndRow;
pivot.RefreshData();
pivot.CalculateData();
// Loop through PivotTable data, colorizing contract items
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
Style style;
style = workBook.CreateStyle();
style.ForegroundColor = CONTRACT_ITEM_COLOR; //Color.Red;
style.Pattern = BackgroundType.Solid;
StyleFlag styleFlag = new StyleFlag();
styleFlag.Font = true;
styleFlag.CellShading = true;
PivotTable pt = pivotTableSheet.PivotTables[0];
pt.Format(currentRowBeingExamined, 0, style); // test - does not work, either
pt.Format(currentRowBeingExamined, 1, style); // " "
//CellArea columnRange = pt.ColumnRange;
//for (int c = columnRange.StartColumn; c < columnRange.EndColumn; c++)
//{
// // pt.Format(columnRange.StartRow + 1, c, style);
// pt.Format(currentRowBeingExamined, c, style);
//}
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
}
UPDATE 6
I've got it pretty much working, now, with the following code:
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 7;
int DESCRIPTION_COL = 0;
int ROWS_BETWEEN_DESCRIPTIONS = 4;
var pivot = pivotTableSheet.PivotTables[0];
var dataBodyRange = pivot.DataBodyRange;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
int rowsUsed = dataBodyRange.EndRow;
pivot.RefreshData();
pivot.CalculateData();
// Loop through PivotTable data, colorizing contract items
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
Style style;
style = workBook.CreateStyle();
style.BackgroundColor = CONTRACT_ITEM_COLOR;
style.Pattern = BackgroundType.Solid;
StyleFlag styleFlag = new StyleFlag();
styleFlag.Font = true;
styleFlag.CellShading = true;
PivotTable pt = pivotTableSheet.PivotTables[0];
pt.Format(currentRowBeingExamined, 0, style);
pt.Format(currentRowBeingExamined, 1, style);
CellArea columnRange = pt.ColumnRange;
for (int c = columnRange.StartColumn; c <= columnRange.EndColumn; c++)
{
pt.Format(currentRowBeingExamined, c, style);
pt.Format(currentRowBeingExamined+1, c, style);
pt.Format(currentRowBeingExamined+2, c, style);
pt.Format(currentRowBeingExamined+3, c, style);
}
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
}
...but the second, third, and fourth rows of the Data column are not being colored:
Why not? How can I fix it?
UPDATE 7
I tried to get the elements of column B to color up, and tried changing this:
pt.Format(currentRowBeingExamined, 0, style);
pt.Format(currentRowBeingExamined, 1, style);
...to this:
pt.Format(currentRowBeingExamined, 0, style);
pt.Format(currentRowBeingExamined, 1, style);
pt.Format(currentRowBeingExamined, 2, style); // <= made no difference
pt.Format(currentRowBeingExamined, 3, style); // " "
...but it made no difference.
So then I wondered if the first two-line snippet above was necessary, but commenting those lines out caused columns A/0 and B/1 to not color up at all:
Here is the current code in context:
PivotTable pt = pivotTableSheet.PivotTables[0];
var style = workBook.CreateStyle();
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
style.BackgroundColor = CONTRACT_ITEM_COLOR;
style.Pattern = BackgroundType.Solid;
pt.Format(currentRowBeingExamined, 0, style);
pt.Format(currentRowBeingExamined, 1, style);
//pt.Format(currentRowBeingExamined, 2, style); <= made no difference
//pt.Format(currentRowBeingExamined, 3, style);
CellArea columnRange = pt.ColumnRange;
for (int c = columnRange.StartColumn; c <= columnRange.EndColumn; c++)
{
pt.Format(currentRowBeingExamined, c, style);
pt.Format(currentRowBeingExamined+1, c, style);
pt.Format(currentRowBeingExamined+2, c, style);
pt.Format(currentRowBeingExamined+3, c, style);
}
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
So how can I get the colorization to span all the columns, including the "Total Purchases", "Sum of Average Price", and "Percentage of Total" cells?
Please change the following two lines
Style style = workBook.CreateStyle();
style.BackgroundColor = CONTRACT_ITEM_COLOR;
into
Style style = workBook.CreateStyle();
style.ForegroundColor= CONTRACT_ITEM_COLOR;
It should fix your issue. Let us know your feedback.
UPDATE 2
Please try one of the following two methods that are for PivotTable specifically
PivotTable.Format()
PivotTable.FormatAll()
UPDATE 5
For PivotTable.Format(), you should use Style.BackgroundColor property instead of Style.ForegroundColor property. So change your line
style.ForegroundColor = CONTRACT_ITEM_COLOR; //Color.Red;
to
style.BackgroundColor = CONTRACT_ITEM_COLOR; //Color.Red;
and it should fix your issue. Thank you.
Note: I am working as Developer Evangelist at Aspose