目录
一、前言
二、添加依赖
三、poi-tl源码改造部分说明
原理解析
方法改造及部分截图
1、resolveDocument:增加对图表的解析
2、resolveCharts:循环word中所有的图表,找出需要解析的图表封装成对象
3、resolveChart:找到图表数据对应EXCEL的第一个sheet的第一个单元格,按照poi-tl解析模板的方式解析,若满足条件,则封装成poi-tl所需的MetaTemplate对象
4、resolveXWPFChart:完全参考自TemplateResolver.resolveXWPFRuns方法
5、parseTemplateFactory:增加XWPFChart的判断
6、createRunTemplate:参考DefaultRunTemplateFactory.createRunTemplate方法,主要是将chart对象封装到pol-tl所需对象中
完整源码
TemplateResolver.java
MyRunTemplate.java
四、插件部分
原理解析
完整源码
MyChartPolicy.java
MyChartAxis.java
MyChartSeries.java
插件使用源码
WordTest.java
图表部分源码解读
扩展
github源码
项目上有这么一个需求:根据word模板动态生成word,模板中存在图表,需要动态修改图表的数据,并保持原有图表的样式不变,如图表的系列颜色、字体大小、字体颜色等。
技术上选用了开源的poi-tl作为主要word模板引擎,满足了最基本的功能需求,有一些额外的需求也能够通过插件的形式完成。由于当前使用的poi-tl版本(v1.7.3)不支持图表模板的替换,网上搜索了相关资料后,改造了poi-tl的部分源码并以开发插件的形式完成了图表模板替换的功能。
若项目上不使用poi-tl,但是有需求需要对word中的图表进行动态数据替换的,也可以参考此文的第四节插件部分的关键代码,插件部分和poi-tl的关系不大,主要是poi4.0.0+操作图表的相关功能,使用poi-tl是为了将XWPFChart对象及数据传给插件,插件对数据进行单独解析。
参考资料:
1、poi-tl官方文档:http://deepoove.com/poi-tl
2、poi在word中生成图表:https://blog.csdn.net/u014427811/article/details/100771314
maven依赖
com.deepoove
poi-tl
1.7.3
com.alibaba
fastjson
1.2.58
poi-tl的原理是读取了word中所有的段落、表格,找出其中符合模板规则的字符串,如:{{data}}。找到对应字符串后,和相应数据一起传给对应的解析插件解析。由于poi-tl只解析了段落和表格部分,因此我们需要改造源码来使poi-tl多解析图表部分。
图表部分的定义规则为:在图表对应的EXCEL的第一个单元格按照poi-tl的定义方式填写模板字符串,对应的数据格式为一个二维数组,程序来解析二维数组重写EXCEL数据以及刷新图表。
如图所示的模板定义为{{chartdata}}:
对应chartdata的JSON数据格式参考:
{
"chartdata": [
[
"这是第一个单元格",
"系列1",
"系列2"
],
[
"一月",
"100",
"50"
],
[
"二月",
"200",
"100"
],
[
"三月",
"300",
"150"
],
[
"四月",
"400",
"200"
],
[
"五月",
"500",
"250"
],
[
"六月",
"600",
"300"
]
]
}
导出效果:
/*
* Copyright 2014-2020 Sayi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.deepoove.poi.resolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import org.apache.commons.lang3.StringUtils;
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.BodyElementType;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFFooter;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.exception.ResolverException;
import com.deepoove.poi.template.BlockTemplate;
import com.deepoove.poi.template.IterableTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.MyRunTemplate;
import com.deepoove.poi.template.run.RunTemplate;
/**
* Resolver
*
* @author Sayi
* @version 1.7.0
*/
public class TemplateResolver extends AbstractResolver {
private static Logger logger = LoggerFactory.getLogger(TemplateResolver.class);
private RunTemplateFactory runTemplateFactory;
public TemplateResolver(Configure config) {
this(config, config.getRunTemplateFactory());
}
private TemplateResolver(Configure config, RunTemplateFactory runTemplateFactory) {
super(config);
this.runTemplateFactory = runTemplateFactory;
}
@Override
public List resolveDocument(XWPFDocument doc) {
List metaTemplates = new ArrayList<>();
if (null == doc)
return metaTemplates;
logger.info("Resolve the document start...");
metaTemplates.addAll(resolveBodyElements(doc.getBodyElements()));
metaTemplates.addAll(resolveHeaders(doc.getHeaderList()));
metaTemplates.addAll(resolveFooters(doc.getFooterList()));
// 增加对图表的解析
metaTemplates.addAll(resolveCharts(doc.getCharts()));
logger.info("Resolve the document end, resolve and create {} MetaTemplates.", metaTemplates.size());
return metaTemplates;
}
@Override
public List resolveBodyElements(List bodyElements) {
List metaTemplates = new ArrayList<>();
if (null == bodyElements)
return metaTemplates;
// current iterable templates state
Deque stack = new LinkedList();
for (IBodyElement element : bodyElements) {
if (element == null)
continue;
if (element.getElementType() == BodyElementType.PARAGRAPH) {
XWPFParagraph paragraph = (XWPFParagraph) element;
RunningRunParagraph runningRun = new RunningRunParagraph(paragraph, templatePattern);
List refactorRuns = runningRun.refactorRun();
if (null == refactorRuns)
continue;
Collections.reverse(refactorRuns);
resolveXWPFRuns(refactorRuns, metaTemplates, stack);
} else if (element.getElementType() == BodyElementType.TABLE) {
XWPFTable table = (XWPFTable) element;
List rows = table.getRows();
if (null == rows)
continue;
for (XWPFTableRow row : rows) {
List cells = row.getTableCells();
if (null == cells)
continue;
cells.forEach(cell -> {
List visitBodyElements = resolveBodyElements(cell.getBodyElements());
if (stack.isEmpty()) {
metaTemplates.addAll(visitBodyElements);
} else {
stack.peek().getTemplates().addAll(visitBodyElements);
}
});
}
}
}
checkStack(stack);
return metaTemplates;
}
@Override
public List resolveXWPFRuns(List runs) {
List metaTemplates = new ArrayList<>();
if (runs == null)
return metaTemplates;
Deque stack = new LinkedList();
resolveXWPFRuns(runs, metaTemplates, stack);
checkStack(stack);
return metaTemplates;
}
private void resolveXWPFRuns(List runs, final List metaTemplates,
final Deque stack) {
for (XWPFRun run : runs) {
String text = null;
if (null == run || StringUtils.isBlank(text = run.getText(0)))
continue;
RunTemplate runTemplate = parseTemplateFactory(text, run);
if (null == runTemplate)
continue;
char charValue = runTemplate.getSign().charValue();
if (charValue == config.getIterable().getLeft()) {
IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);
stack.push(freshIterableTemplate);
} else if (charValue == config.getIterable().getRight()) {
if (stack.isEmpty())
throw new ResolverException(
"Mismatched start/end tags: No start mark found for end mark " + runTemplate);
BlockTemplate latestIterableTemplate = stack.pop();
if (StringUtils.isNotEmpty(runTemplate.getTagName())
&& !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {
throw new ResolverException("Mismatched start/end tags: start mark "
+ latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);
}
latestIterableTemplate.setEndMark(runTemplate);
if (latestIterableTemplate instanceof IterableTemplate) {
latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();
}
if (stack.isEmpty()) {
metaTemplates.add(latestIterableTemplate);
} else {
stack.peek().getTemplates().add(latestIterableTemplate);
}
} else {
if (stack.isEmpty()) {
metaTemplates.add(runTemplate);
} else {
stack.peek().getTemplates().add(runTemplate);
}
}
}
}
/**
* 参考resolveXWPFRuns方法
*
* @param chart
* @param tagName
* @param metaTemplates
* @param stack
*/
private void resolveXWPFChart(XWPFChart chart, String tagName, final List metaTemplates,
final Deque stack) {
if (StringUtils.isBlank(tagName)) {
return;
}
RunTemplate runTemplate = this.parseTemplateFactory(tagName, chart);
if (runTemplate == null) {
return;
}
char charValue = runTemplate.getSign().charValue();
if (charValue == config.getIterable().getLeft()) {
IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);
stack.push(freshIterableTemplate);
} else if (charValue == config.getIterable().getRight()) {
if (stack.isEmpty())
throw new ResolverException(
"Mismatched start/end tags: No start mark found for end mark " + runTemplate);
BlockTemplate latestIterableTemplate = stack.pop();
if (StringUtils.isNotEmpty(runTemplate.getTagName())
&& !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {
throw new ResolverException("Mismatched start/end tags: start mark "
+ latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);
}
latestIterableTemplate.setEndMark(runTemplate);
if (latestIterableTemplate instanceof IterableTemplate) {
latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();
}
if (stack.isEmpty()) {
metaTemplates.add(latestIterableTemplate);
} else {
stack.peek().getTemplates().add(latestIterableTemplate);
}
} else {
if (stack.isEmpty()) {
metaTemplates.add(runTemplate);
} else {
stack.peek().getTemplates().add(runTemplate);
}
}
}
private void checkStack(Deque stack) {
if (!stack.isEmpty()) {
throw new ResolverException(
"Mismatched start/end tags: No end iterable mark found for start mark " + stack.peek());
}
}
List resolveHeaders(List headers) {
List metaTemplates = new ArrayList<>();
if (null == headers)
return metaTemplates;
headers.forEach(header -> {
metaTemplates.addAll(resolveBodyElements(header.getBodyElements()));
});
return metaTemplates;
}
List resolveFooters(List footers) {
List metaTemplates = new ArrayList<>();
if (null == footers)
return metaTemplates;
footers.forEach(footer -> {
metaTemplates.addAll(resolveBodyElements(footer.getBodyElements()));
});
return metaTemplates;
}
/**
* 循环word中所有图表,找出需要进行模板解析的图表
*
* @param charts
* @return
*/
List resolveCharts(List charts) {
List metaTemplates = new ArrayList<>();
if (null == charts)
return metaTemplates;
for (XWPFChart chart : charts) {
List tempMetaTemplates = resolveChart(chart);
metaTemplates.addAll(tempMetaTemplates);
}
return metaTemplates;
}
public List resolveChart(XWPFChart chart) {
List metaTemplates = new ArrayList<>();
if (null == chart) {
return metaTemplates;
}
// current iterable templates state
Deque stack = new LinkedList();
XSSFWorkbook workbook = null;
try {
workbook = chart.getWorkbook();
if (workbook == null || workbook.getNumberOfSheets() <= 0) {
return metaTemplates;
}
XSSFSheet sheet = workbook.getSheetAt(0);
if (sheet == null || sheet.getLastRowNum() <= 0) {
return metaTemplates;
}
XSSFRow row = sheet.getRow(0);
if (row == null) {
return metaTemplates;
}
XSSFCell cell = row.getCell(0);
if (cell == null) {
return metaTemplates;
}
// 找到图表对应的EXCEL的第一个sheet对应的第一个单元格
String cellValue = cell.getStringCellValue();
if (StringUtils.isBlank(cellValue)) {
return metaTemplates;
}
// 判断单元格的值
Matcher matcher = templatePattern.matcher(cellValue);
if (matcher.find()) {
String tagName = matcher.group();
this.resolveXWPFChart(chart, tagName, metaTemplates, stack);
}
} catch (Exception e) {
e.printStackTrace();
logger.error("", e);
}
checkStack(stack);
return metaTemplates;
}
RunTemplate parseTemplateFactory(String text, T obj) {
logger.debug("Resolve where text: {}, and create ElementTemplate", text);
if (templatePattern.matcher(text).matches()) {
String tag = gramerPattern.matcher(text).replaceAll("").trim();
if (obj.getClass() == XWPFRun.class) {
return (RunTemplate) runTemplateFactory.createRunTemplate(tag, (XWPFRun) obj);
} else if (obj.getClass() == XWPFTableCell.class) {
// return CellTemplate.create(symbol, tagName, (XWPFTableCell)
// obj);
return null;
} else if (obj.getClass() == XWPFChart.class) {
XWPFChart chart = (XWPFChart) obj;
return this.createRunTemplate(tag, chart);
}
}
return null;
}
/**
* 参考DefaultRunTemplateFactory.createRunTemplate方法
*
* @param tag
* @param chart
* @return
*/
private RunTemplate createRunTemplate(String tag, XWPFChart chart) {
MyRunTemplate template = new MyRunTemplate();
Set gramerChars = config.getGramerChars();
Character symbol = Character.valueOf(DefaultRunTemplateFactory.EMPTY_CHAR);
if (!"".equals(tag)) {
char fisrtChar = tag.charAt(0);
for (Character chara : gramerChars) {
if (chara.equals(fisrtChar)) {
symbol = Character.valueOf(fisrtChar);
break;
}
}
}
template.setSource(config.getGramerPrefix() + tag + config.getGramerSuffix());
template.setTagName(
symbol.equals(Character.valueOf(DefaultRunTemplateFactory.EMPTY_CHAR)) ? tag : tag.substring(1));
template.setSign(symbol);
template.setRun(null);
template.setChart(chart);
return template;
}
}
package com.deepoove.poi.template.run;
import org.apache.poi.xwpf.usermodel.XWPFChart;
public class MyRunTemplate extends RunTemplate {
private XWPFChart chart;
public XWPFChart getChart() {
return chart;
}
public void setChart(XWPFChart chart) {
this.chart = chart;
}
}
word中图表的数据展现主要分为2部分,EXCEL数据部分、图表展现部分。
直接根据传入的二维表数据,完全按照二维表的格式写入到EXCEL中即可。需要注意的点:A1单元格没有太大的作用;除A1单元格外的第1行作为图表的系列名;除A1单元格外的A列作为序列;除A列、第1行外,剩余部分作为对应的数据,数据在EXCEL中需要转成数值类型,否则生成word后,右键图表打开对应的EXCEL会有数据刷新变没的情况。
package com.deepoove.poi.policy;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTBarSer;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumData;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumDataSource;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumVal;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTSerTx;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrVal;
import com.alibaba.fastjson.JSONArray;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.MyRunTemplate;
import com.wordchart.vo.MyChartAxis;
import com.wordchart.vo.MyChartSeries;
public class MyChartPolicy extends AbstractRenderPolicy {
@Override
public void doRender(RenderContext context) throws Exception {
ElementTemplate elementTemplate = context.getEleTemplate();
if (elementTemplate != null && elementTemplate instanceof MyRunTemplate) {
MyRunTemplate myRunTemplate = (MyRunTemplate) elementTemplate;
XWPFChart chart = myRunTemplate.getChart();
if (chart != null) {
List serList = this.resolveDatas(context.getData());
String sheetName = this.refreshExcel(chart, context.getData());
this.refreshGraphContent(chart, sheetName, context.getData(), serList);
}
}
}
/**
* 刷新EXCEL数据
*
* @param chart
* @param rows
* @return sheet名
*/
private String refreshExcel(XWPFChart chart, JSONArray rows) {
String sheetName = null;
if (chart == null || rows == null) {
return sheetName;
}
try {
XSSFWorkbook workbook = chart.getWorkbook();
// 获取原sheet名
sheetName = workbook.getSheetName(0);
// 删除原有sheet
workbook.removeSheetAt(0);
// 根据原有sheet名新创建sheet
Sheet sheet = workbook.createSheet(sheetName);
this.createRows(rows, sheet);
return sheetName;
} catch (Exception e) {
e.printStackTrace();
return sheetName;
}
}
/**
* 创建行数据
*
* @param rows
* @param sheet
*/
private void createRows(JSONArray rows, Sheet sheet) {
if (rows == null || sheet == null) {
return;
}
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
JSONArray cols = rows.getJSONArray(rowIndex);
Row row = sheet.createRow(rowIndex);
this.createCells(cols, row);
}
}
/**
* 创建单元格数据
*
* @param cols
* @param row
*/
private void createCells(JSONArray cols, Row row) {
if (cols == null || row == null) {
return;
}
int rowNum = row.getRowNum();
for (int colIndex = 0; colIndex < cols.size(); colIndex++) {
String cellValueStr = cols.getString(colIndex);
// 首行、首列分别代表系列名、横坐标,非首行首列均是数值
// 避免值中可能存在非数值,因此做下数值判断
if (rowNum == 0 || colIndex == 0 || !NumberUtils.isNumber(cellValueStr)) {
row.createCell(colIndex).setCellValue(cellValueStr);
} else {
Double cellValue = cols.getDouble(colIndex);
row.createCell(colIndex).setCellValue(cellValue);
}
}
}
/**
* 将数据封装成MyChartSeries对象,便于word中图表解析时使用 数据格式参考
* (留空) 系列1 系列2
* 第一季度 5 10
* 第二季度 10 15
* 第三季度 15 16
* 第四季度 20 4
*
* @param rows
*/
private List resolveDatas(JSONArray rows) {
List serAxisList = new ArrayList();
// 数据为空
if (CollectionUtils.isEmpty(rows)) {
return serAxisList;
}
// 第0行没有数据
JSONArray serJsonArray = rows.getJSONArray(0);
if (CollectionUtils.isEmpty(serJsonArray)) {
return serAxisList;
}
for (int serIndex = 1; serIndex < serJsonArray.size(); serIndex++) {
String seriesName = serJsonArray.getString(serIndex);
MyChartSeries myChartData = new MyChartSeries(seriesName);
serAxisList.add(myChartData);
}
// 第0行数据不全,没有系列名
if (CollectionUtils.isEmpty(serAxisList)) {
return serAxisList;
}
// 第一行开始,第0个单元格代表axis(横坐标)名称,后续的依次为每个系列数值
for (int rowIndex = 1; rowIndex < rows.size(); rowIndex++) {
JSONArray cols = rows.getJSONArray(rowIndex);
String axisName = null;
if (cols != null && cols.size() > 0) {
axisName = cols.getString(0);
}
for (int serIndex = 0; serIndex < serAxisList.size(); serIndex++) {
MyChartSeries myChartData = serAxisList.get(serIndex);
int colIndex = serIndex + 1;
String colValue = null;
if (cols != null && cols.size() > colIndex) {
colValue = cols.getString(colIndex);
}
MyChartAxis myChartAxis = new MyChartAxis(axisName, colValue);
myChartData.getAxisDataList().add(myChartAxis);
}
}
return serAxisList;
}
/**
* 刷新图表数据
*
* @param chart
* @param sheetName
* @param rows
* @param serList
*/
private void refreshGraphContent(XWPFChart chart, String sheetName, JSONArray rows, List serList) {
CTChart ctChart = chart.getCTChart();
CTBarChart ctBarChart = ctChart.getPlotArea().getBarChartArray(0);
// 原有的所有系列
List ctBarSers = ctBarChart.getSerList();
// 按照新系列的数量,对应删减老系列的数量;保留老系列的原因是为了尽可能的保留原有系列样式
if (ctBarSers != null) {
List newCtBarSers = ctBarSers.subList(0, Math.min(serList.size(), ctBarSers.size()));
CTBarSer[] newCtBarSersArray = new CTBarSer[newCtBarSers.size()];
newCtBarSers.toArray(newCtBarSersArray);
ctBarChart.setSerArray(newCtBarSersArray);
}
// 构造每个系列的序列、数据、系列名
for (int serIndex = 0; serIndex < serList.size(); serIndex++) {
MyChartSeries myChartData = serList.get(serIndex);
CTBarSer ctBarSer = null;
if (ctBarSers != null && ctBarSers.size() > serIndex) {
ctBarSer = ctBarChart.getSerArray(serIndex);
}
if (ctBarSer == null) {
ctBarSer = ctBarChart.addNewSer();
} else {
ctBarSer.unsetCat();
ctBarSer.unsetVal();
ctBarSer.unsetTx();
}
// Category Axis Data
CTAxDataSource cat = ctBarSer.addNewCat();
// 获取图表的值
CTNumDataSource val = ctBarSer.addNewVal();
// 系列名称
CTSerTx ctSerTx = ctBarSer.addNewTx();
CTStrData strData = cat.addNewStrRef().addNewStrCache();
CTNumData numData = val.addNewNumRef().addNewNumCache();
CTStrData txData = ctSerTx.addNewStrRef().addNewStrCache();
// 构造序列项、数据
int idx = 0;
for (MyChartAxis mChartAxis : myChartData.getAxisDataList()) {
String axisName = mChartAxis.getAxisName();
String value = mChartAxis.getValue();
CTStrVal sVal = strData.addNewPt();// 序列名称
sVal.setIdx(idx);
sVal.setV(axisName);
CTNumVal numVal = numData.addNewPt();// 序列值
numVal.setIdx(idx);
numVal.setV(value);
++idx;
}
// 设置系列名称
CTStrVal txVal = txData.addNewPt();
txVal.setIdx(0);
txVal.setV(myChartData.getSeries());
numData.addNewPtCount().setVal(idx);
strData.addNewPtCount().setVal(idx);
// 序列区域
String axisDataRange = new CellRangeAddress(1, rows.size() - 1, 0, 0).formatAsString(sheetName, true);
cat.getStrRef().setF(axisDataRange);
// 数据区域
String numDataRange = new CellRangeAddress(1, rows.size() - 1, serIndex + 1, serIndex + 1)
.formatAsString(sheetName, true);
val.getNumRef().setF(numDataRange);
// 系列名区域
String serDataRange = new CellRangeAddress(0, 0, serIndex + 1, serIndex + 1).formatAsString(sheetName,
true);
ctSerTx.getStrRef().setF(serDataRange);
}
}
}
package com.wordchart.vo;
public class MyChartAxis {
private String axisName;
private String value;
public MyChartAxis(String axisName,String value) {
this.axisName = axisName;
this.value = value;
}
public String getAxisName() {
return axisName;
}
public void setAxisName(String axisName) {
this.axisName = axisName;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
package com.wordchart.vo;
import java.util.ArrayList;
import java.util.List;
public class MyChartSeries {
// 系列名称
private String series;
private List axisDataList = new ArrayList();
public MyChartSeries(String series) {
this.series = series;
}
public String getSeries() {
return series;
}
public List getAxisDataList() {
return axisDataList;
}
}
package com.wordchart;
import java.io.IOException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.policy.MyChartPolicy;
public class WordTest {
public static void main(String[] args) throws IOException {
String data = "{\r\n" +
" \"khg\": [\r\n" +
" [\r\n" +
" \"这是第一个单元格\",\r\n" +
" \"系列1\",\r\n" +
" \"系列2\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"一月\",\r\n" +
" \"100\",\r\n" +
" \"50\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"二月\",\r\n" +
" \"200\",\"100\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"三月\",\r\n" +
" \"300\",\"150\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"四月\",\r\n" +
" \"400\",\"200\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"五月\",\r\n" +
" \"500\",\"250\"\r\n" +
" ],\r\n" +
" [\r\n" +
" \"六月\",\r\n" +
" \"600\",\"300\"\r\n" +
" ]\r\n" +
" ]\r\n" +
"}";
JSONObject params = JSON.parseObject(data);
ConfigureBuilder configureBuilder = Configure.newBuilder();
configureBuilder.bind("khg", new MyChartPolicy());
Configure config = configureBuilder.build();
// 核心API采用了极简设计,只需要一行代码
XWPFTemplate.compile("D:\\2、工作目录\\2020年3月\\0323\\模板word-图表3.docx", config).render(params)
.writeToFile("D:\\2、工作目录\\2020年3月\\0323\\生成word\\模板word-图表3-输出.docx");
}
}
先来一张图方便说明:
关键代码为:MyChartPolicy.refreshGraphContent。
CTBarSer:代表所有的系列对象,一个图表包含多个系列,每个系列包含"系列名"、"序列"、"数据"等。如图中就分为3个系列,系列名为:系列1、系列2、系列3;每个系列中包含4个序列:类别1、类别2、类别3、类别4;每个系列与序列两者唯一确定一个数据,如系列1和类别1确定数据为4.3。
CTAxDataSource:序列。
CTNumDataSource:数据。
CTSerTx:系列名。
序列、数据、系列名的java类使用方式都类似,了解了其中一项后,其余的就好理解多了,目前以序列为例:
CTAxDataSource.getStrRef().getStrCache().addNewPt():创建一个新的序列项,可以为该新建的序列项赋值索引及值。
CTAxDataSource.getStrRef().setF():为序列设置EXCEL区域,设置此项后,打开word,改变EXCEL数据时,对应的图表数据项也会自动改变。
目前测试的还是相对简单的图表,没有太深入去完善每一项。若看到此文章的人还有需要扩展的内容,但是不知道poi中对应的api是哪一个,其实可以将xxx.docx改下后缀为xxx.zip,然后找到xxx.zip\word\charts\chart1.xml(若存在多个图表则会有多个,找到想要找的那个就好)。如图为例,找到c:chart标签,此标签代表着XWPFChart.getCTChart对象,想要知道某项属性的api,只要知道你在word图表中设置的值,然后来这个xml里找到你写的那个值,就能找到对应的标签,例如想获取系列,就是XWPFChart.getCTChart.getPlotArea.getBarChartArray.getSerList,和xml很好对上。
https://github.com/Kong0305/wordchart