Resizing legend and axis title in a apache poi bar chart - java

I drew a 3D stacked bar chart using apache poi and I have trouble manipulating the font size of the left axis title and the legend of the chart. I have looked at the ooxml documentation found here but couldn't identify which object is responsible for this. I have added a piece of my code and the resulting chart, I appreciate any help directing me on the right path.
XSSFSheet sheet = wb.getSheetAt(0);
// Bar chart coordinates
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 37, 16, 47);
XSSFChart chart = drawing.createChart(anchor);
// configure axis properties
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Call Duration");
// font size for left axis labels (ticks)
leftAxis.getOrAddTextProperties().setFontSize(8d);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
XDDFChartData data = chart.createData(ChartTypes.BAR3D, bottomAxis, leftAxis);
// create legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
/*DATA ADDITION HERE*/
output bar chart

I have found which xml object addresses the font style of the the bar graph axis and legend. I did it basically by comparing the underlying xl/charts/chart1.xml generated by my java code and another bar graph that I had done with hand using ms excel. I had to do all this because, say for the value axis, apache poi's XDDFValueAxis object doesn't implement the axis title manipulation methods and org.openxmlformats.schemas.drawingml.x2006 API is needed. As I mentioned in my question the doc can be found here. The problem with the documentation is it's not apparent each objects functionality (at least for me), therefore, comparing and analyzing the xml objects gives insight to what actually some of the objects are responsible for. Here's my code piece and it has apache poi 4.1.2 and poi-ooxml-schemas-4.1.2 dependencies. Hope it'll be of help for someone with a similar problem.
XSSFSheet sheet = wb.getSheetAt(0);
// Bar chart coordinates
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 38, 16, 47);
XSSFChart chart = drawing.createChart(anchor);
// configure axis properties
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.getOrAddTextProperties().setFontSize(6d);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Call Duration");
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// font size for left axis labels (ticks)
leftAxis.getOrAddTextProperties().setFontSize(6d);
// create legend
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
// reflect the underlying the xml objects in order to access fields that are not implemented in apache poi
org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx ctValAx = null;
org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend ctLegend = null;
java.lang.reflect.Field ctValRef;
java.lang.reflect.Field chartLegendRef;
try {
ctValRef = XDDFValueAxis.class.getDeclaredField("ctValAx");
ctValRef.setAccessible(true);
ctValAx = (org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx) ctValRef.get(leftAxis);
chartLegendRef = XDDFChartLegend.class.getDeclaredField("legend");
chartLegendRef.setAccessible(true);
ctLegend = (org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend) chartLegendRef.get(legend);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
// set title properties for left axis
CTTitle ctTitle = ctValAx.getTitle();
ctTitle.getTx().getRich().getPArray(0).getRArray(0).getRPr().setSz(600);
// adjust the font size of the legend
ctLegend.addNewTxPr();
ctLegend.getTxPr().addNewBodyPr();
ctLegend.getTxPr().addNewLstStyle(); // font size in hundreds format (6*100)
ctLegend.getTxPr().addNewP().addNewPPr().addNewDefRPr().setSz(600);
/*DATA ADDITION HERE*/

At least for the legend's font size setting using org.apache.poi.xddf.usermodel.text.XDDFTextBody is possible. That has the advantage that this is a high level apache poi class which gets developed further. So if you have XDDFChartLegend legend, then construct a XDDFTextBody from this and use this for font settings.
Example:
...
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM);
XDDFTextBody legendTextBody = new XDDFTextBody(legend);
legendTextBody.getXmlObject().addNewBodyPr();
legendTextBody.addNewParagraph().addDefaultRunProperties().setFontSize(8d);
legend.setTextBody(legendTextBody);
...
For axis font settings using the low level org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx or org.openxmlformats.schemas.drawingml.x2006.chart.CTCatAx is necessary. But there also is org.apache.poi.xddf.usermodel.chart.XDDFTitle which is the high level wrapper for CTValAx-title or CTCatAx-title. So we should using that instead of directly using the CT* classes.
Example:
Do having XDDFTitle getOrSetAxisTitle methods:
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;
}
}
Then do using those as so:
...
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
//bottomAxis.setTitle("...");
XDDFTitle title = getOrSetAxisTitle(bottomAxis);
title.setOverlay(false);
title.setText("...");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(8d);
bottomAxis.getOrAddTextProperties().setFontSize(8d);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
//leftAxis.setTitle("...");
title = getOrSetAxisTitle(leftAxis);
title.setOverlay(false);
title.setText("...");
title.getBody().getParagraph(0).addDefaultRunProperties().setFontSize(8d);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
leftAxis.getOrAddTextProperties().setFontSize(8d);
...

Related

Show SUM Value above Stacked bar with Apache poi

I'm currently working on functionality, that is supposed to generate stacked chart inside .pptx file.
For this I'm using code from here:
java create a chart in a powerpoint using APACHE POI
I did some modifications. Mainly I set grouping to Stacked and overlap to 100 (so the subBars looks like one bigger bar).
Now I need to display SUM of subBars values above each Bar. Aaaaand here comes my question. How can I achieve this (second pohoto below) using Apache Poi and openxmlformats.schemas.drawingml.x2006.chart?
One idea was to create another SubBar on top, make it transparent and set it's label to my desired SUM, but I cannot find a way, to set label (only Value and in this case, my transparent SubBar takes too much space and it just looks terrible - as in the photo below).
This is what I need it to look like:
I cannot find any documentation or anything apart from some threads here on StackOverflow.
Do you guys have some idea, on how to achieve this?
EDIT 1
With help from Alex I was able to print SUM values above BARs. Now I just need to get rid of these 0 values:
EDIT 2
There was a problem mentioned by Axel, that caused zeroes to be displayed (from EDIT 1). Axel edited the code in his answer, so now zeroes are gone.
How would you do that using PowerPoint? The only way I see is using a combination of stacked bar chart with line chart where the line chart displays the sum values and is set invisible. So only the data labels of the line chart are visible. The way as described in How to add total labels to stacked column chart in Excel?.
Using current apache poi 4.1.2 this can be achieved using the new XDDF stuff. For a Excel chart, I have shown that in How to generate editable Stacked-bar-chart using apache poi 4.0.1 and java?.
I will show a complete example for a PowerPoint chart as well:
import java.io.*;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.util.Units;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
import java.util.*;
public class CreatePowerPointStackedBarChartXDDFChart {
public static void main(String[] args) throws Exception {
try (XMLSlideShow slideShow = new XMLSlideShow()) {
XSLFSlide slide = slideShow.createSlide();
// create the data
String[] categories = new String[]{"KW1", "KW2", "KW3", "KW4", "KW5", "KW6"};
int numOfPoints = categories.length;
Double[][] values = new Double [][] {
new Double[]{10d, 0d, 20d, 5d, 30d, 10d},
new Double[]{15d, 35d, 25d, 15d, 10d, 8d},
new Double[]{5d, 15d, 0d, 25d, 15d, 0d},
new Double[]{10d, 5d, 30d, 30d, 20d, 12d}
};
Double[] sums = new Double[numOfPoints];
for (int i = 0; i < sums.length; i++) {
double sum = 0;
for (Double[] valueRow : values) {
sum += valueRow[i];
}
sums[i] = sum;
}
// create the chart
XSLFChart chart = slideShow.createChart();
// add chart to slide
slide.addChart(chart, new java.awt.geom.Rectangle2D.Double(1d*Units.EMU_PER_CENTIMETER, 1d*Units.EMU_PER_CENTIMETER, 20d*Units.EMU_PER_CENTIMETER, 15d*Units.EMU_PER_CENTIMETER));
// bar chart
// create data sources
String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
List<XDDFNumericalDataSource<Double>> valuesData = new ArrayList<XDDFNumericalDataSource<Double>>();
int c = 1;
for (Double[] valueRow : values) {
String valuesDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, c, c));
valuesData.add(XDDFDataSourcesFactory.fromArray(valueRow, valuesDataRange, c));
c++;
}
// create axis
XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
// Set AxisCrossBetween, so the left 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.
leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// create chart data
XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
((XDDFBarChartData)data).setBarDirection(BarDirection.COL);
// stacked bar chart
((XDDFBarChartData)data).setBarGrouping(BarGrouping.STACKED);
((XDDFBarChartData)data).setOverlap((byte)100);
// create series
if (valuesData.size() == 1) {
// if only one series do not vary colors for each bar
((XDDFBarChartData)data).setVaryColors(false);
} else {
// if more than one series do vary colors of the series
((XDDFBarChartData)data).setVaryColors(true);
}
for (int s = 0; s < valuesData.size(); s++) {
XDDFChartData.Series series = data.addSeries(categoriesData, valuesData.get(s));
series.setTitle("Series"+(s+1), chart.setSheetTitle("Series"+(s+1), s+1));
}
// plot chart data
chart.plot(data);
// add data labels
for (int s = 0 ; s < valuesData.size(); s++) {
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).addNewDLbls();
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls()
.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.CTR);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewNumFmt();
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().getNumFmt()
.setSourceLinked(false);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().getNumFmt()
.setFormatCode("0;-0;");
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowVal().setVal(true);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowLegendKey().setVal(false);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowCatName().setVal(false);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowSerName().setVal(false);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowPercent().setVal(false);
chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(s).getDLbls().addNewShowBubbleSize().setVal(false);
}
// line chart
c = values.length + 1;
// create data source
String sumDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, c, c));
XDDFNumericalDataSource<Double> sumData = XDDFDataSourcesFactory.fromArray(sums, sumDataRange, c);
// axis must be there but must not be visible
bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setVisible(false);
leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setVisible(false);
// set correct cross axis
bottomAxis.crossAxis(leftAxis);
leftAxis.crossAxis(bottomAxis);
data = chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
XDDFChartData.Series series = data.addSeries(categoriesData, sumData);
series.setTitle("sum", chart.setSheetTitle("sum", c));
((XDDFLineChartData.Series)series).setSmooth(false);
((XDDFLineChartData.Series)series).setMarkerStyle(MarkerStyle.NONE);
// don't show the line
XDDFShapeProperties shapeProperties = new XDDFShapeProperties();
shapeProperties.setLineProperties(new XDDFLineProperties(new XDDFNoFillProperties()));
series.setShapeProperties(shapeProperties);
// plot chart data
chart.plot(data);
// correct the id and order, must not start 0 again because there are bar series already
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getIdx().setVal(c);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getOrder().setVal(c);
// add data labels
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).addNewDLbls();
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls()
.addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.T);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewNumFmt();
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().getNumFmt()
.setSourceLinked(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().getNumFmt()
.setFormatCode("0;-0;");
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowVal().setVal(true);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowLegendKey().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowCatName().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowSerName().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowPercent().setVal(false);
chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls().addNewShowBubbleSize().setVal(false);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("CreatePowerPointStackedBarChartXDDFChart.pptx")) {
slideShow.write(fileOut);
}
}
}
}

How do I order a bargraph to be positioned in between tick marks?

I added an example in the pictures below of what I want.
This is what I get.
This is what I want.
I've been searching through the the library to try to find something to make this change but I can't seem to find it.
Alternatively, is there a way to add padding to the top or bottom? The main issue is I don't want a bar touching the top or bottom border of the chart.
This is an XDDFChart, here is relevant code.
XDDFCategoryAxis leftAxis = chart.createCategoryAxis(AxisPosition.LEFT);
leftAxis.setTitle("Name");
XDDFValueAxis bottomAxis = chart.createValueAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Volume");
In bar charts the value axis should have set crossBetween val="between", 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.
Also the left axis never will be the category axis and bottom axis never will be the value axis, even not for bar charts which displays BarDirection.BAR instead of BarDirection.COL. There only the XDDFBarChartData's bar direction is different. The axes remain the same.
So for an XDDFChart in general, a bar chrat having BarDirection.BAR should be like:
...
// create axis
XDDFCategoryAxis categoryAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
categoryAxis.setTickLabelPosition(AxisTickLabelPosition.NONE);
XDDFValueAxis valueAxis = chart.createValueAxis(AxisPosition.LEFT);
valueAxis.setCrosses(AxisCrosses.AUTO_ZERO);
// Set AxisCrossBetween, so the left 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.
valueAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// create chart data
XDDFBarChartData data = (XDDFBarChartData)chart.createData(ChartTypes.BAR, categoryAxis, valueAxis);
data.setBarDirection(BarDirection.BAR);
...
Complete example creating a XWPFChart in a Word document:
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.util.Units;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
public class CreateWordXDDFChart {
// methode to set title in the data sheet without creating a Table but using the sheet data only
// creating a Table is not really necessary
static CellReference setTitleInDataSheet(XDDFChart chart, String title, int column) throws Exception {
XSSFWorkbook workbook = chart.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0);
XSSFRow row = sheet.getRow(0); if (row == null) row = sheet.createRow(0);
XSSFCell cell = row.getCell(column); if (cell == null) cell = row.createCell(column);
cell.setCellValue(title);
return new CellReference(sheet.getSheetName(), 0, column, true, true);
}
// methode to fill a series data into the underlying sheet
static void fillSheet(XDDFChart chart, XDDFDataSource<?> categoryData, XDDFNumericalDataSource<?> valuesData) throws Exception {
XSSFWorkbook workbook = chart.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0);
int numOfPoints = categoryData.getPointCount();
for (int i = 0; i < numOfPoints; i++) {
XSSFRow row = sheet.getRow(i + 1); if (row == null) row = sheet.createRow(i + 1); // first row is for title
XSSFCell cell = row.getCell(categoryData.getColIndex()); if (cell == null) cell = row.createCell(categoryData.getColIndex());
cell.setCellValue(categoryData.getPointAt(i).toString());
cell = row.getCell(valuesData.getColIndex()); if (cell == null) cell = row.createCell(valuesData.getColIndex());
cell.setCellValue(valuesData.getPointAt(i).doubleValue());
}
}
public static void main(String[] args) throws Exception {
try (XWPFDocument document = new XWPFDocument()) {
// create the data
String[] categories = new String[]{"C1", "C2", "C3"};
Double[] valuesA = new Double[]{300d, 20d, 10d};
// create the chart
XWPFChart chart = document.createChart(15*Units.EMU_PER_CENTIMETER, 10*Units.EMU_PER_CENTIMETER);
// create data sources
int numOfPoints = categories.length;
String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
String valuesDataRangeA = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1));
XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
XDDFNumericalDataSource<Double> valuesDataA = XDDFDataSourcesFactory.fromArray(valuesA, valuesDataRangeA, 1);
// create axis
XDDFCategoryAxis categoryAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
categoryAxis.setTickLabelPosition(AxisTickLabelPosition.NONE);
XDDFValueAxis valueAxis = chart.createValueAxis(AxisPosition.LEFT);
valueAxis.setCrosses(AxisCrosses.AUTO_ZERO);
// Set AxisCrossBetween, so the left 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.
valueAxis.setCrossBetween(AxisCrossBetween.BETWEEN);
// create chart data
XDDFBarChartData data = (XDDFBarChartData)chart.createData(ChartTypes.BAR, categoryAxis, valueAxis);
data.setBarDirection(BarDirection.BAR);
// create series
data.setVaryColors(true);
XDDFBarChartData.Series series = (XDDFBarChartData.Series)data.addSeries(categoriesData, valuesDataA);
// XDDFChart.setSheetTitle is buggy. It creates a Table but only half way and incomplete.
// Excel cannot opening the workbook after creating that incomplete Table.
// So updating the chart data in Word is not possible.
//series.setTitle("Ser1", chart.setSheetTitle("Ser1", 1));
series.setTitle("Ser1", setTitleInDataSheet(chart, "Ser1", 1));
// since apache oi 4.1.1 XDDFChart does not fill the series in the sheet while plot. Apache poi 4.1.0 had done this: https://svn.apache.org/viewvc/poi/tags/REL_4_1_0/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFChart.java?view=markup#l363
// so we need doing this for each series now
fillSheet(chart, categoriesData, valuesDataA);
// plot chart data
chart.plot(data);
// Write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("CreateWordXDDFChart.docx")) {
document.write(fileOut);
}
}
}
}

Combined plot for hardware monitoring

I am monitoring CPU, RAM and disk IO for a computational pipeline that consists of multiple steps to process the input data. I would like to visualize the hardware consumption for the entire pipeline but also per step of the pipeline.
In order to render hardware consumptions, I'd like to draw a combined plot with jFreeChart with four vertically aligned subplots that all have the same x-axis - time. The top plot is supposed to be a gantt plot to indicate the step of the pipeline. In the second plot I'd like to render the CPU consumption over time. In the third plot the RAM consumption, the bottom plot is for disk IO. The aim is to visually slide horizontally over the plot and see, which step of the pipeline uses how much CPU, RAM and disk.
I'm all set up in eclipse, jFreeChart library installed and all my data is imported and ready to render. All I need is some guidance for how to set up the combined chart up, cause my code blows up, when I try to combine the Gantt plot (which is a category plot) and the CPU plot (which is XY plot):
public JFreeChart createChart() {
final JFreeChart ganttChart = ChartFactory.createGanttChart(
"Gantt of Tasks", // chart title
"Task", // domain axis label
"Time", // range axis label
createGanttDataset(), // data
true, // include legend
true, // tooltips
false // urls
);
JFreeChart cpuChart = ChartFactory.createTimeSeriesChart(
"CPU load", // title
"Time", // x-axis label
"CPU Load", // y-axis label
createCpuDataset(), // data
true, // create legend?
true, // generate tooltips?
false // generate URLs?
);
// parent plot...
final CombinedRangeCategoryPlot plot = new CombinedRangeCategoryPlot();
plot.setGap(10.0);
// add the subplots...
plot.add(ganttChart.getCategoryPlot(), 1);
plot.add(cpuChart.getCategoryPlot(), 1);
plot.setOrientation(PlotOrientation.VERTICAL);
// return a new chart containing the overlaid plot...
return new JFreeChart("Combined GANTT/CPU plot",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
private IntervalCategoryDataset createGanttDataset() {
final TaskSeries taskSeries = new TaskSeries("Tasks");
for (Map.Entry<DoubleLong, String> entry : algoStartEndDates.entrySet()) {
final String taskTitle = entry.getValue();
final long startDate = entry.getKey().n1;
final long endDate = entry.getKey().n2;
taskSeries.add(new Task(taskTitle, new SimpleTimePeriod(startDate, endDate)));
}
final TaskSeriesCollection collection = new TaskSeriesCollection();
collection.add(taskSeries);
return collection;
}
private XYDataset createCpuDataset() {
final TimeSeries timeSeries = new TimeSeries("CPU load");
for (CollectlData data : collectlData) {
final RegularTimePeriod t = new Millisecond(new Date(data.getUtcTime()));
timeSeries.add(t, data.getCpuData().getTotal());
}
final TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(timeSeries);
dataset.setDomainIsPointsInTime(true);
return dataset;
}
I would expect this to create a combined plot, but I'm receiving the error message:
Caused by: java.lang.ClassCastException: class org.jfree.chart.plot.XYPlot cannot be cast to class org.jfree.chart.plot.CategoryPlot (org.jfree.chart.plot.XYPlot and org.jfree.chart.plot.CategoryPlot are in unnamed module of loader 'app')
at org.jfree.chart.JFreeChart.getCategoryPlot(JFreeChart.java:824) ~[jfreechart-1.0.15.jar:1.0.15]

How can I change charts generated by apache poi to not use smoothed lines and show empty cells as gaps?

I am using POI 3.12-beta1 and have code that creates a line chart with multiple datasets and named series in the legend. However, the default settings for line charts in poi generate a line that has been smoothed over the data points. Empty values are also being plotted as 0, but we want the lines to stop at the first column where there is an empty cell.
I can go into the chart properties once it is rendered in an xlsx file and change these settings, but we need to have the xlsx rendered with these settings. I can't find anything in the available API to change these settings.
I used this sample class as a starting point for my code below
http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/LineChart.java
Drawing drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 17, 18, 30);
Chart chart = drawing.createChart(anchor);
ChartLegend legend = chart.getOrCreateLegend();
legend.setPosition(LegendPosition.RIGHT);
LineChartData data = chart.getChartDataFactory().createLineChartData();
ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
int row = 2;
int startCol = 3;
int endCol = 17;
boolean abs = false;
ChartDataSource<Number> xs = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(row, row, startCol, endCol));
row = 10;
int seriesCol = 0;
ChartDataSource<Number> ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(row, row, startCol, endCol));
LineChartSerie ser1 = data.addSerie(xs, ys1);
ser1.setTitle(new CellReference(sheet.getSheetName(), row, seriesCol, abs, abs));
row = 11;
ChartDataSource<Number> ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(row, row, startCol, endCol));
LineChartSerie ser2 = data.addSerie(xs, ys2);
ser2.setTitle(new CellReference(sheet.getSheetName(), row, seriesCol, abs, abs));
row = 12;
ChartDataSource<Number> ys3 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(row, row, startCol, endCol));
LineChartSerie ser3 = data.addSerie(xs, ys3);
ser3.setTitle(new CellReference(sheet.getSheetName(), row, seriesCol, abs, abs));
chart.plot(data, new ChartAxis[] { bottomAxis, leftAxis });
Thanks to Etienne for the code to set blanks as gaps. I got help from a POI developer and here is the solution that solves both issues mentioned in the original question.
XSSFChart chart = (XSSFChart)drawing.createChart(anchor);
// this will set blank values as gaps in the chart so you
// can accurately plot data series of different lengths
CTDispBlanksAs disp = CTDispBlanksAs.Factory.newInstance();
disp.setVal(STDispBlanksAs.GAP);
chart.getCTChart().setDispBlanksAs(disp);
// setup chart, axes, data series, etc
chart.plot(data, new ChartAxis[] { bottomAxis, leftAxis });
// this must occur after the call to chart.plot above
CTPlotArea plotArea = chart.getCTChart().getPlotArea();
for (CTLineChart ch : plotArea.getLineChartList()) {
for (CTLineSer ser : ch.getSerList()) {
CTBoolean ctBool = CTBoolean.Factory.newInstance();
ctBool.setVal(false);
ser.setSmooth(ctBool);
}
}
I couldn't find any solution to that same problem so I ended up trying to find some "creative workaround". It's not a clean approach at all, but I'll share it just in case.
It turns out that the default Chart POI interface doesn't allow to do a lot of operations (modify Axis and Legend, and that's about it).
I do work with XLSX files however, and the implementing XSSFChart class provides a bit more features, such as a setPlotOnlyVisibleCells method that does work in enabling/disabling that option in the "Hidden & Empty Cells" menu. However, that's not what we want!
If you look at the XLSX file format (which is basically XML), you'll notice there's a "dispBlanksAs" XML element as a child of the Chart element, and we'll want to set it to "gap". (POI won't set it, which is equivalent to set it to "zero").
Unfortunately, there's no such method in the POI API currently, so I had to resort to the following dirty code to get it working:
XSSFChart chart = (XSSFChart)drawing.createChart(anchor);
CTDispBlanksAs disp = CTDispBlanksAs.Factory.newInstance();
disp.setVal(STDispBlanksAs.GAP);
chart.getCTChart().setDispBlanksAs(disp);
This code is very likely reprehensible in many ways, but it works for me :)
An important note is that you'll need to have the ooxml-schemas 1.1 jar on the classpath for this to work. It won't work even with poi-ooxml-schemas 3.12, as it doesn't contain the definition for CTDispBlanksAs class.
Hopefully this feature will be supported in a future version of POI.

Display percentage value in Pie Chart

I did the coding to create a pie chart file in png format file; the pie chart shows the percentage, but it's in the bottom of the file. I want the percentage to appear in the chart itself. This is the code I have used:
String query = "SELECT name,flag from mawarid";
JDBCPieDataset dataset = new JDBCPieDataset(
"jdbc:oracle:thin:# 127.0.0.1:1521:XE", "oracle.jdbc.OracleDriver", "", "");
dataset.executeQuery(query);
JFreeChart chart = ChartFactory.createPieChart(
"Test", dataset, true, true, false);
try {
PiePlot plot = (PiePlot) chart.getPlot();
plot.setLegendLabelGenerator(
new StandardPieSectionLabelGenerator("{0} {2}"));
final ChartRenderingInfo info = new ChartRenderingInfo(
new StandardEntityCollection());
final File file1 = new File("Chart5.png");
ChartUtilities.saveChartAsPNG(file1, chart, 600, 400, info);
} catch (Exception e) {
// log exception
}
You can use the same message format in a label generator as you do in your legend label generator:
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} {2}"));
Try using PieLabelDistributor on the PiePlot.
plot.setLabelDistributor(PieLabelDistributor aDistributor)

Categories