往期系列傳送門:
Poi實(shí)現(xiàn)根據(jù)word模板導(dǎo)出-文本段落篇
(需要完整代碼的直接看最后位置!?。。?/span>
前言:
補(bǔ)充Word中圖表的知識(shí):
每個(gè)圖表在word中都有一個(gè)內(nèi)置的Excel,用于操作數(shù)據(jù)。
內(nèi)置Excel有類別、系列、值三個(gè)概念:
poi可以獲取word中的圖表對象,通過這個(gè)圖表對象來操作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);
// 解析替換第一個(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中所有圖表對象
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ù)對象
XDDFChartData chartData = null;
//word圖表均對應(yīng)一個(gè)內(nèi)置的excel,用于保存圖表對應(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中各系列對應(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ù)對象
XDDFChartData.Series series = chartData.getSeries(0);
//替換系列數(shù)據(jù)對象中的分類和值
series.replaceData(catDataSource, valDataSource);
//修改系列數(shù)據(jù)對象中的標(biāo)題
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);
//更新圖表數(shù)據(jù)對象
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軸帶有分類的通過上述方法已經(jīng)不行了,原因是在用
chartData = docChart.getChartSeries().get(i);
這個(gè)方法獲取系列對象時(shí)報(bào)空指針異常??赡苁莗oi不支持這個(gè)格式的x軸。
那我們只能換個(gè)角度來處理了,之前說過Word中每個(gè)圖表都是一個(gè)內(nèi)置Excel控制,那Poi操作Excel我可太會(huì)了,不熟悉的可以看之前Poi處理Excel的文章。
果然,我們可以通過圖表對象拿到XSSFWorkbook
//獲取word中所有圖表對象
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ù)了,頁面顯示的還是之前模板數(shù)據(jù),必須點(diǎn)擊編輯后頁面數(shù)據(jù)才顯示Excel中的值。
現(xiàn)在問題成了,怎么刷新內(nèi)置Excel數(shù)據(jù),簡單圖表通過docChart.plot(chartData);方法直接刷新,現(xiàn)在拿不到了怎么辦?
上一篇(Poi實(shí)現(xiàn)根據(jù)word模板導(dǎo)出-文本段落篇)說到,poi是將word解析成xml操作的,那Word頁面顯示的圖表也應(yīng)該是xml來生成的,我們只需把對應(yīng)標(biāo)簽數(shù)據(jù)也改成我們的數(shù)據(jù),那頁面數(shù)據(jù)和內(nèi)置Excel數(shù)據(jù)不就保持一致了。
通過Debug看下一圖表對象
可以發(fā)現(xiàn)果然有標(biāo)簽是控制值顯示的,下面我們只需按照標(biāo)簽順序找到位置替換值就可以了。
// 內(nèi)置excel數(shù)據(jù)不會(huì)更新到頁面,需要刷新頁面數(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)出效果:
文章來源:http://www.zghlxwxcb.cn/news/detail-785891.html
完整代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-785891.html
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模板中占位符數(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中所有圖表對象
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ì)更新到頁面,需要刷新頁面數(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中所有圖表對象
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ù)對象
XDDFChartData chartData = null;
//word圖表均對應(yīng)一個(gè)內(nèi)置的excel,用于保存圖表對應(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中各系列對應(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ù)對象
XDDFChartData.Series series = chartData.getSeries(0);
//替換系列數(shù)據(jù)對象中的分類和值
series.replaceData(catDataSource, valDataSource);
//修改系列數(shù)據(jù)對象中的標(biāo)題
// CellReference cellReference = docChart.setSheetTitle(seriesNames[i], 1);
// series.setTitle(seriesNames[i], cellReference);
//更新圖表數(shù)據(jù)對象
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) {
//替換模板原來位置
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ū)域信息集合對應(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;
}
}
到了這里,關(guān)于Poi實(shí)現(xiàn)根據(jù)word模板導(dǎo)出-圖表篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!