Does anyone know how to create a surface chart with Apache POI?
Unfortunately I could not find an example anywhere.
I tried to use other chart examples but without success.
Since XDDF was introduced with apache poi 4, this should be used. And XDDFChartData has a subclass XDDFSurface3DChartData.
What one needs to know when using:
A surface 3D chart needs an additional series axis. And this series axis needs to be defined for the XDDFSurface3DChartData.
Minimal example which creates a Excel sheet having a surface 3D chart. This is tested and works using the current apache poi 5.2.2.
import java.io.FileOutputStream;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
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;
public class CreateExcelXDDFSurface3DChart {
public static void main(String[] args) throws Exception {
try (XSSFWorkbook document = new XSSFWorkbook()) {
XSSFSheet sheet = document.createSheet("SurfaceChart");
// create the data
String[] categories = new String[] { "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9" };
Double[] values1 = new Double[] { 10d, 20d, 10d, 15d, 12d, 20d, 10d, 18d, 19d };
Double[] values2 = new Double[] { 20d, 10d, 15d, 20d, 11d, 17d, 18d, 20d, 10d };
Double[] values3 = new Double[] { 14.5d, 14d, 13.5d, 13d, 12.5d, 12d, 11.5d, 11d, 10.5d };
int r = 0;
for (String cat : categories) {
sheet.createRow(r).createCell(0).setCellValue(cat);
sheet.getRow(r).createCell(1).setCellValue(values1[r]);
sheet.getRow(r).createCell(2).setCellValue(values2[r]);
sheet.getRow(r).createCell(3).setCellValue(values3[r]);
r++;
}
// create the chart
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 5, 0, 20, 30);
XDDFChart chart = drawing.createChart(anchor);
// create data sources
int numOfPoints = categories.length;
XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(0, numOfPoints-1, 0, 0));
XDDFNumericalDataSource<Double> valuesData1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, numOfPoints-1, 1, 1));
XDDFNumericalDataSource<Double> valuesData2 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, numOfPoints-1, 2, 2));
XDDFNumericalDataSource<Double> valuesData3 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, numOfPoints-1, 3, 3));
// surface 3D chart
XDDFCategoryAxis categoryAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis valueAxis = chart.createValueAxis(AxisPosition.LEFT);
valueAxis.setCrosses(AxisCrosses.AUTO_ZERO);
// surface 3D chart needs a series axis
XDDFSeriesAxis seriesAxis = chart.createSeriesAxis(AxisPosition.BOTTOM);
seriesAxis.setCrosses(AxisCrosses.AUTO_ZERO);
seriesAxis.crossAxis(valueAxis);
XDDFChartData data = chart.createData(ChartTypes.SURFACE3D, categoryAxis, valueAxis);
// surface 3D chart needs the series axis applied to the XDDFSurface3DChartData
((XDDFSurface3DChartData)data).defineSeriesAxis(seriesAxis);
XDDFChartData.Series series = data.addSeries(categoriesData, valuesData1);
series.setTitle("Series 1", null);
series = data.addSeries(categoriesData, valuesData2);
series.setTitle("Series 2", null);
series = data.addSeries(categoriesData, valuesData3);
series.setTitle("Series 3", null);
chart.plot(data);
// set legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("./CreateExcelXDDFSurface3DChart.xlsx")) {
document.write(fileOut);
}
}
}
}
Btw.: I have never understood where a surface chart will be helpful for data presentation.
Related
I'm currently making an Excel chart using org.apache.poi and oooxml library. I developed the picture below.
enter image description here
enter image description here
XSSFDrawing drawing = (XSSFDrawing)sheet.createDrawingPatriarch();
ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 35, 20, 57);
XSSFChart chart = drawing.createChart(anchor);
CTChart ctChart = ((XSSFChart)chart).getCTChart();
CTPlotArea ctPlotArea = ctChart.getPlotArea();
//여기까지는 똑같음
//the first bar chart
CTBarChart ctBarChart = ctPlotArea.addNewBarChart();
CTBoolean ctBoolean = ctBarChart.addNewVaryColors();
ctBoolean.setVal(true);
ctBarChart.addNewBarDir().setVal(STBarDir.COL);
//the first chart series
CTBarSer ctBarSer = ctBarChart.addNewSer();
CTSerTx ctSerTx = ctBarSer.addNewTx();
CTStrRef ctStrRef = ctSerTx.addNewStrRef();
ctStrRef.setF("Sheet1!$B$10");
ctBarSer.addNewIdx().setVal(0);
CTAxDataSource ctAxDataSource = ctBarSer.addNewCat();
ctStrRef = ctAxDataSource.addNewStrRef();
ctStrRef.setF("Sheet1!$A$11:$A$34");
CTNumDataSource ctNumDataSource = ctBarSer.addNewVal();
CTNumRef ctNumRef = ctNumDataSource.addNewNumRef();
ctNumRef.setF("Sheet1!$B$11:$B$34");
//the second chart series
CTBarSer ctBarSer1 = ctBarChart.addNewSer();
CTSerTx ctSerTx1 = ctBarSer1.addNewTx();
CTStrRef ctStrRef1 = ctSerTx1.addNewStrRef();
ctStrRef1.setF("Sheet1!$C$10");
ctBarSer1.addNewIdx().setVal(1);
CTAxDataSource ctAxDataSource1 = ctBarSer1.addNewCat();
ctStrRef1 = ctAxDataSource1.addNewStrRef();
ctStrRef1.setF("Sheet1!$A$11:$A$34");
CTNumDataSource ctNumDataSource1 = ctBarSer1.addNewVal();
CTNumRef ctNumRef1 = ctNumDataSource1.addNewNumRef();
ctNumRef1.setF("Sheet1!$C$11:$C$34");
// 3번째
CTBarSer ctBarSer2 = ctBarChart.addNewSer();
CTSerTx ctSerTx2 = ctBarSer2.addNewTx();
CTStrRef ctStrRef2 = ctSerTx2.addNewStrRef();
ctStrRef2.setF("Sheet1!$D$10");
ctBarSer2.addNewIdx().setVal(2);
CTAxDataSource ctAxDataSource2 = ctBarSer2.addNewCat();
ctStrRef2 = ctAxDataSource2.addNewStrRef();
ctStrRef2.setF("Sheet1!$A$11:$A$34");
CTNumDataSource ctNumDataSource2 = ctBarSer2.addNewVal();
CTNumRef ctNumRef2 = ctNumDataSource2.addNewNumRef();
ctNumRef2.setF("Sheet1!$D$11:$D$34");
//at least the border lines in Libreoffice Calc ;-)
ctBarSer.addNewSpPr().addNewLn().addNewSolidFill().addNewSrgbClr().setVal(new byte[] {0,0,0});
ctBarSer1.addNewSpPr().addNewLn().addNewSolidFill().addNewSrgbClr().setVal(new byte[] {0,0,0});
ctBarSer2.addNewSpPr().addNewLn().addNewSolidFill().addNewSrgbClr().setVal(new byte[] {0,0,0});
ctBarChart.addNewAxId().setVal(123456); //cat axis 1 (lines)
ctBarChart.addNewAxId().setVal(123457); //val axis 1 (left)
ctBarChart.addNewAxId().setVal(123458); //val axis 1 (right)
ctBarChart.addNewAxId().setVal(123459); //val axis 1 (right)
//cat axis 1
CTCatAx ctCatAx = ctPlotArea.addNewCatAx();
ctCatAx.addNewAxId().setVal(123456); //id of the cat axis
CTScaling ctScaling = ctCatAx.addNewScaling();
ctScaling.addNewOrientation().setVal(STOrientation.MIN_MAX);
ctCatAx.addNewDelete().setVal(false);
ctCatAx.addNewAxPos().setVal(STAxPos.B);
ctCatAx.addNewCrossAx().setVal(123457); //id of the val axis
ctCatAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO);
//val axis 1 (left)
CTValAx ctValAx = ctPlotArea.addNewValAx();
ctValAx.addNewAxId().setVal(123457); //id of the val axis
ctScaling = ctValAx.addNewScaling();
ctScaling.addNewOrientation().setVal(STOrientation.MIN_MAX);
ctValAx.addNewDelete().setVal(false);
ctValAx.addNewAxPos().setVal(STAxPos.L);
ctValAx.addNewCrossAx().setVal(123456); //id of the cat axis
ctValAx.addNewCrosses().setVal(STCrosses.AUTO_ZERO); //this val axis crosses the cat axis at zero
ctValAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO);
As for the amount of "transmission= C cell", the unit is large, so I'm trying to make another value axis on the right, but I kept getting errors, so I asked. How can I create a valueaxis on the right, the number of "transmissions" based on the right, and the "user D cell , transfer count B cell" generates a chart based on the left?
From apache poi 4 on you should use the new XDDF classes to create charts. Playing around with the low level CT* classes is very complex and error prone.
Using line charts I have provided an example already here: Second Line in an Apache-POI chart with seperate axis.
But using bar charts there is an additional problem to solve. If one bar chart series is on primary axis and another is on secondary axis, then the bars will overlap each other. This is because each of the series are oriented only on those series using the same axis. In other words the series on primary axis do not respect the series on the secondary axis when it comes to taking space on the chart. For line charts this is not a problem but for bar charts it is.
To solve this additional series having dummy data (all 0) can be used on both the axes. Those additional series lead to invisible bars (height 0) which also take space and so can be used to shift the visible bars.
The following complete example shows this. It is tested and works using apache poi 4.1.2 and apache poi 5.1.0.
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
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;
public class CreateExcelXDDFChart {
public static void main(String[] args) throws Exception {
try (XSSFWorkbook document = new XSSFWorkbook()) {
XSSFSheet chartSheet = document.createSheet("chart");
XSSFSheet dataSheet = document.createSheet("data");
// create the data
String[] categories = new String[] { "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9" };
Double[] values1 = new Double[] { 1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d };
Double[] values2 = new Double[] { 200d, 300d, 400d, 500d, 600d, 700d, 800d, 900d, 1000d };
int r = 0;
for (String cat : categories) {
dataSheet.createRow(r).createCell(0).setCellValue(cat);
dataSheet.getRow(r).createCell(1).setCellValue(values1[r]);
dataSheet.getRow(r).createCell(2).setCellValue(values2[r]);
r++;
}
// create the chart
XSSFDrawing drawing = chartSheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 0, 16, 22);
XDDFChart chart = drawing.createChart(anchor);
// create data sources
int numOfPoints = categories.length;
// dummy 0-values for the pad data source
Double[] dummyValuesForPad = new Double[numOfPoints];
for (int i = 0; i < numOfPoints; i++) {
dummyValuesForPad[i] = 0d;
}
XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromStringCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 0, 0));
XDDFNumericalDataSource<Double> valuesData1 = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 1, 1));
XDDFNumericalDataSource<Double> valuesData2 = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 2, 2));
// data source for the pad series
XDDFNumericalDataSource<Double> pad = XDDFDataSourcesFactory.fromArray(dummyValuesForPad);
// first bar chart
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.COL);
XDDFChartData.Series series = data.addSeries(categoriesData, valuesData1);
series.setTitle("Series 1", null);
// additional pad series - takes space at right side for primary axis
series = data.addSeries(categoriesData, pad);
series.setTitle("pad", null);
chart.plot(data);
// second bar chart
// bottom axis must be there but must not be visible
bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setVisible(false);
XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
rightAxis.setCrosses(AxisCrosses.MAX);
rightAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// set correct cross axis
bottomAxis.crossAxis(rightAxis);
rightAxis.crossAxis(bottomAxis);
data = chart.createData(ChartTypes.BAR, bottomAxis, rightAxis);
bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.COL);
// additional pad series - takes space at left side for secondary axis
series = data.addSeries(categoriesData, pad);
series.setTitle("pad", null);
series = data.addSeries(categoriesData, valuesData2);
series.setTitle("Series 2", null);
chart.plot(data);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("CreateExcelXDDFChart.xlsx")) {
document.write(fileOut);
}
}
}
}
Above example works when Microsoft Excel is used to show the resulting *.xlsx file. But some other spreadsheet calculation applications are not able to use array literals ({0,0,0,0,0,0,0,0,0}) as data source for chart series. So the more compatible solution will be using a unused cell range as datasource for the additional dummy series. For example use column IV for this as so:
...
// dummy 0-values for the pad data source in column IV
for (int r = 0; r < numOfPoints; r++) {
XSSFRow row = dataSheet.getRow(r); if (row == null) row = dataSheet.createRow(r);
XSSFCell cell = row.createCell(255);
cell.setCellValue(0);
}
// data source for the pad series
XDDFNumericalDataSource<Double> pad = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 255, 255));
...
And also for compatibility reasons you should set colors for the bars as done in the apache poi example in BarChart.java which is available in https://svn.apache.org/repos/asf/poi/trunk/poi-examples/src/main/java/org/apache/poi/examples/xssf/usermodel/. Some other spreadsheet calculation applications will not automatically choose color if not given, as Excel does.
If you have more than one series in one of the bar chart, you need more pad series in the other bar chart which uses the other axis. The pad series are needed to shift the visible bar series and to determine the bar width of them. So if you have two visible bar series on the first chart which uses the left axis and one visible bar series on the second chart which uses the right axis, then you need one additional pad series as the last series in the first bar chart and you need two pad series as the first series in the second bar chart. In other words, you need as much pad series that both bar charts have the same count of series to have the same bar width in both. And the position of the invisible pad series determines how the visible bar series are shifted.
If you need a legend, then the pad series will disturb. So you need deleting them from the legend. In remove specific legend apache poi excel graph XDDFChartLegend I have shown how to do that.
I will provide another complete example which shows all this:
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
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.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class CreateExcelXDDFChart {
public static void main(String[] args) throws Exception {
try (XSSFWorkbook document = new XSSFWorkbook()) {
XSSFSheet chartSheet = document.createSheet("chart");
XSSFSheet dataSheet = document.createSheet("data");
// create the data
String[] categories = new String[] { "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9" };
Double[] values1 = new Double[] { 1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d };
Double[] values2 = new Double[] { 200d, 300d, 400d, 500d, 600d, 700d, 800d, 900d, 1000d };
Double[] values3 = new Double[] { 4.5d, 4d, 3.5d, 3d, 2.5d, 2d, 1.5d, 1d, 0.5d };
int r = 0;
for (String cat : categories) {
dataSheet.createRow(r).createCell(0).setCellValue(cat);
dataSheet.getRow(r).createCell(1).setCellValue(values1[r]);
dataSheet.getRow(r).createCell(2).setCellValue(values2[r]);
dataSheet.getRow(r).createCell(3).setCellValue(values3[r]);
r++;
}
// create the chart
XSSFDrawing drawing = chartSheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 0, 16, 22);
XDDFChart chart = drawing.createChart(anchor);
// create data sources
int numOfPoints = categories.length;
XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromStringCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 0, 0));
XDDFNumericalDataSource<Double> valuesData1 = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 1, 1));
XDDFNumericalDataSource<Double> valuesData2 = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 2, 2));
XDDFNumericalDataSource<Double> valuesData3 = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 3, 3));
/*
// dummy 0-values for the pad data source
Double[] dummyValuesForPad = new Double[numOfPoints];
for (int i = 0; i < numOfPoints; i++) {
dummyValuesForPad[i] = 0d;
}
*/
// data source for the pad series
//XDDFNumericalDataSource<Double> pad = XDDFDataSourcesFactory.fromArray(dummyValuesForPad);
//XDDFNumericalDataSource<Double> pad = XDDFDataSourcesFactory.fromArray(dummyValuesForPad, null);
// dummy 0-values for the pad data source in column IV
for (int i = 0; i < numOfPoints; i++) {
XSSFRow row = dataSheet.getRow(i); if (row == null) row = dataSheet.createRow(i);
XSSFCell cell = row.createCell(255);
cell.setCellValue(0);
}
// data source for the pad series
XDDFNumericalDataSource<Double> pad = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet, new CellRangeAddress(0, numOfPoints-1, 255, 255));
// first bar chart
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.COL);
XDDFChartData.Series series = data.addSeries(categoriesData, valuesData1);
series.setTitle("Series 1", null);
series = data.addSeries(categoriesData, valuesData3);
series.setTitle("Series 3", null);
// additional pad series - takes space at right side for primary axis
series = data.addSeries(categoriesData, pad);
series.setTitle("pad", null);
chart.plot(data);
// set bar colors
solidFillSeries(data, 0, PresetColor.GREEN);
solidFillSeries(data, 1, PresetColor.BLUE);
// second bar chart
// bottom axis must be there but must not be visible
bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setVisible(false);
XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
rightAxis.setCrosses(AxisCrosses.MAX);
rightAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// set correct cross axis
bottomAxis.crossAxis(rightAxis);
rightAxis.crossAxis(bottomAxis);
data = chart.createData(ChartTypes.BAR, bottomAxis, rightAxis);
bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.COL);
// additional pad series - takes space at left side for secondary axis
series = data.addSeries(categoriesData, pad);
series.setTitle("pad", null);
series = data.addSeries(categoriesData, pad);
series.setTitle("pad", null);
series = data.addSeries(categoriesData, valuesData2);
series.setTitle("Series 2", null);
chart.plot(data);
// set bar colors
solidFillSeries(data, 2, PresetColor.RED);
// set legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
// set legend entries for pad series deleted
XDDFLegendEntry legendEntry = getOrAddLegendEntry(legend, 2);
legendEntry.setDelete(true);
legendEntry = getOrAddLegendEntry(legend, 3);
legendEntry.setDelete(true);
legendEntry = getOrAddLegendEntry(legend, 4);
legendEntry.setDelete(true);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("CreateExcelXDDFChart.xlsx")) {
document.write(fileOut);
}
}
}
private static XDDFLegendEntry getOrAddLegendEntry(XDDFChartLegend legend, long index) {
XDDFLegendEntry legendEntry = null;
for (XDDFLegendEntry storedLegendEntry : legend.getEntries()) {
if (storedLegendEntry.getIndex() == index) {
legendEntry = storedLegendEntry;
break;
}
}
if (legendEntry == null) {
legendEntry = legend.addEntry();
legendEntry.setIndex(index);
}
return legendEntry;
}
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);
}
}
I am using POI 4.1.0 to create charts but my bar chart is upside down
How can I flip the chart?
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, col1, row1, col2, row2);
XSSFChart chart = drawing.createChart(anchor);
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFDataSource<String> dataSource = XDDFDataSourcesFactory.fromStringCellRange(sheet,
new CellRangeAddress(startRow, data.getRows().size() + (startRow - 1), 0, 0));
XDDFChartData data = chart.createData(chartObj.getType(), bottomAxis, leftAxis);
XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(sheet,
new CellRangeAddress(startRow, this.data.getRows().size() + (startRow - 1),
chartObj.getColumn(),chartObj.getColumn()));
XDDFChartData.Series series1 = data.addSeries(dataSource, values);
chart.plot(data);
XDDFChartData data = chart.createData(...);
...
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.getCategoryAxis().setOrientation(AxisOrientation.MAX_MIN);
public class LineChart {
public static void main(String[] args) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("linechart");
final int NUM_OF_ROWS = 3;
final int NUM_OF_COLUMNS = 10;
// Create a row and put some cells in it. Rows are 0 based.
Row row;
Cell cell;
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = sheet.createRow((short) rowIndex);
for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(colIndex * (rowIndex + 1.0));
}
}
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 15);
XSSFChart chart = drawing.createChart(anchor);
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
// Use a category axis for the bottom axis.
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
//bottomAxis.setTitle("x"); // https://stackoverflow.com/questions/32010765
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
//leftAxis.setTitle("f(x)");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
XDDFDataSource<Double> xs = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 0, 0, NUM_OF_COLUMNS - 1));
XDDFNumericalDataSource<Double> ys1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 0, NUM_OF_COLUMNS - 1));
XDDFNumericalDataSource<Double> ys2 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(2, 2, 0, NUM_OF_COLUMNS - 1));
XDDFLineChartData data = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) data.addSeries(xs, ys1);
series1.setTitle("2x", null); // https://stackoverflow.com/questions/21855842
series1.setSmooth(false); // https://stackoverflow.com/questions/29014848
series1.setMarkerStyle(MarkerStyle.DOT); // https://stackoverflow.com/questions/39636138
series1.setShowLeaderLines(true);
XDDFLineChartData.Series series2 = (XDDFLineChartData.Series) data.addSeries(xs, ys2);
series2.setTitle("3x", null);
//series2.setSmooth(true);
//series2.setMarkerSize((short) 6);
//series2.setMarkerStyle(MarkerStyle.NONE); // https://stackoverflow.com/questions/39636138
series2.setShowLeaderLines(false);
chart.plot(data);
// if your series have missing values like https://stackoverflow.com/questions/29014848
// chart.displayBlanksAs(DisplayBlanks.GAP);
// https://stackoverflow.com/questions/24676460
solidLineSeries(data, 0, PresetColor.CHARTREUSE);
solidLineSeries(data, 1, PresetColor.BLACK);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart.xlsx")) {
wb.write(fileOut);
}
}
}
//CTPresetLineDashProperties
private static void solidLineSeries(XDDFChartData data, int index, PresetColor color) {
XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(color));
XDDFLineProperties line = new XDDFLineProperties();
//line.setPresetDash(new XDDFPresetLineDash(PresetLineDash.DOT));
line.setFillProperties(fill);
XDDFChartData.Series series = data.getSeries().get(index);
XDDFShapeProperties properties = series.getShapeProperties();
if (properties == null) {
properties = new XDDFShapeProperties();
}
properties.setLineProperties(line);
series.setShapeProperties(properties);
}
}
This is an example from:
http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/LineChart.java
And I use poi version is 4.1.1.
when I use series1.setShowLeaderLines(true) or series2.setShowLeaderLines(false)
it will show label as picture 1, and contains Series Name, Category Name, Value and Legend Key,
also label position is Right.
Is it possible to change it picture 2 which just contain Value and label position is Center?
Thank you
current view what I want
The problem is that XDDF is not complete yet. The XDDFLineChartData.Series.setShowLeaderLines sets default data lables if not present.
As of current Excel versions default data lables means including value, legend key, category name and series name. So all this is shown.
XDDFLineChartData.Series lacks methods for customizing the data labels except of the leader lines. So this needs to be done using the low level ooxml-schemas classes and methods.
In your case:
...
series1.setShowLeaderLines(true); // this sets default data lables
// customizing data labels
int seriesNr = 0;
//chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls()
// .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255,(byte)255,0});
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls()
.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.CTR);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls().addNewShowVal().setVal(true);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls().addNewShowLegendKey().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls().addNewShowCatName().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(seriesNr).getDLbls().addNewShowSerName().setVal(false);
...
I'm trying to create an Excel LineChart to compare both dependencies with different number of node points (as on the screenshot). I've tried to just add an extra axis to the graph, but this way gives no result. How is it possible to solve thе problem?
it should look like this:
ChartLegend legend2 = chart2.getOrCreateLegend();
legend2.setPosition(LegendPosition.RIGHT);
LineChartData data2 = chart2.getChartDataFactory().createLineChartData();
ChartAxis bottomAxis = chart2.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
ChartAxis topAxis = chart2.getChartAxisFactory().createCategoryAxis(AxisPosition.TOP);
ValueAxis leftAxis = chart2.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
ChartDataSource<String> xs = DataSources.fromStringCellRange(sheet, new CellRangeAddress(1, 350, 4, 4));
ChartDataSource<String> xs1 = DataSources.fromStringCellRange(sheet, new CellRangeAddress(1, 10, 7, 7));
ChartDataSource<Number> ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(1, 350, 5, 5));
ChartDataSource<Number> ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 8, 8));
LineChartSeries series1 = data2.addSeries(xs, ys1);
series1.setTitle("1");
LineChartSeries series2 = data2.addSeries(xs1, ys2);
series2.setTitle("2");
chart2.plot(data2, topAxis, bottomAxis, leftAxis);
XSSFChart xssfChart = (XSSFChart) chart2;
CTPlotArea plotArea = xssfChart.getCTChart().getPlotArea();
plotArea.getLineChartArray()[0].getSmooth();
CTBoolean ctBool = CTBoolean.Factory.newInstance();
ctBool.setVal(false);
plotArea.getLineChartArray()[0].setSmooth(ctBool);
for (CTLineSer ser : plotArea.getLineChartArray()[0].getSerArray()) {
ser.setSmooth(ctBool);
}
CTMarker ctMarker = CTMarker.Factory.newInstance();
ctMarker.setSymbol(CTMarkerStyle.Factory.newInstance());
for (CTLineSer ser : plotArea.getLineChartArray()[0].getSerArray()) {
ser.setMarker(ctMarker);
Whenever a OOXML chart has multiple axes, then the chart also needs multiple chart data in the plot area. And all axes needs to be pairs. Some of the axes may be invisible but they must be there.
Your code uses old apache poi classes which are deprecated and will be removed in next version. So here is an complete example using current apache poiversion 4.1.0.
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
public class XSSFLineChartTwoCatAxes {
public static void main(String[] args) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("linechart");
// create data
Row row;
Cell cell;
for (int rowIndex = 0; rowIndex < 4; rowIndex++) {
row = sheet.createRow((short) rowIndex);
if (rowIndex == 0) {
cell = row.createCell(0);
cell.setCellValue("CatA");
for (int colIndex = 1; colIndex < 32; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(colIndex + (colIndex-1)*11);
}
} else if (rowIndex == 1) {
cell = row.createCell(0);
cell.setCellValue("ValA");
for (int colIndex = 1; colIndex < 32; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(new java.util.Random().nextDouble() * 8 + 1);
}
} else if (rowIndex == 2) {
cell = row.createCell(0);
cell.setCellValue("CatB");
for (int colIndex = 1; colIndex < 14; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(colIndex);
}
} else if (rowIndex == 3) {
cell = row.createCell(0);
cell.setCellValue("ValB");
for (int colIndex = 1; colIndex < 14; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(new java.util.Random().nextDouble() * 8 + 1);
}
}
}
// creata anchor
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 15, 25);
// create chart
XSSFChart chart = drawing.createChart(anchor);
// create data sources
XDDFDataSource<Double> cat1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 0, 1, 31));
XDDFNumericalDataSource<Double> val1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 1, 31));
XDDFDataSource<Double> cat2 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(2, 2, 1, 13));
XDDFNumericalDataSource<Double> val2 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(3, 3, 1, 13));
// first line chart
// create axis
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
rightAxis.setCrosses(AxisCrosses.MAX);
// create data and series
XDDFLineChartData data = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, rightAxis);
XDDFLineChartData.Series series = (XDDFLineChartData.Series) data.addSeries(cat1, val1);
series.setTitle("CatA", new CellReference(sheet.getSheetName(), 0, 0, true, true));
series.setSmooth(false);
series.setMarkerStyle(MarkerStyle.NONE);
chart.plot(data);
solidLineSeries(data, 0, PresetColor.BLUE);
// second line chart
// create axis
XDDFCategoryAxis topAxis = chart.createCategoryAxis(AxisPosition.TOP);
topAxis.setCrosses(AxisCrosses.MAX);
rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
rightAxis.setVisible(false); // right axis must be there but can be invisible
rightAxis.setCrosses(AxisCrosses.MAX);
// set correct cross axis
topAxis.crossAxis(rightAxis);
rightAxis.crossAxis(topAxis);
// create data and series
data = (XDDFLineChartData) chart.createData(ChartTypes.LINE, topAxis, rightAxis);
series = (XDDFLineChartData.Series) data.addSeries(cat2, val2);
series.setTitle("CatB", new CellReference(sheet.getSheetName(), 2, 0, true, true));
series.setSmooth(false);
series.setMarkerStyle(MarkerStyle.NONE);
chart.plot(data);
// correct the id and order, must not be 0 again because there is a series already in the other chart
chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getIdx().setVal(1);
chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getOrder().setVal(1);
solidLineSeries(data, 0, PresetColor.GREEN);
// create legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
legend.setOverlay(false);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart.xlsx")) {
wb.write(fileOut);
}
}
}
private static void solidLineSeries(XDDFChartData data, int index, PresetColor color) {
XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(color));
XDDFLineProperties line = new XDDFLineProperties();
line.setFillProperties(fill);
XDDFChartData.Series series = data.getSeries().get(index);
XDDFShapeProperties properties = series.getShapeProperties();
if (properties == null) {
properties = new XDDFShapeProperties();
}
properties.setLineProperties(line);
series.setShapeProperties(properties);
}
}
I have a java file generating an excel sheet containing a ton of data. For better user experience, we decided to simply generate a line chart out of the data.
Generating the chart wasn't difficult, but getting it to be the exact shape as we need it is a problem. Currently, the chart border looks like this:
As you can see, the border is curved at the corner. I want it to look like this:
So, far I have not been able to find a way to do that. The documentation isn't very detailed about this.
So in your opinion rounded corners are outdated? I am this opinion too, but as it seems Microsoft is not. Because if in CTChartSpace the RoundedCorners is not set, then it is trueby default.
But fortunately we can set it as needed using following method:
private static void setRoundedCorners(XSSFChart chart, boolean setVal) {
if (chart.getCTChartSpace().getRoundedCorners() == null) chart.getCTChartSpace().addNewRoundedCorners();
chart.getCTChartSpace().getRoundedCorners().setVal(setVal);
}
Complete Example:
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Chart;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.charts.AxisCrosses;
import org.apache.poi.ss.usermodel.charts.AxisPosition;
import org.apache.poi.ss.usermodel.charts.ChartAxis;
import org.apache.poi.ss.usermodel.charts.ChartDataSource;
import org.apache.poi.ss.usermodel.charts.ChartLegend;
import org.apache.poi.ss.usermodel.charts.DataSources;
import org.apache.poi.ss.usermodel.charts.LegendPosition;
import org.apache.poi.ss.usermodel.charts.LineChartData;
import org.apache.poi.ss.usermodel.charts.ValueAxis;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFChart;
/**
* Line chart example.
*/
public class LineChart {
private static void setRoundedCorners(XSSFChart chart, boolean setVal) {
if (chart.getCTChartSpace().getRoundedCorners() == null) chart.getCTChartSpace().addNewRoundedCorners();
chart.getCTChartSpace().getRoundedCorners().setVal(setVal);
}
public static void main(String[] args) throws IOException {
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("linechart");
final int NUM_OF_ROWS = 3;
final int NUM_OF_COLUMNS = 10;
// Create a row and put some cells in it. Rows are 0 based.
Row row;
Cell cell;
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = sheet.createRow((short) rowIndex);
for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(colIndex * (rowIndex + 1));
}
}
Drawing<?> drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 15);
Chart chart = drawing.createChart(anchor);
setRoundedCorners((XSSFChart)chart, false);
ChartLegend legend = chart.getOrCreateLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);
LineChartData data = chart.getChartDataFactory().createLineChartData();
// Use a category axis for the bottom axis.
ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
ChartDataSource<Number> xs = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 0, 0, NUM_OF_COLUMNS - 1));
ChartDataSource<Number> ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 0, NUM_OF_COLUMNS - 1));
ChartDataSource<Number> ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(2, 2, 0, NUM_OF_COLUMNS - 1));
data.addSeries(xs, ys1);
data.addSeries(xs, ys2);
chart.plot(data, bottomAxis, leftAxis);
// Write the output to a file
FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart.xlsx");
wb.write(fileOut);
fileOut.close();
wb.close();
}
}