1.前言
关于poi 操作word 的吐槽: 山路崎岖, 一言难尽啊!!!
原本项目中的poi 版本是3.17的版本,但是3.17对于在word 中操作图表是有问题的。所以对项目的jar 包进行了升级,升级到了4.1.2。 要求JDK 1.8 以上. 现在用8以下的项目基本上也很少了。话不多说, 进入主题:
2.准备工作
poi版本:4.1.2
涉及到的所有jar 包:
commons-compress-1.18.jar
commons-collections4-4.1.jar
poi-4.1.2.jar
poi-examples-4.1.2.jar
poi-excelant-4.1.2.jar
poi-ooxml-4.1.2.jar
poi-ooxml-schemas-4.1.2.jar
poi-scratchpad-4.1.2.jar
ooxml-schemas-1.4.jar
xmlbeans-3.1.0.jar
3.正文
POI 可以操作word 中的图表类型基本上 跟echarts 差不多。 包含 柱状图(条形图),折线图,雷达图,柱状+折现的组合图,饼图等。 这次就主要说几个常用的图。
POI 操作word 图表的方式分为两种(我接触到的):
第一种:创建一个word 模板,在word 文档中事先插入柱状图,或者其他要用到的图表。通过将数据刷到图表对应的内置EXCEL 表格中,将数据展示在图表中。
第二种:动态插入图表,在word 文档中事先插入对应的标记 exp ${barChart_1} ,找到该标记,将标记替换为空,并将后台生成的图表插入到word 文档中。
3.1 两种方式优缺点对比
第一种:优点:图表的格式可以很好的控制,坐标轴,标题,数据标签,误差线,网格线,图例等都可以事先在模板中设置好,最终只需要关心数据的问题就可以了。 缺点:如果有动态插入,且图表数量不固定的情况下,就无法事先在模板中创建对应的图表进行展示。
第二种:优点:可以做到动态插入,只需要事先在指定的位置中打入标记。 如果标记也不固定可以在插入图表前,先对word 中段落进行遍历,将标记${barChart_1}插入到指定的位置,插入的图表的时候在将插入的标记替换为图表。缺点:样式不容易控制,生成的图表和在word 中直接创建的图表略有差异。基础的插入缺失很多属性, 需要将属性分别设置到图表中。还存在部分属性不生效的问题。(有大神解决的话,本人虚心求教)
3.2 代码展示
下方代码是操作固定模板中事先创建好的图表
tips:(1)在word文档中插入标记的时候需要先在notepad++ 这类纯文本编辑器中将标记写好,然后复制到word 文档中,否则这个标记可能会被word 拆分成多个词,无法进行匹配。(2)输入图表标题的时候,尽量在输入法中一次性将标题输入完成,不要分开多次插入。如图所示:
JAVA poi 动态插入图表, 一些属性的设置
设置图例的位置
XDDFChartLegend legend = chart.getOrAddLegend();
legend.setPosition(LegendPosition.BOTTOM); // 图例位置:上下左右设置柱状图为条形图
XDDFBarChartData barChart = (XDDFBarChartData) chart.createData(ChartTypes.BAR, xAxis, yAxis);
barChart.setBarDirection(BarDirection.COL); // COL 为条形图 BAR 为柱状图设置X轴的文字一直在最下方, 不会因为负数的原因导致图形和X轴的标签文字重合
XDDFCategoryAxis xAxisLine = chartLine.createCategoryAxis(AxisPosition.BOTTOM); // 创建X轴,并且指定位置
xAxisLine.setTickLabelPosition(AxisTickLabelPosition.LOW); // 设置X轴的文字一直在最下方展示数据标签 和调整数据标签的位置
CTPlotArea plotArea = chartLine.getCTChart().getPlotArea();
for (CTLineSer ser : plotArea.getLineChartArray(0).getSerList()) {
CTDLbls ctdLbls = ser.addNewDLbls();
ctdLbls.addNewShowVal().setVal(true);// 是否展示数值
ctdLbls.addNewDLblPos().setVal(STDLblPos.IN_END);//数据标签位置
}设置折线图的线条样式和标记点样式
lineSeries.setSmooth(false); // 线条样式:true平滑曲线,false折线
lineSeries.setMarkerStyle(MarkerStyle.NONE); // 标记点样式设置数据标签的格式
barSeries.getValuesData().setFormatCode("##0.0"); // 保留一位小数
如果有多条折线图或者柱状图, 调整柱状图的颜色
private static void solidFillSeries(CTBarSer ser, int i) {
ListcolorArr = new ArrayList<>();
colorArr.add(new Integer[]{127, 100, 162});
colorArr.add(new Integer[]{155,187,89});
colorArr.add(new Integer[]{192,80,77});
colorArr.add(new Integer[]{79,128,189});
Integer[] color = colorArr.get(i);
CTSRgbColor rgb = CTSRgbColor.Factory.newInstance();
Color col1 = new Color(color[0],color[1],color[2]);
rgb.setVal(new byte[]{(byte) col1.getRed(), (byte) col1.getGreen(), (byte) col1.getBlue()});
CTSolidColorFillProperties fillProp = CTSolidColorFillProperties.Factory.newInstance();
fillProp.setSrgbClr(rgb);
CTShapeProperties ctShapeProperties = CTShapeProperties.Factory.newInstance();
ctShapeProperties.setSolidFill(fillProp);
ser.setSpPr(ctShapeProperties);
}
下方代码是操作固定模板中事先创建号的图表:
public static void charGeneration(XWPFDocument doc, String tit, List dataArray){ if (dataArray != null && dataArray.size() > 0) { List relations = doc.getRelations(); // 获取模版中所有的表格模版 int index=0; for (POIXMLDocumentPart poixmlDocumentPart : relations) if (poixmlDocumentPart instanceof XWPFChart) { //判断是不是图表类型 XWPFChart chart = (XWPFChart) poixmlDocumentPart; String charType = ""; // charType 1 普通图表柱状图 2 折线图单条和2条 3 雷达图 4 多条折线图 XDDFTitle xddfTitletitle = chart.getTitle(); XDDFTextBody body = xddfTitletitle.getBody(); CTTextBody xmlObject = body.getXmlObject(); String tt = xmlObject.toString(); //图表的标题 List keyList = new ArrayList<>(); List keyListTemp = new ArrayList<>(); List titleArr = new ArrayList<>(); //根据属性第一列名称切换数据类型 CTChart ctChart = null; CTPlotArea plotArea = null; if (tt.contains(tit) && tit.equals("图表1")){//折线图 //刷新内置excel数据 charType = "2"; // charType 1 普通图表柱状图 2 折线图 3 雷达图 keyList.add("nd"); keyList.add("value1"); keyList.add("value2"); titleArr.add(""); titleArr.add("餐补"); titleArr.add("交通补贴"); ctChart = chart.getCTChart(); plotArea = ctChart.getPlotArea(); }else if (tt.contains(tit) && tit.contains("图表2")) { charType = "1"; keyList.add("ssnd"); keyList.add("value1"); keyList.add("value2"); titleArr.add(""); titleArr.add("收入"); titleArr.add("支出"); ctChart = chart.getCTChart(); plotArea = ctChart.getPlotArea(); } if (StringUtils.isNotEmpty(charType)) { refreshExcel(chart, dataArray,keyList,titleArr); //刷新页面显示数 List newKey = new ArrayList<>(); //之所以要new 一个新的对象,直接赋值,只是赋值了引用地址 if (charType.equals("1")) {CTBarChart barChart = plotArea.getBarChartArray(0);List serList = barChart.getSerList();int position = 1;refreshNumGraphContent(barChart, serList, dataArray,keyList,titleArr); } if (charType.equals("2")) {CTLineChart lineChart = plotArea.getLineChartArray(0);List serList = lineChart.getSerList();int position = 1;refreshLineStrGraphContent(lineChart, serList, dataArray,keyList,titleArr); } break; } }} public static boolean refreshLineStrGraphContent(CTLineChart lineChart, List> serList, List dataList,List keyList, List titleList) { boolean result = true; int position = 1; if (dataList.size() < 1) { return false; } List tList = new ArrayList(); int keyIndex=1; //更新数据区域 for (int i = 0; i < serList.size(); i++) { CTAxDataSource cat = null; CTNumDataSource val = null; CTLineSer ser =lineChart.getSerArray(i); cat = ser.getCat(); // 获取图表的值 val = ser.getVal(); CTSerTx tx = ser.getTx(); CTStrRef strRefH = cat.getStrRef(); CTStrData strCacheH = strRefH.getStrCache(); CTStrData strCache = tx.getStrRef().getStrCache(); CTNumData numData = val.getNumRef().getNumCache(); strCache.setPtArray((CTStrVal[]) null); // unset old axis text strCacheH.setPtArray((CTStrVal[]) null); // unset old axis text numData.setPtArray((CTNumVal[]) null); // unset old values // set model int idx = 0; CTStrVal strVal1 = strCache.addNewPt();//序列名称 if (titleList.size() == 2) { strVal1.setIdx(i); strVal1.setV(titleList.get(1)); }else{ strVal1.setIdx(i); strVal1.setV(titleList.get(i+1)); } for (int j = 0; j < dataList.size(); j++) { CTStrVal strVal = strCacheH.addNewPt();//序列名称 strVal.setIdx(idx); strVal.setV(dataList.get(j).getString(keyList.get(0))); for (int i1 = 0; i1 < keyList.size(); i1++) { String value = "0"; if (i1 == 0) { if (idx>0){continue; }else{ } }else{ if (StringUtil.checkChinese(dataList.get(j).getString(keyList.get(keyIndex)))){continue; } String zb = "0"; if (dataList.get(j).get(keyList.get(keyIndex))!=null && !dataList.get(j).getString(keyList.get(keyIndex)).trim().equals("--")){zb = dataList.get(j).getString(keyList.get(keyIndex));if(StringUtil.isNullString(zb)){ zb = "0";}if (zb.indexOf("%")>0){ BigDecimal b = new BigDecimal(100); zb = zb.replace("%",""); zb = new BigDecimal(zb).divide(b).setScale(4, BigDecimal.ROUND_HALF_UP).toString();} } if(new BigDecimal(zb)!=null){value=new BigDecimal(zb).toString(); } if(!"0".equals(value)){CTNumVal numVal = numData.addNewPt();//序列值numVal.setIdx(idx);numVal.setV(value); } idx++; break; } } } numData.getPtCount().setVal(idx); if (i==0){ strCache.getPtCount().setVal(idx); String legendDataRange = new CellRangeAddress(0, 0, 1, idx + 1) .formatAsString("Sheet1", true); tx.getStrRef().setF(legendDataRange); //赋值横坐标数据区域 String axDataRange = new CellRangeAddress(1, dataList.size(), 0, 0) .formatAsString("Sheet1", true); cat.getStrRef().setF(axDataRange); } //数据区域 String numDataRange = new CellRangeAddress(1, dataList.size(), i + position, i + position) .formatAsString("Sheet1", false); val.getNumRef().setF(numDataRange); // 设置系列生成方向 if (keyList.size() != 2) { keyIndex++; } } return result; } public static boolean refreshExcel(XWPFChart chart, List dataList,List keyList,List titleArr) { boolean result = true; Workbook wb = new XSSFWorkbook(); Sheet sheet = wb.createSheet("Sheet1"); //根据数据创建excel第一行标题行 sheet.createRow(0).createCell(0).setCellValue(""); for (int i = 1; i < titleArr.size(); i++) { sheet.getRow(0).createCell(i).setCellValue(titleArr.get(i)==null?"":titleArr.get(i)); } //遍历数据行 for (int i = 0; i < dataList.size(); i++) { JSONObject baseFormMap = dataList.get(i);//数据行 //fldNameArr字段属性 for (int j = 0; j < keyList.size(); j++) { if(sheet.getRow(i+1)==null){ if(j==0){ try {sheet.createRow(i+1).createCell(j).setCellValue(baseFormMap.getString(keyList.get(j))==null?"":baseFormMap.getString(keyList.get(j))); } catch (Exception e) {if(baseFormMap.get(keyList.get(i))==null){ sheet.createRow(i+1).createCell(j).setCellValue("");}else{ sheet.createRow(i+1).createCell(j).setCellValue(baseFormMap.getString(keyList.get(j)));} } } }else{ String dvl = baseFormMap.getString(keyList.get(j)); if (StringUtil.checkChinese(dvl)) { continue; } double value=0d; String zb = "0"; if (baseFormMap.getString(keyList.get(j))!=null && !baseFormMap.getString(keyList.get(j)).trim().equals("--")){ zb = baseFormMap.getString(keyList.get(j)); if(StringUtil.isNullString(zb)){zb = "0"; } if (zb.indexOf("%")>0){BigDecimal b = new BigDecimal(100);zb = zb.replace("%","");zb = new BigDecimal(zb).divide(b).setScale(4, BigDecimal.ROUND_UP).toString(); } } if(new BigDecimal(zb)!=null){ value=new BigDecimal(zb).doubleValue(); } if(StringUtils.isEmpty(dvl)){ sheet.getRow(i+1).createCell(j); }else{ sheet.getRow(i+1).createCell(j).setCellValue(value); } } } } // 更新嵌入的workbook POIXMLDocumentPart xlsPart = chart.getRelations().get(0); OutputStream xlsOut = xlsPart.getPackagePart().getOutputStream(); try { wb.write(xlsOut); xlsOut.close(); } catch (IOException e) { e.printStackTrace(); result = false; } finally { if (wb != null) { try { wb.close(); } catch (IOException e) { e.printStackTrace(); result = false; } } } return result; }
动态插入图表:
public static void insetChart(XWPFDocument document, List list) throws Exception {//根据数据的条数, 插入对应数量的标记boolean breakPoint = true; // 终止多层循环 // 1、创建word文档对象 List paragraphs = document.getParagraphs(); if (hxqyList.size() > 0) { for (XWPFParagraph per : paragraphs) { if (breakPoint) { List runs = per.getRuns(); for (XWPFRun run : runs) { //获取文本的值 String text = run.getText(0); if (StringUtils.isNotEmpty(text)) {if (text.contains("${hxqyChart_1}")) { for (int i = 0; i < hxqySize; i++) { run.addCarriageReturn(); XWPFRun run1 = per.createRun(); run1.setText("${hxqyChart_"+(i+2)+"}"); run1.addCarriageReturn(); run1.setFontFamily("宋体"); run1.setFontSize(14); per.addRun(run1); } breakPoint=false; break;} } } } } } for (JSONObject hxObject : hxqyList) { int runIndex = 1; for (XWPFParagraph per : paragraphs) { List runs = per.getRuns(); for (XWPFRun run : runs) { //获取文本的值 String text = run.getText(0); if (StringUtils.isNotEmpty(text)) { if (text.contains("hxqyChart")) {run.setText("1.2."+runIndex+hxObject.getString("nsrmc"), 0);per.setAlignment(ParagraphAlignment.LEFT);//对齐方式run.setText(hxObject.getString(""), 1);run.addBreak();String zcfzTitle = "柱状图";String zcfzTitle1 = "折线图";//插入柱状图insertBarchar(document, run,zcfzTitle,hxObject.getJSONArray("zcfzzk_zzt"));//插入折线图insertLineChart(document,run,zcfzTitle1,hxObject.getJSONArray("zcfzzk_zxt")); } } } } runIndex++; } }private static void insertBarchar(XWPFDocument document, XWPFRun run, String title, JSONArray dataArray) throws Exception{ List dataObjList = new ArrayList<>(); if (dataArray != null && dataArray.size() > 0) { dataObjList = JSONObject.parseArray(dataArray.toJSONString(), JSONObject.class); } // 2、创建chart图表对象,抛出异常 XWPFChart chart = document.createChart(run, (int)(14.5 * Units.EMU_PER_CENTIMETER), 9 * Units.EMU_PER_CENTIMETER); chart.setChartTopMargin(1000L); // 3、图表相关设置 chart.setTitleText(title); // 图表标题 chart.setTitleOverlay(false); // 图例是否覆盖标题 // 4、图例设置 XDDFChartLegend legend = chart.getOrAddLegend(); legend.setPosition(LegendPosition.BOTTOM); // 图例位置:上下左右 String[] xAxisData = new String[4]; Double[] yAxisData = new Double[4]; Double[] yAxisData1 = new Double[4]; for (int i = 0; i < dataObjList.size(); i++) { if (title.equals("柱状图1")) { //双柱状图 xAxisData[i] = dataObjList.get(i).getString("ssnd"); yAxisData[i] = dataObjList.get(i).getDouble("value1"); yAxisData1[i] = dataObjList.get(i).getDouble("value2"); }else if (title.equals("柱状图2")) {//单柱状图 xAxisData[i] = dataObjList.get(i).getString("ssnd"); yAxisData[i] = dataObjList.get(i).getDouble("value1"); } } // 5、X轴(分类轴)相关设置 XDDFCategoryAxis xAxis = chart.createCategoryAxis(AxisPosition.BOTTOM); // 创建X轴,并且指定位置 XDDFCategoryDataSource xAxisSource = XDDFDataSourcesFactory.fromArray(xAxisData); // 设置X轴数据 // 6、Y轴(值轴)相关设置 XDDFValueAxis yAxis = chart.createValueAxis(AxisPosition.LEFT); // 创建Y轴,指定位置 yAxis.setCrossBetween(AxisCrossBetween.BETWEEN); // 设置图柱的位置:BETWEEN居中 // 7、创建柱状图对象 XDDFBarChartData barChart = (XDDFBarChartData) chart.createData(ChartTypes.BAR, xAxis, yAxis); barChart.setBarDirection(BarDirection.COL); // 设置柱状图的方向:BAR横向,COL竖向,默认是BAR if (title.equals("图表1")) { XDDFNumericalDataSource yAxisSource = XDDFDataSourcesFactory.fromArray(yAxisData); // 设置Y轴数据 XDDFNumericalDataSource yAxisSource1 = XDDFDataSourcesFactory.fromArray(yAxisData1); // 设置Y轴数据 // 8、加载柱状图数据集 XDDFBarChartData.Series barSeries = (XDDFBarChartData.Series) barChart.addSeries(xAxisSource, yAxisSource); XDDFBarChartData.Series barSeries2 = (XDDFBarChartData.Series) barChart.addSeries(xAxisSource, yAxisSource1); barSeries.setTitle("收入",null); barSeries2.setTitle("支出",null); barSeries.getValuesData().setFormatCode("##0.00"); barSeries2.getValuesData().setFormatCode("##0.00"); }else{ XDDFNumericalDataSource yAxisSource = XDDFDataSourcesFactory.fromArray(yAxisData); // 设置Y轴数据 // 8、加载柱状图数据集 XDDFBarChartData.Series barSeries = (XDDFBarChartData.Series) barChart.addSeries(xAxisSource, yAxisSource); barSeries.getValuesData().setFormatCode("##0.00"); barSeries.setTitle("收入",null); } CTPlotArea plotArea = chart.getCTChart().getPlotArea(); for (CTBarSer ser : plotArea.getBarChartArray(0).getSerList()) { CTDLbls ctdLbls = ser.addNewDLbls(); ctdLbls.addNewShowCatName().setVal(false);// 是否展示对应x轴上的值(类型名称) ctdLbls.addNewShowVal().setVal(true);// 是否展示数值 ctdLbls.addNewShowSerName().setVal(false);// 是否展示归属折线名称(系列名称) ctdLbls.addNewShowLegendKey().setVal(false);// 是否展示图例(图例项标示) ctdLbls.addNewDLblPos().setVal(STDLblPos.IN_END); } // 9、绘制柱状图 chart.plot(barChart); }private static void insertLineChart(XWPFDocument document,XWPFRun run,String title,JSONArray dataArray) throws Exception{ List dataObjList = JSONObject.parseArray(dataArray.toJSONString(), JSONObject.class); XWPFChart chartLine = document.createChart(run, (int)(14.5 * Units.EMU_PER_CENTIMETER), 9 * Units.EMU_PER_CENTIMETER); chartLine.setChartTopMargin(1000L); // 3、图表相关设置 chartLine.setTitleText(title); // 图表标题 chartLine.setTitleOverlay(false); // 图例是否覆盖标题 // 4、图例设置 XDDFChartLegend legendLine = chartLine.getOrAddLegend(); legendLine.setPosition(LegendPosition.BOTTOM); // 图例位置:上下左右 // 5、X轴(分类轴)相关设置 XDDFCategoryAxis xAxisLine = chartLine.createCategoryAxis(AxisPosition.BOTTOM); // 创建X轴,并且指定位置 xAxisLine.setTickLabelPosition(AxisTickLabelPosition.LOW); // 设置X周的文字一直在最下方 String[] xAxisDataLine = new String[4]; Double[] yAxisDataLine_1 = new Double[4]; Double[] yAxisDataLine_2 = new Double[4]; for (int i = 0; i < dataObjList.size(); i++) { xAxisDataLine[i] = dataObjList.get(i).getString("ssqj"); if (title.equals("图表1")) { yAxisDataLine_1[i] = dataObjList.get(i).getDouble("value1"); yAxisDataLine_2[i] = dataObjList.get(i).getDouble("value2"); } else if (title.equals("图表2")) { yAxisDataLine_1[i] = dataObjList.get(i).getDouble("bdl_1"); } } XDDFCategoryDataSource xAxisSourceLine = XDDFDataSourcesFactory.fromArray(xAxisDataLine); // 设置X轴数据 // 6、Y轴(值轴)相关设置 XDDFValueAxis yAxisLine = chartLine.createValueAxis(AxisPosition.LEFT); // 创建Y轴,指定位置 yAxisLine.setCrossBetween(AxisCrossBetween.BETWEEN); // 设置图柱的位置:BETWEEN居中 // 7、创建柱状图对象 XDDFLineChartData lineChart = (XDDFLineChartData) chartLine.createData(ChartTypes.LINE, xAxisLine, yAxisLine); if (title.equals("图表1")) {//双折线图 XDDFNumericalDataSource yAxisSourceLine = XDDFDataSourcesFactory.fromArray(yAxisDataLine_1); // 设置Y轴数据 XDDFNumericalDataSource yAxisSourceLine2 = XDDFDataSourcesFactory.fromArray(yAxisDataLine_2); // 设置Y轴数据 // 8、加载柱状图数据集 XDDFLineChartData.Series lineSeries = (XDDFLineChartData.Series) lineChart.addSeries(xAxisSourceLine, yAxisSourceLine); lineSeries.setTitle("收入", null); // 图例标题 XDDFLineChartData.Series lineSeries2 = (XDDFLineChartData.Series) lineChart.addSeries(xAxisSourceLine, yAxisSourceLine2); lineSeries2.setTitle("支出", null); // 图例标题 setLineSeriesStyle(lineSeries); setLineSeriesStyle(lineSeries2); }else{ XDDFNumericalDataSource yAxisSourceLine = XDDFDataSourcesFactory.fromArray(yAxisDataLine_1); // 设置Y轴数据 XDDFLineChartData.Series lineSeries = (XDDFLineChartData.Series) lineChart.addSeries(xAxisSourceLine, yAxisSourceLine); lineSeries.setTitle("营业收入",null); setLineSeriesStyle(lineSeries); } CTPlotArea plotArea = chartLine.getCTChart().getPlotArea(); for (CTLineSer ser : plotArea.getLineChartArray(0).getSerList()) { CTDLbls ctdLbls = ser.addNewDLbls(); ctdLbls.addNewShowCatName().setVal(false);// 是否展示对应x轴上的值(类型名称) ctdLbls.addNewShowVal().setVal(true);// 是否展示数值 ctdLbls.addNewShowSerName().setVal(false);// 是否展示归属折线名称(系列名称) ctdLbls.addNewShowLegendKey().setVal(false);// 是否展示图例(图例项标示) ctdLbls.addNewDLblPos().setVal(STDLblPos.IN_END);//数据标签 } // 9、绘制柱状图 chartLine.plot(lineChart); }
来源地址:https://blog.csdn.net/baidu_29596947/article/details/120526139