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;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

以unity技术开发视角对android权限的讲解

目录 前言 Android权限分类 普通权限 普通权限定义 普通权限有哪些 危险权限 危险权限的定义 危险权限有哪些 动态申请权限实例 申请单个权限实例 第一步&#xff1a;在清单文件中声明权限 第二步&#xff1a;在代码中进行动态申请权限 申请多个权限实例 第一步&am…

大众汽车宣布将ChatGPT,批量集成在多种汽车中!

1月9日&#xff0c;大众汽车在官网宣布&#xff0c;将ChatGPT批量集成到电动、内燃机汽车中。 大众表示&#xff0c;将ChatGPT与其IDA语音助手相结合&#xff0c;用户通过自然语言就能与ChatGPT进行互动&#xff0c;例如&#xff0c;帮我看看最近的三星米其林饭店在哪里&#…

8.1、5G网络切片认识篇

首先&#xff0c;3G上网时代来临&#xff0c;流量高速增长&#xff0c;但是网络资源有限&#xff0c;不可能保证所有业务都能全速进行&#xff0c;总得捡重要的首先保障&#xff0c;因此就对业务进行分类&#xff0c;给予不同优先级的业务不同的资源&#xff0c;不同的服务质量…

时序预测 | Matlab基于CNN-LSTM-SAM卷积神经网络-长短期记忆网络结合空间注意力机制的时间序列预测(多指标评价)

时序预测 | Matlab基于CNN-LSTM-SAM卷积神经网络-长短期记忆网络结合空间注意力机制的时间序列预测(多指标评价) 目录 时序预测 | Matlab基于CNN-LSTM-SAM卷积神经网络-长短期记忆网络结合空间注意力机制的时间序列预测(多指标评价)预测效果基本介绍程序设计参考资料 预测效果 …

基于apache的http文件服务配置

背景&#xff1a; 公司的产品使用的第三方模组可以OTA&#xff0c;厂家提供的是window开启软件&#xff0c;这样就可以在本机做http下载服务器&#xff0c;然后使用端口映射的方式&#xff0c;公开到外网&#xff0c;这样就可以进行4G网络访问内网服务器了。但这个有个弊端&am…

redis 主从同步和故障切换的几个坑

数据不一致 当我们从节点读取一个数据时&#xff0c;和主节点读取的数据不一致&#xff0c;这是因为主从同步的命令是异步进行的&#xff0c;一般情况下是主从同步延迟导致的&#xff0c;为什么会延迟&#xff0c; 主要二个原因 1、网络状态不好 2、网络没问题&#xff0c;从节…

高通平台开发系列讲解(USB篇)Ubuntu 下如何使用模块

文章目录 一、查看VID、PID二、adb添加2.1、在udev下添加模块的VID2.2、重启adb服务三、虚拟串口添加(AT、Diag)沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要图解高通平台上位机使用方法 一、查看VID、PID 在ubuntu下使用模块进行AT指令发送,Diag等串…

【愚公系列】2023年12月 HarmonyOS教学课程 043-Stage模型(ExtensionAbility组件)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

【Storm实战】1.1 图解Storm的抽象概念

文章目录 0. 前言1. Storm 中的抽象概念1.1 流 (Stream)1.2 拓扑 (Topology)1.3 Spout1.4 Bolt1.5 任务 (Task)1.6 工作者 (Worker) 2. 形象的理解Storm的抽象概念2.1 流 (Stream)2.2 拓扑 (Topology)2.3 Spout2.4 Bolt2.5 任务 (Task)2.6 工作者 (Worker)场景1场景2 3.参考文档…

详解CAS及ABA问题

&#x1f308;&#x1f308;&#x1f308;今天给大家分享的是 CAS 问题。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈️✈️动动…

Hex2Bin转换软件、Bootloader 、OTA加密升级 、STM32程序加密、其他MCU同样适用

说明&#xff1a;这个工具可以将 Hex 文件 转换为 Bin 格式文件&#xff0c;软件是按自己开发 STM32 OAT 功能需求开发的一款辅助 上位机软件。 文中的介绍时 bootloader boot 文档在补充完善中... 有兴趣的朋友可留言探讨。 1. 软件功能&#xff1a; 1.生成 bin&#x…

基于模块自定义扩展字段的后端逻辑实现(二)

目录 一&#xff1a;创建表 二&#xff1a;代码逻辑 上一节我们详细讲解了自定义扩展字段的逻辑实现和表的设计&#xff0c;这一节我们以一个具体例子演示下&#xff0c;如何实现一个订单模块的自定义扩展数据。 一&#xff1a;创建表 订单主表: CREATE TABLE t_order ( …

VSCode C/C++(gdb)调试指南

1、安装插件 2、F5开启调试 左侧侧边栏->确保打开回调栈 右键函数栈->查看反汇编 3、打印寄存器、函数反汇编等 命令&#xff1a; 查看main反汇编 -exec disassemble /m main 查看寄存器 -exec info r 打印某个变量 -exec print s 或者 --s 打印寄存器&#xff0c;如p…

如何在没有密码的情况下将 iPhone 13/14/15 恢复出厂设置

您想知道如何在没有密码的情况下将 iPhone 13/14/15 恢复出厂设置吗&#xff1f; 出厂重置 iPhone 13/14/15 成为所有 iPhone 机型中最简单的。大多数情况下&#xff0c;iPhone 13/14/15 是在 iOS 15 或更高版本的 iOS 版本上&#xff0c;Apple 更新了无需密码重置 iPhone 13/…

IoT 物联网 MQTT 协议 5.0 版本新特性

MQTT 是一种基于发布/订阅模式的轻量级消息传输协议&#xff0c;专门为设备资源有限和低带宽、高延迟的不稳定网络环境的物联网场景应用而设计&#xff0c;可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于智能硬件、智慧城市、智慧农业、智慧医疗、新零…

GAMES101-Assignment5

一、问题总览 在这次作业中&#xff0c;要实现两个部分&#xff1a;光线的生成和光线与三角的相交。本次代码框架的工作流程为&#xff1a; 从main 函数开始。我们定义场景的参数&#xff0c;添加物体&#xff08;球体或三角形&#xff09;到场景中&#xff0c;并设置其材质&…

项目管理:风险的来源及管理方法

项目风险是项目管理中的难点之一&#xff0c;虽然我们无法将其完全消除&#xff0c;但可以提前做好准备&#xff0c;将风险降至最低。 项目风险如同暗礁潜伏&#xff0c;你和团队需时刻保持警惕。以下几种风险需特别关注&#xff1a; 措施不足&#xff1a;成本与行动的误差&…

加速科技ST2500 数模混合信号测试设备累计装机量突破500台!

国产数字机&#xff0c;测试中国芯&#xff01;新年伊始&#xff0c;国产半导体测试设备领军企业加速科技迎来了振奋人心的一刻&#xff0c;ST2500 数模混合信号测试设备累计装机量突破500台&#xff01;加速科技凭借其持续的创新能力、完善的解决方案能力、专业热忱的本地化服…

软件定义存储

软件定义存储源于VMware公司于2012年提出的软件定义的数据中心&#xff08;SDDC&#xff09;。存储作为软件定义的数据中心不可或缺的一部分&#xff0c;其以虚拟化为基础&#xff0c;但又不仅限于虚拟化。存储虚拟化一般只能在专门的硬件设备上应用&#xff0c;很多设备都是经…

笔记本摄像头模拟监控推送RTSP流

使用笔记本摄像头模拟监控推送RTSP流 一、基础安装软件准备 本文使用软件下载链接:下载地址 FFmpeg软件: Download ffmpeg 选择Windows builds by BtbN 一个完整的跨平台解决方案&#xff0c;用于录制、转换和流式传输音频和视频。 EasyDarwin软件&#xff1a;Download Easy…