1

我想使用 Apache poi 4.0.1 和 Java 语言为 Excel 创建堆积条形图

  • 输出 excel 文件的扩展名应为 .xlsx
  • 生成的图表应该有图表标题和数据标签
  • 生成的图表还应该能够在每列的顶部显示所有数据的总和(您可以看到每列的总和显示在黄色框中)
  • 您可以参考下图更清楚地了解我在寻找什么。

在此处输入图像描述


  • 堆积条形图的数据

Date    Category High Medium Low
10/01   3        0    3      0
10/02   3        0    2      1
10/03   3        0    2      1
10/04   4        1    2      1
10/05   11       1    7      3
10/08   14       1    10     3
10/09   15       1    11     3
10/10   15       1    11     3
10/11   15       0    11     4
10/12   8        0    6      2
4

1 回答 1

5

源自https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/中的条形图和折线图示例,我们也可以得到此处要求的组合图表。但到目前为止,并非所有请求都只能使用高级XDDF课程。一些修正是必要的,我们需要使用底层的低级ooxml-schemas-1.4bean。所以ooxml-schemas-1.4.jar需要在类路径中。

在以下代码中,对官方示例进行了以下更改:

leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);已设置。因此类别轴与笔画之间的值轴相交,而不是笔画的中点。否则,对于第一个和最后一个类别,条形仅可见一半宽。

chart.getCTChart().getPlotArea().getBarChartArray(0).addNewOverlap().setVal((byte)100);设置 100% 的重叠。否则,单个系列的酒吧并没有真正堆叠,而是并排保持。

只能使用底层的低级ooxml-schemas-1.4bean 添加数据标签。

附加折线图必须有自己的轴,这些轴正确地相互交叉。但是那些轴必须是不可见的。

因为在添加到图表时,折线图并不知道已经存在的条形图,它的IDs 再次以 0 开头。但这对于组合图表是错误的。所以我们需要更正id和order。它不能再次以 0 开头,因为已经有三个条形系列。

以下代码需要StackedBarAndLineChart.xlsx在 range 的第一张表中提供问题中提供的数据A1:E11

代码:

import java.io.*;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;

public class StackedBarAndLineChart {

 public static void main(String[] args) throws IOException {
  try (FileInputStream in = new FileInputStream("StackedBarAndLineChart.xlsx"); 
       XSSFWorkbook wb = (XSSFWorkbook)WorkbookFactory.create(in)) {
   XSSFSheet sheet = wb.getSheetAt(0);

   // determine the type of the category axis from it's first category value (value in A2 in this case)
   XDDFDataSource date = null;
   CellType type = CellType.ERROR;
   Row row = sheet.getRow(1);
   if (row != null) {
    Cell cell = row.getCell(0);
    if (cell != null) {
     type = cell.getCellType();
     if (type == CellType.STRING) {   
      date = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1, 10, 0, 0));
     } else  if (type == CellType.NUMERIC) {  
      date = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 0, 0));
     } else  if (type == CellType.FORMULA) {  
      type = cell.getCachedFormulaResultType();
      if (type == CellType.STRING) {   
       date = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(1, 10, 0, 0));
      } else  if (type == CellType.NUMERIC) {  
       date = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 0, 0));
      }
     }
    }
   }
   if (date != null) { // if no type of category axis found, don't create a chart at all    
    XDDFNumericalDataSource<Double> high = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 2, 2));
    XDDFNumericalDataSource<Double> medium = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 3, 3));
    XDDFNumericalDataSource<Double> low = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 4, 4));
    XDDFNumericalDataSource<Double> category = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(1, 10, 1, 1));

    XSSFDrawing drawing = sheet.createDrawingPatriarch();
    XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 6, 0, 16, 20);

    XSSFChart chart = drawing.createChart(anchor);
    XDDFChartLegend legend = chart.getOrAddLegend();
    legend.setPosition(LegendPosition.RIGHT);

    // bar chart

    XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
    XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
    leftAxis.setTitle("Number of defects");
    leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);

    // category axis crosses the value axis between the strokes and not midpoint the strokes
    leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);

    XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
    XDDFChartData.Series series1 = data.addSeries(date, high);
    series1.setTitle("high", new CellReference(sheet.getSheetName(), 0, 2, true, true));
    XDDFChartData.Series series2 = data.addSeries(date, medium);
    series2.setTitle("medium", new CellReference(sheet.getSheetName(), 0, 3, true, true));
    XDDFChartData.Series series3 = data.addSeries(date, low);
    series3.setTitle("low", new CellReference(sheet.getSheetName(), 0, 4, true, true));
    chart.plot(data);

    XDDFBarChartData bar = (XDDFBarChartData) data;
    bar.setBarDirection(BarDirection.COL);

    // looking for "Stacked Bar Chart"? uncomment the following line
    bar.setBarGrouping(BarGrouping.STACKED);

    // correcting the overlap so bars really are stacked and not side by side
    chart.getCTChart().getPlotArea().getBarChartArray(0).addNewOverlap().setVal((byte)100);

    solidFillSeries(data, 0, PresetColor.CORNFLOWER_BLUE);
    solidFillSeries(data, 1, PresetColor.LIGHT_SALMON);
    solidFillSeries(data, 2, PresetColor.LIGHT_GRAY);

    // add data labels
    for (int s = 0 ; s < 3; 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().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);
    }

    // line chart

    // 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);
    XDDFLineChartData.Series series4 = (XDDFLineChartData.Series)data.addSeries(date, category);
    series4.setTitle("total", null);
    series4.setSmooth(false);
    series4.setMarkerStyle(MarkerStyle.STAR);
    chart.plot(data);

    // correct the id and order, must not start 0 again because there are three bar series already
    chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getIdx().setVal(3);
    chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getOrder().setVal(3);

    solidLineSeries(data, 0, PresetColor.YELLOW);

    // add data labels
    chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).addNewDLbls();
    chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls()
     .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255,(byte)255,0});
    chart.getCTChart().getPlotArea().getLineChartArray(0).getSerArray(0).getDLbls()
     .addNewDLblPos().setVal(org.openxmlformats.schemas.drawingml.x2006.chart.STDLblPos.CTR);
    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);
   }

   // Write the output to a file
   try (FileOutputStream fileOut = new FileOutputStream("StackedBarAndLineChartResult.xlsx")) {
    wb.write(fileOut);
   }
  }
 }

 private static void solidFillSeries(XDDFChartData data, int index, PresetColor color) {
  XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(color));
  XDDFChartData.Series series = data.getSeries().get(index);
  XDDFShapeProperties properties = series.getShapeProperties();
  if (properties == null) {
   properties = new XDDFShapeProperties();
  }
  properties.setFillProperties(fill);
  series.setShapeProperties(properties);
 }

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

结果:

在此处输入图像描述


编辑 2019-03-01:

我已经稍微改进了我的代码。它现在根据其第一个类别值(在本例中为 A2 中的值)确定类别轴的类型。对于数据标签,明确设置了位置,并且明确设置了只显示值而不显示图例键、类别名称或系列名称。

于 2019-02-28T15:57:24.473 回答