案例

利用 POI 进行 word 模板替换已经有很成熟的方案了,开源的工具就有 easypoi,以及最近发现的工具 poi-tl,都是模板替换,用法大同小异。

团队内部现在总结发现,用 easypoi 进行 excel 导入导出,模板替换比较方便,但是在 word 方面目前来看 poi-tl,优势更大,因为其既拥有 easypoi 的优势,又弥补了一些不足,比如图片的环绕,可以通过其插件解决,而且也提供自定义插件支持,可以更灵活。

但是最近有个需求,在word模板中有图表的存在,现存的两个工具都无法满足,只能支持图片插入。因此决定通过自定义其引用插件,来满足需求。

在自定义插件时,发现使用poi-tl还是很方便和简单的,麻烦的是自己去操作图表,由于自己太菜以及对 poi 的认识还严重不足,走了很多路,所以这里记录下。

引用

学习 poi 完全是瞎撞,也不知道各位大佬是如何学习的,为了解决图表问题,能用的资料太少了,这里就贴一些:

  1. java使用poi在word中生成柱状图、折线图、饼图、柱状图+折线图组合图、动态表格、文本替换、图片替换、更新内置Excel数据、更新插入的文本框内容、合并表格单元格
  2. 官方 javaDoc
  3. 官方 示例源码

这里感谢下 我们都有 博主提供的示例源码。

使用记录

目前只是研究了下折线图,其他的还没来的及。

获取图表,定位模板图表

我们都有 博主的思路是通过,图表的编辑数据第一行第一列的交叉格子(org.apache.poi.xssf.usermodel.XSSFCell),在这里输入标识符来定位,其方法提供是getZeroData

我将方法稍微调整了下,思路是一样的,直接贴代码:

// 获取所有图表
List<XWPFChart> charts = document.getCharts();
for (XWPFChart chart : charts) {XSSFWorkbook workbook = chart.getWorkbook();XSSFSheet sheet1 = workbook.getSheetAt(0);XSSFCell cell = sheet1.getRow(0).getCell(0);String firstCellValue = cell.getStringCellValue();// optionalText() 为标识符获取方法if (StrUtil.equals(firstCellValue, optionalText())) {findCharts.add(chart);}
}

定位代码后,就是操作了

渲染数据

数据结构

渲染数据之前是填入数据,我们都有 博主提供的数据结构,个人觉得太凌乱了,所以我总结了一下,定义如下

@Data
public class LineChartRenderData {/*** 图名*/private String title;/*** 折线*/private List<LineData> lines;/*** 横坐标*/private List<String> pointX;@Datapublic static class LineData {/*** excel title 折线图 标题*/private String title;/*** 值*/private Map<String, BigDecimal> points;}
}

因为目前只争对了折现,所以用 LineData 来定义每条线的数据,以及其名称。pointX表示每个点的坐标名称,title表示整个图表名称。

绘制线条

绘制线条,其实就是在拼接xml文件,这里简单写下这两天对于这块的理解:

  1. XDDFLineChartData类有个属性是 chart(org.openxmlformats.schemas.drawingml.x2006.chart.CTLineChart) ,这里其实就是整个图的xml实体化,我看到大部分获取的方式是这样的:
CTChart ctChart = chart.getCTChart();
CTPlotArea plotArea = ctChart.getPlotArea();
// 折线图
CTLineChart lineChart = plotArea.getLineChartArray(0);
List<LineChartRenderData.LineData> lines = data.getLines();

我在这里偷了个懒,直接用反射获取了,也不知道后期会不会有些其他问题:

CTLineChart lineChart = (CTLineChart) ReflectUtil.getFieldValue(lineChartData, "chart");
  1. 拿到 lineChart 后将获取线条集合然后将其清空lineChart.getSerList().clear()
  2. 拼接 xml,绘制线条。其实主要是拼接<c:ser></c:ser>,大概层次就是:
<c:ser><c:idx val="0"/><c:order val="0"/><c:tx> </c:tx><c:spPr> </c:spPr><c:marker> </c:marker><c:cat> </c:cat><c:val> </c:val><c:smooth val="0"/><c:extLst> </c:extLst>
</c:ser>

对应的方法就是一系列的 addNew*方法,其中需要注意的就是一些 orderidxptCount这些标签都是计数类的,跟子标签数量都是有关系的,要多注意。还有就是 ctNumRef.addNewExtLst().addNewExt()方法有问题,jar包丢失,也不晓得是不是因为我的maven有问题
4. 这里重点讲下<c:tx></c:tx><c:cat></c:cat>以及<c:val></c:val><c:tx></c:tx>控制线条名称,<c:cat></c:cat>控制横坐标,每个点的名称,<c:val></c:val>则是数据了。

public static void renderChart(XWPFChart chart, LineChartRenderData data) {// 获取图表中的 单个图 之所以是数组 可能存在复合图表List<XDDFChartData> series = chart.getChartSeries();for (XDDFChartData chartData : series) {// 折线图XDDFLineChartData lineChartData = (XDDFLineChartData) chartData;CTLineChart lineChart = (CTLineChart) ReflectUtil.getFieldValue(lineChartData, "chart");// 清空lineChart.getSerList().clear();for (int i = 0; i < data.getLines().size(); i++) {LineChartRenderData.LineData lineData = data.getLines().get(i);int size = data.getPointX().size();// 系列名称 单条线名称 对应  <c:tx></c:tx>CTLineSer ctLineSer = lineChart.addNewSer();ctLineSer.addNewIdx().setVal(i);ctLineSer.addNewOrder().setVal(i);CTSerTx tx = ctLineSer.addNewTx();String lineRange = new CellRangeAddress(0, 0, i + 1, i + 1).formatAsString("Sheet1", true);CTStrRef txCTStrRef = tx.addNewStrRef();txCTStrRef.setF(lineRange);CTStrData ctStrData = txCTStrRef.addNewStrCache();CTStrVal txCTStrVal = ctStrData.addNewPt();txCTStrVal.setV(lineData.getTitle());txCTStrVal.setIdx(0);ctStrData.addNewPtCount().setVal(1);// <c:spPr></c:spPr>// <c:spPr>//      <a:ln w="28575" cap="rnd">//        <a:solidFill>//          <a:schemeClr val="accent1"/>//        </a:solidFill>//        <a:round/>//      </a:ln>//      <a:effectLst/>// </c:spPr>// 线条颜色ctLineSer.addNewSpPr().addNewLn().addNewSolidFill().addNewSchemeClr().setVal(STSchemeColorVal.Enum.forInt(7));//     <c:marker>//      <c:symbol val="none"/>//    </c:marker>ctLineSer.addNewMarker().addNewSymbol().setNil();//     <c:smooth val="0"/>ctLineSer.addNewSmooth().setVal(false);// <c:extLst>//      <c:ext uri="{C3380CC4-5D6E-409C-BE32-E72D297353CC}" xmlns:c16="http://schemas.microsoft.com/office/drawing/2014/chart">//        <c16:uniqueId val="{00000000-2505-4B5B-950D-4F4D615D4F57}"/>//      </c:ext>//    </c:extLst>ctLineSer.addNewExtLst();// <c:cat>//      <c:strRef>//        <c:f>Sheet1!$A$2:$A$3</c:f>//        <c:strCache>//          <c:ptCount val="2"/>//          <c:pt idx="0">//            <c:v>类别 1</c:v>//          </c:pt>//          <c:pt idx="1">//            <c:v>类别 2</c:v>//          </c:pt>//        </c:strCache>//      </c:strRef>//    </c:cat>// <c:cat></c:cat> x 轴 字段名CTAxDataSource cat = ctLineSer.addNewCat();// <c:val></c:val> y 轴 数据CTNumDataSource val = ctLineSer.addNewVal();// 渲染 X 轴// <c:cat> <c:strRef>   </c:strRef> </c:cat>CTStrRef ctStrRef = cat.addNewStrRef();// <c:f>Sheet1!$A$2:$A$3</c:f>// excel 表格 x 轴 数据范围, 第一列 1 - sizeString xRange = new CellRangeAddress(1, size, 0, 0).formatAsString("Sheet1", true);ctStrRef.setF(xRange);// X 轴数据CTStrData strData = ctStrRef.addNewStrCache();// 总数strData.addNewPtCount().setVal(size);//   <c:pt idx="0">//      <c:v>X1</c:v>//    </c:pt>for (int j = 0; j < size; j++) {CTStrVal ctStrVal = strData.addNewPt();ctStrVal.setIdx(j);ctStrVal.setV(data.getPointX().get(j));}// 渲染 点位 数据// <c:numRef>//        <c:f>Sheet1!$B$2:$B$3</c:f>//        <c:numCache>//          <c:formatCode>General</c:formatCode>//          <c:ptCount val="2"/>//          <c:pt idx="0">//            <c:v>4.3</c:v>//          </c:pt>//          <c:pt idx="1">//            <c:v>2.5</c:v>//          </c:pt>//        </c:numCache>//      </c:numRef>CTNumRef ctNumRef = val.addNewNumRef();// excel 表格 y 轴 数据范围, 第 i + 1 列 1 - size// CellRangeAddress 参数范围 https://blog.csdn.net/aerchi/article/details/7787891// CellRangeAddress(起始行号,终止行号, 起始列号,终止列号)String yRange = new CellRangeAddress(1, size, i + 1, i + 1).formatAsString("Sheet1", true);ctNumRef.setF(yRange);CTNumData numData = ctNumRef.addNewNumCache();// 总数numData.addNewPtCount().setVal(size);numData.setFormatCode("General");for (int j = 0; j < size; j++) {String key = data.getPointX().get(j);BigDecimal value = lineData.getPoints().get(key);CTNumVal ctNumVal = numData.addNewPt();ctNumVal.setIdx(j);ctNumVal.setV(value.toString());}}}
绘制excel数据

这个就比较简单了,只是把 我们都有 博主的方法,按照新的数据结构处理了下,然后看了下评论加了点代码。方法大意就是操作excel表格,将数据写进去。

 public static void renderExcel(XWPFChart chart, LineChartRenderData data) throws IOException {Workbook wb = new XSSFWorkbook();Sheet sheet = wb.createSheet("Sheet1");List<LineChartRenderData.LineData> lines = data.getLines();//根据数据创建excel第一行标题行for (int i = 0; i < lines.size(); i++) {if (sheet.getRow(0) == null) {sheet.createRow(0).createCell(i + 1).setCellValue(lines.get(i).getTitle());} else {sheet.getRow(0).createCell(i + 1).setCellValue(lines.get(i).getTitle());}}// 渲染数据for (int i = 0; i < data.getPointX().size(); i++) {String key = data.getPointX().get(i);Row row = sheet.createRow(i + 1);row.createCell(0).setCellValue(key);for (int j = 0; j < lines.size(); j++) {row.createCell(j + 1).setCellValue(lines.get(j).getPoints().get(key).doubleValue());}}List<POIXMLDocumentPart> pxdList = chart.getRelations();if (pxdList != null && pxdList.size() > 0) {for (int i = 0; i < pxdList.size(); i++) {// 判断为sheet再去进行更新表格数据if (pxdList.get(i).toString().contains("sheet")) {POIXMLDocumentPart xlsPart = pxdList.get(i);OutputStream xlsOut = xlsPart.getPackagePart().getOutputStream();try {wb.write(xlsOut);xlsOut.close();break;} finally {if (wb != null) {wb.close();}}}}}}

到此就可以输出文档了。

office 打不开

有点时候文档渲染成功了,可以用 WPS 打开,office却打不开,不用怀疑,肯定是 xml 拼接的有问题,最简单的方式就是拿到渲染后的文件与目标文件对比,看哪个标签出问题了,然后。

最后

这种方式虽然可以插入图表,但是还存在很多问题,只能说提供了思路,目前存在的问题:
1. 无法更改图表的标题
2. 生成图表的样式就相当于没了,线条颜色也会存在问题。
3. 不够灵活,最好的替代方式还是通过模板来渲染,不更改样式,只提供数据

最后的最后放个源码地址:apache-poi-word-chart

利用 POI 修改插入图表相关推荐

  1. 【JAVA - POI 合集】之 POI 操作word 图表,柱状图,折线图,雷达图,条形图 poi4.1.2

    1.前言 关于poi 操作word 的吐槽: 山路崎岖, 一言难尽啊!!! 原本项目中的poi 版本是3.17的版本,但是3.17对于在word 中操作图表是有问题的.所以对项目的jar 包进行了升级 ...

  2. Java利用poi生成word(包含插入图片,动态表格,行合并)

    Java利用poi生成word(包含插入图片,动态表格,行合并) 测试模板样式: 图表 1 Word生成结果: 图表 2 需要的jar包:(具体jar可自行去maven下载) Test测试类: imp ...

  3. 利用python获取word图表数据和修改图表信息

    利用python获取word图表数据和修改图表信息 起因咸鱼有个人问word怎么修改图表信息,想用docx库找不到关于图表的方法,这里用了Win32com. import time import wi ...

  4. Powerpoint插入图表(转)

    Powerpoint插入图表(转) 形象直观的图表与文字数据相比更容易让人理解,插入在幻灯片中的图表使幻灯片的显示效果更加清晰.PowerPoint 2003中附带了一种叫Microsoft Grap ...

  5. 利用Tableau绘制各类图表

    利用Tableau绘制各类图表 Tableau部分 Tableau下载和安装 使用Tableau连接数据源 连接EXCEL数据源 连接CSV数据源 连接PDF数据源 连接MySQL数据源(Deskto ...

  6. abaqus python 读取文件_利用Python修改Abaqus的inp文件(关键字)

    利用Python修改Abaqus的inp文件(关键字) Abaqus的关键字可以直接打开inp文件或者在Abaqus的key word里面直接修改,当然除了这个,还可以利用Python语言进行修改,也 ...

  7. 图表点编辑数据无反应_word插入图表无法编辑数据

    Word2007文档中的图表功能相对于Word2003的图表工具Microsoft Graph而言,应用更灵活,功能更强大.要想充分发挥图表功能,用户应当在Word2007文档中创建图表,而不是在Wo ...

  8. 蓄力-利用POI进行excel的导入导出(包含图片)

    这里写自定义目录标题 利用POI进行excel的导入导出 引入的jar包 excel导入 主方法: 将excel里面的图片转成数据 xls格式 xlsx格式 将图片数据转成字节流的方式传输到FTP服务 ...

  9. tex插入excel图表_Excel:以编程方式插入图表。

    tex插入excel图表 As we all know that a Chart is a graphical representation of data. Charts help us to vi ...

最新文章

  1. 用python画猫咪怎么画-如何用Python实现可视化地图
  2. Zuul 2 : The Netflix Journey to Asynchronous, Non-Blocking Systems--转
  3. 解决浏览器下载excel文件时显示“文件已损坏”
  4. Linux系统挂载NTFS文件系统
  5. 查看dataloader的大小_一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系
  6. 用python编制一个的类_常见面试题整理--Python概念篇
  7. 网段:192.168.6.0的机器A要访问网段:10.1.56.0的机器B
  8. React Native实例之房产搜索APP
  9. httpclient 连接池工具类_C# 中 HttpClient 的简单使用
  10. Bitbake基本语法
  11. 1500个字给你讲透什么是分账系统
  12. linux cpan 参数配置,Perl学习笔记之CPAN使用介绍
  13. SPSS软件的数据分析与GDP和人口老龄化的预测
  14. 服务注册与发现-全搞懂
  15. 软件测试工程师零基础入门指南
  16. antd Table设置固定高度
  17. 基于豹子捕猎的函数寻优算法
  18. 用计算机套路女孩电话,套路女孩子的情话抖音 套路女朋友的情话大全句子
  19. aomei动态磁盘管理器_程序员需要了解的硬核知识之磁盘
  20. android-支持多种屏幕[声明适用于 Android 3.2 的平板电脑布局] 七

热门文章

  1. 一文教你如何使用Mybatis Plugin 以及Druid Filer 改写SQL
  2. 2021 年 11 月信息系统项目管理师考前案例分析学习资料---马军老师编著
  3. Blender:导入obj渲染及导出图片+深度图+法向图
  4. python应用——分治法实现循环赛
  5. iOS永久签名不掉签工具,轻松签超详使用教程
  6. 【电路】自用人体感应灯(HC-SR501人体感应模块)
  7. sql float保留两位
  8. vue-生成二维码【生成、点击输入框内叉号移除生成的二维码、输入框聚焦】
  9. 机器学习:样本权重的理解
  10. vue3+vite UC浏览器兼容