基于poi-tl根据word模板动态生成图表

目录

 

一、前言

二、添加依赖

三、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源码改造部分说明

原理解析

poi-tl的原理是读取了word中所有的段落、表格,找出其中符合模板规则的字符串,如:{{data}}。找到对应字符串后,和相应数据一起传给对应的解析插件解析。由于poi-tl只解析了段落和表格部分,因此我们需要改造源码来使poi-tl多解析图表部分。

图表部分的定义规则为:在图表对应的EXCEL的第一个单元格按照poi-tl的定义方式填写模板字符串,对应的数据格式为一个二维数组,程序来解析二维数组重写EXCEL数据以及刷新图表。

如图所示的模板定义为{{chartdata}}:

基于poi-tl根据word模板动态生成图表_第1张图片

对应chartdata的JSON数据格式参考:

{
    "chartdata": [
        [
            "这是第一个单元格",
            "系列1",
            "系列2"
        ],
        [
            "一月",
            "100",
            "50"
        ],
        [
            "二月",
            "200",
            "100"
        ],
        [
            "三月",
            "300",
            "150"
        ],
        [
            "四月",
            "400",
            "200"
        ],
        [
            "五月",
            "500",
            "250"
        ],
        [
            "六月",
            "600",
            "300"
        ]
    ]
}

导出效果:

基于poi-tl根据word模板动态生成图表_第2张图片

 

方法改造及部分截图

1、resolveDocument:增加对图表的解析

基于poi-tl根据word模板动态生成图表_第3张图片

2、resolveCharts:循环word中所有的图表,找出需要解析的图表封装成对象

3、resolveChart:找到图表数据对应EXCEL的第一个sheet的第一个单元格,按照poi-tl解析模板的方式解析,若满足条件,则封装成poi-tl所需的MetaTemplate对象

4、resolveXWPFChart:完全参考自TemplateResolver.resolveXWPFRuns方法

5、parseTemplateFactory:增加XWPFChart的判断

基于poi-tl根据word模板动态生成图表_第4张图片

6、createRunTemplate:参考DefaultRunTemplateFactory.createRunTemplate方法,主要是将chart对象封装到pol-tl所需对象中

完整源码

TemplateResolver.java

/*
 * 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;
	}

}

MyRunTemplate.java

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会有数据刷新变没的情况。

完整源码

MyChartPolicy.java

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); } } }

MyChartAxis.java

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;
	}

}

MyChartSeries.java

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;
	}
}

插件使用源码

WordTest.java

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");
	}

}

图表部分源码解读

先来一张图方便说明:

基于poi-tl根据word模板动态生成图表_第5张图片

关键代码为: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很好对上。

基于poi-tl根据word模板动态生成图表_第6张图片

github源码

https://github.com/Kong0305/wordchart

你可能感兴趣的:(基于poi-tl根据word模板动态生成图表)