最近项目中有一个需求,要把echarts堆叠柱状图保存为图片,传到后台再导出到Excel中
下面分几个步骤分享一下实现的过程以及遇到的问题。
首先,我用的poi是3.15版本的,echarts是最新的4.0版本(echarts应该是哪个版本都支持的)
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<script type="text/javascript" src="${staticPath}/www/echarts.min.js?t=${currentTimeMillis}"></script>
1.第一步:在页面画出echarts图表,下面代码仅供参考,详细查看ecahrts官网。代码里面关键的两点是
(1)animation:false //设置option中的属性关闭动画,否则后面导出到Excel中的图片只有横纵坐标,没有数据
(2)生成echarts图表后,return myChart.getDataURL(); 返回echarts图的base64编码,默认为png格式。
function queryCostStructure(reportYear,reportType){
//基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main1left'));
// 指定图表的配置项和数据
var option = {
tooltip: {
trigger: "axis",
},
legend: {
itemWidth:15,
itemHeight:15,
data:['材料费','设备费','人员费'],
},
grid: {
top: '2%', //柱状图距离父容器div顶端的距离
left: '2%', //柱状图距离父容器div左端的距离
right: '2%', //柱状图距离父容器div右端的距离
bottom: '0%', //柱状图距离父容器div底端的距离
containLabel: true //grid 区域是否包含坐标轴的刻度标签
},
xAxis: {
data: [],//横轴坐标
splitLine:{
show:false,
},
axisLabel :{interval:0,rotate:50} //rotate表示横轴文字倾斜角度
},
yAxis: {
splitLine:{
show:false,
},
},
series: [{
name: '材料费',
type: 'bar',
stack:'费用构成',
data: [],
barWidth : 20,//柱图宽度
itemStyle:{
normal:{color:"#00BFFF"},
}
},{
name: '设备费',
type: 'bar',
stack:'费用构成',//表示在哪一列
data: [],
itemStyle:{
normal:{color:"#40E0D0"},
}
},{
name: '人员费',
type: 'bar',
stack:'费用构成',//表示在哪一列
data: [],
itemStyle:{
normal:{color:"#F4A460"},
}
}],
animation:false //关闭动画,否则导出到Excel是无数据的
};
$.ajax({
type: "post",
url: "${adminPath}/budgetStatistic/projectResearchBudgetEcharts",
dataType:"json",
async:false,
data: {"reportYear":reportYear,
"reportType":reportType},
success:function(result){
}
});
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
//获取echarts图的base64编码,为png格式。
return myChart.getDataURL();
}
2.第二步:将获取的echarts图表的base64编码传到后台
在传值时遇到了好多问题,最开始我用window.location.href直接向后台发请求,但是后台报错“java.lang.IllegalArgumentException: Request header is too large”,查询之后发现,window.location.href方法发送请求默认是用get方式,而echarts图表的base64编码比较大,get请求方式不支持。
想了半天,改用ajax传值,
$.post("${adminPath}/budgetStatistic/export/exportForEchartsImg",{'picBase64Info':picBase64Info},function(data){});
不报错了,后台也能接到值了,但是最后Excel下载的时候出问题了,后台没有报错,但是页面没有任何反应,下载路径里也没有下载的文件,这时的我很是上火! 又查了资料,发现ajax返回的是字符型数据,而此处我们需要的是返回文件流到浏览器,我们才能下载到文件,两者不兼容,这个请求方式也被pass掉。
最后方案,使用表单的方式发行请求,既满足可以使用post方式发请求,又不与下载后刷新页面冲突。
document.write("<form action='${adminPath}/budgetStatistic/export/exportForEchartsImg' method=post name='form1' style='display:none'>");
document.write("<input type=hidden name='picBase64InfoCostStr' value='"+picBase64InfoCostStr+"'/>");
document.write("<input type=hidden name='picBase64InfoCategory' value='"+picBase64InfoCategory+"'/>");
document.write("<input type=hidden name='picBase64InfoExpectedGain' value='"+picBase64InfoExpectedGain+"'/>");
document.write("<input type=hidden name='picBase64InfoProjectType' value='"+picBase64InfoProjectType+"'/>");
document.write("</form>");
document.form1.submit();
至此,前两步已经大功告成,获取到ecahrts的base64编码,并且发送给后台了。
3.第三部,后台解析base64编码,并导出到Excel中
我们拿到的数据样式大致是这样的
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABI4AAAEsCAYAAAClh/jbAAAgAElEQVR4XuzdB3hUVfo
/8O+dSe8hCSlAaKJ0BBEQFBBQseFiwUVkURHEgtjWdV0bLPpTV10BRYodsWH5i64guIiAqKyASu8lgfTeJzNz/8 ........
解析时只要“base64,”后面的部分,所以需要截取一下。图片数据通过表单传递到了后台,可以在后台获取到信息。但是有一点必须注意:数据中的 "+"加号会因为传递变为 " "空格,所以需要替换一下。
后台代码实现过程如下:
1.创建Excel工作簿
2.创建sheet页
3.创建绘图画布(这里注明,一个sheet只能创建一个画布,但一个画布中可以添加多张图片)
4.处理前台传来的base64编码
5.将BASE64编码格式转为byte数组
6.利用HSSFPatriarch将图片以流的方式写入EXCEL
7.下载Excel
由于我是多个图片导入到一个Excel的一个sheet中,所以把4、5、6步骤封装到一个方法中,避免代码重复
详细代码如下
/**
* 科研项目数据分析-----保存为图片导出Excel
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/export/exportForEchartsImg")
public void exportForEchartsImg(@RequestParam(value = "picBase64InfoCostStr") String picBase64InfoCostStr,
@RequestParam(value = "picBase64InfoCategory") String picBase64InfoCategory,
@RequestParam(value = "picBase64InfoExpectedGain") String picBase64InfoExpectedGain,
@RequestParam(value = "picBase64InfoProjectType") String picBase64InfoProjectType,
HttpServletRequest request,HttpServletResponse response) throws Exception {
/*
* 数据样式:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABI4AAAEsCAYAAAClh/jbAAAgAElEQVR4XuzdB3hUVfo
* /8O+dSe8hCSlAaKJ0BBEQFBBQseFiwUVkURHEgtjWdV0bLPpTV10BRYodsWH5i64guIiAqKyASu8lgfTeJzNz/8 ........
* 图片编码只要‘base64,’后面的部分
* 图片数据通过表单传递到了后台,可以在后台获取到信息。但是有一点必须注意:数据中的 "+"加号会因为传递变为 " "空格,所以需要替换一下
*/
String dataChartCostStr = (URLDecoder.decode(picBase64InfoCostStr.substring(22),"UTF-8")).replaceAll(" ", "+");
String dataChartCategory = (URLDecoder.decode(picBase64InfoCategory.substring(22),"UTF-8")).replaceAll(" ", "+");
String dataChartExpectedGain = (URLDecoder.decode(picBase64InfoExpectedGain.substring(22),"UTF-8")).replaceAll(" ", "+");
String dataChartProjectType = (URLDecoder.decode(picBase64InfoProjectType.substring(22),"UTF-8")).replaceAll(" ", "+");
//创建Excel工作簿
HSSFWorkbook wb = new HSSFWorkbook();
//创建sheet页
HSSFSheet sheet = wb.createSheet("可视数据");
//创建绘图(画布),注明:一个sheet只能创建一个画布,但一个画布中可以添加多张图片
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
//费用构成-图表图片
createPictureInExcel(dataChartCostStr, patriarch, wb, (short) 1, 1, (short) 11, 25);
//所述领域-图表图片
createPictureInExcel(dataChartCategory, patriarch, wb, (short) 1, 27, (short) 11, 53);
//预期增益-图表图片
createPictureInExcel(dataChartExpectedGain, patriarch, wb, (short) 1, 55, (short) 11, 81);
//项目类别-图表图片
createPictureInExcel(dataChartProjectType, patriarch, wb, (short) 1, 83, (short) 22, 109);
//图片写入Excel
OutputStream out = response.getOutputStream();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + new String("echarts.xls".getBytes("gb2312"), "ISO8859-1" ));
wb.write(out);
wb.close();
}
/**
* 将图片保存到excel中
* @param dataChart 图片的BASE64格式编码
* @param patriarch Excel-sheet 画布
* @param wb Excel工作簿
* @param col1 指定起始的单元格行索引
* @param row1 指定起始的单元格列索引
* @param col2 指定结束的单元格行索引
* @param row2 指定结束的单元格列索引
* @throws Exception
*/
@SuppressWarnings("restriction")
public void createPictureInExcel(String dataChart,HSSFPatriarch patriarch,HSSFWorkbook wb,
short col1, int row1,short col2, int row2) throws Exception{
//用于将BASE64编码格式转为byte数组
BASE64Decoder base64Decoder = new BASE64Decoder();
ByteArrayOutputStream dataChartoutStream = new ByteArrayOutputStream();
//将dataChartStringin作为输入流,读取图片存入image中
ByteArrayInputStream dataChartin = new ByteArrayInputStream( base64Decoder.decodeBuffer(dataChart));
BufferedImage dataChartbufferImg = ImageIO.read(dataChartin);
//利用HSSFPatriarch将图片写入EXCEL
ImageIO.write(dataChartbufferImg, "png", dataChartoutStream);
/*
* 指定绘图区域位置及大小
* HSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2)
* 参数说明:
* dx1 dy1 起始单元格中的x,y坐标.
* dx2 dy2 结束单元格中的x,y坐标.
* col1,row1 指定起始的单元格,下标从0开始.
* col2,row2 指定结束的单元格 ,下标从0开始.
* 详情参考博客 https://www.cnblogs.com/1175429393wljblog/p/9809868.html
*/
HSSFClientAnchor anchorCostStr = new HSSFClientAnchor(0, 0, 0, 0, col1, row1, col2, row2);
//画图
patriarch.createPicture(anchorCostStr, wb.addPicture(dataChartoutStream.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG));
}
这样就可以完成功能了,导出的Excel如下