I need to change the first and the last bars. As you can see, both bars are halfed due the limits of the chart.
Which class from Apache POI librar should I use to change that?
This is the code which is drawing the chart
private ByteArrayInputStream generateFileDetails(List<ClientDataRequest> clientDataRequest, String metricName){
ByteArrayOutputStream file = new ByteArrayOutputStream();
try{
try(XSSFWorkbook workbook = new XSSFWorkbook()){
XSSFSheet sheet = workbook.createSheet(metricName);
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setUnderline(Font.U_SINGLE);
CellStyle headerCellStyle = workbook.createCellStyle();
headerCellStyle.setFillBackgroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
headerCellStyle.setFont(headerFont);
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 5, 1, 14, 10+ clientDataRequest.size());
Row headerRow = sheet.createRow(anchor.getRow2()+1);
headerRow.createCell(0).setCellValue("Client Name");
headerRow.createCell(1).setCellValue("Value");
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
headerRow.getCell(i).setCellStyle(headerCellStyle);
}
for (int i = 0; i < clientDataRequest.size(); i++) {
Row row = sheet.createRow(anchor.getRow2()+2+i);
row.createCell(0).setCellValue(clientDataRequest.get(i).getClientName());
sheet.autoSizeColumn(0);
row.createCell(1).setCellValue(clientDataRequest.get(i).getValue());
sheet.autoSizeColumn(1);
}
XSSFChart chart = drawing.createChart(anchor);
chart.setTitleText(metricName);
chart.setTitleOverlay(false);
//XDDFChartLegend legend = chart.getOrAddLegend();
//legend.setPosition(LegendPosition.TOP_RIGHT);
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Client Name");
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Value");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
XDDFDataSource<String> clientNames = XDDFDataSourcesFactory.fromStringCellRange(sheet,
new CellRangeAddress(anchor.getRow2()+2, anchor.getRow2()+1+clientDataRequest.size(), 0, 0));
XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(sheet,
new CellRangeAddress(anchor.getRow2()+2, anchor.getRow2()+1+clientDataRequest.size(), 1, 1));
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
XDDFChartData.Series series = data.addSeries(clientNames, values);
XDDFLineProperties line = new XDDFLineProperties();
series.setTitle(null, null);
data.setVaryColors(false);
chart.plot(data);
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.BAR);
workbook.write(file);
}
} catch (Exception e){
}
return new ByteArrayInputStream(file.toByteArray());
}
It does not have many documentation and samples about the Apache POI.
For a bar chart you additional need set AxisCrossBetween for the value axis, so the value axis crosses the category axis between the categories. Else first and last category is exactly on cross points and the bars are only half visible.
In your case:
...
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Value");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(org.apache.poi.xddf.usermodel.chart.AxisCrossBetween.BETWEEN);
...
Related
I'm trying to set the fill color of each individual bar of my chart to a separate custom color but I have yet to discover how.
Here's my code so far. The layout is fine but the chart bars are all the same color (cornflower blue). I'm aware that I can use the XDDFChartData:setVaryColors option to use different colors but I can't find a way to specify which colors to use (say in my example 'Books' in red, 'DVDs' in blue, etc.).
final Workbook workbook = new XSSFWorkbook();
final Sheet sheet = workbook.createSheet("MySheet");
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellValue("Product");
cell = row.createCell(2);
cell.setCellValue("Orders");
row = sheet.createRow(2);
cell = row.createCell(1);
cell.setCellValue("Books");
cell = row.createCell(2);
cell.setCellValue(50);
row = sheet.createRow(3);
cell = row.createCell(1);
cell.setCellValue("DVDs");
cell = row.createCell(2);
cell.setCellValue(25);
row = sheet.createRow(4);
cell = row.createCell(1);
cell.setCellValue("CDs");
cell = row.createCell(2);
cell.setCellValue(140);
XSSFDrawing drawing = ((XSSFSheet) sheet).createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 4, 1, 12, 15 );
XSSFChart chart = drawing.createChart(anchor);
chart.setTitleText("Products");
chart.setTitleOverlay(false);
CTBoolean FALSE = CTBoolean.Factory.newInstance();
FALSE.setVal(false);
chart.getCTChartSpace().setRoundedCorners(FALSE);
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setMajorTickMark(AxisTickMark.NONE);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFDataSource<String> compliances = XDDFDataSourcesFactory.fromStringCellRange((XSSFSheet) sheet, new CellRangeAddress(2, 4, 1, 1));
XDDFNumericalDataSource<Double> components = XDDFDataSourcesFactory.fromNumericCellRange((XSSFSheet) sheet, new CellRangeAddress(2, 4, 2, 2));
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
XDDFChartData.Series series1 = data.addSeries(compliances, components);
series1.setTitle("Components by Compliance", null);
chart.plot(data);
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.BAR);
CTSRgbColor rgb = CTSRgbColor.Factory.newInstance();
Color col1 = new Color(100, 149, 237);
rgb.setVal(new byte[]{(byte) col1.getRed(), (byte) col1.getGreen(), (byte) col1.getBlue()});
CTSolidColorFillProperties fillProp = CTSolidColorFillProperties.Factory.newInstance();
fillProp.setSrgbClr(rgb);
CTShapeProperties ctShapeProperties = CTShapeProperties.Factory.newInstance();
ctShapeProperties.setSolidFill(fillProp);
chart.getCTChart().getPlotArea().getBarChartList().get(0).getSerList().get(0).setSpPr(ctShapeProperties);
IntStream.range(0, 10).forEach(sheet::autoSizeColumn);
Any ideas?
Cheers!
It seems as if you wants coloring each data point instead of the bar series. If so then this is the same problem as with how to set custom colors in pie 3D Chart using Apache poi 4.1.2. Difference is only the chart type.
There is no XDDF method to set data point colors until now. Only series colors can be set using XDDF methods. So one must use the underlying org.openxmlformats.schemas.drawingml.x2006.chart.* classes. Following complete example shows this. It sets data point colors from a bunch of formerly set rgb byte arrays.
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.chart.*;
public class BarChartOneSeries {
public static void main(String[] args) throws IOException {
final Workbook workbook = new XSSFWorkbook();
final Sheet sheet = workbook.createSheet("MySheet");
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellValue("Product");
cell = row.createCell(2);
cell.setCellValue("Orders");
row = sheet.createRow(2);
cell = row.createCell(1);
cell.setCellValue("Books");
cell = row.createCell(2);
cell.setCellValue(50);
row = sheet.createRow(3);
cell = row.createCell(1);
cell.setCellValue("DVDs");
cell = row.createCell(2);
cell.setCellValue(25);
row = sheet.createRow(4);
cell = row.createCell(1);
cell.setCellValue("CDs");
cell = row.createCell(2);
cell.setCellValue(140);
XSSFDrawing drawing = ((XSSFSheet) sheet).createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 4, 1, 12, 15 );
XSSFChart chart = drawing.createChart(anchor);
chart.setTitleText("Products");
chart.setTitleOverlay(false);
CTBoolean FALSE = CTBoolean.Factory.newInstance();
FALSE.setVal(false);
chart.getCTChartSpace().setRoundedCorners(FALSE);
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setMajorTickMark(AxisTickMark.NONE);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFDataSource<String> compliances = XDDFDataSourcesFactory.fromStringCellRange((XSSFSheet) sheet, new CellRangeAddress(2, 4, 1, 1));
XDDFNumericalDataSource<Double> components = XDDFDataSourcesFactory.fromNumericCellRange((XSSFSheet) sheet, new CellRangeAddress(2, 4, 2, 2));
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
XDDFChartData.Series series = data.addSeries(compliances, components);
series.setTitle("Components by Compliance", null);
chart.plot(data);
XDDFBarChartData bar = (XDDFBarChartData) data;
bar.setBarDirection(BarDirection.BAR);
// do not auto delete the title; is necessary for showing title in Calc
if (chart.getCTChart().getAutoTitleDeleted() == null) chart.getCTChart().addNewAutoTitleDeleted();
chart.getCTChart().getAutoTitleDeleted().setVal(false);
// data point colors; is necessary for showing data points in Calc
// some rgb colors to choose
byte[][] colors = new byte[][] {
new byte[] {127,(byte)255, 127},
new byte[] {(byte)200, (byte)200, (byte)200},
new byte[] {(byte)255,(byte)255, 127},
new byte[] {(byte)255, 127, 127},
new byte[] {(byte)255, 0, 0},
new byte[] {0, (byte)255, 0},
new byte[] {0, 0, (byte)255},
new byte[] {80, 80, 80}
};
// set data point colors
int pointCount = series.getCategoryData().getPointCount();
for (int p = 0; p < pointCount; p++) {
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(p);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(0).getDPtArray(p)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(colors[p]);
}
// write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-bar-chart.xlsx")) {
workbook.write(fileOut);
}
workbook.close();
}
}
I have started working with Apache POI to create an Excel sheet that contains a chart. The chart is using data that is pretty simple, a date column and a numerical value column. I want to display the data in date reverse order so the newest data is at the top row in the spreadsheet, but I would like the chart to show the data in date normal order, because I'm also including a trend line, and the direction of the trend line is completely dependent on the ordering of the data. The date is being displayed on the x-axis, with the y-axis containing the numerical values. What I want to do is basically reverse all of the data on the chart. Is there some way to do this? In Google Sheets, it happens automatically, but that is not an option.
Here's the code I'm currently experimenting with:
public void addChart() {
final int NUM_OF_ROWS = ytd.size();
final int NUM_OF_COLUMNS = 2;
// Create a row and put some cells in it. Rows are 0 based.
XSSFRow row;
XSSFCell cell;
List<String> dates = new ArrayList<>();
List<Double> values = new ArrayList<>();
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = (XSSFRow)sheet.createRow((short) rowIndex);
row.createCell(0).setCellValue(ytd.get(rowIndex).getShipDate());
dates.add(ytd.get(rowIndex).getShipDate());
for (int colIndex = 1; colIndex < NUM_OF_COLUMNS; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(new BigDecimal(ytd.get(rowIndex).getSquareFeet().replaceAll("[,]", "")).doubleValue());
values.add(new BigDecimal(ytd.get(rowIndex).getSquareFeet().replaceAll("[,]", "")).doubleValue());
}
}
Collections.reverse(dates);
Collections.reverse(values);
for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
row = sheet.getRow((short) rowIndex);
row.createCell(2).setCellValue(dates.get(rowIndex));
for (int colIndex = 3; colIndex < 4; colIndex++) {
cell = row.createCell((short) colIndex);
cell.setCellValue(values.get(rowIndex));
}
}
XSSFDrawing drawing = (XSSFDrawing) sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(5, 5, 20, 20, 4, 4, 20, 25);
XSSFChart chart = drawing.createChart(anchor);
CTChart ctChart = ((XSSFChart) chart).getCTChart();
// XSSFChart chart = drawing.createChart(anchor);
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);
// Use a category axis for the bottom axis.
XDDFDateAxis bottomAxis = chart.createDateAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Ship Date"); // https://stackoverflow.com/questions/32010765
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Square Feet");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
XDDFDataSource<Double> xs = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, NUM_OF_ROWS - 1, 2, 2));
XDDFNumericalDataSource<Double> ys1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, NUM_OF_ROWS-1, 3, 3));
XDDFLineChartData data = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) data.addSeries(xs, ys1);
series1.setTitle("SqFt", null); // https://stackoverflow.com/questions/21855842
series1.setSmooth(false); // https://stackoverflow.com/questions/29014848
series1.setMarkerStyle(MarkerStyle.DOT); // https://stackoverflow.com/questions/39636138
chart.plot(data);
// sheet.setColumnHidden(2, true);
// sheet.setColumnHidden(3, true);
// 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.TURQUOISE);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0)
.addNewTrendline()
.addNewTrendlineType()
.setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STTrendlineType.LINEAR);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart.xlsx")) {
workbook.write(fileOut);
} catch (FileNotFoundException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
I've tried putting the data in columns that are hidden, which works fine in LibreOffice, but doesn't work in excel until I un-hide the columns. So does anyone know if there is a way to control the ordering of the graph data, separate from the data in the table?
I found the solution: you can change the direction of the data by modifying the category axis on the chart, using the enum AxisOrientation. A side effect of getting the data to chart in the order I want, is that the labels for the y-axis now appear on the right instead of the left, but I think I can live with that.
I use poi version is 4.1.1.
Now I want to make first column doesn't display on y-axis, means I want to add some space between y-axis and the first column.
I notice there is an option "Axis position" on x-axis, when it set to "Between tick marks", it will work as I want.
But I don't know how to make it using apache-poi. Is it possible to make it?
here is the "Asis position in excel"
now the line chart looks like
what I want
Thanks everyone!!!
Here is the code example from Apache website:
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("a", null); // https://stackoverflow.com/questions/21855842
//series1.setSmooth(false); // https://stackoverflow.com/questions/29014848
series1.setMarkerStyle(MarkerStyle.NONE); // https://stackoverflow.com/questions/39636138
XDDFLineChartData.Series series2 = (XDDFLineChartData.Series) data.addSeries(xs, ys2);
series2.setTitle("b", null);
//series2.setSmooth(true);
//series2.setMarkerSize((short) 6);
series2.setMarkerStyle(MarkerStyle.NONE); // https://stackoverflow.com/questions/39636138
CTBoolean ctBoolean = CTBoolean.Factory.newInstance();
ctBoolean.setVal(false);
CTPlotArea plotArea = chart.getCTChart().getPlotArea();
chart.getCTChartSpace().setRoundedCorners(ctBoolean);
for (CTLineSer ser : plotArea.getLineChartArray()[0].getSerArray()) {
CTDLbls ctdLbls = ser.addNewDLbls();
ctdLbls.setShowSerName(ctBoolean);
ctdLbls.setShowLegendKey(ctBoolean);
ctdLbls.setShowLeaderLines(ctBoolean);
ctdLbls.setShowCatName(ctBoolean);
CTDLblPos ctdLblPos = CTDLblPos.Factory.newInstance();
ctdLblPos.setVal(STDLblPos.CTR);
CTDLblPos.Factory.newInstance();
ctdLbls.setDLblPos(ctdLblPos);
}
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.LIGHT_GREEN);
solidLineSeries(data, 1, PresetColor.DARK_RED);
// 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();
if (index == 0) {
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);
}
}
Per default the value axis and the category axis crosses exactly on category point. So if value axis crosses category axis at point 0, then it looks like your result now.
But if x axis is a category axis, then there is a setting XDDFValueAxis.setCrossBetween. If that is set to AxisCrossBetween.BETWEEN then the value axis crosses the category axis between the category points. This looks as you want.
So in your case:
...
// 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);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
...
Btw.: You should get rid of deprecated methods.
For example the get...Array() methods of ooxml-schemas are deprecated. Either you get a concrete item from the array using get...Array(item) or you get a List using get...List().
In your case:
...
//for (CTLineSer ser : plotArea.getLineChartArray()[0].getSerArray()) {
for (CTLineSer ser : plotArea.getLineChartArray(0).getSerList()) {
...
And also XDDFChartData.getSeries() is deprecated. If you need one series do using XDDFChartData.getSeries(index)
In your case:
...
//XDDFChartData.Series series = data.getSeries().get(index);
XDDFChartData.Series series = data.getSeries(index);
...
I do like to create a line chart base on my data-source but unfortunately, when the line chart y-series across multiple cells it hit NullPointException while plot chat.
I found the error occurred while the column were blank and force to set "0" to make it work.
Anyway, I could plot chart on the empty cells without hard-coded "0"?
Below is the way how i hard-coded the default value,
for (int i = 0; headers.size() > i; i++) {
row.createCell(i, CellType.NUMERIC);
}
Below is LineChart code:
int rows = numberOfRows - 1;
int cols = headers.size();
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 15);
XSSFChart chart = drawing.createChart(anchor);
chart.displayBlanksAs(DisplayBlanks.GAP);
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);
// Use a category axis for the bottom axis.
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setOrientation(AxisOrientation.MAX_MIN);
bottomAxis.setTitle("Date");
XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
rightAxis.setTitle("Rates");
rightAxis.setCrosses(AxisCrosses.AUTO_ZERO);
XDDFChartLegend chartLegend = chart.getOrAddLegend();
chartLegend.setPosition(LegendPosition.TOP_RIGHT);
chartLegend.setOverlay(false);
XDDFLineChartData lineChartData = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, rightAxis);
lineChartData.setVaryColors(false);
XDDFDataSource<String> xs = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1, rows, 1, 1));
for (int col = 2; cols > col; col++) {
XDDFNumericalDataSource<Double> ys = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, rows, col, col));
XDDFLineChartData.Series series = (XDDFLineChartData.Series) lineChartData.addSeries(xs, ys);
series.setTitle(headers.get(col), null);
series.setSmooth(false);
series.setMarkerStyle(MarkerStyle.NONE);
}
chart.plot(lineChartData);
The scatter chart example included with Apache POI shows how to set the color of a line (that connects markers in a series), but I cannot figure out how to set the color of a marker for a series. I see that I can change the marker icon (with setMarkerStyle (javadoc), but that does not appear to have editable color properties).
From a previous SO question I suspect that it will be necessary to disable the vary colors configuration, but I still do not know how to set the colors after that step (if the following line is even necessary).
((XSSFChart)chart).getCTChart().getPlotArea().getScatterChartArray(0)
.addNewVaryColors().setVal(false);
How can I specify the marker icon colors in the example below?
public class ScatterChart {
public static void main(String[] args) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("Sheet 1");
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.TOP_RIGHT);
XDDFValueAxis bottomAxis = chart.createValueAxis(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));
XDDFScatterChartData data = (XDDFScatterChartData) chart.createData(ChartTypes.SCATTER, bottomAxis, leftAxis);
XDDFScatterChartData.Series series1 = (XDDFScatterChartData.Series) data.addSeries(xs, ys1);
series1.setTitle("2x", null); // https://stackoverflow.com/questions/21855842
series1.setSmooth(false); // https://stackoverflow.com/questions/39636138
XDDFScatterChartData.Series series2 = (XDDFScatterChartData.Series) data.addSeries(xs, ys2);
series2.setTitle("3x", null);
chart.plot(data);
solidLineSeries(data, 0, PresetColor.CHARTREUSE);
solidLineSeries(data, 1, PresetColor.TURQUOISE);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-scatter-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);
}
}
The previous question you linked is from May 2018 and is about the pure XSSFChart stuff from apache poi upto version 3.17. This is outdated since apache poi 4.0.0 introduced the new XDDF stuff.
Nevertheless setting the marker color is not supported until now using only the high level XDDF classes. The marker has shape properties having fill properties of the same kind as the series itself. So we can use XDDFSolidFillProperties and XDDFShapeProperties as for the line settings. But to get the marker, we need using the low level underlying ooxml-schemas-1.4 beans.
Example:
...
series2.setMarkerStyle(MarkerStyle.DIAMOND);
series2.setMarkerSize((short)15);
XDDFSolidFillProperties fillMarker = new XDDFSolidFillProperties(XDDFColor.from(new byte[]{(byte)0xFF, (byte)0xFF, 0x00}));
XDDFShapeProperties propertiesMarker = new XDDFShapeProperties();
propertiesMarker.setFillProperties(fillMarker);
chart.getCTChart().getPlotArea().getScatterChartArray(0).getSerArray(1).getMarker()
.addNewSpPr().set(propertiesMarker.getXmlObject());
...