Poi实现根据word模板导出-图表篇

news2024/12/28 4:03:55

往期系列传送门:

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.coloradmin.cn/o/1369862.html

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

相关文章

kubernetes 容器监控 Sysdig Falco

开头语 写在前面&#xff1a;如有问题&#xff0c;以你为准&#xff0c; 目前24年应届生&#xff0c;各位大佬轻喷&#xff0c;部分资料与图片来自网络 内容较长&#xff0c;页面右上角目录方便跳转 Sysdig 监控容器系统调用 介绍 资料 Sysdig:一个非常强大的系统监控、分…

PostgreSQL的常见错误和解决方法

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 在学习新的东西时&#xff0c;会犯很多的错误&#xff0c;会遇到很多坑。我们在填坑与犯错中不断进步成长。 以下是在学习pgsql中…

【驱动序列】C#获取电脑硬件之CPU信息,以及它都有那些品牌

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是是《驱动序列》文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识…

【深度学习】SDXL tensorRT 推理,Stable Diffusion 转onnx,转TensorRT

文章目录 sdxl 转 diffusers转onnx转TensorRT sdxl 转 diffusers def convert_sdxl_to_diffusers(pretrained_ckpt_path, output_diffusers_path):import osos.environ["HF_ENDPOINT"] "https://hf-mirror.com" # 设置 HF 镜像源&#xff08;国内用户使…

k8s的集群调度:

k8s的集群调度&#xff1a; Scheduler:负责调度资源&#xff0c;把pod调度到node节点 预算策略 优先策略 list-watch k8s集群当中&#xff0c;通过list-watch的机制进行每个组件的协作&#xff0c;保持数据同步&#xff0c;每个组件之间的解耦 Kubectl配置文件&#xff0c…

1.9 day7 IO进程线程

使用消息队列完成两个进程间的通信 进程1 #include <myhead.h> struct migbuf {long a;//消息类型char b[1024];//消息正文 }; #define SIZE (sizeof(struct migbuf)-sizeof(long)) int main(int argc, const char *argv[]) {//创建key值key_t key0;if((keyftok(".…

从文本(.txt)文件中读取数据时出现中文乱码

前言 当需要从记事本中读取数据时&#xff0c;发现读取的数据会出现中文乱码&#xff0c;我尝试了C和C读取文件&#xff0c;发现都是这样。 乱码原因 文本文件的保存默认使用UTF-8编码方式&#xff0c;而VS编译器的编码方式是GBK&#xff0c;所以不同的编码方式导致了乱码。…

6.1.2捕捉图像(内含5D博客长截图,你们都去哪儿了?)

6.1.2捕捉图像 利用HyperSnap6可以很方便地捕捉全屏、虚拟桌面、窗口、控件、整页、按钮、活动窗口和区域。除此之外&#xff0c;它还可以进行自由捕捉和特殊捕捉。 1&#xff0e;捕捉窗口或控件 利用传统的“PrintScreen”或“AltPrintScreen”键只能捕捉整个屏幕或当前活动…

numpy100练习题,包含相应使用函数解释

取自github开源项目&#xff1a;numpy100题 文章目录 1. 导入numpy库并简写为 np (★☆☆)2. 打印numpy的版本和配置说明 (★☆☆)3. 创建一个长度为10的空向量 (★☆☆)4. 如何找到任何一个数组的内存大小&#xff1f; (★☆☆)5. 如何从命令行得到numpy中add函数的说明文档?…

【Python学习】Python学习10-列表

目录 【Python学习】Python学习10-列表 前言创建语法访问列表中的值更新和删除列表元素操作列表列表截取Python列表函数&方法参考 文章所属专区 Python学习 前言 本章节主要说明Python的列表List。 创建语法 创建一个列表 通过方括号和逗号分割创建&#xff0c;列表数据…

springboot学生成绩管理系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

书生大模型全链路开源体系

书生浦语大模型全链路开源体系开源了哪些东西 数据书生万卷&#xff1a;一个2TB的涵盖多种模态与任务的数据集预训练InternLM-Train&#xff1a;微调XTuner&#xff1a;可供你低成本微调模型的工具箱部署LMDeploy&#xff1a;一个服务端场景下、transformer 结构 LLM 部署工具…

使用开源通义千问模型(Qwen)搭建自己的大模型服务

目标 1、使用开源的大模型服务搭建属于自己的模型服务&#xff1b; 2、调优自己的大模型&#xff1b; 选型 采用通义千问模型&#xff0c;https://github.com/QwenLM/Qwen 步骤 1、下载模型文件 开源模型库&#xff1a;https://www.modelscope.cn/models mkdir -p /data/…

Camunda ServiceTask

一&#xff1a;Java class Java class实现JavaDelegate接口&#xff0c;只需要配置类的全限定名即可&#xff0c;不需要被Spring容器管理。 public class JavaClassServiceTask implements JavaDelegate {Overridepublic void execute(DelegateExecution execution) throws …

k8s的集群调度---下

前情回顾 预算策略&#xff1a;过滤出合适的节点 优选策略&#xff1a;选择部署的节点 nodeName&#xff1a;硬匹配&#xff0c;不走调度策略。node01. nodeSelector&#xff1a;根据节点的标签选择&#xff0c;会走调度算法。 只要是走调度算法&#xff0c;在不满足预算策…

荣誉 | 数说故事荣登2023粤港澳大湾区科创百强;上榜甲子光年2023中国数字经济榜

硬科技向前&#xff0c;加「数」战新年 2024新年伊始&#xff0c;数说故事就迎来了两大喜讯 上榜2023粤港澳大湾区科创榜 荣登【光年20】2023中国数字经济产品创新榜 ...... 以技术为笔&#xff0c;创新为墨 「数」写着数说故事在科技领域的强大实力与潜力 为新年注入更…

Selenium 学习(0.18)——软件测试之基本路径测试

1、基本路径法测试的概念 是一种白盒测试方法&#xff0c;它在程序控制流图的基础上&#xff0c;通过分析控制构造的环行复杂性&#xff0c;导出基本可执行路径集合&#xff0c;从而设计测试用例的方法。 要保证在测试中程序的每一个可执行语句至少执行一次 【这和语句…

基于pytorch的房价预测

简介 本文主要介绍的基于pytorch和房价预测深度学习网络构建。 该系统使用的是网络上的开源数据&#xff1a; 实现了对房价数据的处理&#xff0c;包括词频统计、情感分析等&#xff0c;并将分析结果以图表形式进行展示。通过这个系统&#xff0c;用户可以便捷地进行分析和可…

scVI与MultiVI

scVI&#xff1a;https://docs.scvi-tools.org/en/stable/user_guide/models/scvi.html MultiVI&#xff1a;https://docs.scvi-tools.org/en/stable/user_guide/models/multivi.html 目录 scVI生成推理任务 MultiVI生成推理 scVI single cell variational inference提出了一个…

JAVA实现文件上传至阿里云

注册阿里云账号后,开通好对象存储服务&#xff08;OSS&#xff09;&#xff0c;三个月试用 阿里云登录页 (aliyun.com) 目录 一.创建Bucket 二.获取AccessKey&#xff08;密钥&#xff09; 三.参考官方SDK文件&#xff0c;编写入门程序 1.复制阿里云OSS依赖&#xff0c;粘贴…