4

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).

enter image description here

This is what I need it to look like: enter image description here

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: enter image description here

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.

Jake_3H
  • 372
  • 4
  • 14

1 Answers1

1

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);
   }
  }
 }

}
Axel Richter
  • 56,077
  • 6
  • 60
  • 87
  • Thank you!! This is exactly what I was lookin for. Originally I was using CT.. objects, but this code seems to be much more user friendly, so I'll go with these. – Jake_3H Jul 04 '20 at 08:14
  • One more question. How can I not display bar label, when it's value is 0? With CT object I was able to skip it(theoretically), but here it sets the whole series at once from array. Can I delete it afterwards or somehow set each subBar individually to series? – Jake_3H Jul 05 '20 at 10:24
  • I tried your example and it shows 0 there. U sure it works with your example? Maybe we have different version of some lib or something, but i got Apache POI 4.1.2. – Jake_3H Jul 05 '20 at 13:48
  • @AxelRitcher: I've updated the post, please check the last picture. – Jake_3H Jul 05 '20 at 14:23
  • @AxelRitcher: I'm using Microsoft 365 for business Version 2005 (Build 12827.20336). Can you please upload the .pptx file so I can try to open it? To see if it really is an issue with PowerPoint. – Jake_3H Jul 05 '20 at 14:44
  • @Jake_3H: Well I found the problem. `Office 365` needs setting show percent and show bubble-size to `false` explicitly. Else number format is not applied. See changed code in my answer. What a crap. – Axel Richter Jul 05 '20 at 16:44
  • That was it! Makes no sense to me, why the formatting was ignored because of this, but, thank you. You've been a great help! ^_^ – Jake_3H Jul 05 '20 at 22:09