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); // 将图例移出可视区域
对于目前项目而言,影响不大。不改动了