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

如何在Java应用程序中动态地在excel报告中包含仪表图,java,excel,graph,charts,apache-poi,Java,Excel,Graph,Charts,Apache Poi,我打算从我的应用程序下载excel报告 单击excel上的下载按钮,将生成报告。这项措施的实施没有引起太多关注。然而,下一个要求是我正在努力寻找答案。我的要求是在excel文档中包括仪表图表,如下所示。我无法在ApachePOI中看到同样的规定。这将有助于获得一些指针 Microsoft Excel不提供仪表图表。您的屏幕截图显示的是甜甜圈图和饼图的组合 在图中,甜甜圈图有每个仪表段的数据点(段),最后一段的数据点(段)填满整圈。第一段根据需要着色(例如红色、黄色、绿色),而最后一段不可见(隐

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

单击excel上的下载按钮,将生成报告。这项措施的实施没有引起太多关注。然而,下一个要求是我正在努力寻找答案。我的要求是在excel文档中包括仪表图表,如下所示。我无法在ApachePOI中看到同样的规定。这将有助于获得一些指针


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);
      }
    }
  }
}
import java.io.FileOutputStream;
导入java.io.IOException;
导入org.apache.poi.ss.usermodel.Cell;
导入org.apache.poi.ss.usermodel.Row;
导入org.apache.poi.ss.util.CellRangeAddress;
导入org.apache.poi.xddf.usermodel.XDDFNoFillProperties;
导入org.apache.poi.xddf.usermodel.XDDFLineProperties;
导入org.apache.poi.xddf.usermodel.XDDFShapeProperties;
导入org.apache.poi.xddf.usermodel.chart.XDDFChart;
导入org.apache.poi.xddf.usermodel.chart.XDDFChartData;
导入org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
导入org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
导入org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
导入org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
导入org.apache.poi.xddf.usermodel.chart.ChartTypes;
导入org.apache.poi.xssf.usermodel.XSSFChart;
导入org.apache.poi.xssf.usermodel.XSSFClientAnchor;
导入org.apache.poi.xssf.usermodel.XSSFDrawing;
导入org.apache.poi.xssf.usermodel.xssfheet;
导入org.apache.poi.xssf.usermodel.xssf工作簿;
公共类油炸圈饼{
//方法从XDDFChart获取形状属性
私有静态XDDFShapeProperties GetOradChartSpaceShapeProperties(XDDFChart图表){
if(chart.getCTChartSpace().getSpPr()==null)chart.getCTChartSpace().addnewspr();
返回新的XDDFShapeProperties(chart.getCTChartSpace().getSpPr());
}
//XDDFDoughnutChartData缺少方法setHoleSize。这为CTDoughnutChart提供了此类方法。
私有静态void setHoleSize(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart图表,短尺寸){
如果(大小==null){
if(chart.isSetHoleSize()){
chart.unsetHoleSize();
}
}否则{
如果(尺寸<0 | | 100<尺寸){
抛出新的IllegalArgumentException(“大小必须介于0和100之间”);
}
if(chart.isSetHoleSize()){
chart.getHoleSize().setVal(大小);
}否则{
chart.addNewHoleSize().setVal(大小);
}
}
}
//XDDFDoughnutChartData缺少方法setFirstSliceAngle。这为CTDoughnutChart提供了此类方法
私有静态void setFirstSliceAngle(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart图表,整数角度){
如果(角度==null){
if(chart.isSetFirstSliceAng()){
chart.ang();
}
}否则{
如果(角度<0 | | 360<角度){
抛出新的IllegalArgumentException(“角度必须介于0和360之间”);
}
if(chart.isSetFirstSliceAng()){
chart.getFirstSliceAng().setVal(角度);
}否则{
chart.addNewFirstSliceAng().setVal(角度);
}
}
}
公共静态void main(字符串[]args)引发IOException{
尝试(XSSFWorkbook wb=new XSSFWorkbook()){
XSSFSheet sheet=wb.createSheet(“仪表图”);
//设置数据
行行;
行=工作表。创建行(0);
row.createCell(0).setCellValue(“Ptr.Pos:”);
row.createCell(1).setCellValue(75);//单元格B1是指针位置=第一个饼图段
行=工作表。创建行(1);
row.createCell(0.setCellValue(“Ptr.Thickn.:”);
row.createCell(1).setCellValue(1);//单元格B2是指针厚度=第二个饼图段
行=工作表。创建行(2);
createCell(0).setCellValue(“Helper:”);
row.createCell(1).setCellFormula(“200-B1-B2”);//单元格B3是计算第三个饼图段大小(最大为整圈)所需的辅助公式
行=工作表。创建行(3);
row.createCell(0).setCellValue(“Helper:”);//需要第4行,因为图表将有4个类别;使用更多类别时需要更多类别
XSSFDrawing=sheet.createdrawingparhical();
XSSFClientAnchor-anchor=drawing.createAnchor(0,0,0,0,6,5,20);
XSSFChart chart=drawing.createChart(锚定);
//将图表背景设置为无填充和无线条
XDDFShapeProperties shapeProperties=GetOradChartSpaceShapeProperties(图表);
setFillProperties(新的XDDFNoFillProperties());
shapeProperties.setLineProp