如何在 Java 应用程序的 excel 动态报告中包含仪表图

How to include gauge graph in an excel report on the fly in a Java application

我打算从我的应用程序下载 excel 报告

单击 excel 上的下载按钮将生成报告。这已经在没有太多担忧的情况下实施。然而,下一个要求是我正在努力寻找答案的地方。我的要求是在 excel 文档中包含仪表图表,如下所示。我无法在 Apache POI 中看到相同的规定。得到一些指点会很有帮助。

Microsoft Excel 不提供规格图表。您的屏幕截图显示的是圆环图和饼图的组合。

圆环图有每个仪表段的数据点(段)和最后一个填满整个圆的段的数据点(段)。第一段根据需要着色(例如红色、黄色、绿色),而最后一段不可见(隐藏)。所以它看起来像一个半圆甜甜圈。

饼图有指针位置、指针粗细和最后一个填满整个圆的数据点(段)。第一个数据点的值决定了指针的位置。第一段和最后一段是不可见的(隐藏的)。只有指针粗细部分是可见的,并且指针应该如何显示是彩色的。

Apache poi 在当前版本 apache poi 5.0.0 中提供圆环图和饼图。不幸的是 XDDFDoughnutChartData 到目前为止还不完整。它缺少设置孔大小和第一切片角度的方法。因此,虽然可以使用默认 XDDF 方法创建圆环图,但需要额外的方法来设置孔大小和第一个切片角度。在这种情况下需要设置第一个切片角度,因为它需要为 270 度才能在正确位置显示半圆图。

下面是一个完整的例子,显示了上面的内容。

import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.XDDFNoFillProperties;
import org.apache.poi.xddf.usermodel.XDDFLineProperties;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.chart.XDDFChart;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class DoughnutAndPieChart {
    
  //method to get shape properties from XDDFChart
  private static XDDFShapeProperties getOrAddChartSpaceShapeProperties(XDDFChart chart) {
    if (chart.getCTChartSpace().getSpPr() == null) chart.getCTChartSpace().addNewSpPr();
    return new XDDFShapeProperties(chart.getCTChartSpace().getSpPr());
  }
  
  //XDDFDoughnutChartData lacks method setHoleSize. This provides such method for CTDoughnutChart.
  private static void setHoleSize(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Short size) {
    if (size == null) {
      if (chart.isSetHoleSize()) {
        chart.unsetHoleSize();
      }
    } else {
      if (size < 0 || 100 < size) {
        throw new IllegalArgumentException("size must be between 0 and 100");
      }
      if (chart.isSetHoleSize()) {
        chart.getHoleSize().setVal(size);
      } else {
        chart.addNewHoleSize().setVal(size);
      }
    }
  }
  
  //XDDFDoughnutChartData lacks method setFirstSliceAngle. This provides such method for CTDoughnutChart
  private static void setFirstSliceAngle(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Integer angle) {
    if (angle == null) {
      if (chart.isSetFirstSliceAng()) {
        chart.unsetFirstSliceAng();
      }
    } else {
      if (angle < 0 || 360 < angle) {
        throw new IllegalArgumentException("angle must be between 0 and 360");
      }
      if (chart.isSetFirstSliceAng()) {
        chart.getFirstSliceAng().setVal(angle);
      } else {
        chart.addNewFirstSliceAng().setVal(angle);
      }
    }
  }
      
  public static void main(String[] args) throws IOException {
    try (XSSFWorkbook wb = new XSSFWorkbook()) {
      XSSFSheet sheet = wb.createSheet("gauge chart");

      //set data
      Row row;
      row = sheet.createRow(0);
      row.createCell(0).setCellValue("Ptr.Pos.:");
      row.createCell(1).setCellValue(75); // cell B1 is pointer position = first pie chart segment
      row = sheet.createRow(1);
      row.createCell(0).setCellValue("Ptr.Thickn.:");
      row.createCell(1).setCellValue(1); // cell B2 is pointer thickness = second pie chart segment
      row = sheet.createRow(2);
      row.createCell(0).setCellValue("Helper:");
      row.createCell(1).setCellFormula("200-B1-B2"); // cell B3 is helper formula needed to calculate third pie chart segment size up to full circle
      row = sheet.createRow(3);
      row.createCell(0).setCellValue("Helper:"); // row 4 is needed as chart will have 4 categories; needs more when more categories used

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

      XSSFChart chart = drawing.createChart(anchor);
      
      //set chart's background to no fill and no line
      XDDFShapeProperties shapeProperties = getOrAddChartSpaceShapeProperties(chart);
      shapeProperties.setFillProperties(new XDDFNoFillProperties());
      shapeProperties.setLineProperties(new XDDFLineProperties(new XDDFNoFillProperties()));

      //data source for categories
      XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromArray(new String[]{"1", "2", "3", "4"});
    
      //doughnut chart = three segments (red yellow, green) plus one segment to be invisible (hidden)
      XDDFNumericalDataSource<Double> val = XDDFDataSourcesFactory.fromArray(new Double[]{25d, 50d, 25d, 100d});
      XDDFChartData data = chart.createData(ChartTypes.DOUGHNUT, null, null);
      data.setVaryColors(true);
      XDDFChartData.Series series = data.addSeries(cat, val);
      chart.plot(data);
      
      //set hole size and first slice angle for the doughnut chart
      setHoleSize(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), (short)50);
      setFirstSliceAngle(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), 270);
      
      //set data point (segments) color
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(0)
          .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, 0, 0}); //red
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(1)
          .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, (byte)255, 0}); //yellow
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(2)
          .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, (byte)255, 0}); //green
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(3);
      chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(3).addNewSpPr().addNewNoFill(); //invisible (hidden)

      //pie chart = segments: pointer position, pointer thickness, up to full circle
      val = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 3, 1, 1));
      data = chart.createData(ChartTypes.PIE, null, null);
      data.setVaryColors(true);
      ((XDDFPieChartData)data).setFirstSliceAngle(270);
      series = data.addSeries(cat, val);
      chart.plot(data);
      
      //correct the id and order, must not start 0 again because there is a doughnut series already
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getIdx().setVal(1);
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getOrder().setVal(1);
      
      //set data point (segments) color
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(0).addNewSpPr().addNewNoFill(); //invisible (hidden)
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(1)
          .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, 0, 0}); //black
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
      chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(2).addNewSpPr().addNewNoFill(); //invisible (hidden)

      //write the output to a file
      try (FileOutputStream fileOut = new FileOutputStream("ooxml-doughnut-and-pie-chart.xlsx")) {
        wb.write(fileOut);
      }
    }
  }
}

结果是:

更改 B1 以更改指针位置。

这不适用于 apache poi 4.1.2 及更低版本。对于那些较低版本的圆环图,可以按照 Doughnut Chart Apache-POI.

中的描述创建