如何将Excel版Apache POI柱线组合图代码适配为PowerPoint可用版本
Apache POI 生成PPT柱形折线组合图表实现方案
实现效果和下图完全匹配:
适配可行性说明
Excel的图表实现逻辑可以直接迁移到PPT场景,Apache POI针对PPT的XSLFChart类和Excel的XSSFChart类底层逻辑同源,只需要替换容器层API,同时重构数据读取逻辑,就可以支持直接传入列表作为输入参数,不需要依赖外部Excel文件。
前置依赖
建议使用Apache POI 5.2.0及以上版本,低版本对PPT次坐标轴的支持存在已知缺陷,可能导致折线数据不显示。Maven依赖配置如下:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> </dependency>
核心实现代码
以下代码支持直接传入分类标签列表、柱形系列数值列表、折线系列数值列表三个参数,直接生成目标组合图表插入到PPT幻灯片中:
import org.apache.poi.xslf.usermodel.*; import org.apache.poi.util.Units; import org.openxmlformats.schemas.drawingml.x2006.chart.*; import java.io.FileOutputStream; import java.util.List; public class PptComboChartGenerator { /** * 生成柱线组合图表并插入到指定幻灯片 * @param slide 目标幻灯片 * @param x 图表左上角X坐标(单位:磅) * @param y 图表左上角Y坐标(单位:磅) * @param width 图表宽度(单位:磅) * @param height 图表高度(单位:磅) * @param categories X轴分类标签列表 * @param barValues 柱形系列数值列表(绑定主Y轴) * @param lineValues 折线系列数值列表(绑定次Y轴) * @param barSeriesName 柱形系列名称 * @param lineSeriesName 折线系列名称 * @throws Exception */ public void generateComboChart(XSLFSlide slide, int x, int y, int width, int height, List<String> categories, List<Double> barValues, List<Double> lineValues, String barSeriesName, String lineSeriesName) throws Exception { // 创建图表容器 XSLFChart chart = slide.createChart(); chart.setAnchor(new java.awt.Rectangle(x, y, width, height)); // 初始化图表底层结构 CTChart ctChart = chart.getCTChart(); CTPlotArea plotArea = ctChart.getPlotArea(); // 创建柱形系列(主坐标轴) CTBarChart barChart = plotArea.addNewBarChart(); barChart.addNewVaryColors().setVal(false); barChart.addNewBarDir().setVal(STBarDir.COL); // 绑定柱形系列分类和数值 CTBarSer barSer = barChart.addNewSer(); barSer.addNewIdx().setVal(0); barSer.addNewTx().setV(barSeriesName); // 分类轴数据 CTAxDataSource catDs = barSer.addNewCat(); CTStrData catStrData = catDs.addNewStrLit(); for (int i = 0; i < categories.size(); i++) { catStrData.addNewPt().setIdx(i); catStrData.getPtArray(i).setV(categories.get(i)); } // 柱形数值数据 CTNumDataSource barValDs = barSer.addNewVal(); CTNumData barNumData = barValDs.addNewNumLit(); for (int i = 0; i < barValues.size(); i++) { barNumData.addNewPt().setIdx(i); barNumData.getPtArray(i).setV(barValues.get(i).toString()); } // 绑定柱形到主坐标轴 barChart.addNewAxId().setVal(12345); // 主类别轴ID barChart.addNewAxId().setVal(12346); // 主数值轴ID // 创建折线系列(次坐标轴) CTLineChart lineChart = plotArea.addNewLineChart(); lineChart.addNewVaryColors().setVal(false); CTLineSer lineSer = lineChart.addNewSer(); lineSer.addNewIdx().setVal(1); lineSer.addNewTx().setV(lineSeriesName); lineSer.addNewSmooth().setVal(false); // 折线是否平滑 // 折线复用相同分类轴 CTAxDataSource lineCatDs = lineSer.addNewCat(); CTStrData lineCatStrData = lineCatDs.addNewStrLit(); for (int i = 0; i < categories.size(); i++) { lineCatStrData.addNewPt().setIdx(i); lineCatStrData.getPtArray(i).setV(categories.get(i)); } // 折线数值数据 CTNumDataSource lineValDs = lineSer.addNewVal(); CTNumData lineNumData = lineValDs.addNewNumLit(); for (int i = 0; i < lineValues.size(); i++) { lineNumData.addNewPt().setIdx(i); lineNumData.getPtArray(i).setV(lineValues.get(i).toString()); } // 绑定折线到次坐标轴 lineChart.addNewAxId().setVal(12345); // 共享主类别轴 lineChart.addNewAxId().setVal(12347); // 次数值轴ID // 配置主类别轴 CTCatAx catAx = plotArea.addNewCatAx(); catAx.addNewAxId().setVal(12345); catAx.addNewScaling().addNewOrientation().setVal(STOrientation.MAX_MIN); catAx.addNewAxPos().setVal(STAxPos.B); catAx.addNewCrossAx().setVal(12346); catAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO); // 配置主数值轴(柱形用) CTValAx mainValAx = plotArea.addNewValAx(); mainValAx.addNewAxId().setVal(12346); mainValAx.addNewScaling().addNewOrientation().setVal(STOrientation.MIN_MAX); mainValAx.addNewAxPos().setVal(STAxPos.L); mainValAx.addNewCrossAx().setVal(12345); mainValAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO); // 配置次数值轴(折线用) CTValAx secondaryValAx = plotArea.addNewValAx(); secondaryValAx.addNewAxId().setVal(12347); secondaryValAx.addNewScaling().addNewOrientation().setVal(STOrientation.MIN_MAX); secondaryValAx.addNewAxPos().setVal(STAxPos.R); secondaryValAx.addNewCrossAx().setVal(12345); secondaryValAx.addNewCrosses().setVal(STCrosses.MAX); secondaryValAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO); // 显示图例 ctChart.addNewLegend().addNewLegendPos().setVal(STLegendPos.B); } // 调用示例 public static void main(String[] args) throws Exception { try (XMLSlideShow ppt = new XMLSlideShow()) { XSLFSlide slide = ppt.createSlide(); PptComboChartGenerator generator = new PptComboChartGenerator(); // 传入自定义列表数据 List<String> categories = List.of("1月", "2月", "3月", "4月", "5月", "6月"); List<Double> barValues = List.of(120.0, 200.0, 150.0, 250.0, 180.0, 300.0); List<Double> lineValues = List.of(0.8, 0.95, 0.75, 0.9, 0.82, 0.98); generator.generateComboChart(slide, Units.toEMU(50), Units.toEMU(50), Units.toEMU(600), Units.toEMU(400), categories, barValues, lineValues, "销售额", "完成率"); try (FileOutputStream out = new FileOutputStream("combo_chart.pptx")) { ppt.write(out); } } } }
代码说明
- 所有输入数据都通过参数列表传入,没有依赖任何外部Excel文件读取逻辑
- 柱形系列默认绑定左侧主Y轴,折线系列绑定右侧次Y轴,两个Y轴的刻度范围会根据传入的数值自动计算
- 可通过调整坐标、宽高参数控制图表在幻灯片中的位置和大小,也可以扩展代码添加系列颜色、刻度格式等自定义样式
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

