最近在做统计报表,对比了一下echarts、flot、Highcharts,还是选择了开源的echarts,主要表现美观,而且对IE8支持好(😭😭😭兼容IE8什么的最讨厌了),整理了一下支持导出多个echarts图表到excel文档的方法,而且导出到excel中是按图片正常比例显示的,提交表单防跳转。
导出成品图
1.首先看echarts代码,以柱状图和饼状图为例
html代码
<!-- 柱状图 -->
<div class="echarts" id="useStats-bar-chart"></div>
<!-- 饼状图 -->
<div class="echarts" id="useStats-pie-chart"></div>
javascript代码
// 柱状图
var barChart = echarts.init(document.getElementById("useStats-bar-chart"));
var baroption = {
// 背景要设置成白色,不然导出来乌漆嘛黑的看不清
backgroundColor: '#ffffff',
tooltip : {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid:{
x:30,
y:48,
x2:40,
y2:66
},
calculable : true,
xAxis : [
{
type : 'category',
data : ['乌鲁木齐市','克拉玛依市','吐鲁番地区',
'哈密地区','昌吉回族自治州','博尔塔拉蒙古自治州',
'巴音郭楞蒙古自治州','阿克苏地区','克孜勒苏柯尔克孜自治州',
'喀什地区','和田地区','伊犁哈萨克自治州',
'塔城地区','阿勒泰地区','石河子',
'阿拉尔','图木舒克','五家渠'],
axisLabel: {
interval: 0,// 间隔显示数,0表示全部显示
//坐标轴刻度标签的相关设置。
formatter : function(params){
// 换行
return chartsLinefeed(params);
}
}
}
],
yAxis : [
{
type : 'value'
}
],
series : [
{
name:'xxx使用量',
type:'bar',
data:[300, 50, 100, 30, 50, 60, 20, 80, 70, 50, 30, 20, 70, 300, 20, 150, 45, 90],
markPoint : {
data : [
{type : 'max', name: '最大值'},
{type : 'min', name: '最小值'}
]
},
markLine : {
data : [
{type : 'average', name: '平均值'}
]
},
itemStyle: {
normal: {
color: 'rgba(28,132,198,.6)',//柱子颜色
// borderColor: '#1c84c6',//边框颜色
barBorderWidth: 3,
barBorderRadius: 2,
label: {
show: true,
position: 'insideLeft'
}
}
}
}
]
};
barChart.setOption(baroption);
window.onresize = barChart.resize;
// 饼状图
var pieChart = echarts.init(document.getElementById("useStats-pie-chart"));
var pieoption = {
backgroundColor: '#ffffff',
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient : 'vertical',
x : 'left',
data:['乌鲁木齐市','克拉玛依市','吐鲁番地区',
'哈密地区','昌吉回族自治州','博尔塔拉蒙古自治州',
'巴音郭楞蒙古自治州','阿克苏地区','克孜勒苏柯尔克孜自治州',
'喀什地区','和田地区','伊犁哈萨克自治州',
'塔城地区','阿勒泰地区','石河子',
'阿拉尔','图木舒克','五家渠']
},
calculable : true,
series : [
{
name:'xxx使用量',
type:'pie',
radius : '80%',
center: ['50%', '50%'],
/*在series中添加itemStyle即可直观显示饼型数值*/
itemStyle:{
normal:{
label:{
show: true,
formatter: '{b} : {c} ({d}%)'
},
labelLine :{show:true}
}
},
data:[
{value:300, name:'乌鲁木齐市'},
{value:50, name:'克拉玛依市'},
{value:100, name:'吐鲁番地区'},
{value:30, name:'哈密地区'},
{value:50, name:'昌吉回族自治州'},
{value:60, name:'博尔塔拉蒙古自治州'},
{value:20, name:'巴音郭楞蒙古自治州'},
{value:80, name:'阿克苏地区'},
{value:70, name:'克孜勒苏柯尔克孜自治州'},
{value:50, name:'喀什地区'},
{value:30, name:'和田地区'},
{value:20, name:'伊犁哈萨克自治州'},
{value:70, name:'塔城地区'},
{value:300, name:'阿勒泰地区'},
{value:20, name:'石河子'},
{value:150, name:'阿拉尔'},
{value:45, name:'图木舒克'},
{value:90, name:'五家渠'}
]
}
]
};
pieChart.setOption(pieoption);
$(window).resize(pieChart.resize);
上成品图:
2.导出功能
html代码:
<!--form的target指定一个空的iframe,防止导出后跳转页面-->
<iframe name="none_iframe" style="display:none;"></iframe>
<form method="post" id="none_form" target="none_iframe" style="display:none"></form>
javascript代码
// 导出图表
function exportCharts(){
// excel的文件名称
var fileName = "xxx使用统计图表";
/**
* 柱状图的内容,base64格式数据,这里支持多个图表导出,有几个图就写几个fileContent和fileSize,只有一个的话写一个就够了
* 图片格式data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABI4AAAEsCAYAAAClh/jbAAAgAElEQVR4XuzdB3hUVfo/8O+dSe8hCSlAaKJ0BBEQFBBQseFiwUVkURHEgtjWdV0bLPpTV10BRYodsWH5i64guIiAqKyASu8lgfTeJzNz/8 ........
**/
var fileContent1 = encodeURIComponent(barChart.getDataURL());
// 饼状图内容,需要encodeURIComponent编码,防止传输过程符号丢失
var fileContent2 = encodeURIComponent(pieChart.getDataURL());
// 图片宽度和高度,为了计算图片比例,以便在excel中以正常比例显示,不至于拉伸
var fileSize1 = barChart.getWidth() + ":" + barChart.getHeight();
var fileSize2 = pieChart.getWidth() + ":" + pieChart.getHeight();
// form表单的请求地址,ctx是你的项目地址
$("#none_form").attr("action", ctx + "cozuxg/eeiduc");
$("#none_form").append("<input type=hidden name='fileName' value='"+fileName+"'/>");
$("#none_form").append("<input type=hidden name='fileContents' value='"+fileContent1+"'/>");
$("#none_form").append("<input type=hidden name='fileContents' value='"+fileContent2+"'/>");
$("#none_form").append("<input type=hidden name='fileSizes' value='"+fileSize1+"'/>");
$("#none_form").append("<input type=hidden name='fileSizes' value='"+fileSize2+"'/>");
// 提交表单
$("#none_form").submit();
// 删除本次表单数据,这里要删除子元素,不然下次导出不了
$("#none_form").empty();
}
java代码,这部分是公用的,所有导出echarts图表都调这一个接口
/**
* 通用导出图片到excel
* @param fileContents base64图片内容
* @param fileName excel文件名
* @param fileSizes 文件尺寸,宽:高,因为在excel中是按单元格来设置图片的宽和高,所以要按图片的宽高比例算出在excel中需要多少个单元格
* @param request 请求
* @param response 返回
*/
@PostMapping("/eeiduc")
public void exportExcelForImg(String[] fileContents, String fileName, String[] fileSizes,
HttpServletRequest request,HttpServletResponse response) throws Exception
{
//创建Excel工作簿,xls格式用HSSFWorkbook,xlsx格式用SXSSFWorkbook
HSSFWorkbook wb = new HSSFWorkbook();
//创建sheet页
HSSFSheet sheet = wb.createSheet(fileName);
//创建绘图(画布),注明:一个sheet只能创建一个画布,但一个画布中可以添加多张图片
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
String fileContent;
String fileSize;
// 图片宽度
Double width;
// 图片高度
Double height;
// 指定起始的单元格行索引,默认1
int col1 = 1;
// 指定起始的单元格列索引,row1=上一个row2+1,+1是为了两个图片之间隔开一行
int row1;
// 指定结束的单元格行索引,相当于宽,默认18
int col2 = 18;
// 指定结束的单元格列索引,相当于高,row2=row1+col2*(height/width)*3.5,乘以3.5是因为单元格的宽是高的3.5倍
int row2 = 0;
// 支持多个图表导入
for(int i = 0; i < fileContents.length;i++){
fileContent = fileContents[i];
if(fileContent.length() > 0){
fileSize = fileSizes[i];
width = Double.valueOf(fileSize.split(":")[0]);
height = Double.valueOf(fileSize.split(":")[1]);
// 图片编码只要‘base64,’后面的部分
fileContent = URLDecoder.decode(fileContent,"UTF-8").substring(22);
row1 = row2 + 1;
row2 = row1 + (int) Math.ceil(col2 * (height / width) * 3.5);
ExcelUtil.createPictureInExcel(fileContent, patriarch, wb, (short) col1, row1, (short) col2, row2);
}
}
// 重命名,文件名前面加时间,如20200805135638_xxx使用统计.xls,这部分代码按项目风格自行来定
String timeStr = DateUtils.parseDateToStr("yyyyMMddHHmmss", DateUtils.getNowDate());
fileName = timeStr + "_" + fileName + ".xls";
//图片写入Excel
OutputStream out = response.getOutputStream();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("gb2312"), "ISO8859-1" ));
wb.write(out);
wb.close();
}
ExcelUtil.createPictureInExcel代码
/**
* 将图片保存到excel中
* @param dataChart 图片的BASE64格式编码
* @param patriarch Excel-sheet 画布
* @param wb Excel工作簿
* @param col1 指定起始的单元格行索引
* @param row1 指定起始的单元格列索引
* @param col2 指定结束的单元格行索引
* @param row2 指定结束的单元格列索引
* @throws Exception
*/
@SuppressWarnings("restriction")
public static 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开始.
*/
HSSFClientAnchor anchorCostStr = new HSSFClientAnchor(0, 0, 0, 0, col1, row1, col2, row2);
//画图
patriarch.createPicture(anchorCostStr, wb.addPicture(dataChartoutStream.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG));
}
导出成品图
我这里是用固定宽度18个单元格来算高度需要多少个单元格,如果有需要也可以用固定高度来算宽度,到这里就大功告成了,祝大家工作愉快!