Apache POI will not set superscript inside a TextBox - java

I need to use Apache POI(4.1.1) to write some text inside a textbox (or rectangle shape), and this text must have superscripts in it. I can get this to work for data inside a cell, but not when using a textbox. Here is a minimal example:
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.*;
import java.io.FileOutputStream;
public class SuperScriptTest {
public static void main(String[] args) throws Exception {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet ws = wb.createSheet("Sheet 1");
XSSFFont fontInfo = wb.createFont();
fontInfo.setFontName("Arial");
fontInfo.setFontHeightInPoints((short) 11);
XSSFFont fontInfoSuperscript = wb.createFont();
fontInfoSuperscript.setFontName("Arial");
fontInfoSuperscript.setFontHeightInPoints((short) 11);
fontInfoSuperscript.setTypeOffset(Font.SS_SUPER);
fontInfoSuperscript.setColor(IndexedColors.RED.getIndex());
Row row = ws.createRow(0);
Cell cell = row.createCell(0);
// Writing to a cell produces desired results:
XSSFRichTextString richString = new XSSFRichTextString("Level3");
richString.applyFont(fontInfo);
richString.applyFont(5, 6, fontInfoSuperscript);
cell.setCellValue(richString);
// Writing to a textbox does not:
XSSFDrawing drawing = ws.createDrawingPatriarch();
XSSFTextBox txtBox = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 0, 5, 2, 7));
XSSFRichTextString richString2 = new XSSFRichTextString("Level3");
richString2.applyFont(fontInfo);
richString2.applyFont(5, 6, fontInfoSuperscript);
txtBox.setText(richString2);
try (FileOutputStream fileOut = new FileOutputStream("Superscript.xlsx")) {
wb.write(fileOut);
}
}
}
The cell will give me the right font and size, and properly superscript the 3 and turn it red.
The textbox will give me the correct font and size, and will color the 3, but it will not superscript the 3.
Thank you for any help.

In Excel cells and text box shapes have different kinds of font settings. For Excel cells spreadsheet settings are used while for text box shapes drawing settings are used.
Because XSSFRichTextString mostly gets used for Excel cell values and shared strings, it internally uses spreadsheet settings. When it comes to XSSFRichTextStrings in context of shapes, the settings need to be converted. This is done in XSSFSimpleShape.setText(XSSFRichTextString str) using the method XSSFSimpleShape.applyAttributes(CTRPrElt pr, CTTextCharacterProperties rPr).
In spreadsheet settings CTRPrElt there is a CTVerticalAlignFontProperty used to set baseline, superscript or subscript. In drawing settings CTTextCharacterProperties there is a baseline attrtibute used which is 0 for baseline, +n% for superscript and -n% for subscript. There n% is the distance from the baseline.
Until now the XSSFSimpleShape.applyAttributes lacks converting CTVerticalAlignFontProperty to CTTextCharacterProperties.setBaseline. To get this, one could patching XSSFSimpleShape.applyAttributes like so:
private static void applyAttributes(CTRPrElt pr, CTTextCharacterProperties rPr) {
...
if (pr.sizeOfVertAlignArray() > 0) {
CTVerticalAlignFontProperty vertAlign = pr.getVertAlignArray(0);
if (vertAlign.getVal() == STVerticalAlignRun.BASELINE) {
rPr.setBaseline(0);
} else if (vertAlign.getVal() == STVerticalAlignRun.SUPERSCRIPT) {
rPr.setBaseline(30000); //30% distance from baseline == default superscript
} else if (vertAlign.getVal() == STVerticalAlignRun.SUBSCRIPT) {
rPr.setBaseline(-25000); //-25% distance from baseline == default subscript
}
}
}
For superscript 30% distance from baseline is choosen because that is the default when someone ticks text effect superscript in text box font settings. For subscript -25% distance from baseline is choosen because that is the default when someone ticks text effect subscript in text box font settings.
Complete example having own setText and applyAttributes to show that it works:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.main.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.*;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.*;
import org.apache.poi.hssf.util.HSSFColor;
import java.io.FileOutputStream;
public class SuperScriptTest {
static void setText(XSSFSimpleShape shape, XSSFRichTextString str) {
XSSFWorkbook wb = (XSSFWorkbook) shape.getDrawing().getParent().getParent();
//str.setStylesTableReference(wb.getStylesSource()); // cannot be done here since XSSFRichTextString.setStylesTableReference has protected access
CTTextParagraph p = CTTextParagraph.Factory.newInstance();
if (str.numFormattingRuns() == 0) {
CTRegularTextRun r = p.addNewR();
CTTextCharacterProperties rPr = r.addNewRPr();
rPr.setLang("en-US");
rPr.setSz(1100);
r.setT(str.getString());
} else {
for (int i = 0; i < str.getCTRst().sizeOfRArray(); i++) {
CTRElt lt = str.getCTRst().getRArray(i);
CTRPrElt ltPr = lt.getRPr();
if (ltPr == null) {
ltPr = lt.addNewRPr();
}
CTRegularTextRun r = p.addNewR();
CTTextCharacterProperties rPr = r.addNewRPr();
rPr.setLang("en-US");
applyAttributes(ltPr, rPr);
r.setT(lt.getT());
}
}
//clearText(); //replaced by it's code, 3 lines below
shape.getTextParagraphs().clear();
CTTextBody txBody = shape.getCTShape().getTxBody();
txBody.setPArray(null); // remove any existing paragraphs
CTShape ctShape = shape.getCTShape();
ctShape.getTxBody().setPArray(new CTTextParagraph[] { p });
//shape.getTextParagraphs().add(new XSSFTextParagraph(ctShape.getTxBody().getPArray(0), ctShape)); // cannot be done here since XSSFTextParagraph contructor is not public
}
static void applyAttributes(CTRPrElt pr, CTTextCharacterProperties rPr) {
if (pr.sizeOfBArray() > 0) {
rPr.setB(pr.getBArray(0).getVal());
}
if (pr.sizeOfUArray() > 0) {
STUnderlineValues.Enum u1 = pr.getUArray(0).getVal();
if (u1 == STUnderlineValues.SINGLE) {
rPr.setU(STTextUnderlineType.SNG);
} else if (u1 == STUnderlineValues.DOUBLE) {
rPr.setU(STTextUnderlineType.DBL);
} else if (u1 == STUnderlineValues.NONE) {
rPr.setU(STTextUnderlineType.NONE);
}
}
if (pr.sizeOfIArray() > 0) {
rPr.setI(pr.getIArray(0).getVal());
}
if (pr.sizeOfRFontArray() > 0) {
CTTextFont rFont = rPr.isSetLatin() ? rPr.getLatin() : rPr.addNewLatin();
rFont.setTypeface(pr.getRFontArray(0).getVal());
}
if (pr.sizeOfSzArray() > 0) {
int sz = (int) (pr.getSzArray(0).getVal() * 100);
rPr.setSz(sz);
}
if (pr.sizeOfColorArray() > 0) {
CTSolidColorFillProperties fill = rPr.isSetSolidFill() ? rPr.getSolidFill() : rPr.addNewSolidFill();
org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor xlsColor = pr.getColorArray(0);
if (xlsColor.isSetRgb()) {
CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr();
clr.setVal(xlsColor.getRgb());
} else if (xlsColor.isSetIndexed()) {
HSSFColor indexed = HSSFColor.getIndexHash().get((int) xlsColor.getIndexed());
if (indexed != null) {
byte[] rgb = new byte[3];
rgb[0] = (byte) indexed.getTriplet()[0];
rgb[1] = (byte) indexed.getTriplet()[1];
rgb[2] = (byte) indexed.getTriplet()[2];
CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr();
clr.setVal(rgb);
}
}
}
if (pr.sizeOfVertAlignArray() > 0) {
CTVerticalAlignFontProperty vertAlign = pr.getVertAlignArray(0);
if (vertAlign.getVal() == STVerticalAlignRun.BASELINE) {
rPr.setBaseline(0);
} else if (vertAlign.getVal() == STVerticalAlignRun.SUPERSCRIPT) {
rPr.setBaseline(30000); //30% distance from baseline == default superscript
} else if (vertAlign.getVal() == STVerticalAlignRun.SUBSCRIPT) {
rPr.setBaseline(-25000); //-25% distance from baseline == default subscript
}
}
}
public static void main(String[] args) throws Exception {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet ws = wb.createSheet("Sheet 1");
XSSFFont fontInfo = wb.createFont();
fontInfo.setFontName("Arial");
fontInfo.setFontHeightInPoints((short) 11);
XSSFFont fontInfoSuperscript = wb.createFont();
fontInfoSuperscript.setFontName("Arial");
fontInfoSuperscript.setFontHeightInPoints((short) 11);
fontInfoSuperscript.setTypeOffset(Font.SS_SUPER);
//fontInfoSuperscript.setTypeOffset(Font.SS_SUB);
fontInfoSuperscript.setColor(IndexedColors.RED.getIndex());
Row row = ws.createRow(0);
Cell cell = row.createCell(0);
// Writing to a cell produces desired results:
XSSFRichTextString richString = new XSSFRichTextString("Level3");
richString.applyFont(fontInfo);
richString.applyFont(5, 6, fontInfoSuperscript);
cell.setCellValue(richString);
// Writing to a textbox does not:
XSSFDrawing drawing = ws.createDrawingPatriarch();
XSSFTextBox txtBox = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 0, 5, 2, 7));
XSSFRichTextString richString2 = new XSSFRichTextString("Level3");
richString2.applyFont(fontInfo);
richString2.applyFont(5, 6, fontInfoSuperscript);
//txtBox.setText(richString2);
setText(txtBox, richString2);
try (FileOutputStream fileOut = new FileOutputStream("Superscript.xlsx")) {
wb.write(fileOut);
}
}
}

Related

Setting axis title and adding values to the bar

I need to change the properties (like size of font) of the title axis (circled in red)
I'm using the apache-poi library and here's the part of the code which generates both titles
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Client Name");
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Value ("+clientDataObjectRequest.getDataFormatCodeValue()+")");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
I've already tried to use the method getOrAddTextProperties().setFontSize() but this change only the size of the labels (client names and numbers) and not the title itself.
In yellow, I would like to add the precisely number of the value. For example, the value of "client 3" is 53,78, so I need to put this value in the chart just after the end of the bar (marked as yellow).
The question about formatting the axes titles is answered already. See Resizing legend and axis title in a apache poi bar chart.
"To add the precisely number of the value" to the data bars means to have data labels set. This is not directly supported ny Apache POI until now. So using low level org.openxmlformats.schemas.drawingml.x2006.chart.* classes is necessary. See https://stackoverflow.com/search?tab=newest&q=user%3a3915431%20add%20data%20labels. Of course this is not very comfortable. So I have tried to create a method void setDataLabels(XDDFChartData.Series series, int pos, boolean... show) for this.
There int pos can be one of the following:
INT_BEST_FIT 1 - positions data label at best fit to be readable
INT_B 2 - positions data label at bottom
INT_CTR 3 - positions data label at center
INT_IN_BASE 4 - positions data label inside at base
INT_IN_END 5 - positions data label inside at the end
INT_L 6 - positions data label at left
INT_OUT_END 7 - positions data label outside at the end
INT_R 8 - positions data label at right
INT_T 9 - positions data label at top
Not all chart types support all positions.
The boolean... show is a list of boolean to tell what shall visible (true) or not visible (false) in the data labels. Sequence is as follows:
ShowVal, ShowLegendKey, ShowCatName, ShowSerName, ShowPercent, ShowBubbleSize, ShowLeaderLines
Complete example:
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.PresetColor;
import org.apache.poi.xddf.usermodel.XDDFColor;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.XDDFSolidFillProperties;
import org.apache.poi.xddf.usermodel.chart.AxisCrosses;
import org.apache.poi.xddf.usermodel.chart.AxisCrossBetween;
import org.apache.poi.xddf.usermodel.chart.AxisPosition;
import org.apache.poi.xddf.usermodel.chart.BarDirection;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xddf.usermodel.chart.LegendPosition;
import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFLineChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFValueAxis;
import org.apache.poi.xddf.usermodel.chart.XDDFTitle;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* Bar chart example.
*/
public final class BarChart {
private BarChart() {}
public static void main(String[] args) throws IOException {
Object[][] chartData = new Object[][]{
new Object[]{"Client 1", "Client 2", "Client 3", "Client 4", "Client 5", "Client 6", "Client 7", "Client 8"},
new Object[]{12, 2.345d, 34, 6, 45.6d, .456d, 50.5d, 12.345d},
};
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("barchart");
// put sheet data
Row row;
Cell cell;
int rowIndex = 0;
int colIndex = 0;
for (Object[] dataRow : chartData) {
row = sheet.createRow((short) rowIndex);
colIndex = 0;
for (Object value : dataRow) {
cell = row.createCell((short) colIndex);
if (value instanceof String) cell.setCellValue((String)value);
if (value instanceof Number) cell.setCellValue(((Number)value).doubleValue());
colIndex++;
}
rowIndex++;
}
// create the chart
// chart data sources
XDDFDataSource<String> xs = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(0, 0, 0, colIndex - 1));
XDDFNumericalDataSource<Double> ys1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 0, colIndex - 1));
// chart in drawing
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 3, 8, 20);
XSSFChart chart = drawing.createChart(anchor);
chart.setTitleText("Turnover Rate");
chart.setTitleOverlay(false);
chart.getFormattedTitle().getParagraph(0).addDefaultRunProperties().setFontSize(20d);
// category axis
XDDFCategoryAxis catAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFTitle title = getOrSetAxisTitle(catAxis);
title.setOverlay(false);
title.setText("Client Name");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(12d);
// value axis
XDDFValueAxis valAxis = chart.createValueAxis(AxisPosition.LEFT);
title = getOrSetAxisTitle(valAxis);
title.setOverlay(false);
title.setText("Value (Percentage)");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(12d);
// cross axes
valAxis.setCrosses(AxisCrosses.AUTO_ZERO);
valAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// chart data
XDDFChartData data = chart.createData(ChartTypes.BAR, catAxis, valAxis);
// series
XDDFChartData.Series series1 = data.addSeries(xs, ys1);
series1.setTitle("Series 1", null); // there must be a series title even if no legend is needed
// add data labels
setDataLabels(series1, 7, true); // pos 7 = INT_OUT_END, showVal = true
// plot chart
chart.plot(data);
// in order to transform a bar chart into a column chart, you just need to change the bar direction
XDDFBarChartData bar = (XDDFBarChartData) data;
// bar.setBarDirection(BarDirection.COL);
// set series fill color
solidFillSeries(data, 0, PresetColor.BLUE);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("./ooxml-bar-chart.xlsx")) {
wb.write(fileOut);
}
}
}
private static void solidFillSeries(XDDFChartData data, int index, PresetColor color) {
XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(color));
XDDFChartData.Series series = data.getSeries(index);
XDDFShapeProperties properties = series.getShapeProperties();
if (properties == null) {
properties = new XDDFShapeProperties();
}
properties.setFillProperties(fill);
series.setShapeProperties(properties);
}
private static XDDFTitle getOrSetAxisTitle(XDDFValueAxis axis) {
try {
java.lang.reflect.Field _ctValAx = XDDFValueAxis.class.getDeclaredField("ctValAx");
_ctValAx.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx ctValAx =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx)_ctValAx.get(axis);
if (!ctValAx.isSetTitle()) {
ctValAx.addNewTitle();
}
XDDFTitle title = new XDDFTitle(null, ctValAx.getTitle());
return title;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
private static XDDFTitle getOrSetAxisTitle(XDDFCategoryAxis axis) {
try {
java.lang.reflect.Field _ctCatAx = XDDFCategoryAxis.class.getDeclaredField("ctCatAx");
_ctCatAx.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx ctCatAx =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx)_ctCatAx.get(axis);
if (!ctCatAx.isSetTitle()) {
ctCatAx.addNewTitle();
}
XDDFTitle title = new XDDFTitle(null, ctCatAx.getTitle());
return title;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
private static void setDataLabels(XDDFChartData.Series series, int pos, boolean... show) {
/*
INT_BEST_FIT 1
INT_B 2
INT_CTR 3
INT_IN_BASE 4
INT_IN_END 5
INT_L 6
INT_OUT_END 7
INT_R 8
INT_T 9
*/
try {
org.openxmlformats.schemas.drawingml.x2006.chart.CTDLbls ctDLbls = null;
if (series instanceof XDDFBarChartData.Series) {
java.lang.reflect.Field _ctBarSer = XDDFBarChartData.Series.class.getDeclaredField("series");
_ctBarSer.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTBarSer ctBarSer =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTBarSer)_ctBarSer.get((XDDFBarChartData.Series)series);
if (ctBarSer.isSetDLbls()) ctBarSer.unsetDLbls();
ctDLbls = ctBarSer.addNewDLbls();
if (!(pos == 3 || pos == 4 || pos == 5 || pos == 7)) pos = 3; // bar chart does not provide other pos
ctDLbls.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.Enum.forInt(pos));
} else if (series instanceof XDDFLineChartData.Series) {
java.lang.reflect.Field _ctLineSer = XDDFLineChartData.Series.class.getDeclaredField("series");
_ctLineSer.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTLineSer ctLineSer =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTLineSer)_ctLineSer.get((XDDFLineChartData.Series)series);
if (ctLineSer.isSetDLbls()) ctLineSer.unsetDLbls();
ctDLbls = ctLineSer.addNewDLbls();
if (!(pos == 3 || pos == 6 || pos == 8 || pos == 9 || pos == 2)) pos = 3; // line chart does not provide other pos
ctDLbls.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.Enum.forInt(pos));
} else if (series instanceof XDDFPieChartData.Series) {
java.lang.reflect.Field _ctPieSer = XDDFPieChartData.Series.class.getDeclaredField("series");
_ctPieSer.setAccessible(true);
org.openxmlformats.schemas.drawingml.x2006.chart.CTPieSer ctPieSer =
(org.openxmlformats.schemas.drawingml.x2006.chart.CTPieSer)_ctPieSer.get((XDDFPieChartData.Series)series);
if (ctPieSer.isSetDLbls()) ctPieSer.unsetDLbls();
ctDLbls = ctPieSer.addNewDLbls();
if (!(pos == 3 || pos == 1 || pos == 4 || pos == 5)) pos = 3; // pie chart does not provide other pos
ctDLbls.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.Enum.forInt(pos));
}// else if ...
if (ctDLbls != null) {
ctDLbls.addNewShowVal().setVal((show.length>0)?show[0]:false);
ctDLbls.addNewShowLegendKey().setVal((show.length>1)?show[1]:false);
ctDLbls.addNewShowCatName().setVal((show.length>2)?show[2]:false);
ctDLbls.addNewShowSerName().setVal((show.length>3)?show[3]:false);
ctDLbls.addNewShowPercent().setVal((show.length>4)?show[4]:false);
ctDLbls.addNewShowBubbleSize().setVal((show.length>5)?show[5]:false);
ctDLbls.addNewShowLeaderLines().setVal((show.length>6)?show[6]:false);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Result:

Resizing an EMF image in XSSFWorkbook

I am trying to resize an EMF image after inserting it into an XSSFWorkbook.
There is support resizing PNG, JPEG and DIB images.
private void addNormalFigure(XSSFSheet sheet, byte[] imgData){
XSSFWorkbook w = sheet.getWorkbook();
int picIdx = w.addPicture(imgData, Workbook.PICTURE_TYPE_EMF);
CreationHelper helper = w.getCreationHelper();
XSSFDrawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
// Column : A
anchor.setCol1(0);
// Row : 4
anchor.setRow1(4);
Picture pic = drawing.createPicture(anchor, picIdx);
double height = 2.0;
double width = 2.0;
// sets the anchor.col2 to 0 and anchor.row2 to 0
pic.getImageDimension().setSize(inchToPixel(width), inchToPixel(height));
// pic.resize(); -> this too sets the anchor.col2 to 0 and anchor.row2 to 0
}
private double inchToPixel(double in) {
return in * 96.0;
}
Is there any way we can workaround this problem?
Apache poi not provides to get image dimensions from Picture of type Workbook.PICTURE_TYPE_EMF. The used ImageUtils method getImageDimension only is able to get dimensions from JPEG, PNG and DIB pictures,
But apache poi provides org.apache.poi.hemf.usermodel.HemfPicture. So
import org.apache.poi.hemf.usermodel.HemfPicture;
...
HemfPicture hemfPicture = new HemfPicture(new ByteArrayInputStream(imgData));
System.out.println(hemfPicture.getSize());
...
will print the correct dimensions.
But then it lacks a possibility to set those dimensions to the anchor. There is a method scaleCell in ImageUtils but not public accessible. So only way would be to copy source code of that method into your code.
Following complete example does that. It uses methos scaleCell having same code as in ImageUtils. The method resizeHemfPicture resizes the Picture to the HemfPicture dimension.
This is tested and works using apache poi 5.0.0 and Java 12.
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Units;
import org.apache.poi.ss.util.ImageUtils;
import org.apache.poi.hemf.usermodel.HemfPicture;
import java.util.function.Consumer;
import java.util.function.Function;
import java.awt.Dimension;
import java.io.FileInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
class CreateExcelEMFPicture {
public static double getRowHeightInPixels(Sheet sheet, int rowNum) {
Row r = sheet.getRow(rowNum);
double points = (r == null) ? sheet.getDefaultRowHeightInPoints() : r.getHeightInPoints();
return Units.toEMU(points)/(double)Units.EMU_PER_PIXEL;
}
private static void scaleCell(final double targetSize,
final int startCell,
final int startD,
Consumer<Integer> endCell,
Consumer<Integer> endD,
final int hssfUnits,
Function<Integer,Number> nextSize) {
if (targetSize < 0) {
throw new IllegalArgumentException("target size < 0");
}
int cellIdx = startCell;
double dim, delta;
for (double totalDim = 0, remDim;; cellIdx++, totalDim += remDim) {
dim = nextSize.apply(cellIdx).doubleValue();
remDim = dim;
if (cellIdx == startCell) {
if (hssfUnits > 0) {
remDim *= 1 - startD/(double)hssfUnits;
} else {
remDim -= startD/(double)Units.EMU_PER_PIXEL;
}
}
delta = targetSize - totalDim;
if (delta < remDim) {
break;
}
}
double endDval;
if (hssfUnits > 0) {
endDval = delta/dim * (double)hssfUnits;
} else {
endDval = delta * Units.EMU_PER_PIXEL;
}
if (cellIdx == startCell) {
endDval += startD;
}
endCell.accept(cellIdx);
endD.accept((int)Math.rint(endDval));
}
static void resizeHemfPicture(Picture picture, HemfPicture hemfPicture) {
ClientAnchor anchor = picture.getClientAnchor();
boolean isHSSF = (anchor instanceof HSSFClientAnchor);
double height = hemfPicture.getSize().getHeight();
double width = hemfPicture.getSize().getWidth();
Sheet sheet = picture.getSheet();
int WIDTH_UNITS = 1024;
int HEIGHT_UNITS = 256;
scaleCell(width, anchor.getCol1(), anchor.getDx1(), anchor::setCol2, anchor::setDx2, isHSSF ? WIDTH_UNITS : 0, sheet::getColumnWidthInPixels);
scaleCell(height, anchor.getRow1(), anchor.getDy1(), anchor::setRow2, anchor::setDy2, isHSSF ? HEIGHT_UNITS : 0, (row) -> getRowHeightInPixels(sheet, row));
}
public static void main(String[] args) throws Exception{
Workbook workbook = new XSSFWorkbook(); String filePath = "./CreateExcelEMFPicture.xlsx";
//Workbook workbook = new HSSFWorkbook(); String filePath = "./CreateExcelEMFPicture.xls";
CreationHelper helper = workbook.getCreationHelper();
//add picture data to this workbook.
FileInputStream is = new FileInputStream("./image.emf");
byte[] bytes = IOUtils.toByteArray(is);
int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_EMF);
is.close();
HemfPicture hemfPicture = new HemfPicture(new ByteArrayInputStream(bytes));
System.out.println(hemfPicture.getSize()); // returns correct dimension
Sheet sheet = workbook.createSheet("Sheet1");
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
anchor.setCol1(0);
anchor.setRow1(4);
Picture picture = drawing.createPicture(anchor, pictureIdx);
System.out.println(picture.getImageDimension()); // 0, 0
System.out.println(ImageUtils.getDimensionFromAnchor(picture)); // 0, 0
//picture.resize(); //will not work
resizeHemfPicture(picture, hemfPicture);
System.out.println(ImageUtils.getDimensionFromAnchor(picture)); // correct dimension
FileOutputStream out = new FileOutputStream(filePath);
workbook.write(out);
out.close();
workbook.close();
}
}

Java edit bar chart in ppt by using poi

I am a newbie to using POI at work.Now i'm going to use POI in java to read a bar chart in the PPT.I've added several series x to it in advance,which are the column headers of the excel to which the bar graph belongs.
But i can only read the first three columns by default with the POI.In addition,once I modify the column header of the bar chart,or want to add a fourth column(thar is,add a color)to a bar chart with only three columns, the bar chart cannot be edited when I open the PPT,indicating that the node of the bar chart is damaged.
So is there a master who can help talking how to use POI to prroperly add a color to the bar chart(add a series)?
Eg: when I debug to the "
long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
It show nullpointerexecption, I don't know how structure in ppt the bar-chart is.So I want know how to update the bar-chart。
The code is :
public class PPTDemo {
public void run() {
try {
SlideShow slideShow = SlideShowFactory.create(new File("./res/1.pptx"));
for (Object o : slideShow.getSlides()) {
XSLFSlide slider = (XSLFSlide) o;
// 第一页
if (slider.getSlideNumber() == 1) {
for (POIXMLDocumentPart.RelationPart part : slider.getRelationParts()) {
POIXMLDocumentPart documentPart = part.getDocumentPart();
// 是图表
if (documentPart instanceof XSLFChart) {
XSLFChart chart = (XSLFChart) documentPart;
// 查看里面的图表数据,才能知道是什么图表
CTPlotArea plot = chart.getCTChart().getPlotArea();
// 测试数据
List<SeriesData> seriesDatas = Arrays.asList(
new SeriesData("", Arrays.asList(
new NameDouble("行1", Math.random() * 100),
new NameDouble("行2", Math.random() * 100),
new NameDouble("行3", Math.random() * 100),
new NameDouble("行4", Math.random() * 100),
new NameDouble("行5", Math.random() * 100)
)),
new SeriesData("", Arrays.asList(
new NameDouble("行1", Math.random() * 100),
new NameDouble("行2", Math.random() * 100),
new NameDouble("行3", Math.random() * 100),
new NameDouble("行4", Math.random() * 100),
new NameDouble("行5", Math.random() * 100)
))
);
XSSFWorkbook workbook = chart.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0);
// 柱状图
if (!plot.getBarChartList().isEmpty()) {
CTBarChart barChart = plot.getBarChartArray(0);
updateChartExcelV(seriesDatas, workbook, sheet);
workbook.write(chart.getPackagePart().getOutputStream());
int i = 0;
for (CTBarSer ser : barChart.getSerList()) {
updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
++i;
}
}
// 饼图
else if (!plot.getPieChartList().isEmpty()) {
// 示例饼图只有一列数据
updateChartExcelV(Arrays.asList(seriesDatas.get(0)), workbook, sheet);
workbook.write(chart.getPackagePart().getOutputStream());
CTPieChart pieChart = plot.getPieChartArray(0);
int i = 0;
for (CTPieSer ser : pieChart.getSerList()) {
updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
++i;
}
}
}
}
}
}
try {
try (FileOutputStream out = new FileOutputStream("./res/o1.pptx")) {
slideShow.write(out);
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
}
}
/**
* 更新图表的关联 excel, 值是纵向的
*
* #param param
* #param workbook
* #param sheet
*/
protected void updateChartExcelV(List<SeriesData> seriesDatas, XSSFWorkbook workbook, XSSFSheet sheet) {
XSSFRow title = sheet.getRow(0);
for (int i = 0; i < seriesDatas.size(); i++) {
SeriesData data = seriesDatas.get(i);
if (data.name != null && !data.name.isEmpty()) {
// 系列名称,不能修改,修改后无法打开 excel
// title.getCell(i + 1).setCellValue(data.name);
}
int size = data.value.size();
for (int j = 0; j < size; j++) {
XSSFRow row = sheet.getRow(j + 1);
if (row == null) {
row = sheet.createRow(j + 1);
}
NameDouble cellValu = data.value.get(j);
XSSFCell cell = row.getCell(0);
if (cell == null) {
cell = row.createCell(0);
}
cell.setCellValue(cellValu.name);
cell = row.getCell(i + 1);
if (cell == null) {
cell = row.createCell(i + 1);
}
cell.setCellValue(cellValu.value);
}
int lastRowNum = sheet.getLastRowNum();
if (lastRowNum > size) {
for (int idx = lastRowNum; idx > size; idx--) {
sheet.removeRow(sheet.getRow(idx));
}
}
}
}
/**
* 更新 chart 的缓存数据
*
* #param data 数据
* #param serTitle 系列的标题缓存
* #param catDataSource 条目的数据缓存
* #param numDataSource 数据的缓存
*/
protected void updateChartCatAndNum(SeriesData data, CTSerTx serTitle, CTAxDataSource catDataSource,
CTNumDataSource numDataSource) {
// 更新系列标题
// serTitle.getStrRef().setF(serTitle.getStrRef().getF()); //
// serTitle.getStrRef().getStrCache().getPtArray(0).setV(data.name);
// TODO cat 也可能是 numRef
long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
long ptNumCnt = numDataSource.getNumRef().getNumCache().getPtCount().getVal();
int dataSize = data.value.size();
for (int i = 0; i < dataSize; i++) {
NameDouble cellValu = data.value.get(i);
CTStrVal cat = ptCatCnt > i ? catDataSource.getStrRef().getStrCache().getPtArray(i)
: catDataSource.getStrRef().getStrCache().addNewPt();
cat.setIdx(i);
cat.setV(cellValu.name);
CTNumVal val = ptNumCnt > i ? numDataSource.getNumRef().getNumCache().getPtArray(i)
: numDataSource.getNumRef().getNumCache().addNewPt();
val.setIdx(i);
val.setV(String.format("%.2f", cellValu.value));
}
// 更新对应 excel 的range
catDataSource.getStrRef().setF(
replaceRowEnd(catDataSource.getStrRef().getF(),
ptCatCnt,
dataSize));
numDataSource.getNumRef().setF(
replaceRowEnd(numDataSource.getNumRef().getF(),
ptNumCnt,
dataSize));
// 删除多的
if (ptNumCnt > dataSize) {
for (int idx = dataSize; idx < ptNumCnt; idx++) {
catDataSource.getStrRef().getStrCache().removePt(dataSize);
numDataSource.getNumRef().getNumCache().removePt(dataSize);
}
}
// 更新个数
catDataSource.getStrRef().getStrCache().getPtCount().setVal(dataSize);
numDataSource.getNumRef().getNumCache().getPtCount().setVal(dataSize);
}
/**
* 替换 形如: Sheet1!$A$2:$A$4 的字符
*
* #param range
* #return
*/
public static String replaceRowEnd(String range, long oldSize, long newSize) {
Pattern pattern = Pattern.compile("(:\\$[A-Z]+\\$)(\\d+)");
Matcher matcher = pattern.matcher(range);
if (matcher.find()) {
long old = Long.parseLong(matcher.group(2));
return range.replaceAll("(:\\$[A-Z]+\\$)(\\d+)", "$1" + Long.toString(old - oldSize + newSize));
}
return range;
}
/**
* 一个系列的数据
*/
public static class SeriesData {
/**
* value 系列的名字
*/
public String name;
public List<NameDouble> value;
public SeriesData(java.util.List<NameDouble> value) {
this.value = value;
}
public SeriesData(String name, List<NameDouble> value) {
this.name = name;
this.value = value;
}
public SeriesData() {
}
}
/**
*
*/
public class NameDouble {
public String name;
/**
*/
public double value;
public NameDouble(String name, double value) {
this.name = name;
this.value = value;
}
#SuppressWarnings("unused")
public NameDouble() {
}
}
}
Using current apache poi 5.0.0 updating a chart in PowerPoint is possible using the new XDDF classes. That avoids using the ooxml-schemas classes (CT... classes) directly. Using CT classes directly is error prone and needs very good knowlegde about the internally XML structure of Office Open XML.
What one needs to know is that chart data is stored in an embedded Excel workbook. So while updating the data the need is always updating the data in that workbook and updating the data in the chart.
The following example is a minimal reproducible example for how to do this.
The template BarChartSample.pptx contained a bar chart having only one series an one category. It defines the chart format. It looks like so:
The code is like this:
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 PowerPointChangeChartData {
//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 updateChart(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;
// set new category data
XDDFCategoryDataSource category = null;
int c = 0;
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
}
category = XDDFDataSourcesFactory.fromStringCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart
// series 1, is present already
c = 1;
// set new values in sheet and in chart
XDDFNumericalDataSource<Double> values = null;
for (int r = rMin; r < rMax+1; 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
}
values = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c));
XDDFChartData.Series series1 = chartData.getSeries(0);
series1.replaceData(category, values); // in chart
// set new title in sheet and in chart
String series1Title = (String)data[0][c];
chartDataSheet.getRow(0).getCell(c).setCellValue(series1Title); // in sheet
series1.setTitle(series1Title, new CellReference(sheetName, 0, c, true, true)); // in chart
series1.plot();
//further series, all new created
int seriesCount = data[0].length - 1;
for (int s = 2; s <= seriesCount; s++) {
c++;
// set new values
for (int r = rMin; r < rMax+1; 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
}
values = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c));
XDDFChartData.Series series = chartData.addSeries(category, values); // in chart
// set new title
String seriesTitle = (String)data[0][c];
XSSFCell cell = chartDataSheet.getRow(0).getCell(c); if (cell == null) cell = chartDataSheet.getRow(0).createCell(c);
cell.setCellValue(seriesTitle); // in sheet
series.setTitle(seriesTitle, new CellReference(sheetName, 0, c, true, true)); // in chart
series.plot();
}
// 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);
}
}
}
}
public static void main(String[] args) throws Exception {
String filePath = "BarChartSample.pptx"; // has template bar chart
String filePathNew = "BarChartSample_New.pptx";
Object[][] data = new Object[][] { // new data 3 series, 5 categories
{"", "Amount", "Values", "Others"}, // series title
{"Jan", 321d, 456d, 222d}, // category 1
{"Feb", 543d, 567d, 111d}, // category 2
{"Mar", 432d, 123d, 333d}, // category 3
{"Apr", 210d, 234d, 444d}, // category 4
{"May", 198d, 345d, 444d} // category 5
};
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream(filePath));
XSLFChart chart = slideShow.getCharts().get(0);
updateChart(chart, data);
FileOutputStream out = new FileOutputStream(filePathNew);
slideShow.write(out);
out.close();
slideShow.close();
}
}
The result looks like so:
Hint: The code uses a patched version of XSSFTable.updateHeaders as the current version fails updating the table headers. See Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file.

Issue with export CrossTab data into excel sheet using Java Apache POI 3.15 version

I am collecting data from my company portal and trying to export it into an excel sheet. I successfully completed for non-crosstab report data. But the issue is coming when the data has crosstab.
Data:
I have saved in data in ArrayList best on my best understanding.
1.
rowHeader:[Quarter, Subcategory]
2.
rowElement:[2016 Q1, 2016 Q2, 2016 Q3, 2016 Q4, Audio Equipment, Cameras, Computers, Electronics - Miscellaneous, TV's, Video Equipment]
3.
columnHeader:[Year, 2016]
4.
columnHeaders[Quarter, Subcategory, Year, 2016]
5.
metricsColumn:[Metrics, Profit, Profit Forecast, Profit Margin, Revenue, Revenue Forecast]
6.
listData:[[$9,579, $8,823, 19.42%, $49,320, $ 39,456], [$11,449, $9,619, 20.07%, $57,040, $ 53,047], [$4,901, $3,784, 20.02%, $24,480, $ 21,298], [$12,444, $9,525, 19.89%, $62,576, $ 53,815], [$8,820, $8,059, 20.19%, $43,675, $ 35,814], [$16,375, $12,986, 20.44%, $80,130, $ 70,514], [$8,526, $6,929, 15.78%, $54,020, $ 49,698], [$11,602, $9,578, 17.31%, $67,032, $ 63,680], [$4,675, $4,380, 16.83%, $27,780, $ 25,752], [$11,699, $9,421, 16.60%, $70,469, $ 54,966], [$9,386, $7,179, 17.52%, $53,563, $ 49,974], [$10,150, $9,213, 16.96%, $59,864, $ 48,490], [$8,508, $6,772, 17.16%, $49,571, $ 47,092], [$16,429, $13,529, 18.52%, $88,712, $ 83,389], [$6,009, $5,391, 18.05%, $33,295, $ 29,200], [$11,792, $9,791, 17.21%, $68,534, $ 64,285], [$9,243, $7,952, 17.15%, $53,886, $ 49,953], [$14,282, $11,679, 18.03%, $79,193, $ 74,441], [$10,999, $8,538, 15.60%, $70,511, $ 65,575], [$14,475, $11,433, 16.32%, $88,718, $ 78,515], [$5,765, $5,029, 16.77%, $34,373, $ 31,847], [$11,335, $9,567, 15.28%, $74,168, $ 62,672], [$11,990, $11,230, 16.83%, $71,255, $ 64,414], [$13,864, $11,328, 16.87%, $82,203, $ 70,695]]
Till so far, I successfully, completed exporting Headers section.
I am struggling, now exporting data into an excel sheet because of the crosstab we need to do column merge and row merge for every Quarters and Years.
If someone can help me, to write any sample code/ pseudo code for it, it would be a very very helpful for me. I ave stuck with this issue from the last 4 days. I am not getting how to do this part as I never used any exclsheet API. here I am using APACHE POI -3.15 version.
Code till so far, which export Header section:
//Create a Row
Row headerRow = sheet.createRow(0);
//int totalcolumns = rowHeader.size() + columnHeader.size();
int mergeHeader = 0;
if(metricsColumn != null) {
mergeHeader = 1;
}
//Create Cells //[Quarter, Subcategory, Year, 2016]
int j = rowHeader.size();
int row = 0;
for (int i = 0; i < columnHeaders.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(columnHeaders.get(i));
if(j > 0) {
sheet.addMergedRegion(new CellRangeAddress(row, mergeHeader, i, i));
}
j--;
if (i == columnHeaders.size() - 1 && metricsColumn.size() > 0) {
sheet.addMergedRegion(new CellRangeAddress(0,0,i,i + metricsColumn.size() - 2));
}
cell.setCellStyle(headerCellStyle);
}
int k = 0;
int rowNum1 = 1;
int cellNum1 = 2;
Row row1 = sheet.createRow(rowNum1);
for (int i = rowHeader.size(); i < metricsColumn.size(); i++) {
while (k < metricsColumn.size()) {
Cell cell = row1.createCell(cellNum1++);
cell.setCellValue(metricsColumn.get(k));
cell.setCellStyle(headerCellStyle);
k++;
}
}
I would make the structures for headers and data a little bit different:
The headers for example:
String[][] headers = new String[][] {
new String[] {"Quarter", "Subcategory", "Year", "2016", null, null, null, null},
new String[] { null, null, "Metrics", "Profit", "Profit Forecast", "Profit Margin", "Revenue", "Revenue Forecast"}
};
Maybe List<List<String>> also would be possible.
The row headers:
String[] quarters = new String[]{"2016 Q1", "2016 Q2", "2016 Q3", "2016 Q4"};
String[] subcategories = new String[]{"Audio Equipment", "Cameras", "Computers", "Electronics - Miscellaneous", "TV's", "Video Equipment"};
Maybe List<String> also would be possible.
The values:
Double[][] values = new Double[][]{
new Double[]{9579d, 8823d, .1942, 49320d,39456d},
new Double[]{11449d, 9619d, .2007, 57040d, 53047d},
...
new Double[]{13864d, 11328d, .1687, 82203d, 70695d}
};
Maybe List<List<Double>> also would be possible.
Then I would also provide basic formatting instructions in structures. So it is later possible to set them using loops.
For example the basic table structure:
int[] columnWidths = new int[]{15*256, 15*256, 15*256, 13*256, 13*256, 13*256, 13*256, 13*256};
For example the headers horizontal alignment:
HorizontalAlignment[][] horizontalAlignments = new HorizontalAlignment[][]{
new HorizontalAlignment[]{HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, null, null, null, null},
new HorizontalAlignment[]{null, null, HorizontalAlignment.RIGHT, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER}
};
And the merged regions for the headers:
int[][] headerMergedRegions = new int[][]{
new int[]{0, 0, 3, 7},
new int[]{0, 1, 0, 0},
new int[]{0, 1, 1, 1}
};
Then I would set the much used number formats in default column styles:
String[] columnCellStyles = new String[]{"default", "default", "default", "currency", "currency", "percent", "currency", "currency"};
...
// creating default cell style having default font
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setFont(defaultFont);
// we need currency style for numbers later
CellStyle currency = workbook.createCellStyle();
currency.cloneStyleFrom(defaultCellStyle);
currency.setDataFormat(format.getFormat("$#,##0"));
// we need percent style for numbers later
CellStyle percent = workbook.createCellStyle();
percent.cloneStyleFrom(defaultCellStyle);
percent.setDataFormat(format.getFormat("0.00%"));
...
// set default column styles
for (int c = 0; c < columnCellStyles.length; c++) {
String style = columnCellStyles[c];
if ("default".equals(style)) {
sheet.setDefaultColumnStyle(c, defaultCellStyle);
} else if ("currency".equals(style)) {
sheet.setDefaultColumnStyle(c, currency);
} else if ("percent".equals(style)) {
sheet.setDefaultColumnStyle(c, percent);
}
}
So when we set the values later, the cell styles can come from default column style. For this a method CellStyle getPreferredCellStyle(Cell cell) could be used.
For formatting I would rely on methods from CellUtil. So not all needed CellStyles needs to be created on workbook level but can be set to the cell into the loops.
For cell borders I would use PropertyTemplate. This makes setting borders much more easier.
Complete example:
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellUtil;
import org.apache.poi.ss.util.PropertyTemplate;
import java.util.Map;
import java.util.HashMap;
class CreateExcelSpecial {
static CellStyle getPreferredCellStyle(Cell cell) {
// a method to get the preferred cell style for a cell
// this is either the already applied cell style
// or if that not present, then the row style (default cell style for this row)
// or if that not present, then the column style (default cell style for this column)
CellStyle cellStyle = cell.getCellStyle();
// if no explicit cell style applied then cellStyle.getIndex() is 0 for XSSF
// or 15 (0xF = the index to the default ExtendedFormatRecord (0xF)) for HSSF
if ((cell instanceof XSSFCell && cellStyle.getIndex() == 0) || (cell instanceof HSSFCell && cellStyle.getIndex() == 15)) cellStyle = cell.getRow().getRowStyle();
if (cellStyle == null) cellStyle = cell.getSheet().getColumnStyle(cell.getColumnIndex());
if (cellStyle == null) cellStyle = cell.getCellStyle();
return cellStyle;
}
public static void main(String[] args) throws Exception {
// the data
// basic table structure
int[] columnWidths = new int[]{15*256, 15*256, 15*256, 13*256, 13*256, 13*256, 13*256, 13*256};
String[] columnCellStyles = new String[]{"default", "default", "default", "currency", "currency", "percent", "currency", "currency"};
// headers content and formatting
String[][] headers = new String[][] {
new String[] {"Quarter", "Subcategory", "Year", "2016", null, null, null, null},
new String[] { null, null, "Metrics", "Profit", "Profit Forecast", "Profit Margin", "Revenue", "Revenue Forecast"}
};
HorizontalAlignment[][] horizontalAlignments = new HorizontalAlignment[][]{
new HorizontalAlignment[]{HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, null, null, null, null},
new HorizontalAlignment[]{null, null, HorizontalAlignment.RIGHT, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER}
};
VerticalAlignment[][] verticalAlignments = new VerticalAlignment[][]{
new VerticalAlignment[]{VerticalAlignment.TOP, VerticalAlignment.TOP, VerticalAlignment.CENTER, VerticalAlignment.CENTER, null, null, null, null},
new VerticalAlignment[]{null, null, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM}
};
Boolean[][] wrapTexts = new Boolean[][]{
new Boolean[]{false, false, false, false, null, null, null, null},
new Boolean[]{null, null, false, false, true, true, false, true}
};
int[][] headerMergedRegions = new int[][]{
new int[]{0, 0, 3, 7},
new int[]{0, 1, 0, 0},
new int[]{0, 1, 1, 1}
};
// row headers
String[] quarters = new String[]{"2016 Q1", "2016 Q2", "2016 Q3", "2016 Q4"};
String[] subcategories = new String[]{"Audio Equipment", "Cameras", "Computers", "Electronics - Miscellaneous", "TV's", "Video Equipment"};
// data
Double[][] values = new Double[][]{
new Double[]{9579d, 8823d, .1942, 49320d,39456d},
new Double[]{11449d, 9619d, .2007, 57040d, 53047d},
new Double[]{4901d, 3784d, .2002, 24480d, 21298d},
new Double[]{12444d, 9525d, .1989, 62576d, 53815d},
new Double[]{8820d, 8059d, .2019, 43675d, 35814d},
new Double[]{16375d, 12986d, .2044, 80130d, 70514d},
new Double[]{8526d, 6929d, .1578, 54020d, 49698d},
new Double[]{11602d, 9578d, .1731, 67032d, 63680d},
new Double[]{4675d, 4380d, .1683, 27780d, 25752d},
new Double[]{11699d, 9421d, .1660, 70469d, 54966d},
new Double[]{9386d, 7179d, .1752, 53563d, 49974d},
new Double[]{10150d, 9213d, .1696, 59864d, 48490d},
new Double[]{8508d, 6772d, .1716, 49571d, 47092d},
new Double[]{16429d, 13529d, .1852, 88712d, 83389d},
new Double[]{6009d, 5391d, .1805, 33295d, 29200d},
new Double[]{11792d, 9791d, .1721, 68534d, 64285d},
new Double[]{9243d, 7952d, .1715, 53886d, 49953d},
new Double[]{14282d, 11679d, .1803, 79193d, 74441d},
new Double[]{10999d, 8538d, .1560, 70511d, 65575d},
new Double[]{14475d, 11433d, .1632, 88718d, 78515d},
new Double[]{5765d, 5029d, .1677, 34373d, 31847d},
new Double[]{11335d, 9567d, .1528, 74168d, 62672d},
new Double[]{11990d, 11230d, .1683, 71255d, 64414d},
new Double[]{13864d, 11328d, .1687, 82203d, 70695d}
};
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fileout = new FileOutputStream("./Excel.xlsx") ) {
// we need PropertyTemplate later
PropertyTemplate propertyTemplate = new PropertyTemplate();
// we need properties map for cell styles later
Map<String, Object> properties;
// we need DataFormat later
DataFormat format = workbook.createDataFormat();
// creating default font
Font defaultFont = workbook.createFont();
defaultFont.setFontName("Calibri");
defaultFont.setFontHeightInPoints((short)12);
// we need font in bold and white for headings
Font defaultFontWhite = workbook.createFont();
defaultFontWhite.setFontName("Calibri");
defaultFontWhite.setFontHeightInPoints((short)12);
defaultFontWhite.setBold(true);
defaultFontWhite.setColor(IndexedColors.WHITE.getIndex());
// creating default cell style having default font
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setFont(defaultFont);
// we need currency style for numbers later
CellStyle currency = workbook.createCellStyle();
currency.cloneStyleFrom(defaultCellStyle);
currency.setDataFormat(format.getFormat("$#,##0"));
// we need percent style for numbers later
CellStyle percent = workbook.createCellStyle();
percent.cloneStyleFrom(defaultCellStyle);
percent.setDataFormat(format.getFormat("0.00%"));
Sheet sheet = workbook.createSheet();
// set column widths
for (int c = 0; c < columnWidths.length; c++) sheet.setColumnWidth(c, columnWidths[c]);
// set default column styles
for (int c = 0; c < columnCellStyles.length; c++) {
String style = columnCellStyles[c];
if ("default".equals(style)) {
sheet.setDefaultColumnStyle(c, defaultCellStyle);
} else if ("currency".equals(style)) {
sheet.setDefaultColumnStyle(c, currency);
} else if ("percent".equals(style)) {
sheet.setDefaultColumnStyle(c, percent);
}
}
int rowIdx = 0; // looping row index
int colIdx = 0; // looping column index
// input and format headers
int i = 0;
for (String[] headerRow : headers) {
Row row = sheet.createRow(rowIdx++);
colIdx = 0;
int j = 0;
for (String header : headerRow) {
Cell cell = row.createCell(colIdx++);
cell.setCellValue(header);
properties = new HashMap<String, Object>();
properties.put(CellUtil.FILL_PATTERN, FillPatternType.SOLID_FOREGROUND);
properties.put(CellUtil.FILL_FOREGROUND_COLOR, IndexedColors.BLUE.getIndex()); //do using only IndexedColors for fills
properties.put(CellUtil.FONT, defaultFontWhite.getIndexAsInt()); // white font
if (horizontalAlignments.length == headers.length) {
HorizontalAlignment[] hAligns = horizontalAlignments[i];
if (hAligns.length == headerRow.length) {
HorizontalAlignment hAlign = hAligns[j];
properties.put(CellUtil.ALIGNMENT, hAlign); // horizontal alignment
}
}
if (verticalAlignments.length == headers.length) {
VerticalAlignment[] hAligns = verticalAlignments[i];
if (hAligns.length == headerRow.length) {
VerticalAlignment vAlign = hAligns[j];
properties.put(CellUtil.VERTICAL_ALIGNMENT, vAlign); // vertical alignment
}
}
if (wrapTexts.length == headers.length) {
Boolean[] isWrapTexts = wrapTexts[i];
if (isWrapTexts.length == headerRow.length) {
Boolean isWrapText = isWrapTexts[j];
properties.put(CellUtil.WRAP_TEXT, isWrapText); // wrap text
}
}
CellUtil.setCellStyleProperties(cell, properties); //do using CellUtil for **add** new properties to already applied cell styles
j++;
}
i++;
}
// set header merged regions
for (int[] mergedRegion : headerMergedRegions) {
if (mergedRegion.length == 4) {
sheet.addMergedRegion(new CellRangeAddress(mergedRegion[0], mergedRegion[1], mergedRegion[2], mergedRegion[3]));
}
}
// draw header borders
propertyTemplate.drawBorders(new CellRangeAddress(0, headers.length-1, 0, headers[0].length-1), BorderStyle.MEDIUM, IndexedColors.WHITE.getIndex(), BorderExtent.ALL); //since we have merged regions we can simply drawing all borders here
// input and format row headers
for (String quarter : quarters) {
Row row = sheet.createRow(rowIdx++);
Cell cell = row.createCell(0); // quarter in column 0 (A)
cell.setCellValue(quarter);
CellUtil.setVerticalAlignment(cell, VerticalAlignment.TOP); // quarter cells are top aligned
for (String subcategory : subcategories) {
cell = row.createCell(1); // subcategory in column 1 (B)
cell.setCellValue(subcategory);
sheet.addMergedRegion(new CellRangeAddress(rowIdx, rowIdx, 1, 2));
row = sheet.createRow(rowIdx++);
}
rowIdx--;
sheet.addMergedRegion(new CellRangeAddress(rowIdx-subcategories.length, rowIdx-1, 0, 0));
}
rowIdx -= quarters.length * subcategories.length; // reset the rowIdx
// input values - number formats came from default column style
for (Double[] rowValues : values) {
Row row = sheet.getRow(rowIdx++); //get the row instead of creating it
colIdx = 3; // values are from column 3 (D) on
for (Double value : rowValues) {
Cell cell = row.createCell(colIdx++);
cell.setCellValue(value);
cell.setCellStyle(getPreferredCellStyle(cell));
}
}
// draw data borders
propertyTemplate.drawBorders(new CellRangeAddress(headers.length, headers.length+values.length-1, 0, headers[0].length-1), BorderStyle.MEDIUM, IndexedColors.LIGHT_ORANGE.getIndex(), BorderExtent.ALL); //since we have merged regions we can simply drawing all borders here
//apply the PropertyTemplate borders
propertyTemplate.applyBorders(sheet);
workbook.write(fileout);
}
}
}

How to move position of chart in excel by Java POI

I want to add row in excel by java POI and I try with both shiftRows() function and createRow() function
both function can add row in excel but below chart position is remain and not move
I also like to move (shift down) the position of chart
I use poi version 3.9
Can anyone give me the advice or idea to move the position of that chart image
As the fact, the data range of chart also not changed. I need not only to move the position of charts but also need to increase the data range of chart
thanks!!
The shifting of the drawing anchors which determine the chart positions is possible. The method void insertRowsShiftShapes(Sheet sheet, int startRow, int n) does this for all drawing anchors which are affected of row inserting process into the sheet.
The correcting of the chart data ranges which are affected of the row inserting into the sheet is complicated as said already. It is not well tested and not ready yet. But I will provide it as a working draft. I hope it is a useful start point for further programming.
For running the code the ooxml-schemas-1.3.jar is needed as mentioned in apache poi FAQ
A good resource for documentation of the ooxml-schema objects for me is grepcode
Examples: CTTwoCellAnchor, CTPieChart, CTPieSer
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import java.io.*;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieSer;
import java.util.List;
class InsertRowsAboveChart {
//a method for shift rows and shift all anchors in drawing below the shifted rows
private static void insertRowsShiftShapes(Sheet sheet, int startRow, int n) {
java.util.List<CTTwoCellAnchor> drawingAnchors = ((XSSFDrawing)sheet.getDrawingPatriarch()).getCTDrawing().getTwoCellAnchorList();
for (CTTwoCellAnchor drawingAnchor : drawingAnchors) {
int fromRow = drawingAnchor.getFrom().getRow();
int toRow = drawingAnchor.getTo().getRow();
if (fromRow >= startRow) {
drawingAnchor.getFrom().setRow(fromRow + n);
drawingAnchor.getTo().setRow(toRow + n);
}
}
sheet.shiftRows(startRow, sheet.getLastRowNum(), n);
correctDataRangesOfCharts(sheet, startRow, n);
}
//a method for correcting data ranges for charts which are affected of the shifted rows
//!!working draft, not ready yet!!
private static void correctDataRangesOfCharts(Sheet sheet, int startRow, int n) {
java.util.List<XSSFChart> charts = ((XSSFDrawing)sheet.getDrawingPatriarch()).getCharts();
for (XSSFChart chart : charts) {
//pie charts
java.util.List<CTPieChart> piecharts = chart.getCTChart().getPlotArea().getPieChartList();
for (CTPieChart piechart : piecharts) {
java.util.List<CTPieSer> pieseries = piechart.getSerList();
for (CTPieSer pieserie : pieseries) {
boolean strRefchanged = false;
if (pieserie.getCat().isSetMultiLvlStrRef()) {
String strRef = pieserie.getCat().getMultiLvlStrRef().getF();
//todo: this only corrects the end row of the ranges, should also correct start row if affected
int strRefEndRow = Integer.parseInt(strRef.substring(strRef.lastIndexOf('$') + 1));
if (strRefEndRow >= startRow) {
strRef = strRef.substring(0, strRef.lastIndexOf('$') +1) + (strRefEndRow + n);
pieserie.getCat().getMultiLvlStrRef().setF(strRef);
strRefchanged = true;
}
} else if (pieserie.getCat().isSetStrRef()) {
String strRef = pieserie.getCat().getStrRef().getF();
int strRefEndRow = Integer.parseInt(strRef.substring(strRef.lastIndexOf('$') + 1));
if (strRefEndRow >= startRow) {
strRef = strRef.substring(0, strRef.lastIndexOf('$') +1) + (strRefEndRow + n);
pieserie.getCat().getStrRef().setF(strRef);
strRefchanged = true;
}
}
if (strRefchanged) {
String numRef = pieserie.getVal().getNumRef().getF();
int numRefEndRow = Integer.parseInt(numRef.substring(numRef.lastIndexOf('$') + 1));
if (numRefEndRow >= startRow) {
numRef = numRef.substring(0, numRef.lastIndexOf('$') +1) + (numRefEndRow + n);
pieserie.getVal().getNumRef().setF(numRef);
}
}
}
}
//pie charts end
}
}
public static void main(String[] args) {
try {
InputStream inp = new FileInputStream("Workbook.xlsx");
Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);
//sheet.shiftRows(3, 5, 4);
insertRowsShiftShapes(sheet, 2, 4);
FileOutputStream fileOut = new FileOutputStream("Workbook.xlsx");
wb.write(fileOut);
wb.close();
} catch (InvalidFormatException ifex) {
} catch (FileNotFoundException fnfex) {
} catch (IOException ioex) {
}
}
}

Categories

Resources