java - 如何使用 apache poi 4.0.1 和 java 生成可编辑的堆叠条形图?

标签 java excel apache-poi stacked-chart apache-poi-4

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

  • 输出 Excel 文件的扩展名应为 .xlsx
  • 生成的图表应具有图表标题和数据标签
  • 生成的图表还应该能够在每列顶部显示所有数据的总和(您可以看到每列的总计显示在黄色框中)
  • 您可以引用下图,更清楚地了解我正在寻找的内容。
<小时/>

enter image description here

<小时/>
  • Data for stacked bar chart
<小时/>
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

最佳答案

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

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

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

chart.getCTChart().getPlotArea().getBarChartArray(0).addNewOverlap().setVal((byte)100); 设置 100% 的重叠。否则,单个系列的条形图并未真正堆叠,而是并排放置。

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

附加折线图必须有自己的轴,并且轴彼此正确交叉。但这些轴必须是不可见的。

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

以下代码需要一个 StackedBarAndLineChart.xlsx,其中包含第一张表中问题中提供的数据,范围位于 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);
 }
}

结果:

enter image description here

<小时/>

编辑2019-03-01:

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

关于java - 如何使用 apache poi 4.0.1 和 java 生成可编辑的堆叠条形图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54919555/

相关文章:

java - 在 Android 中的 JUnit 测试用例中创建文件

python - 使用 openpyxl 时出现 "Bad magic number for file header"错误

java - 使用java POI打印excel中的数据

java - map 。 java 。返回值

java - 更改 RecyclerView 中每隔一个元素的颜色

python - 在Python中使用comtypes将Excel文档转换为pdf时出错

python - 如何在 Python 中跳过前几行 Excel 工作表

java - 使用 Apache poi 在 excel 中设计折线图

java - 如何在 JAVA 中使用 apache poi 删除 Excel 中的警告?

java - getHibernateTemplate().find() 中的 SQL 注入(inject)