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.rhkb.cn/news/236478.html

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

相关文章

网络通信过程的一些基础问题

客户端A在和服务器进行TCP/IP通信时&#xff0c;发送和接收数据使用的是同一个端口吗&#xff1f; 这个问题可以这样来思考&#xff1a;在客户端A与服务器B建立连接时&#xff0c;A需要指定一个端口a向服务器发送数据。当服务器接收到A的报文时&#xff0c;从报文头部解析出A的…

报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法

我起初想要在macOS系统安装pip包&#xff0c;首先在终端安装homebrew&#xff0c;敲了命令&#xff1a;/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错&#xff0c;报错内容&#xff1a;curl: (6) Could not resolve host: raw.…

Asp .Net Core 系列: 集成 CORS跨域配置

文章目录 什么是CORS?Asp .Net Core 中如何配置CORS?CorsPolicyBuilder类详解注册以及使用策略三种方式EnableCors 和 DisableCors 特性关于带证书与不带证书代码的实现跨源&#xff08;cross-origin&#xff09;不带请求证书(Credentials)跨源&#xff08;cross-origin&…

【论文阅读】Self-supervised Learning: Generative or Contrastive

Abstract 研究了在计算机视觉、自然语言处理和图形学习中用于表示的新的自监督学习方法。全面回顾了现有的实证方法&#xff0c;并根据其目的将其归纳为三大类&#xff1a;生成性、对比性和生成性对比&#xff08;对抗性&#xff09;。进一步收集了关于自我监督学习的相关理论…

Mac 安装Nginx教程

Nginx官网 Nginx官网英文 1.在终端输入brew search nginx 命令检查nginx是否安装了 2. 安装命令&#xff1a;brew install nginx 3. 查看Nginx信息命令brew info nginx 4. 启动 nginx方式&#xff1a;在终端里输入 nginx 5.查看 nginx 是否启动成功 在浏览器中访问http://l…

经典算法-模拟退火算法求解旅行商问题TSP

经典算法-模拟退火算法求解旅行商问题TSP 旅行商问题&#xff08;Traveling Salesman Problem, TSP&#xff09;是组合优化中的经典问题。简单地说&#xff0c;一个旅行商需要访问N个城市&#xff0c;并返回到出发城市&#xff0c;问题是找到最短的可能路线&#xff0c;使得每…

PPT插件-布局参考-增加便携尺寸功能

PPT自带的尺寸为很久的尺寸&#xff0c;很多尺寸不常用&#xff0c;这里增加一些画册尺寸&#xff0c;用于PPT排版设计。 软件介绍 PPT大珩助手是一款全新设计的Office PPT插件&#xff0c;它是一款功能强大且实用的PPT辅助工具&#xff0c;支持Wps Word和Office Word&#x…

B059-权限管理系统01

目录 知识点介绍项目演示项目搭建动态菜单查询分析(权限表分析)权限系统表分析角色模块pageInfopageHelper实现前端动态分页高级查询新增与修改删除角色 分配权限-表分析角色授权数据-一级和二级权限查询 知识点介绍 项目演示 准备数据库 准备工程auth_new tips&#xff1a;…

Android Studio打包有哪些优势

大家好&#xff0c;现在移动应用程序的快速发展&#xff0c;开发者需要一个强大又可靠的开发环境来创建和打包高质量的 Android 应用程序。Android Studio 是一款由 Google 官方开发的 Android 应用程序开发环境&#xff0c;提供了许多的优势和便利&#xff0c;那究竟都有哪些优…

使用Linux防火墙管理HTTP流量

在Linux系统中&#xff0c;防火墙是用于控制网络流量的重要工具。通过防火墙&#xff0c;你可以根据需要限制、过滤或允许特定的网络流量&#xff0c;从而提高系统的安全性。在处理HTTP流量时&#xff0c;防火墙可以帮助你实施访问控制、流量监控和其他安全策略。 iptables i…

持续赋能波卡生态创新,OneBlock+ 社区 2023 年度回顾

OneBlock 开发者社区成立于 2018 年&#xff0c;历经五年的积累与沉淀&#xff0c;已经成长为行业内领先的 Substrate 开发者社区。我们以成熟的社区生态&#xff0c;通过 Substrate 技术与波卡生态的相关优质文章、项目方与开发者专访、线上线下技术热点对谈、多阶段开发者课程…

C语言—数据类型

变量和基本数据类型 变量类型的概念 变量是在程序中可以发生变化的量&#xff0c;变量是有类型的&#xff0c;变量的类型决定了变量存储空间的大小以及如何解释存储的位模式。 1字节&#xff08;Byte&#xff09;8位&#xff08;bit&#xff09; 定义格式 存储类型 数据…

java JDBC 连接数据库(增删查改)

必须先插入工具包 代码 public static void main(String[] args) {DataSource ds JdbcHelper.getDs();System.out.println(ds);JdbcTemplate jdbcTemplatenew JdbcTemplate(ds);System.out.println(jdbcTemplate);//新增String sql1"insert into biao values(null,?,?,…

【Web】forward 和 redirect 的区别

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Web ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 Forward&#xff08;转发&#xff09;&#xff1a; Redirect&#xff08;重定向&#xff09;&#xff1a; 区别总结&#xff1a; …

主题-----读微信公众号

1.SOA 面向服务的架构&#xff08;Service-Oriented Architecture&#xff0c;SOA&#xff09;还没有一个公认的定义。许多组织从不同的角度和不同的侧面对 SOA 进行了描述&#xff0c;较为典型的有以下三个&#xff1a; &#xff08;1&#xff09;W3C 的定义&#xff1a;SOA 是…

Python基础知识:整理11 模块的导入、自定义模块和安装第三方包

1 模块的导入 1.1 使用import 导入time模块&#xff0c;使用sleep功能&#xff08;函数&#xff09; import time print("start") time.sleep(3) print("end")1.2 使用from 导入time的sleep功能 from time import sleep print("start") slee…

SpringMVC ResponseEntity常见使用场景

ResponseEntity 作为 Spring MVC controller层 的 HTTP response&#xff0c;包含 status code, headers, body 这三部分。 正常场景 RestController Slf4j public class SearchController {AutowiredUserService userService;RequestMapping(value "/getAllStudents4&…

ROS2学习笔记三:话题Topic

目录 前言 1 话题简介 2 常用指令 3 RCLCPP实现实现话题 3.1 创建工作空间 3.2 代码编写 3.2.1 发布端编写 3.2.2 发布端编写 前言 ROS2中的一个重要概念是话题&#xff08;Topic&#xff09;。话题是一种通过发布者和订阅者之间进行异步通信的机制。发布者&#xff0…

归并排序例题——逆序对的数量

做道简单一点的题巩固一下 归并排序实现步骤 将整个区间 [l, r] 划分为 [l, mid] 和 [mid1, r]。 递归排序 [l, mid] 和 [mid1, r]。 将左右两个有序序列合并为一个有序序列。 题目描述 给定一个长度为 n 的整数数列&#xff0c;请计算数列中的逆序对的数量。 逆序对的定义…

Java面试题(java高级面试题)

线程池的核心线程数设置为多大比较合理&#xff1f; Worker线程在执行的过程中&#xff0c;有一部计算时间需要占用CPU&#xff0c;另一部分等待时间不需要占用CPU&#xff0c;通过量化分析&#xff0c;例如打日志进行统计&#xff0c;可以统计出整个Worker线程执行过程中这两…