Poi实现根据word模板导出-图表篇
往期系列传送门:
Poi实现根据word模板导出-文本段落篇
(需要完整代码的直接看最后位置!!!)
前言:
补充Word中图表的知识:
每个图表在word中都有一个内置的Excel,用于操作数据。

内置Excel有类别、系列、值三个概念:

poi可以获取word中的图表对象,通过这个图表对象来操作Excel的系列、类别、值。这样是不是思路很清晰了,直接看代码:
public void exportWord() throws Exception {//获取word模板InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");try {ZipSecureFile.setMinInflateRatio(-1.0d);// 获取docx解析对象XWPFDocument document = new XWPFDocument(is);// 解析替换第一个图表数据,根据具体业务封装数据List<Number[]> singleBarValues = new ArrayList<>();// 第一个系列的值singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});// 第二个系列的值singleBarValues.add(new Number[]{42.2, 24.41, 31.73, 74.83, 53.28});// x轴的值String[] xValues = new String[]{"山东省", "河南省", "北京市", "天津市", "陕西省"};changeChart1(document, singleBarValues, xValues);// 输出新文件FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");document.write(fos);document.close();fos.close();} catch (Exception e) {e.printStackTrace();}}private static XWPFChart changeChart1(XWPFDocument document, List<Number[]> singleBarValues, String[] xValues) {//获取word中所有图表对象List<XWPFChart> charts = document.getCharts();//获取第一个单系列柱状图XWPFChart docChart = charts.get(0);//系列信息String[] seriesNames = {"出生人口数","出生率"};//分类信息String[] cats = xValues;// 业务数据singleBarValues.add(singleBarValues.get(0));singleBarValues.add(singleBarValues.get(1));//获取图表数据对象XDDFChartData chartData = null;//word图表均对应一个内置的excel,用于保存图表对应的数据//excel中 第一列第二行开始的数据为分类信息//CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。//excel中分类信息的范围String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));//根据分类信息的范围创建分类信息的数据源XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);//更新数据for (int i = 0; i < seriesNames.length; i++) {chartData = docChart.getChartSeries().get(i);//excel中各系列对应的数据的范围String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));//根据数据的范围创建值的数据源Number[] val = singleBarValues.get(i);XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);//获取图表系列的数据对象XDDFChartData.Series series = chartData.getSeries(0);//替换系列数据对象中的分类和值series.replaceData(catDataSource, valDataSource);//修改系列数据对象中的标题
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);//更新图表数据对象docChart.plot(chartData);}//图表整体的标题 传空值则不替换标题
// if (!Strings.isNullOrEmpty(title)) {
// docChart.setTitleText(title);
// docChart.setTitleOverlay(false);
// }return docChart;}
导出效果:

重点!重点!重点!
看下面这个图表用上述方法能成功导出吗?

像这种x轴带有分类的通过上述方法已经不行了,原因是在用
chartData = docChart.getChartSeries().get(i);
这个方法获取系列对象时报空指针异常。可能是poi不支持这个格式的x轴。
那我们只能换个角度来处理了,之前说过Word中每个图表都是一个内置Excel控制,那Poi操作Excel我可太会了,不熟悉的可以看之前Poi处理Excel的文章。
果然,我们可以通过图表对象拿到XSSFWorkbook
//获取word中所有图表对象
List<XWPFChart> charts = doc.getCharts();
//获取第一个单系列柱状图
XWPFChart singleBarChar = charts.get(1);XSSFWorkbook workbook = singleBarChar.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
// 更新内置excel数据
for (int i = 1; i <= lastRowNum; i++) {XSSFRow row = sheet.getRow(i);XSSFCell cell = row.getCell(2);cell.setCellValue(Double.parseDouble(n1[i - 1].toString()));XSSFCell cell1 = row.getCell(3);cell1.setCellValue(Double.parseDouble(n2[i - 1].toString()));
}
处理完后,发现导出的Word虽然内置的Excel数据替换成我们的数据了,页面显示的还是之前模板数据,必须点击编辑后页面数据才显示Excel中的值。
现在问题成了,怎么刷新内置Excel数据,简单图表通过docChart.plot(chartData);方法直接刷新,现在拿不到了怎么办?
上一篇(Poi实现根据word模板导出-文本段落篇)说到,poi是将word解析成xml操作的,那Word页面显示的图表也应该是xml来生成的,我们只需把对应标签数据也改成我们的数据,那页面数据和内置Excel数据不就保持一致了。
通过Debug看下一图表对象

可以发现果然有标签是控制值显示的,下面我们只需按照标签顺序找到位置替换值就可以了。
// 内置excel数据不会更新到页面,需要刷新页面数据
CTChart ctChart = singleBarChar.getCTChart();
CTPlotArea plotArea = ctChart.getPlotArea();
// 第一列数据
CTBarChart barChartArray = plotArea.getBarChartArray(0);
List<CTBarSer> serList = barChartArray.getSerList();
CTBarSer ctBarSer = serList.get(0);
List<CTNumVal> ptList = ctBarSer.getVal().getNumRef().getNumCache().getPtList();
for (int i = 0; i < ptList.size(); i++) {ptList.get(i).setV(String.valueOf(n1[i]));
}
// 第二列数据
CTLineChart lineChartArray = plotArea.getLineChartArray(0);
List<CTLineSer> lineSers = lineChartArray.getSerList();
CTLineSer ctLineSer = lineSers.get(0);
List<CTNumVal> ptList1 = ctLineSer.getVal().getNumRef().getNumCache().getPtList();
for (int i = 0; i < ptList1.size(); i++) {ptList1.get(i).setV(String.valueOf(n2[i]));
}
导出效果:

完整代码:
package com.javacoding.controller;import cn.hutool.core.util.RandomUtil;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.drawingml.x2006.chart.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.*;
import java.util.*;@RestController
@RequestMapping("/word")
public class WordController {@RequestMapping("/export")public void exportWord() throws Exception {//获取word模板InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");try {ZipSecureFile.setMinInflateRatio(-1.0d);// 获取docx解析对象XWPFDocument document = new XWPFDocument(is);// 解析替换文本段落对象// 替换word模板中占位符数据,根据业务自行封装Map<String, String> params = new HashMap<>();params.put("area1", "山东省");params.put("area2", "河南省");params.put("area3", "北京市");params.put("area4", "天津市");params.put("area5", "陕西省");params.put("areaRate1", "42.15");params.put("areaRate2", "22.35");params.put("areaRate3", "42.35");params.put("areaRate4", "23.11");params.put("areaRate5", "15.34");changeText(document, params);// 解析替换第一个图表数据,根据具体业务封装数据List<Number[]> singleBarValues = new ArrayList<>();// 第一个系列的值singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});// 第二个系列的值singleBarValues.add(new Number[]{42.2, 24.41, 31.73, 74.83, 53.28});// x轴的值String[] xValues = new String[]{"山东省", "河南省", "北京市", "天津市", "陕西省"};changeChart1(document, singleBarValues, xValues);// 解析替换第二个图表数据,根据具体业务封装数据List<Number[]> singleBarValues2 = new ArrayList<>();Number[] n1 = new Number[32];Number[] n2 = new Number[32];for (int i = 0; i < 32; i++) {n1[i] = RandomUtil.randomInt(1000000);n2[i] = RandomUtil.randomDouble(1);}singleBarValues2.add(n1);singleBarValues2.add(n2);changeChart2(document, singleBarValues2);// 输出新文件FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");document.write(fos);fos.close();} catch (Exception e) {e.printStackTrace();}}private static void changeChart2(XWPFDocument doc, List<Number[]> singleBarValues2) throws IOException, InvalidFormatException {// 业务数据Number[] n1 = singleBarValues2.get(0);Number[] n2 = singleBarValues2.get(1);//获取word中所有图表对象List<XWPFChart> charts = doc.getCharts();//获取第一个单系列柱状图XWPFChart singleBarChar = charts.get(1);XSSFWorkbook workbook = singleBarChar.getWorkbook();XSSFSheet sheet = workbook.getSheetAt(0);int lastRowNum = sheet.getLastRowNum();// 更新内置excel数据for (int i = 1; i <= lastRowNum; i++) {XSSFRow row = sheet.getRow(i);XSSFCell cell = row.getCell(2);cell.setCellValue(Double.parseDouble(n1[i - 1].toString()));XSSFCell cell1 = row.getCell(3);cell1.setCellValue(Double.parseDouble(n2[i - 1].toString()));}// 内置excel数据不会更新到页面,需要刷新页面数据CTChart ctChart = singleBarChar.getCTChart();CTPlotArea plotArea = ctChart.getPlotArea();// 第一列数据CTBarChart barChartArray = plotArea.getBarChartArray(0);List<CTBarSer> serList = barChartArray.getSerList();CTBarSer ctBarSer = serList.get(0);List<CTNumVal> ptList = ctBarSer.getVal().getNumRef().getNumCache().getPtList();for (int i = 0; i < ptList.size(); i++) {ptList.get(i).setV(String.valueOf(n1[i]));}// 第二列数据CTLineChart lineChartArray = plotArea.getLineChartArray(0);List<CTLineSer> lineSers = lineChartArray.getSerList();CTLineSer ctLineSer = lineSers.get(0);List<CTNumVal> ptList1 = ctLineSer.getVal().getNumRef().getNumCache().getPtList();for (int i = 0; i < ptList1.size(); i++) {ptList1.get(i).setV(String.valueOf(n2[i]));}}private static XWPFChart changeChart1(XWPFDocument document, List<Number[]> singleBarValues, String[] xValues) {//获取word中所有图表对象List<XWPFChart> charts = document.getCharts();//获取第一个单系列柱状图XWPFChart docChart = charts.get(0);//系列信息String[] seriesNames = {"出生人口数","出生率"};//分类信息String[] cats = xValues;// 业务数据singleBarValues.add(singleBarValues.get(0));singleBarValues.add(singleBarValues.get(1));//获取图表数据对象XDDFChartData chartData = null;//word图表均对应一个内置的excel,用于保存图表对应的数据//excel中 第一列第二行开始的数据为分类信息//CellRangeAddress(1, categories.size(), 0, 0) 四个参数依次为 起始行 截止行 起始列 截止列。//excel中分类信息的范围String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));//根据分类信息的范围创建分类信息的数据源XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);//更新数据for (int i = 0; i < seriesNames.length; i++) {chartData = docChart.getChartSeries().get(i);//excel中各系列对应的数据的范围String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));//根据数据的范围创建值的数据源Number[] val = singleBarValues.get(i);XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);//获取图表系列的数据对象XDDFChartData.Series series = chartData.getSeries(0);//替换系列数据对象中的分类和值series.replaceData(catDataSource, valDataSource);//修改系列数据对象中的标题
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);//更新图表数据对象docChart.plot(chartData);}//图表整体的标题 传空值则不替换标题
// if (!Strings.isNullOrEmpty(title)) {
// docChart.setTitleText(title);
// docChart.setTitleOverlay(false);
// }return docChart;}private void changeText(XWPFDocument document, Map<String, String> params) {//获取段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {//判断此段落时候需要进行替换String text = paragraph.getText();if(checkText(text)){List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {//替换模板原来位置String value = changeValue(run.toString(), params);if (Objects.nonNull(value)) {run.setText(value, 0);}}}}}/*** 判断文本中时候包含$* @param text 文本* @return 包含返回true,不包含返回false*/public static boolean checkText(String text){boolean check = false;if(text.indexOf("$")!= -1){check = true;}return check;}/*** 匹配传入信息集合与模板* @param value 模板需要替换的区域* @param textMap 传入信息集合* @return 模板需要替换区域信息集合对应值*/public static String changeValue(String value, Map<String, String> textMap){Set<Map.Entry<String, String>> textSets = textMap.entrySet();for (Map.Entry<String, String> textSet : textSets) {//匹配模板与替换值 格式${key}String key = "${"+textSet.getKey()+"}";if(value.indexOf(key)!= -1){value = value.replace(key, textSet.getValue());}}//模板未匹配到区域替换为空if(checkText(value)){value = "";}return value;}}
相关文章:
Poi实现根据word模板导出-图表篇
往期系列传送门: Poi实现根据word模板导出-文本段落篇 (需要完整代码的直接看最后位置!!!) 前言: 补充Word中图表的知识: 每个图表在word中都有一个内置的Excel,用于…...
windows或mac端口转发
摘要 在内网开发中,由于出于公司安全考虑,部分IP192.168.0.100访问只能针对固定IP192.168.0.200开放,此时我需要通过我的电脑192.168.0.300去访问,由于未对我电脑IP192.168.0.300授权,导致我访问不到,此时…...
Linux工具-搭建文件服务器
当我们使用linux系统作为开发环境时,经常需要在Linux系统之间、Linux和Windows之间传输文件。 对少量文件进行传输时,可以使用scp工具在两台主机之间实现文件传输: rootubuntu:~$ ssh --help unknown option -- - usage: ssh [-46AaCfGgKkMN…...
深入理解@DubboReference与@DubboService【三】
欢迎来到我的博客,代码的世界里,每一行都是一个故事 探索Dubbo的核心:深入理解DubboReference与DubboService【三】 前言DubboService注解基本概念使用示例高级特性 DubboReference注解基本概念使用示例服务调用流程 最佳实践注解的最佳使用方…...
linux主机的免密登录
实现linux主机之间的相互免密登录 在进行远程登录的时,服务器和主机间进行认证阶段分为: 基于口令认证(不安全,易被抓包拦截获取) 客户机连接服务器时,服务器将自己的公钥返回给客户机 客户机会将服务器的…...
Git常用命令和QA(网摘)
主要内容 常用命令git checkout --orphan 分支与 git checkout -b 分支区别git如何创建一个新的空白分支branchgit开发分支本地分支合并远程分支git remote prune origingit log如何退出?如何退出git log或git commit模式git log如何退出git commit 的退出 git强制p…...
PHP AES 加密示例
PHP中实现AES加密的一个基本示例涉及到使用openssl_encrypt函数。这个函数允许你使用不同的加密算法,包括AES。下面是一个简单的示例,展示了如何使用AES加密一个字符串。 首先,你需要确定几个关键的参数: 数据(Data&…...
第十九章:特殊工具与技术
第十九章:特殊工具与技术 对于很多程序员来说,他们很少会用到本章的介绍的内容。 一.控制内存分配 我们能够重载new和delete,但其实不是对new和delete的重载,只是对new和delete操作符后面的函数进行重载。 当我们使用一条new表…...
大数据深度学习卷积神经网络CNN:CNN结构、训练与优化一文全解
文章目录 大数据深度学习卷积神经网络CNN:CNN结构、训练与优化一文全解一、引言1.1 背景和重要性1.2 卷积神经网络概述 二、卷积神经网络层介绍2.1 卷积操作卷积核与特征映射卷积核大小多通道卷积 步长与填充步长填充 空洞卷积(Dilated Convolution&…...
RabbitMQ(九)死信队列
目录 一、简介1.1 定义1.2 何时进入死信队列?1.3 死信消息的变化1.4 死信队列的应用场景1.5 死信消息的生命周期 二、代码实现2.1 死信队列的配置步骤2.2 配置类2.3 配置文件2.4 生产者2.5 业务消费者2.6 死信消费者2.7 测试结果 三、总结四、补充4.1 启动报错 ineq…...
KEI5许可证没到期,编译却出现Error: C9555E: Failed to check out a license.问题解决
一、编译出现如下报错 二、检查一下许可证 三、许可证在许可日期内,故应该不是许可证的问题 四、检查一下编译器,我用的是这个,这几个编译器的区别其实我不太明白,但我把问题解决是选的这个 五、找到编译器的路径,去复…...
南京观海微电子----时序图绘制工具
Wavedrom 是一款功能强大且简单易用的文本转图表工具,被广泛应用于生成时序图、波形图等交互式波形。其特点在于使用简单的文本语法,使得开发人员能够以可视化的方式表示数字信号和时间序列数据。Wavedrom 的优势在于其高度灵活性和可扩展性,…...
Gin CORS 跨域请求资源共享与中间件
Gin CORS 跨域请求资源共享与中间件 文章目录 Gin CORS 跨域请求资源共享与中间件一、同源策略1.1 什么是浏览器的同源策略?1.2 同源策略判依据1.3 跨域问题三种解决方案 二、CORS:跨域资源共享简介(后端技术)三 CORS基本流程1.CORS请求分类2.基本流程 四、CORS两种…...
TS:.d.ts 文件 和 declare 的作用
1 declare 做外部声明1.1 声明外部类型1.2 声明外部模块1.2.1 解决引入资源模块报错1.2.2 跳过对第三方库的类型检查 1.3 声明外部变量1.4 声明外部命名空间(作用域) 2 .d.ts 文件做外部声明3 declare global {} 在模块中做外部声明 先说一下我对 .d.ts文…...
JavaScript-jQuery2-笔记
1.获取元素文本、属性、内部结构、表单中的值 获取标签中所夹的文本内容:text() 获取标签的属性值:prop(属性名) 获取表单元素的内容:如 文本框中的内容 val() 获取元素的内部html结构:html() 2.筛选选择器 筛选选择器࿱…...
设计模式之多线程版本的if------Balking模式
系列文章目录 设计模式之避免共享的设计模式Immutability(不变性)模式 设计模式之并发特定场景下的设计模式 Two-phase Termination(两阶段终止)模式 设计模式之避免共享的设计模式Copy-on-Write模式 设计模式之避免共享的设计模…...
mybatis核心配置文件介绍
mybatis核心配置文件 1. properties配置介绍 properties标签:加载外部的资源配置文件 属性:resource 指定要引入的配置文件路径 在核心配置文件中,通过:${key}方式引入外部配置文件的数据 jdbc.peroperties 的文件内容…...
Linux完全卸载Anaconda3和MiniConda3
如何安装Anaconda3和MiniConda3请看这篇文章: 安装Anaconda3和MiniConda3_minianaconda3-CSDN博客文章浏览阅读474次。MiniConda3官方版是一款优秀的Python环境管理软件。MiniConda3最新版只包含conda及其依赖项如果您更愿意拥有conda以及超过720个开源软件包&…...
Apache Answer,最好的开源问答系统
Apache Answer是一款适合任何团队的问答平台软件。无论是社区论坛、帮助中心还是知识管理平台,你可以永远信赖 Answer。 目前该项目在github超过10K星,系统采用go语言开发,安装配置简单,界面清洁易用,且开源免费。项目…...
【C】内存分配
首先,回顾一下内存分配。所有程序都必须预留足够的内存来存储程序使用的数据。这些内存中有些是自动分配的: float x; int place[100]; 这些声明预留了足够的空间,还为内存提供了一个标识符,可以使用x或place识别数据。 1、mal…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...
boost::filesystem::path文件路径使用详解和示例
boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类,封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解,包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...
内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...
