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

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云原生Kubernetes: Kubeadm部署K8S 1.29版本 单Master架构

目录 一、实验 1.环境 2.K8S master节点环境准备 3.K8S master节点安装kubelet、kubeadm、kubectl 3.K8S node节点环境准备与软件安装 4.K8S master节点部署服务 5.K8S node节点部署 6.K8S master节点查看集群 7.容器网络&#xff08;CNI&#xff09;部署 8.K8S 集群…

使用Excel批量给数据添加单引号和逗号

表格制作过程如下&#xff1a; A2表格暂时为空&#xff0c;模板建立完成以后&#xff0c;用来放置原始数据&#xff1b; 在B2表格内输入公式&#xff1a; ""&A2&""&"," 敲击回车&#xff1b; 解释&#xff1a; B2表格的公式&q…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷③

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷3 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷3 模块一 …

Deno 1.22 发布

目录 更新默认的类型检查模式 移除Deno.emit()Deno.formatDiagnostics()和Deno.applySourceMap() API 默认启用Deno命名空间 --no-config标识 Navigator.userAgent 更新 Deno.resolveDns() API 引入新的Response.json()静态方法 在 LSP 默认启用 Linting 对测试运行程…

springboot项目创建及采用本地tomcat打包发布

springboot项目发布 maven使用 解压maven安装包 修改配置文件settings.xml 更改镜像(使用maven添加依赖时&#xff0c;选择下载的地址&#xff0c;百度云已提供) <mirror><id>nexus-aliyun</id><mirrorOf>*</mirrorOf><name>Nexus aliyu…

2024PMP考试新考纲-【过程领域】近期典型真题和超详细解析

前面的文章&#xff0c;华研荟讲解了三十多道PMP新考纲下的【人员People领域】的近年真题&#xff0c;这篇文章开始为大家分享【过程Process领域】的新考纲下的真题&#xff0c;进一步帮助大家体会和理解新考纲下PMP的考试特点和如何应用知识来解题&#xff0c;并且举一反三&am…

Linux网络的命令和配置

目录 一、网络配置命令 1、配置和管理网络接口 1.1 ifconfig 1.2 ip 1.2.1 ip link 1.2.2 ip addr 1.3 修改网络接口名 1.3.1 临时修改网络接口名 1.3.2 永久修改网络接口名 1.4 永久配置单网卡 1.5 永久配置双网卡 1.6 ethtool 2、查看和设置主机中路由表信息…

【算法】链表-20240109

这里写目录标题 一、141. 环形链表二、876. 链表的中间结点三、面试题 02.01. 移除重复节点 一、141. 环形链表 简单 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中…

Vue3:Axios配置及使用

Axios官方 一、安装&#xff1a; //使用 npm: $ npm install axios//使用 bower: $ bower install axios//使用 yarn: $ yarn add axios 在package-lock.json文件可以查看axios版本 二、配置&#xff1a; milliaAxios.js 配置axios import axios from axios // 创建一个 ax…

qt打包完整详细过程 包你成功

找问题找了一个多小时&#xff0c;不停调试&#xff0c;还修改文件路径&#xff0c;配置路径&#xff0c;开机关机&#xff0c;最后终于做出来了&#xff0c;得出来了一个结论 我绝对是天才 首先 看这个视频 k14 打包发布_哔哩哔哩_bilibili 不出意外&#xff0c;你绝对会在…

FreeRTOS学习——任务通知

一、什么是任务通知 FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能&#xff0c;每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法&#xff0c;使用消息通知比通过二进制信号量方式解除阻塞任务快 45%&#xff0c; 并且更加省内存&#xff08;无需创建队 列&#…

555断线报警器电路图

电路的核心部分由NE555组成&#xff0c;R1、R2、C1和NE555组成一个频率越为3KHz左右的多谐振荡电路&#xff0c;当电路接通电源时&#xff0c;振荡器开始工作蜂鸣器LS1发出响声&#xff1b;当1和2被短接时&#xff0c;振荡器的工作条件被破坏&#xff0c;LS1停止工作。 电路分…

React ant table警告:Each child in a list should have a unique “key“ prop.

如下图&#xff1a; 原因 React Ant table表格每一行都需要一个唯一标识来确保不重复&#xff0c;如果不加该属性&#xff0c;就会出现这个警告。 修复 添加这一行&#xff1a; rowKey{(record) > record.id} # id为行idTable代码段&#xff1a; <TabledataSourc…

华为mux vlan+DHCP+单臂路由用法配置案例

最终效果&#xff1a; vlan 2模拟局域网服务器&#xff0c;手动配置地址&#xff0c;也能上公网 vlan 3、4用dhcp分配地址 vlan 4的用户之间不能互通&#xff0c;但可以和其它vlan通&#xff0c;也能上公网 vlan 3的用户不受任何限制可以和任何vlan通&#xff0c;也能上公网 交…

How can I be sure that I am pulling a trusted image from docker?

1、Error response from daemon: manifest for jenkins:latest not found: manifest unknown: manifest unknown 2、Error response from daemon: pull access denied for nacos, repository does not exist or may require ‘docker login’: denied: requested access to th…

[足式机器人]Part3 机构运动学与动力学分析与建模 Ch00-2(4) 质量刚体的在坐标系下运动

本文仅供学习使用&#xff0c;总结很多本现有讲述运动学或动力学书籍后的总结&#xff0c;从矢量的角度进行分析&#xff0c;方法比较传统&#xff0c;但更易理解&#xff0c;并且现有的看似抽象方法&#xff0c;两者本质上并无不同。 2024年底本人学位论文发表后方可摘抄 若有…

基于宝塔搭建Discuz!论坛

一、安装宝塔 我是在我的虚拟机上安装图的宝塔 虚拟机版本&#xff1a;Ubuntu 18.04 wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh 6dca892c安装完成之后在浏览器输入你的地址 https://你的域名&#xff08;或…

上市公司-是否数字化转型数据集(2000-2022年)

参照《经济评论》中张欣&#xff08;2023&#xff09;、《中国工业经济》中巫强&#xff08;2023&#xff09;的做法&#xff0c;团队对上市公司-是否数字化转型进行测算。若年报中出现数字化转型的关键词&#xff0c;则视企业的是否数字化转型赋值为1&#xff0c;否则为0。使用…

【漏洞复现】锐捷EG易网关login.php命令注入漏洞

Nx01 产品简介 锐捷EG易网关是一款综合网关&#xff0c;由锐捷网络完全自主研发。它集成了先进的软硬件体系架构&#xff0c;配备了DPI深入分析引擎、行为分析/管理引擎&#xff0c;可以在保证网络出口高效转发的条件下&#xff0c;提供专业的流控功能、出色的URL过滤以及本地化…

python统计分析——箱线图(sns.boxplot)

资料来源&#xff1a;用python学统计学、帮助文档 使用seaborn.boxplot()函数绘制箱线图 import numpy as np import pandas as pd from matplotlib import pyplot as plt import seaborn as snsdata_setnp.array([2,3,3,4,4,4,4,5,5,6]) dfpd.DataFrame({type:[A,A,A,A,A,A,…