主页 > 开源代码  > 

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模板导出-图表篇