如何使用 apache POI 使用 SXSSFSheet 创建 excel 图表?

How do i create excel chart with SXSSFSheet using apache POI?

我的项目使用 SXSSFWorkbook 来生成 excel 文件,因为应用程序中生成的 excel 文件也可能很大。现在我还需要在这些 excel 文件中包含 excel 图表,我无法使用 SXSSFWorkbook 创建图表。我得到的唯一示例是 XSSFWorkbook。有什么方法可以让 excel 图表与 SXSSFWorkbook?

我为 XSSFWorkbook 获取的示例如下所示。我正在寻找 SXSSFWorkbook

的类似示例
    XSSFWorkbook wb = new XSSFWorkbook()        
    XSSFSheet sheet = wb.createSheet("barchart");
    XSSFDrawing drawing = sheet.createDrawingPatriarch();
    XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 3, 1, 10, DATA_START_ROW - 2);

    XSSFChart chart = drawing.createChart(anchor);
    chart.setTitleText("Trend (Claim Type)");
    chart.setTitleOverlay(false);

    XDDFChartLegend legend = chart.getOrAddLegend();
    legend.setPosition(LegendPosition.TOP_RIGHT);

    XDDFCategoryAxis bottomAxis = chart
            .createCategoryAxis(AxisPosition.BOTTOM);
    bottomAxis.setTitle("Period");
    XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
    leftAxis.setTitle("Cost");

    XDDFDataSource<String> periods = XDDFDataSourcesFactory
            .fromStringCellRange(sheet,
                    new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 1, 1));

    XDDFNumericalDataSource<Double> allowed = XDDFDataSourcesFactory
            .fromNumericCellRange(sheet,
                    new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 2, 2));

    XDDFNumericalDataSource<Double> total = XDDFDataSourcesFactory
            .fromNumericCellRange(sheet,
                    new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 3, 3));

    XDDFLineChartData pdata = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);

    XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) pdata.addSeries(periods, total);
    series1.setTitle("Total Paid", null);
    series1.setSmooth(false);
    series1.setMarkerStyle(MarkerStyle.STAR);

    XDDFLineChartData.Series series2 = (XDDFLineChartData.Series) pdata.addSeries(periods, allowed);
    series2.setTitle("Allowed", null);
    series2.setSmooth(true);
    series2.setMarkerSize((short) 6);
    series2.setMarkerStyle(MarkerStyle.SQUARE);

    chart.plot(pdata);


    // Write output to an excel file
    try (FileOutputStream fileOut = new FileOutputStream(
            getReportFolderPath())) {

        //wb.write(fileOut);
    } catch (Exception ex) {

    }

UPDATE -> 我试过如下操作,但在执行 chart.plot(pdata) 时仍然使数组越界。我想做的是使用 SXSSFWorkbook 创建一个 XSSFSheet 并在此 sheet 中绘制图表。但是,使用 SXSSFDrawing 创建 ClientAnchor 并像 XSSFChart 图表一样创建 XSSFChart = sxssfDrawing.createChart(anchor)

        sxssfSheet.createDrawingPatriarch();
        XSSFSheet xssfSheet = workbook.getXSSFWorkbook().getSheet(sheet.getSheetName());
        chart.drawChart(sxssfSheet, xssfSheet, xssfSheet.getDrawingPatriarch());

    public void drawChart(SXSSFSheet sSheet, XSSFSheet xSheet, XSSFDrawing drawing) {
    SXSSFDrawing sxssfDrawing = sSheet.createDrawingPatriarch();
    ClientAnchor anchor = sxssfDrawing.createAnchor(0, 0, 0, 0, 3, 1, 10, chartIndexDTO.getDataRowStartIndex() - 2);

    XSSFChart chart = drawing.createChart(anchor);
    chart.setTitleText("Claim Type vs Allowed Amount");
    chart.setTitleOverlay(false);

    XDDFChartLegend legend = chart.getOrAddLegend();
    legend.setPosition(LegendPosition.TOP_RIGHT);

    XDDFCategoryAxis bottomAxis = chart
            .createCategoryAxis(AxisPosition.BOTTOM);
    bottomAxis.setTitle("Claim Type");
    XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
    leftAxis.setTitle("Allowed");
    leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);

    XDDFDataSource<String> xAxis = XDDFDataSourcesFactory
            .fromStringCellRange(xSheet,
                    new CellRangeAddress(chartIndexDTO.getDataRowStartIndex(), chartIndexDTO.getDataRowEndIndex(), 0, 0));

    XDDFNumericalDataSource<Double> yAxis = XDDFDataSourcesFactory
            .fromNumericCellRange(xSheet,
                    new CellRangeAddress(chartIndexDTO.getDataRowStartIndex(), chartIndexDTO.getDataRowEndIndex(), 1, 1));

    XDDFChartData pdata = chart
            .createData(ChartTypes.BAR, bottomAxis, leftAxis);
    XDDFChartData.Series series1 = pdata.addSeries(xAxis, yAxis);
    series1.setTitle("Allowed", null);
    pdata.setVaryColors(true);
    chart.plot(pdata);

    XDDFBarChartData bar = (XDDFBarChartData) pdata;
    bar.setBarDirection(BarDirection.COL);
}

问题是 SXSSFDrawing 直到现在才提供创建图表。

但是 SXSSFWorkbook 可以从 XSSFWorkbook 创建。而这个XSSFWorkbook已经可以上图了。最多 apache poi 4(可能最多 apache poi 5.0.0)使用 XDDF 是不可能的,因为不可能从空数据范围创建图表。但是现在使用 apache poi 5.1.0 是可能的。

因此,如果图表数据范围的位置和大小已知,则可以创建具有该图表的 XSSFWorkbook。图表首先指向一个空数据范围。所以使用的内存大小不会那么大。然后这个 XSSFWorkbook 可以用来创建 SXSSFWorkbook 并且 SXSSFWorkbook 可以用来流式传输大数据量。

以下完整示例显示了这一点。

import java.io.FileOutputStream;

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

public final class SXSSFLineChart {
    
 static void createXSSFSheetWithLineChart(XSSFSheet sheet,  String chartTitle, String catAxisTitle, String yAxisTitle, XSSFCell[] headers, CellRangeAddress dataRange, XSSFClientAnchor anchor) {

  XSSFDrawing drawing = sheet.createDrawingPatriarch();

  XSSFChart chart = drawing.createChart(anchor);
  chart.setTitleText(chartTitle);
  chart .setTitleOverlay(false);

  XDDFChartLegend legend = chart.getOrAddLegend();
  legend.setPosition(LegendPosition.TOP_RIGHT);

  XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
  bottomAxis.setTitle(catAxisTitle);
  XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
  leftAxis.setTitle(yAxisTitle);
    
  XDDFDataSource<String> xs = XDDFDataSourcesFactory.fromStringCellRange(sheet, new CellRangeAddress(
                                                                                 dataRange.getFirstRow(), dataRange.getLastRow(), 
                                                                                 dataRange.getFirstColumn(), dataRange.getFirstColumn()));
  XDDFNumericalDataSource<Double> ys1 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress( 
                                                                                            dataRange.getFirstRow(), dataRange.getLastRow(), 
                                                                                            dataRange.getFirstColumn()+1, dataRange.getFirstColumn()+1));
  XDDFNumericalDataSource<Double> ys2 = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(
                                                                                            dataRange.getFirstRow(), dataRange.getLastRow(), 
                                                                                            dataRange.getFirstColumn()+2, dataRange.getFirstColumn()+2));

  XDDFChartData data = chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
  XDDFChartData.Series series1 = data.addSeries(xs, ys1);
  series1.setTitle(headers[0].getStringCellValue(), new CellReference(headers[0]));
  ((XDDFLineChartData.Series )series1).setSmooth(false);
  ((XDDFLineChartData.Series )series1).setMarkerStyle(MarkerStyle.STAR);
            
  XDDFChartData.Series series2 = data.addSeries(xs, ys2);
  series2.setTitle(headers[1].getStringCellValue(), new CellReference(headers[1]));
  ((XDDFLineChartData.Series )series2).setSmooth(true);
  ((XDDFLineChartData.Series )series2).setMarkerSize((short) 6);
  ((XDDFLineChartData.Series )series2).setMarkerStyle(MarkerStyle.SQUARE);

  chart.plot(data);
 }     

 static void streamDataIntoSXSSFWorkbook(SXSSFSheet sheet, int dataStartRow, int dataSize) {
  for (int r = dataStartRow; r < dataStartRow + dataSize; r++) {
   SXSSFRow row = sheet.createRow(r);
   SXSSFCell cell = row.createCell(0); cell.setCellValue("Per" + r);
   double d = new java.util.Random().nextDouble() * 1000;
   cell = row.createCell(1); cell.setCellValue(d);
   cell = row.createCell(2); cell.setCellValue(d / (new java.util.Random().nextDouble()+1));
  }
 }

 public static void main(String[] args) throws Exception {
        
  final int DATA_START_ROW = 1;
  final int DATA_SIZE = 100;

  XSSFWorkbook wb = new XSSFWorkbook();
  XSSFSheet sheet = wb.createSheet();
  XSSFRow row = sheet.createRow(0);
  XSSFCell cell = row.createCell(0); cell.setCellValue("Period");
  XSSFCell[] headers = new XSSFCell[2];
  cell = row.createCell(1); cell.setCellValue("Total Paid");
  headers[0] = cell;
  cell = row.createCell(2); cell.setCellValue("Allowed");
  headers[1] = cell;
 
  createXSSFSheetWithLineChart(sheet, "Trend (Claim Type)", "Period", "Cost", headers,
   new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + DATA_SIZE - 1, 0, 2), 
   new XSSFClientAnchor(0, 0, 0, 0, 3, 1, 20, DATA_START_ROW + 20)
  );
  
  SXSSFWorkbook sWb = new SXSSFWorkbook(wb);
  SXSSFSheet sSheet = sWb.getSheetAt(0);
  streamDataIntoSXSSFWorkbook(sSheet, DATA_START_ROW, DATA_SIZE);
  
  FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart-sxssf.xlsx");
  sWb.write(fileOut);
  fileOut.close();
  sWb.dispose();
 }
}

我正在回答我自己的问题,因为我找到了有效的解决方案。从 SXSSFSheet.getDrawingPatriarch() 创建 XSSFSheet 并像以前一样为我做所有事情。我在下面发布示例代码:

SXSSFWorkbook wb = new SXSSFWorkbook(null, 100, true);  
SXSSFSheet sheet = workbook.createSheet("barchart");
sheet.createDrawingPatriarch();
XSSFDrawing drawing = sheet.getDrawingPatriarch();

XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 3, 1, 10, DATA_START_ROW - 2);
XSSFChart chart = drawing.createChart(anchor);
chart.setTitleText("Trend (Claim Type)");
chart.setTitleOverlay(false);

XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.TOP_RIGHT);

XDDFCategoryAxis bottomAxis = chart
        .createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Period");
XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Cost");

XDDFDataSource<String> periods = XDDFDataSourcesFactory
        .fromStringCellRange(sheet,
                new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 1, 1));

XDDFNumericalDataSource<Double> allowed = XDDFDataSourcesFactory
        .fromNumericCellRange(sheet,
                new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 2, 2));

XDDFNumericalDataSource<Double> total = XDDFDataSourcesFactory
        .fromNumericCellRange(sheet,
                new CellRangeAddress(DATA_START_ROW, DATA_START_ROW + data.size(), 3, 3));

XDDFLineChartData pdata = (XDDFLineChartData) chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);

XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) pdata.addSeries(periods, total);
series1.setTitle("Total Paid", null);
series1.setSmooth(false);
series1.setMarkerStyle(MarkerStyle.STAR);

XDDFLineChartData.Series series2 = (XDDFLineChartData.Series) pdata.addSeries(periods, allowed);
series2.setTitle("Allowed", null);
series2.setSmooth(true);
series2.setMarkerSize((short) 6);
series2.setMarkerStyle(MarkerStyle.SQUARE);

chart.plot(pdata);


// Write output to an excel file
try (FileOutputStream fileOut = new FileOutputStream(
        getReportFolderPath())) {

    //wb.write(fileOut);
} catch (Exception ex) {

}

我还必须对 pom.xml 进行如下更改:

<dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.1.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml-lite</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-full</artifactId>
        <version>5.1.0</version>
    </dependency>