最近因为业务需求,需要做poi自动导出Ecxel表格和Chart折线图的功能。
所以我在网上找到了一篇关于poi生成Chart图表的博客,代码很详细,但是缺少相关注释说明。
想要将它改造成自己需要的样子费了不少功夫,网上关于poi生成Chart图比较详细的博客又少,所以特此分享一下。
首先需要导入jar包,这里提供两种版本,4.1.1和4.1.2版本的都可以,具体如下:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-examples</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-excelant</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/fr.opensagres.xdocreport/xdocreport -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>xdocreport</artifactId>
<version>1.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.xmlbeans/xmlbeans -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>3.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
导入jar包后,先来说下我想实现的功能:
在固定的模板中(看个人公司的模板是什么样子的),外部只需要传入参数,即可动态生成Excel表格和相关的Chart折线图。
下面为我想要做出的效果图:
由上图可以看出来,我们只需要传入相关的参数,即可自动生成对应数量的Sheet页,并在Sheet页中自动生成Excel表和相关的折线图。
好了,下面直接上代码:
首先根据模板初始化数据,同时这也是我的测试类:
public static void main(String[] args) {
String filePath = "E:\\programming\\备份数据\\NewChart.xlsx"; //文件路径
List<String> sheetNameArr = new ArrayList<>(Arrays.asList("总体分析","承保")); //Sheet页名称
ArrayList<String> titleArr = new ArrayList<>(Arrays.asList("月份","1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"));//表头字段
NewTestCharUtil.setSheetNameArr(sheetNameArr);
NewTestCharUtil.setTitleArr(titleArr);
Map<String,List> dataMap = new HashMap<>();//数据集合
List<BaseFormMap> dataList = new ArrayList<>(); //数据集合1
BaseFormMap baseFormMap1 = new BaseFormMap();
baseFormMap1.put("value1","2019年运维");
baseFormMap1.put("value2",1245);
baseFormMap1.put("value3",1908);
baseFormMap1.put("value4",345);
baseFormMap1.put("value5",3456);
baseFormMap1.put("value6",167);
baseFormMap1.put("value7",856);
baseFormMap1.put("value8",2345);
baseFormMap1.put("value9",453);
baseFormMap1.put("value10",2343);
baseFormMap1.put("value11",785);
baseFormMap1.put("value12",723);
baseFormMap1.put("value13",567);
BaseFormMap baseFormMap2 = new BaseFormMap();
baseFormMap2.put("value1","2020年运维");
baseFormMap2.put("value2",4562);
baseFormMap2.put("value3",1234);
baseFormMap2.put("value4",543);
baseFormMap2.put("value5",237);
baseFormMap2.put("value6",628);
baseFormMap2.put("value7",231);
baseFormMap2.put("value8",894);
baseFormMap2.put("value9",786);
baseFormMap2.put("value10",0);
baseFormMap2.put("value11",0);
baseFormMap2.put("value12",0);
baseFormMap2.put("value13",0);
BaseFormMap baseFormMap3 = new BaseFormMap();
baseFormMap3.put("value1","2020年开发");
baseFormMap3.put("value2",234);
baseFormMap3.put("value3",243);
baseFormMap3.put("value4",562);
baseFormMap3.put("value5",1278);
baseFormMap3.put("value6",512);
baseFormMap3.put("value7",675);
baseFormMap3.put("value8",444);
baseFormMap3.put("value9",121);
baseFormMap3.put("value10",0);
baseFormMap3.put("value11",0);
baseFormMap3.put("value12",0);
baseFormMap3.put("value13",0);
dataList.add(baseFormMap1);
dataList.add(baseFormMap2);
dataList.add(baseFormMap3);
List<BaseFormMap> dataList2 = new ArrayList<>(); //数据集合2
BaseFormMap baseFormMap4 = new BaseFormMap();
baseFormMap4.put("value1","2019年运维");
baseFormMap4.put("value2",123);
baseFormMap4.put("value3",2378);
baseFormMap4.put("value4",5474);
baseFormMap4.put("value5",6734);
baseFormMap4.put("value6",223);
baseFormMap4.put("value7",546);
baseFormMap4.put("value8",123);
baseFormMap4.put("value9",999);
baseFormMap4.put("value10",234);
baseFormMap4.put("value11",456);
baseFormMap4.put("value12",234);
baseFormMap4.put("value13",678);
BaseFormMap baseFormMap5 = new BaseFormMap();
baseFormMap5.put("value1","2020年运维");
baseFormMap5.put("value2",123);
baseFormMap5.put("value3",3453);
baseFormMap5.put("value4",567);
baseFormMap5.put("value5",345);
baseFormMap5.put("value6",5478);
baseFormMap5.put("value7",567);
baseFormMap5.put("value8",343);
baseFormMap5.put("value9",899);
baseFormMap5.put("value10",1233);
baseFormMap5.put("value11",0);
baseFormMap5.put("value12",0);
baseFormMap5.put("value13",2342);
BaseFormMap baseFormMap6 = new BaseFormMap();
baseFormMap6.put("value1","2020年开发");
baseFormMap6.put("value2",678);
baseFormMap6.put("value3",454);
baseFormMap6.put("value4",123);
baseFormMap6.put("value5",1278);
baseFormMap6.put("value6",456);
baseFormMap6.put("value7",893);
baseFormMap6.put("value8",123);
baseFormMap6.put("value9",6734);
baseFormMap6.put("value10",0);
baseFormMap6.put("value11",234);
baseFormMap6.put("value12",0);
baseFormMap6.put("value13",0);
dataList2.add(baseFormMap4);
dataList2.add(baseFormMap5);
dataList2.add(baseFormMap6);
dataMap.put("dataList",dataList);
dataMap.put("dataList2",dataList2);
NewTestCharUtil.setDataMap(dataMap);
boolean result = NewTestCharUtil.createChart(filePath,true,"line",dataMap);
System.out.println(result);
}
NewTestCharUtil是我创建的poi工具类,其中定义了两个主要的方法,分别是创建Excel和创建Chart图的方法,如下:
public class NewTestCharUtil {
private static Map<String,List> dataMap; //用于存放数据集
private static List<String> sheetNameArr; //用于存放Sheet页名字
private static List<String> titleArr; //用于存放Excel表头字段
/**
* 创建Excel数据表
* @param workbook 工作簿对象
* @param sheet Sheet页对象
* @param sheetName Sheet页名称
* @param fldNameArr 字段名
* @param dataList 数据集合
* @param titleArr 标题集合
* @return
*/
public static boolean createExcel(XSSFWorkbook workbook,
XSSFSheet sheet,
String sheetName,
List<String> fldNameArr,
List<BaseFormMap> dataList,
List<String> titleArr){
//第一行标题样式
XSSFCellStyle title1Style = workbook.createCellStyle();
title1Style.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex()); //单元格填充色(蓝色)
title1Style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //填充图案
title1Style.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式(居中)
//第二行标题样式
XSSFCellStyle title2Style = workbook.createCellStyle();
title2Style.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex()); //单元格填充色(蓝色)
title2Style.setFillPattern(FillPatternType.SOLID_FOREGROUND); //填充图案
title2Style.setBorderBottom(BorderStyle.THIN); //下边框
title2Style.setBorderTop(BorderStyle.THIN); //上边框
title2Style.setBorderLeft(BorderStyle.THIN); //左边框
title2Style.setBorderRight(BorderStyle.THIN); //右边框
title2Style.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式(居中)
//数据行单元格样式
XSSFCellStyle cellStyle = workbook.createCellStyle();
cellStyle.setBorderBottom(BorderStyle.THIN); //下边框
cellStyle.setBorderTop(BorderStyle.THIN); //上边框
cellStyle.setBorderLeft(BorderStyle.THIN); //左边框
cellStyle.setBorderRight(BorderStyle.THIN); //右边框
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式(居中)
//数据行第一个单元格样式
XSSFCellStyle cell1Style = workbook.createCellStyle();
cell1Style.setBorderBottom(BorderStyle.THIN); //下边框
cell1Style.setBorderTop(BorderStyle.THIN); //上边框
cell1Style.setBorderLeft(BorderStyle.THIN); //左边框
cell1Style.setBorderRight(BorderStyle.THIN); //右边框
cell1Style.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式(居中)
//列宽自适应
for (int i=1; i<fldNameArr.size(); i++){
sheet.autoSizeColumn(i,true);
}
//设置第一列单元格宽度
sheet.setColumnWidth(0,sheet.getColumnWidth(0)*25/10);
//创建第一行
XSSFRow tableTitleNameRow = sheet.createRow(0);
XSSFCell tableTitleName = tableTitleNameRow.createCell(0);
tableTitleName.setCellValue(sheetName); //第一行标题单元格赋值
//合并第一行单元格
CellRangeAddress cra = new CellRangeAddress(0,0,0,fldNameArr.size()-1);
sheet.addMergedRegion(cra);
//对合并后的单元格进行样式设置
RegionUtil.setBorderBottom(BorderStyle.THIN,cra,sheet);
RegionUtil.setBorderTop(BorderStyle.THIN,cra,sheet);
RegionUtil.setBorderLeft(BorderStyle.THIN,cra,sheet);
RegionUtil.setBorderRight(BorderStyle.THIN,cra,sheet);
//对合并后的单元格进行居中
sheet.getRow(0).getCell(0).setCellStyle(title1Style);
XSSFRow tableTitleRow = sheet.createRow(1); //创建第二行
//遍历标题集合
for(int i=0; i<titleArr.size(); i++){
tableTitleRow.createCell(i).setCellValue((String) titleArr.get(i)); //第二行标题赋值
tableTitleRow.getCell(i).setCellStyle(title2Style); //第二行标题设置样式
}
int rowIndex = 2; //数据行计数器(从第三行开始创建)
//遍历创建数据行
for(BaseFormMap dataMap:dataList){
XSSFRow row = sheet.createRow(rowIndex);
int cellIndex = 0; //单元格计数器
//遍历字段名,将值从Map中取出
for(String valueName:fldNameArr){
if(cellIndex==0){
row.createCell(cellIndex).setCellValue((String)dataMap.get(valueName)); //对数据行单元格进行赋值
}else{
row.createCell(cellIndex).setCellValue((int)dataMap.get(valueName)); //对数据行单元格进行赋值
}
row.getCell(cellIndex).setCellStyle(cellStyle); //数据单元格设置样式
cellIndex++; //单元格计数器
}
rowIndex++; //行计数器自增
}
return true;
}
/**
* 创建Chart图表
* @param fullPath 文件保存路径
* @param isCreated 是否创建Chart
* @param type Chart图类型
* @param dataMap 数据集合
* @return
*/
public static boolean createChart(String fullPath,
boolean isCreated,
String type,
Map<String,List> dataMap){
//定义工作簿对象
XSSFWorkbook workbook = new XSSFWorkbook();
//定义返回值
boolean result = false;
//定义流对象
FileOutputStream fos = null;
try {
//判断数据量和sheet量是否相等
if(dataMap.keySet().size()!=sheetNameArr.size()){
System.out.println("dataMap数据量和sheet量不相等,无法创建!");
return result;
}
//判断全路径是否为空
if(fullPath!=null || !"".equals(fullPath)){
//获取需要创建的Excel类型
String excelType = fullPath.substring(fullPath.lastIndexOf("."));
if(!".xlsx".equals(excelType)){
System.out.println("需创建的文件类型不是xlsx,创建失败!");
}
}else {
System.out.println("全路径不能为空!");
return false;
}
//用于接收dataMap集合的键集
List<String> dataMapKeyArr = new ArrayList<>();
//循环遍历dataMap的键集
for(String key:dataMap.keySet()){
dataMapKeyArr.add(key);
}
//dataMap的键集的计数器
int mapKeyIndex = 0;
//判断是否创建Chart
if(isCreated){
//判断Chart图类型是否正确
if("".equals(type) || null==type || !"line".equals(type)){
System.out.println("Chart图类型不正确,无法创建!");
return false;
}
//定义画布对象
XSSFDrawing drawing = null;
//遍历SheetNameArr,用于多次创建Sheet对象
for (String sheetName:sheetNameArr){
//判断Sheet页名字是否为空
if(sheetName==null || "".equals(sheetName)){
System.out.println("Sheet页名字不能为空!");
return false;
}
//创建数据集合
List<BaseFormMap> dataList = new ArrayList<>();
dataList = dataMap.get(dataMapKeyArr.get(mapKeyIndex));
mapKeyIndex++; //键集计数器自增
//获取BaseFormMap的键集
List<String> fldNameArr = new ArrayList<>();
if(fldNameArr.size()==0){
BaseFormMap baseFormMap = dataList.get(0);
for (Object key: baseFormMap.keySet()){
fldNameArr.add((String) key);
}
}
//创建Sheet对象
XSSFSheet sheet = workbook.createSheet(sheetName);
//创建Excel表格
result = createExcel(workbook, sheet, sheetName, fldNameArr, dataList, titleArr);
if(!result){
System.out.println("Excel创建失败!");
return result;
}
//创建一个画布
drawing = sheet.createDrawingPatriarch();
//前四个参数是画布位置偏移量参数,后四个参数是画布大小参数(col1:画布左上角位置;row1:从哪行开始创建画布;col2:画布宽度;row2:画布高度)
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, dataMap.size()+4, (dataList.size()-1)*6, 27);
//创建一个Chart对象
XSSFChart chart = drawing.createChart(anchor);
//创建一个CTChar对象
CTChart ctChart = chart.getCTChart();
//创建绘图区
CTPlotArea ctPlotArea = ctChart.getPlotArea();
//判断需要创建的Chart图类型
if("line".equals(type)){
//创建折线图
CTLineChart ctLineChart = ctPlotArea.addNewLineChart();
CTBoolean ctBoolean = ctLineChart.addNewVaryColors();
ctLineChart.addNewGrouping().setVal(STGrouping.STANDARD); //STANDARD标准模式/STACKED堆叠模式(不要使用STACKED堆叠模式)
//创建序列,并且设置选中区域
for (int i = 0; i < dataList.size(); i++) {
CTLineSer ctLineSer = ctLineChart.addNewSer();
CTSerTx ctSerTx = ctLineSer.addNewTx();
//图例区
CTStrRef ctStrRef = ctSerTx.addNewStrRef();
//参数1:从哪行开始获取数据 参数2:获取到哪行结束 参数3:从哪个单元格开始获取数据 参数4:获取到哪个单元格结束
String legendDataRange = new CellRangeAddress(i+2,i+2, 0, 0).formatAsString(sheetName, true);
ctStrRef.setF(legendDataRange);
ctStrRef.setF(legendDataRange);
ctLineSer.addNewIdx().setVal(i);
//横坐标区
CTAxDataSource cttAxDataSource = ctLineSer.addNewCat();
ctStrRef = cttAxDataSource.addNewStrRef();
//参数1:从哪行开始获取数据 参数2:获取到哪行结束 参数3:从哪个单元格开始获取数据 参数4:获取到哪个单元格结束
String axisDataRange = new CellRangeAddress(1, 1, 1, dataList.get(i).values().size()).formatAsString(sheetName, true);
ctStrRef.setF(axisDataRange);
//数据区域
CTNumDataSource ctNumDataSource = ctLineSer.addNewVal();
CTNumRef ctNumRef = ctNumDataSource.addNewNumRef();
//参数1:从哪行开始获取数据 参数2:获取到哪行结束 参数3:从哪个单元格开始获取数据 参数4:获取到哪个单元格结束
String numDataRange = new CellRangeAddress(i+2, i+2, 1, dataList.get(i).values().size()).formatAsString(sheetName, true);
ctNumRef.setF(numDataRange);
//设置标签格式
ctBoolean.setVal(false);
CTDLbls newDLbls = ctLineSer.addNewDLbls();
newDLbls.setShowLegendKey(ctBoolean);//折线节点上是否显示图例
ctBoolean.setVal(true);
newDLbls.setShowVal(ctBoolean);//折线节点上是否显示值
ctBoolean.setVal(false);
newDLbls.setShowCatName(ctBoolean);
newDLbls.setShowSerName(ctBoolean);
//newDLbls.setShowPercent(ctBoolean);
//newDLbls.setShowBubbleSize(ctBoolean);
//newDLbls.setShowLeaderLines(ctBoolean);
//是否开启平滑曲线
CTBoolean addNewSmooth = ctLineSer.addNewSmooth();
addNewSmooth.setVal(false);
//是否是堆积曲线
CTMarker addNewMarker = ctLineSer.addNewMarker();
CTMarkerStyle addNewSymbol = addNewMarker.addNewSymbol();
//设置节点形状:CIRCLE:圆形节点 DASH:破折号节点(短横线)DIAMOND:菱形节点 DOT:短横线节点(很短)PICTURE:图表为图片 PLUS:+节点 SQUARE:方块节点 STAR:*形节点 TRIANGLE:三角形节点 X:X形节点
addNewSymbol.setVal(STMarkerStyle.NONE);
}
//telling the BarChart that it has axes and giving them Ids
ctLineChart.addNewAxId().setVal(123456);
ctLineChart.addNewAxId().setVal(123458);
//设置X轴
CTCatAx ctCatAx = ctPlotArea.addNewCatAx();
ctCatAx.addNewAxId().setVal(123456); //id of the cat axis
CTScaling ctScaling = ctCatAx.addNewScaling();
ctScaling.addNewOrientation().setVal(STOrientation.MIN_MAX);//MAX_MIN:X轴从左到右由大到小(会导致Y轴显示在右侧,图像左右反转) MIN_MAX:正常显示
ctCatAx.addNewAxPos().setVal(STAxPos.L);
ctCatAx.addNewCrossAx().setVal(123458); //id of the val axis
ctCatAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO); //HIGH:X轴坐标显示在顶部 LOW:X轴坐标正常显示 NEXT_TO:X轴坐标正常显示
//设置Y轴
CTValAx ctValAx = ctPlotArea.addNewValAx();
ctValAx.addNewAxId().setVal(123458); //id of the val axis
ctScaling = ctValAx.addNewScaling();
ctScaling.addNewOrientation().setVal(STOrientation.MIN_MAX); //MAX_MIN:Y轴坐标从上到下由小到大(会导致X轴显示在顶部,图像上下反转) MIN_MAX:正常显示
ctValAx.addNewAxPos().setVal(STAxPos.B);
ctValAx.addNewCrossAx().setVal(123456); //id of the cat axis
ctValAx.addNewTickLblPos().setVal(STTickLblPos.NEXT_TO); //HIGH:Y轴刻度左侧显示,坐标值右侧显示 LOW:Y轴坐标正常显示 NEXT_TO:Y轴坐标正常显示
ctValAx.addNewDelete().setVal(false);//是否隐藏Y轴
ctCatAx.addNewDelete().setVal(false);//是否隐藏X轴
//legend图注
CTLegend ctLegend = ctChart.addNewLegend();
ctLegend.addNewLegendPos().setVal(STLegendPos.B);
ctLegend.addNewOverlay().setVal(false);
}
}
}else{
//遍历SheetNameArr,用于多次创建Sheet对象
for (String sheetName:sheetNameArr){
//创建Sheet对象
XSSFSheet sheet = workbook.createSheet(sheetName);
List<BaseFormMap> dataList = new ArrayList<>();
dataList = dataMap.get(dataMapKeyArr.get(mapKeyIndex));
//获取BaseFormMap的键集
List<String> fldNameArr = new ArrayList<>();
if(fldNameArr.size()==0){
BaseFormMap baseFormMap = dataList.get(0);
for (Object key: baseFormMap.keySet()){
fldNameArr.add((String) key);
}
}
//创建Excel表格
result = createExcel(workbook, sheet, sheetName, fldNameArr, dataList, titleArr);
if(!result){
System.out.println("Excel创建失败!");
return result;
}
mapKeyIndex++; //键集计数器自增
}
}
fos = new FileOutputStream(fullPath);
workbook.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null!=fos){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
public static Map<String, List> getDataMap() {return dataMap;}
public static void setDataMap(Map<String, List> dataMap) {NewTestCharUtil.dataMap = dataMap;}
public static List<String> getSheetNameArr() {return sheetNameArr;}
public static void setSheetNameArr(List<String> sheetNameArr) {NewTestCharUtil.sheetNameArr = sheetNameArr;}
public static List<String> getTitleArr() {return titleArr;}
public static void setTitleArr(List<String> titleArr) {NewTestCharUtil.titleArr = titleArr;}
}
以上代码运行之后,就能创建出本博客一开始想要实现的效果。
因为我暂时只实现了一个折线图,所以目前只能图表类型只能传"line",后期等我实现了其他形式的chart图,我再回来补充。
在使用的时候,有几个需要注意的点:
1.`STGrouping.STANDAR`用于设置Chart图的模式,分为标准模式和堆叠模式,折线图创建的时必须用标准模式,使用堆叠模式会使折线出现偏差;
2.`addNewSmooth.setVal(false);`设置为ture可以让原本有棱有角的折线图,变得如牛奶般丝滑....
3.`STMarkerStyle.NONE`是折线图中折点的形状,具体的可以参照上面注释中的值设置(谁让我闲得慌把所有折点形状都试了一遍呢~)
4.`createChart()`方法中传入的isCreated,是创建chart图的开关,传入false可以只创建Excel,不创建chart,传入ture可以同时创建;
5.最重要的一点:chart图创建时,是根据我们提供的选值范围坐标,从之前创建的Excel图选定需要遍历的数据范围,然后自动建图,建图正确与否、数值获取正确与否的关键,在于我们提供的选值范围坐标!
有兴趣的还可以再加上一段根据路径中文件后缀,自动创建对应类型Excel文件的代码,这个比较简单,我就不赘述了。