利用POI生成各种复杂的统计图
前言
这是一种比较通用并且相对比较稳妥的方法,有利于复杂统计图的拓展,缺点是效率低下,如果只是想简单快捷的同学请移步CV大法,本文仅做参考。
需要的maven依赖如下:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.1.0</version>
</dependency>
步骤
-
新建一个.xlsx(其他的我没有试过,并且我最终输出的文件也是.xlsx格式的)在xml上用假数据画一个自己想要的图(例如下面这样的柱状图)
-
在idea中以CTChart的方式打开这个图,利用如下代码
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("这里填上假的xml路径"));
//如果测试的xlsx文件有多个图,get函数得到的图按照当时创建图的时候的顺顺序依次获取
XSSFChart xssfChart = workbook.getSheetAt(0).createDrawingPatriarch().getCharts().get(0);
CTChart ctChart = xssfChart.getCTChart();
System.out.println(ctChart.toString());
3.最终控制台上显示了xml格式字符串,这个xml格式中的各个节点是“模仿”这个图的关键。
//真的xml文件太长了,所以这里就贴一部分,具体的在后面会一步一步说。
<c:title>
<c:overlay val="0"/>
<c:spPr>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
<a:effectLst/>
</c:spPr>
<c:txPr>
<a:bodyPr rot="0" spcFirstLastPara="1" vertOverflow="ellipsis" vert="horz" wrap="square" anchor="ctr" anchorCtr="1"/>
<a:lstStyle/>
<a:p>
<a:pPr>
<a:defRPr sz="1400" b="0" i="0" u="none" strike="noStrike" kern="1200" spc="0" baseline="0">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="65000"/>
<a:lumOff val="35000"/>
</a:schemeClr>
</a:solidFill>
<a:latin typeface="+mn-lt"/>
<a:ea typeface="+mn-ea"/>
<a:cs typeface="+mn-cs"/>
</a:defRPr>
</a:pPr>
<a:endParaRPr lang="zh-CN"/>
</a:p>
</c:txPr>
</c:title>
- 在上面的节点中我们可以看到大量的一层包一层的节点,这些节点在poi中会有对应的函数,我们只要一步一步照着这个节点依样画葫芦即可。
- 在“依样画葫芦”之前,我们还需要做一点点的准备:
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("因为我直接就在原来的表格上画,所以路径和上面是一样的,如果是实际应用,要按照具体的表格存放位置确定"));
XSSFChart chart = workbook.getSheetAt(0).createDrawingPatriarch().createChart(new XSSFClientAnchor(0, 0, 0, 0, 0, 27, 0 + 5, 27 + 15));
CTChart ctChart = chart.getCTChart();
6.虽然poi中封装了几乎所有的xml节点,但是在实际情况中我们并不需要用到所有的节点,例如上面的title,表示的是图表的标题,如果不想要,可以直接忽略。
7.在xml文件上,紧接着title的是plotarea节点,这个决定了画图的区域以及区域中的实际统计图,是不能忽略的。plotarea节点具体如下:
<c:plotArea>
<c:layout/>
<c:barChart>
<c:barDir val="col"/>
<c:grouping val="clustered"/>
<c:varyColors val="0"/>
<c:ser>
<c:idx val="0"/>
<c:order val="0"/>
<c:tx>
<c:strRef>
<c:f>Sheet1!$B$1</c:f>
<c:strCache>
<c:ptCount val="1"/>
<c:pt idx="0">
<c:v>title2</c:v>
</c:pt>
</c:strCache>
</c:strRef>
</c:tx>
<c:spPr>
<a:solidFill>
<a:schemeClr val="accent1"/>
</a:solidFill>
<a:ln>
<a:noFill/>
</a:ln>
<a:effectLst/>
</c:spPr>
<c:invertIfNegative val="0"/>
<c:cat>
<c:strRef>
<c:f>Sheet1!$A$2:$A$6</c:f>
<c:strCache>
<c:ptCount val="5"/>
<c:pt idx="0">
<c:v>C1</c:v>
</c:pt>
<c:pt idx="1">
<c:v>C2</c:v>
</c:pt>
<c:pt idx="2">
<c:v>C3</c:v>
</c:pt>
<c:pt idx="3">
<c:v>C4</c:v>
</c:pt>
<c:pt idx="4">
<c:v>C5</c:v>
</c:pt>
</c:strCache>
</c:strRef>
</c:cat>
<c:val>
<c:numRef>
<c:f>Sheet1!$B$2:$B$6</c:f>
<c:numCache>
<c:formatCode>General</c:formatCode>
<c:ptCount val="5"/>
<c:pt idx="0">
<c:v>1</c:v>
</c:pt>
<c:pt idx="1">
<c:v>2</c:v>
</c:pt>
<c:pt idx="2">
<c:v>3</c:v>
</c:pt>
<c:pt idx="3">
<c:v>4</c:v>
</c:pt>
<c:pt idx="4">
<c:v>5</c:v>
</c:pt>
</c:numCache>
</c:numRef>
</c:val>
<c:extLst>
<c:ext uri="{C3380CC4-5D6E-409C-BE32-E72D297353CC}" xmlns:c16="http://schemas.microsoft.com/office/drawing/2014/chart">
<c16:uniqueId val="{00000000-1C8F-4380-A5A1-F07389DC9975}"/>
</c:ext>
</c:extLst>
</c:ser>
<c:dLbls>
<c:showLegendKey val="0"/>
<c:showVal val="0"/>
<c:showCatName val="0"/>
<c:showSerName val="0"/>
<c:showPercent val="0"/>
<c:showBubbleSize val="0"/>
</c:dLbls>
<c:gapWidth val="219"/>
<c:overlap val="-27"/>
<c:axId val="1761604960"/>
<c:axId val="1761633424"/>
</c:barChart>
<c:catAx>
<c:axId val="1761604960"/>
<c:scaling>
<c:orientation val="minMax"/>
</c:scaling>
<c:delete val="0"/>
<c:axPos val="b"/>
<c:numFmt formatCode="General" sourceLinked="1"/>
<c:majorTickMark val="none"/>
<c:minorTickMark val="none"/>
<c:tickLblPos val="nextTo"/>
<c:spPr>
<a:noFill/>
<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="15000"/>
<a:lumOff val="85000"/>
</a:schemeClr>
</a:solidFill>
<a:round/>
</a:ln>
<a:effectLst/>
</c:spPr>
<c:txPr>
<a:bodyPr rot="-60000000" spcFirstLastPara="1" vertOverflow="ellipsis" vert="horz" wrap="square" anchor="ctr" anchorCtr="1"/>
<a:lstStyle/>
<a:p>
<a:pPr>
<a:defRPr sz="900" b="0" i="0" u="none" strike="noStrike" kern="1200" baseline="0">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="65000"/>
<a:lumOff val="35000"/>
</a:schemeClr>
</a:solidFill>
<a:latin typeface="+mn-lt"/>
<a:ea typeface="+mn-ea"/>
<a:cs typeface="+mn-cs"/>
</a:defRPr>
</a:pPr>
<a:endParaRPr lang="zh-CN"/>
</a:p>
</c:txPr>
<c:crossAx val="1761633424"/>
<c:crosses val="autoZero"/>
<c:auto val="1"/>
<c:lblAlgn val="ctr"/>
<c:lblOffset val="100"/>
<c:noMultiLvlLbl val="0"/>
</c:catAx>
<c:valAx>
<c:axId val="1761633424"/>
<c:scaling>
<c:orientation val="minMax"/>
</c:scaling>
<c:delete val="0"/>
<c:axPos val="l"/>
<c:majorGridlines>
<c:spPr>
<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="15000"/>
<a:lumOff val="85000"/>
</a:schemeClr>
</a:solidFill>
<a:round/>
</a:ln>
<a:effectLst/>
</c:spPr>
</c:majorGridlines>
<c:numFmt formatCode="General" sourceLinked="1"/>
<c:majorTickMark val="none"/>
<c:minorTickMark val="none"/>
<c:tickLblPos val="nextTo"/>
<c:spPr>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
<a:effectLst/>
</c:spPr>
<c:txPr>
<a:bodyPr rot="-60000000" spcFirstLastPara="1" vertOverflow="ellipsis" vert="horz" wrap="square" anchor="ctr" anchorCtr="1"/>
<a:lstStyle/>
<a:p>
<a:pPr>
<a:defRPr sz="900" b="0" i="0" u="none" strike="noStrike" kern="1200" baseline="0">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="65000"/>
<a:lumOff val="35000"/>
</a:schemeClr>
</a:solidFill>
<a:latin typeface="+mn-lt"/>
<a:ea typeface="+mn-ea"/>
<a:cs typeface="+mn-cs"/>
</a:defRPr>
</a:pPr>
<a:endParaRPr lang="zh-CN"/>
</a:p>
</c:txPr>
<c:crossAx val="1761604960"/>
<c:crosses val="autoZero"/>
<c:crossBetween val="between"/>
</c:valAx>
<c:spPr>
<a:noFill/>
<a:ln>
<a:noFill/>
</a:ln>
<a:effectLst/>
</c:spPr>
</c:plotArea>
可以看到,即使是默认生成的柱状统计图,plotarea中的节点也非常非常的多,但是正如之前说到的,想要画成一个统计图,实际上并不需要这么多的节点,甚至在节点中,也并不是所有的属性都需要用到。所以实际上的节点配置并不会很多。
一个一个来看,首先是<c:plotArea>,这部分对应poi中的代码表示为
CTPlotArea plotArea = ctChart.getPlotArea();
接着是<c:barChart>,这部分定义了我们需要的统计图,根据统计图的不同,这个节点的名称也会有所不同,例如<c:LineChart>,但是现在暂时不用管这些,java对应代码如下:
//显然,plotArea显示了节点之间的嵌套关系,这个之后会表现得更加明显。
CTBarChart ctBarChart = plotArea.addNewBarChart();
接下来是
<c:barDir val="col"/>
<c:grouping val="clustered"/>
<c:varyColors val="1"/>
对应设置了柱状图的横纵向表示,形式为簇状,不适用颜色变化
对应poi表示为
ctBarChart.addNewBarDir().setVal(STBarDir.COL);
ctBarChart.addNewGrouping().setVal(STBarGrouping.CLUSTERED);
ctBarChart.addNewVaryColors().setVal(true);
接下来是数据序列节点
<c:ser>
对应
CTBarSer ctBarSer = ctBarChart.addNewSer();
因为接下来的节点规律和上面的完全相同,所以我就简单写了
<c:idx val="0"/> -->ctBarSer.addNewIdx().setVal(0);
<c:order val="0"/> -->可以不用管
<c:tx>
<c:strRef>
<c:f>Sheet1!$B$1</c:f>
<c:strCache>
<c:ptCount val="1"/>
<c:pt idx="0">
<c:v>title2</c:v>
</c:pt>
</c:strCache>
</c:strRef>
</c:tx>
--> 只要管一个节点即可
ctBarSer.addNewTx().addNewStrRef().setF("Sheet1!$B$1");
<c:spPr>
<a:solidFill>
<a:schemeClr val="accent1"/>
</a:solidFill>
<a:ln>
<a:noFill/>
</a:ln>
<a:effectLst/>
</c:spPr>
----> 颜色设定,都可以不用管
<c:invertIfNegative val="0"/> -->不用管
<c:cat>
<c:strRef>
<c:f>Sheet1!$A$2:$A$6</c:f>
<c:strCache>
<c:ptCount val="5"/>
<c:pt idx="0">
<c:v>C1</c:v>
</c:pt>
<c:pt idx="1">
<c:v>C2</c:v>
</c:pt>
<c:pt idx="2">
<c:v>C3</c:v>
</c:pt>
<c:pt idx="3">
<c:v>C4</c:v>
</c:pt>
<c:pt idx="4">
<c:v>C5</c:v>
</c:pt>
</c:strCache>
</c:strRef>
</c:cat>
--> 只要管如下节点即可
ctBarSer.addNewCat().addNewStrRef().setF("Sheet1!$A$2:$A$6");
<c:val>
<c:numRef>
<c:f>Sheet1!$B$2:$B$6</c:f>
<c:numCache>
<c:formatCode>General</c:formatCode>
<c:ptCount val="5"/>
<c:pt idx="0">
<c:v>1</c:v>
</c:pt>
<c:pt idx="1">
<c:v>2</c:v>
</c:pt>
<c:pt idx="2">
<c:v>3</c:v>
</c:pt>
<c:pt idx="3">
<c:v>4</c:v>
</c:pt>
<c:pt idx="4">
<c:v>5</c:v>
</c:pt>
</c:numCache>
</c:numRef>
</c:val>--> 只要管如下节点即可
ctBarSer.addNewVal().addNewNumRef().setF("Sheet1!$B$2:$B$6");
<c:extLst> --> 不用管
<c:dLbls>
<c:showLegendKey val="0"/>
<c:showVal val="0"/>
<c:showCatName val="0"/>
<c:showSerName val="0"/>
<c:showPercent val="0"/>
<c:showBubbleSize val="0"/>
</c:dLbls>
--> 一些数据图例和样式的设定,暂时也可以不用管
<c:gapWidth val="219"/>间距,暂时可以不用管
<c:overlap val="-27"/> 重叠
<c:axId val="1761604960"/>
<c:axId val="1761633424"/> -->坐标轴id,最好是自己设
ctBarChart.addNewAxId().setVal(123456);
ctBarChart.addNewAxId().setVal(123457);
<c:catAx> --->x轴设定,实在分不清就直接照着写
CTCatAx ctCatAx = plotArea.addNewCatAx();
<c:axId val="1761604960"/> --> ctCatAx.addNewAxId().setVal(123456); // id of the cat axis
<c:scaling>
<c:orientation val="minMax"/> --> ctCatAx.addNewScaling().addNewOrientation().setVal(STOrientation.MIN_MAX);
</c:scaling>
<c:delete val="0"/> --> ctCatAx.addNewDelete().setVal(false);
<c:axPos val="b"/> --> ctCatAx.addNewAxPos().setVal(STAxPos.B);
<c:tickLblPos val="nextTo"/> --> ctCatAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO);
<c:crossAx val="1761633424"/> --> ctCatAx.addNewCrossAx().setVal(123457);
<c:crosses val="autoZero"/> --> ctCatAx.addNewCrosses().setVal(STCrosses.AUTO_ZERO);
<c:valAx>和<c:catAx>的配置方法完全一样,所以就只写java代码了
CTValAx ctValAx = plotArea.addNewValAx();
ctValAx.addNewAxId().setVal(123457); // id of the val axis
ctValAx.addNewScaling().addNewOrientation().setVal(STOrientation.MIN_MAX);
ctValAx.addNewDelete().setVal(false);
ctValAx.addNewAxPos().setVal(STAxPos.L);
ctValAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO);
ctValAx.addNewCrosses().setVal(STCrosses.AUTO_ZERO);
ctValAx.addNewCrossAx().setVal(123456);
最后是图例显示
// legend
CTLegend ctLegend = ctChart.addNewLegend();
ctLegend.addNewLegendPos().setVal(STLegendPos.B);
ctLegend.addNewOverlay().setVal(false);
好,写(chao)到这一步就基本上差不多了,最后只剩下把我们设定好的统计图实实在在的写到xlsx表格中去了。
//这个是拿来检验自己画的图上有哪些节点,一般也不用细看
//System.out.println(ctChart);
workbook.write(new FileOutputStream("C:\\Users\\dpx5803\\IdeaProjects\\springboot-helloworld\\test2.xlsx"));
workbook.close();
检验一下效果
额…最后的效果并不是完全一样的,其原因主要是我在配置节点的时候多配置了一些属性,比如最后的legend部分的代码(显示为下方的图例)。看到这里还有兴趣的话可以跟自己生成的xml字符串比对一下,看看哪里不一样,这里就不说了。