旅游攻略網(wǎng)站開發(fā)龍崗網(wǎng)站設(shè)計(jì)
往期系列傳送門:
Poi實(shí)現(xiàn)根據(jù)word模板導(dǎo)出-文本段落篇
(需要完整代碼的直接看最后位置!!!)
前言:
補(bǔ)充Word中圖表的知識(shí):
每個(gè)圖表在word中都有一個(gè)內(nèi)置的Excel,用于操作數(shù)據(jù)。
內(nèi)置Excel有類別、系列、值三個(gè)概念:
poi可以獲取word中的圖表對(duì)象,通過(guò)這個(gè)圖表對(duì)象來(lái)操作Excel的系列、類別、值。這樣是不是思路很清晰了,直接看代碼:
public void exportWord() throws Exception {//獲取word模板InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");try {ZipSecureFile.setMinInflateRatio(-1.0d);// 獲取docx解析對(duì)象XWPFDocument document = new XWPFDocument(is);// 解析替換第一個(gè)圖表數(shù)據(jù),根據(jù)具體業(yè)務(wù)封裝數(shù)據(jù)List<Number[]> singleBarValues = new ArrayList<>();// 第一個(gè)系列的值singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});// 第二個(gè)系列的值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中所有圖表對(duì)象List<XWPFChart> charts = document.getCharts();//獲取第一個(gè)單系列柱狀圖XWPFChart docChart = charts.get(0);//系列信息String[] seriesNames = {"出生人口數(shù)","出生率"};//分類信息String[] cats = xValues;// 業(yè)務(wù)數(shù)據(jù)singleBarValues.add(singleBarValues.get(0));singleBarValues.add(singleBarValues.get(1));//獲取圖表數(shù)據(jù)對(duì)象XDDFChartData chartData = null;//word圖表均對(duì)應(yīng)一個(gè)內(nèi)置的excel,用于保存圖表對(duì)應(yīng)的數(shù)據(jù)//excel中 第一列第二行開始的數(shù)據(jù)為分類信息//CellRangeAddress(1, categories.size(), 0, 0) 四個(gè)參數(shù)依次為 起始行 截止行 起始列 截止列。//excel中分類信息的范圍String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));//根據(jù)分類信息的范圍創(chuàng)建分類信息的數(shù)據(jù)源XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);//更新數(shù)據(jù)for (int i = 0; i < seriesNames.length; i++) {chartData = docChart.getChartSeries().get(i);//excel中各系列對(duì)應(yīng)的數(shù)據(jù)的范圍String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));//根據(jù)數(shù)據(jù)的范圍創(chuàng)建值的數(shù)據(jù)源Number[] val = singleBarValues.get(i);XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);//獲取圖表系列的數(shù)據(jù)對(duì)象XDDFChartData.Series series = chartData.getSeries(0);//替換系列數(shù)據(jù)對(duì)象中的分類和值series.replaceData(catDataSource, valDataSource);//修改系列數(shù)據(jù)對(duì)象中的標(biāo)題
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);//更新圖表數(shù)據(jù)對(duì)象docChart.plot(chartData);}//圖表整體的標(biāo)題 傳空值則不替換標(biāo)題
// if (!Strings.isNullOrEmpty(title)) {
// docChart.setTitleText(title);
// docChart.setTitleOverlay(false);
// }return docChart;}
導(dǎo)出效果:
重點(diǎn)!重點(diǎn)!重點(diǎn)!
看下面這個(gè)圖表用上述方法能成功導(dǎo)出嗎?
像這種x軸帶有分類的通過(guò)上述方法已經(jīng)不行了,原因是在用
chartData = docChart.getChartSeries().get(i);
這個(gè)方法獲取系列對(duì)象時(shí)報(bào)空指針異常??赡苁莗oi不支持這個(gè)格式的x軸。
那我們只能換個(gè)角度來(lái)處理了,之前說(shuō)過(guò)Word中每個(gè)圖表都是一個(gè)內(nèi)置Excel控制,那Poi操作Excel我可太會(huì)了,不熟悉的可以看之前Poi處理Excel的文章。
果然,我們可以通過(guò)圖表對(duì)象拿到XSSFWorkbook
//獲取word中所有圖表對(duì)象
List<XWPFChart> charts = doc.getCharts();
//獲取第一個(gè)單系列柱狀圖
XWPFChart singleBarChar = charts.get(1);XSSFWorkbook workbook = singleBarChar.getWorkbook();
XSSFSheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
// 更新內(nèi)置excel數(shù)據(jù)
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()));
}
處理完后,發(fā)現(xiàn)導(dǎo)出的Word雖然內(nèi)置的Excel數(shù)據(jù)替換成我們的數(shù)據(jù)了,頁(yè)面顯示的還是之前模板數(shù)據(jù),必須點(diǎn)擊編輯后頁(yè)面數(shù)據(jù)才顯示Excel中的值。
現(xiàn)在問(wèn)題成了,怎么刷新內(nèi)置Excel數(shù)據(jù),簡(jiǎn)單圖表通過(guò)docChart.plot(chartData);方法直接刷新,現(xiàn)在拿不到了怎么辦?
上一篇(Poi實(shí)現(xiàn)根據(jù)word模板導(dǎo)出-文本段落篇)說(shuō)到,poi是將word解析成xml操作的,那Word頁(yè)面顯示的圖表也應(yīng)該是xml來(lái)生成的,我們只需把對(duì)應(yīng)標(biāo)簽數(shù)據(jù)也改成我們的數(shù)據(jù),那頁(yè)面數(shù)據(jù)和內(nèi)置Excel數(shù)據(jù)不就保持一致了。
通過(guò)Debug看下一圖表對(duì)象
可以發(fā)現(xiàn)果然有標(biāo)簽是控制值顯示的,下面我們只需按照標(biāo)簽順序找到位置替換值就可以了。
// 內(nèi)置excel數(shù)據(jù)不會(huì)更新到頁(yè)面,需要刷新頁(yè)面數(shù)據(jù)
CTChart ctChart = singleBarChar.getCTChart();
CTPlotArea plotArea = ctChart.getPlotArea();
// 第一列數(shù)據(jù)
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]));
}
// 第二列數(shù)據(jù)
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]));
}
導(dǎo)出效果:
完整代碼:
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解析對(duì)象XWPFDocument document = new XWPFDocument(is);// 解析替換文本段落對(duì)象// 替換word模板中占位符數(shù)據(jù),根據(jù)業(yè)務(wù)自行封裝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);// 解析替換第一個(gè)圖表數(shù)據(jù),根據(jù)具體業(yè)務(wù)封裝數(shù)據(jù)List<Number[]> singleBarValues = new ArrayList<>();// 第一個(gè)系列的值singleBarValues.add(new Number[]{3232, 5222, 2424, 2346, 3123});// 第二個(gè)系列的值singleBarValues.add(new Number[]{42.2, 24.41, 31.73, 74.83, 53.28});// x軸的值String[] xValues = new String[]{"山東省", "河南省", "北京市", "天津市", "陜西省"};changeChart1(document, singleBarValues, xValues);// 解析替換第二個(gè)圖表數(shù)據(jù),根據(jù)具體業(yè)務(wù)封裝數(shù)據(jù)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 {// 業(yè)務(wù)數(shù)據(jù)Number[] n1 = singleBarValues2.get(0);Number[] n2 = singleBarValues2.get(1);//獲取word中所有圖表對(duì)象List<XWPFChart> charts = doc.getCharts();//獲取第一個(gè)單系列柱狀圖XWPFChart singleBarChar = charts.get(1);XSSFWorkbook workbook = singleBarChar.getWorkbook();XSSFSheet sheet = workbook.getSheetAt(0);int lastRowNum = sheet.getLastRowNum();// 更新內(nèi)置excel數(shù)據(jù)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()));}// 內(nèi)置excel數(shù)據(jù)不會(huì)更新到頁(yè)面,需要刷新頁(yè)面數(shù)據(jù)CTChart ctChart = singleBarChar.getCTChart();CTPlotArea plotArea = ctChart.getPlotArea();// 第一列數(shù)據(jù)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]));}// 第二列數(shù)據(jù)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中所有圖表對(duì)象List<XWPFChart> charts = document.getCharts();//獲取第一個(gè)單系列柱狀圖XWPFChart docChart = charts.get(0);//系列信息String[] seriesNames = {"出生人口數(shù)","出生率"};//分類信息String[] cats = xValues;// 業(yè)務(wù)數(shù)據(jù)singleBarValues.add(singleBarValues.get(0));singleBarValues.add(singleBarValues.get(1));//獲取圖表數(shù)據(jù)對(duì)象XDDFChartData chartData = null;//word圖表均對(duì)應(yīng)一個(gè)內(nèi)置的excel,用于保存圖表對(duì)應(yīng)的數(shù)據(jù)//excel中 第一列第二行開始的數(shù)據(jù)為分類信息//CellRangeAddress(1, categories.size(), 0, 0) 四個(gè)參數(shù)依次為 起始行 截止行 起始列 截止列。//excel中分類信息的范圍String catDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, 0, 0));//根據(jù)分類信息的范圍創(chuàng)建分類信息的數(shù)據(jù)源XDDFDataSource<?> catDataSource = XDDFDataSourcesFactory.fromArray(cats, catDataRange, 0);//更新數(shù)據(jù)for (int i = 0; i < seriesNames.length; i++) {chartData = docChart.getChartSeries().get(i);//excel中各系列對(duì)應(yīng)的數(shù)據(jù)的范圍String valDataRange = docChart.formatRange(new CellRangeAddress(1, cats.length, i + 1, i + 1));//根據(jù)數(shù)據(jù)的范圍創(chuàng)建值的數(shù)據(jù)源Number[] val = singleBarValues.get(i);XDDFNumericalDataSource<Number> valDataSource = XDDFDataSourcesFactory.fromArray(val, valDataRange, i + 1);//獲取圖表系列的數(shù)據(jù)對(duì)象XDDFChartData.Series series = chartData.getSeries(0);//替換系列數(shù)據(jù)對(duì)象中的分類和值series.replaceData(catDataSource, valDataSource);//修改系列數(shù)據(jù)對(duì)象中的標(biāo)題
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);//更新圖表數(shù)據(jù)對(duì)象docChart.plot(chartData);}//圖表整體的標(biāo)題 傳空值則不替換標(biāo)題
// 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) {//判斷此段落時(shí)候需要進(jìn)行替換String text = paragraph.getText();if(checkText(text)){List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {//替換模板原來(lái)位置String value = changeValue(run.toString(), params);if (Objects.nonNull(value)) {run.setText(value, 0);}}}}}/*** 判斷文本中時(shí)候包含$* @param text 文本* @return 包含返回true,不包含返回false*/public static boolean checkText(String text){boolean check = false;if(text.indexOf("$")!= -1){check = true;}return check;}/*** 匹配傳入信息集合與模板* @param value 模板需要替換的區(qū)域* @param textMap 傳入信息集合* @return 模板需要替換區(qū)域信息集合對(duì)應(yīng)值*/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());}}//模板未匹配到區(qū)域替換為空if(checkText(value)){value = "";}return value;}}