1、环境
JDK8
POI 5.2.3
Springboot2.7
2、DEMO
pom
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency>
java
@GetMapping("/export")public ResponseEntity<byte[]> exportExcel() throws IOException {// 创建工作簿和工作表XSSFWorkbook workbook = new XSSFWorkbook();XSSFSheet sheet = workbook.createSheet("Chart");// 自定义数据源List<String> xValues = Arrays.asList("A", "B", "C", "D", "E");List<Double> line1Values = Arrays.asList(10.0, 15.0, 20.0, 25.0, 30.0);List<Double> line2Values = Arrays.asList(5.0, 7.0, 9.0, 11.0, 13.0);XSSFSheet hiddenSheet = workbook.createSheet("Hidden Data");workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheet), true);Row hiddenRow = hiddenSheet.createRow(0);for (int i = 0; i < xValues.size(); i++) {Cell cell = hiddenRow.createCell(i);cell.setCellValue(xValues.get(i));}for (int i = 0; i < 10; i++) {createChart("title" + i, sheet,hiddenSheet,i,xValues,line1Values,i%2 == 0 ? line2Values: null);}// 将工作簿写入字节数组ByteArrayOutputStream outputStream = new ByteArrayOutputStream();workbook.write(outputStream);workbook.close();// 设置响应头HttpHeaders headersResponse = new HttpHeaders();headersResponse.setContentType(MediaType.APPLICATION_OCTET_STREAM);headersResponse.setContentDispositionFormData("attachment", "chart.xlsx");return new ResponseEntity<>(outputStream.toByteArray(), headersResponse, HttpStatus.OK);}private void createChart(String title, XSSFSheet sheet, XSSFSheet hiddenSheet, int index, List<String> xValues, List<Double> line1Values, List<Double> line2Values) {XSSFDrawing drawing = sheet.createDrawingPatriarch();XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 5, index * 21, 15, index * 21 + 20);XSSFChart chart = drawing.createChart(anchor);chart.setTitleText(title);chart.setTitleOverlay(false);XDDFCategoryAxis xAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);xAxis.setTitle(" ");XDDFValueAxis yAxis = chart.createValueAxis(AxisPosition.LEFT);yAxis.setTitle(" ");XDDFDataSource<String> xs = XDDFDataSourcesFactory.fromStringCellRange(hiddenSheet,new CellRangeAddress(0, 0, 0, xValues.size() - 1));XDDFLineChartData lineChartData = (XDDFLineChartData) chart.createData(ChartTypes.LINE, xAxis, yAxis);XDDFNumericalDataSource<Double> line1 = XDDFDataSourcesFactory.fromArray(line1Values.toArray(new Double[0]));XDDFLineChartData.Series series1 = (XDDFLineChartData.Series) lineChartData.addSeries(xs, line1);if (CollUtil.isNotEmpty(line2Values)) {XDDFNumericalDataSource<Double> line2 = XDDFDataSourcesFactory.fromArray(line2Values.toArray(new Double[0]));XDDFLineChartData.Series series2 = (XDDFLineChartData.Series) lineChartData.addSeries(xs, line2);}chart.plot(lineChartData);}
说明,封装了一个方法,createChart。
图表位置
XSSFClientAnchor anchor = drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2);
参数说明:
-
dx1
和dy1
:- 表示图表左上角相对于单元格左上角的偏移量(以 EMU 为单位)。
- 1 EMU = 1/360000 厘米。
- 如果你不需要精确控制偏移量,可以将这两个值设置为
0
。
-
dx2
和dy2
:- 表示图表右下角相对于单元格右下角的偏移量(以 EMU 为单位)。
- 同样,如果你不需要精确控制偏移量,可以将这两个值设置为
0
。
-
col1
和row1
:- 表示图表左上角所在的单元格列号和行号(从 0 开始计数)。
- 例如,
col1 = 5
表示图表左上角位于第 6 列(F 列),row1 = 0
表示图表左上角位于第 1 行。
-
col2
和row2
:- 表示图表右下角所在的单元格列号和行号(从 0 开始计数)。
- 例如,
col2 = 15
表示图表右下角位于第 16 列(P 列),row2 = 20
表示图表右下角位于第 21 行。
hiddenSheet存在的目的是
1. Apache POI 的图表数据绑定机制
Apache POI 的图表功能是基于 Excel 的底层结构设计的。在 Excel 中,图表的数据源通常是从工作表中的单元格范围(CellRangeAddress
)中读取的。也就是说,图表的 X 轴和 Y 轴数据必须绑定到某个单元格范围,即使这些单元格并不直接显示在工作表中。
核心原因:
- Apache POI 的
XDDFDataSourcesFactory.fromStringCellRange()
方法要求传入一个有效的单元格范围。 - 如果没有单元格范围,POI 不知道如何为图表提供数据源,因此会导致图表无法正确显示。
所以我采用了还是用了隐藏的方式。对于整个sheet的布局
3、效果
有个小bug
这个系列1这几个字去不掉,图表工具提示中的“系列名称”(如“系列1”)是由 Excel 自动生成的。虽然 Apache POI 不支持直接修改或自定义工具提示的内容
一下方法可以一试。
// 隐藏图例(可选)XDDFChartLegend legend = chart.getOrAddLegend();legend.setPosition(LegendPosition.TOP); // 将图例移出可视区域
对于目前项目而言,影响不大。不改动了