(一)EChart.js 简单入门
最近有一个统计的项目要做,在前端的数据需要用图表的形式展示。网上搜索了一下,发现有几种统计图库。
MSChart
这个是Visual Studio里的自带控件,使用比较简单,不过数据这块需要在后台绑定。
ichartjs
是一款基于HTML5的图形库。使用纯javascript语言, 利用HTML5的canvas标签绘制各式图形。 支持饼图、环形图、折线图、面积图、柱形图、条形图等。
Chart.js
也是一款基于HTML5的图形库和ichartjs整体类似。不过Chart.js的教程文档没有ichartjs的详细。不过感觉在对于移动的适配上感觉比ichartjs要好一点。
ECharts.js
这是我准备在这个项目中使用的图形库,这也是一款基于HTML5的图形库。图形的创建也比较简单,直接引用Javascript即可。使用这个库的原因主要有三点,一个是因为这个库是百度的项目,而且一直有更新,目前最新的是EChart 3;第二个是这个库的项目文档比较详细,每个点都说明的比较清楚,而且是中文的,理解比较容易;第三点是这个库支持的图形很丰富,并且可以直接切换图形,使用起来很方便。
官网:ECharts.js
下面来简单说明一下EChart.js的使用。
第一步,引用Js文件
<script type="text/javascript" src="js/echarts.js"></script>
js文件有几个版本,可以根据实际需要引用需要的版本。下载链接
第二步,准备一个放图表的容器
<div id="chartmain" style="600px; height: 400px;"></div>
第三步,设置参数,初始化图表
<script type="text/javascript"> //指定图标的配置和数据 var option = { title:{ text:'ECharts 数据统计' }, tooltip:{}, legend:{ data:['用户来源'] }, xAxis:{ data:["Android","IOS","PC","Ohter"] }, yAxis:{ }, series:[{ name:'访问量', type:'line', data:[500,200,360,100] }] }; //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); //使用制定的配置项和数据显示图表 myChart.setOption(option); </script>
这样简单的一个统计图表就出来了,官网使用的柱状图,我这边改用了折线图。
柱状图其实也很简单,只要修改一个参数就可以了。把series里的type 值修改为"bar"
饼图和折线图、柱状图有一点区别。主要是在参数和数据绑定上。饼图没有X轴和Y轴的坐标,数据绑定上也是采用value 和name对应的形式。
var option = { title:{ text:'ECharts 数据统计' }, series:[{ name:'访问量', type:'pie', radius:'60%', data:[ {value:500,name:'Android'}, {value:200,name:'IOS'}, {value:360,name:'PC'}, {value:100,name:'Ohter'} ] }] };
、
Echarts 数据绑定
简单的统计表已经可以生成,不过之前图标数据都是直接写在参数里面的,而实际使用中,我们的数据一般都是异步读取的。EChart.js对于数据异步读取这块提供了异步加载的方法。
绑定多组数据
很多时候需要展示的数据不单单是一组数据,很多时候会进行一个数据对比。这个时候只需要在series中增加一组数据,legend中添加一下这个数据组的name
<!DOCTYPE html> <html> <head> <title>ECharts.js 数据绑定</title> <meta charset="utf-8"> <script type="text/javascript" src="js/echarts.js"></script> </head> <body> <div id="chartmain" style="600px; height: 400px;"></div> <script type="text/javascript"> //指定图标的配置和数据 var option = { title:{ text:'ECharts 数据统计' }, legend:{ data:['访问量','用户量'] }, xAxis:{ data:["Android","IOS","PC","Other"] }, yAxis:{}, series:[ { name:'访问量', type:'bar', data:[180,420,333,83] }, { name:'用户量', type:'bar', data:[125,330,230,60] } ] }; //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); //使用制定的配置项和数据显示图表 myChart.setOption(option); </script> </body> </html>
效果展示
(二)数据异步加载
EChart中实现异步数据的更新非常简单,在图表初始化后不管任何时候只要通过 jQuery 等工具异步获取数据后通过 setOption
填入数据和配置项就行。
绑定数据的方式有两种,一种是写写好一些图表参数,然后数据留空,然后在异步读取数据的时候,绑定数据。还有一种就是直接异步读取数据的时候同时设置图表参数和数据绑定。
首先我们准备一份需要加载的数据文件data.json,数据内容:
{"name":["Android","IOS","PC","Other"],"data":[420,200,360,100]}
第一种异步加载的时候设置图表参数和绑定数据
<script type="text/javascript"> //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); //异步加载的配置项和数据显示图表 $.get('data.json').done(function (data) { data = eval('('+data+')'); myChart.setOption({ title:{ text:'ECharts 异步加载数据' }, tooltip:{}, legend:{ data:['访问量'] }, xAxis:{ data:data.name }, yAxis:{}, series:[ { name:'访问量', type:'bar', data:data.data } ] }) }) </script>
第二种先设置图表参数,后绑定数据
<script type="text/javascript"> //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); //设置图标配置项 myChart.setOption({ title:{ text:'ECharts 异步加载数据' }, tooltip:{}, legend:{ data:['访问量'] }, xAxis:{ data:[] }, yAxis:{}, series:[ { name:'访问量', type:'bar', data:[] } ] }) //异步加载数据 $.get('data.json').done(function (data) { data = eval('('+data+')'); myChart.setOption({ xAxis:{ data:data.name }, series:[ { //根据名字对应到相应的系列 name:"访问量", data:data.data } ] }) }) </script>
效果展示
因为是异步加载,所以有时候数据加载会慢,或者延迟。在数据没有加载前,图表这样的。面对这样的图表,肯定会觉得这是没有数据吗,还是图表有问题.对于这块ECharts增加了一个加载动画。
Loading动画加载
//打开loading动画 myChart.showLoading(); //加载数据函数 function bindData(){ //为了效果明显,我们做了延迟读取数据 setTimeout(function(){ //异步加载数据 $.get('data.json').done(function (data) { //获取数据后,隐藏loading动画 myChart.hideLoading(); data = eval('('+data+')'); myChart.setOption({ xAxis:{ data:data.name }, series:[ { //根据名字对应到相应的系列 name:"访问量", data:data.data } ] }) }) },2000) } bindData();
效果展示
数据动态实时更新
<script type="text/javascript"> //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); var base = + new Date(2017,3,8); var oneDay = 24*3600*1000; var date = []; var data = [Math.random()*150]; var now = new Date(base); var day = 30; function addData(shift){ now = [now.getFullYear(),now.getMonth()+1,now.getDate()].join('/'); date.push(now); data.push((Math.random()-0.5)*10+data[data.length-1]); if (shift) { console.log(data); date.shift(); data.shift(); } now = new Date(+new Date(now)+oneDay); } for (var i = 0; i < day; i++) { addData(); } //设置图标配置项 myChart.setOption({ title:{ text:'ECharts 30天内数据实时更新' }, xAxis:{ type:"category", boundaryGap:false, data:date }, yAxis:{ boundaryGap:[0,'100%'], type:'value' }, series:[{ name:'成交', type:'line', smooth:true, //数据光滑过度 symbol:'none', //下一个数据点 stack:'a', areaStyle:{ normal:{ color:'red' } }, data:data }] }) setInterval(function(){ addData(true); myChart.setOption({ xAxis:{ data:date }, series:[{ name:'成交', data:data }] }); },1000) </script>
效果展示
(三)ECharts.js 交互组件
ECharts.js有很多的交互组件,一般经常用到的组件有这些:
title:标题组件,包含主标题和副标题。
legend:图例组件,展现了不同系列的标记(symbol),颜色和名字。可以通过点击图例控制哪些系列不显示。
xAxis:直角坐标系 grid 中的 x 轴,一般情况下单个 grid 组件最多只能放左右两个 x 轴,多于两个 x 轴需要通过配置 offset 属性防止同个位置多个 x 轴的重叠。
yAxis:直角坐标系 grid 中的 y 轴,一般情况下单个 grid 组件最多只能放左右两个 y 轴,多于两个 y 轴需要通过配置 offset 属性防止同个位置多个 Y 轴的重叠。
tooltip:提示框组件,就是当你的鼠标悬浮在图表上的提示内容。
toolbox:工具栏组件。内置有导出图片、数据视图、动态类型切换、数据区域缩放、重置五个工具。
series:系列列表。我理解为数据列表。这里可以定义每组数据内容,以及数据的展现形式。
timeline:提供了在多个ECharts option 之间进行切换、播放等操作的功能。
dataZoom:用于区域缩放,从而能自由关注细节的数据信息,或者概览数据整体,或者去除离群点的影响。
....
官方给出的案例是dataZoom组件。它是用于区域缩放,从而能自由关注细节的数据信息,或者概览数据整体,或者去除离群点的影响。主要是对 数轴(axis)
进行操作。
效果展示
toolbox组件
其中很多组件其实我们都会用到,不过使用的都是一些基本配置。比如title组件,往往只写一个text 值。legend,会一些每个系列数据的name等等。
因为后面项目需要将图表保存为图片,以及一种数据多种展现形势,所以就研究一下toolbox组件的使用。
toolbox参数:
show:工具栏默认是隐藏的。所以一定要设置show为true显示出来。
orient:工具栏的的布局方向,可选值有horizontal(横向)和vertical(竖向)。默认值是horizontal
itemSize:工具栏的大小。默认值是15。
itemGap:工具栏每个工具之间的距离,默认值是10。
showTitle:鼠标悬浮的是否显示每个工具的说明,默认是true。
feature:这个是设置工具栏里要显示哪些工具,以及这些工具的样式等。
默认的插件工具:
savaAsImage:保存图片
restore:还原配置
dataView:数据视图工具,可以展现图表所用的数据,并且可以编辑数据,再将编辑后的数据展示出来。同时也可以设置为数据为只读。
optionToContent:并且可以通过对显示出来的数据进行排版编辑,以HTML展现。
optionToOption:在使用 optionToContent 的情况下,如果支持数据编辑后的刷新,需要自行通过该函数实现组装 option 的逻辑。
dataZoom:数据区域缩放。目前只支持直角坐标系的缩放。
xAxisIndex、yAxisIndex:分别控制xAxis和yAxis轴的缩放。
除了使用默认的工具意外,我们还可以根据需求自定义工具。需要注意的是,每个自定义的工具,名称必须以“my”打头。在onclick函数中编写需要进行的操作。
toolbox:{ show:true, orient:'vertical', feature:{ magicType:{type:['line','bar']}, restore:{}, saveAsImage:{}, dataZoom:{ show:true, xAxisIndex:[0,3] }, myTool1:{ show:true, title:'自定义工具一', icon: 'path://M432.45,595.444c0,2.177-4.661,6.82-11.305,6.82c-6.475,0-11.306-4.567-11.306-6.82s4.852-6.812,11.306-6.812C427.841,588.632,432.452,593.191,432.45,595.444L432.45,595.444z M421.155,589.876c-3.009,0-5.448,2.495-5.448,5.572s2.439,5.572,5.448,5.572c3.01,0,5.449-2.495,5.449-5.572C426.604,592.371,424.165,589.876,421.155,589.876L421.155,589.876z M421.146,591.891c-1.916,0-3.47,1.589-3.47,3.549c0,1.959,1.554,3.548,3.47,3.548s3.469-1.589,3.469-3.548C424.614,593.479,423.062,591.891,421.146,591.891L421.146,591.891zM421.146,591.891', onclick:function(){ alert("this is myTool1"); } }, myTool2:{ show:true, title:'自定义工具二', icon: 'image://http://echarts.baidu.com/images/favicon.png', onclick:function(){ alert("this is myTool2"); } } } }
magicType:设置可切换的图表类型。目前支持的只有4种,line折线图、bar柱状图、stack堆叠模式、tiled平铺模式。
brush:选框组件的控制按钮。
iconStyle:公用的icon样式设置
zlevel:所有图形的zlevel值。zlevel用于Canvas分层。
z:所有图形的z值。z不会创建Canvas层。比zlevel等级低。
left、top、right、bottom、width、height:工具栏的样式,边距设置。
<script type="text/javascript"> //初始化echarts实例 var myChart = echarts.init(document.getElementById('chartmain')); var option = { title:{ text:"马云和马化腾期末成绩图", subtext:'本图表纯属虚构', }, anmation:false, legend:{ data:["马云成绩","马化腾成绩"], left:'50%', top:5 }, tooltip:{ trigger:"axis" }, xAxis:{ type:'category', boundaryGap:false, data:['语文','数学','英语','历史','体育','生物','化学'] }, yAxis:{ type:'value', axisLabel:{ formatter:'{value}分' }, min:20 }, toolbox:{ show:true, orient:'vertical', itemSize:20, itemGap:20, feature:{ dataView:{ readOnly:true, backgroundColor:'#f5f5f5', optionToContent:function(opt){ var axisData = opt.xAxis[0].data; var series = opt.series; var table ='<table style="100%;text-align:center;border:1px solid red;"><tbody><tr>' +'<td>学生</td>' +'<td>'+series[0].name+'</td>' +'<td>'+series[1].name+'</td>' +'</tr>'; for (var i = 0; i < axisData.length; i++) { table +='<tr>' +'<td>'+axisData[i]+'</td>' +'<td>'+series[0].data[i]+'</td>' +'<td>'+series[1].data[i]+'</td>' +'</tr>' } table +='</tbody></table>'; return table; } }, dataZoom:{ show:true, xAxisIndex:[0,3] }, magicType:{type:['line','bar','stack','tiled']}, restore:{}, saveAsImage:{}, myTool1:{ show:true, title:'自定义工具一', icon: 'path://M432.45,595.444c0,2.177-4.661,6.82-11.305,6.82c-6.475,0-11.306-4.567-11.306-6.82s4.852-6.812,11.306-6.812C427.841,588.632,432.452,593.191,432.45,595.444L432.45,595.444z M421.155,589.876c-3.009,0-5.448,2.495-5.448,5.572s2.439,5.572,5.448,5.572c3.01,0,5.449-2.495,5.449-5.572C426.604,592.371,424.165,589.876,421.155,589.876L421.155,589.876z M421.146,591.891c-1.916,0-3.47,1.589-3.47,3.549c0,1.959,1.554,3.548,3.47,3.548s3.469-1.589,3.469-3.548C424.614,593.479,423.062,591.891,421.146,591.891L421.146,591.891zM421.146,591.891', onclick:function(){ alert("this is myTool1"); } }, myTool2:{ show:true, title:'自定义工具二', icon: 'image://http://echarts.baidu.com/images/favicon.png', onclick:function(){ alert("this is myTool2"); } } }, }, series:[ { name:'马云成绩', type:'line', data:[90,88,75,82,95,89,97], markLine:{ data:[{type:'average',name:'平均值'}] }, markPoint:{ data:[ {type:'max',name:'最高分'}, {type:'min',name:'最低分'} ] } }, { name:'马化腾成绩', type:'line', data:[55,45,99,60,35,45,74], markLine:{ data:[{type:'average',name:'平均值'}] }, markPoint:{ data:[ {type:'max',name:'最高分'}, {type:'min',name:'最低分'} ] } } ] } myChart.setOption(option); </script>
效果展示
(四)ECharts.js 移动端显示
现在很多时候,我们是在用手机、pad等一些移动端设备来进行办公获取数据。所以我们的图表很多时候是需要用移动端设置来查看的,而我们的图表有时候因为数据的偏多,会出现遮挡和重叠的情况。这个时候就需要对移动端的图标显示做一些优化,ECharts对于移动端的优化和支持主要有2个方面。
一、ECharts组件的定位和布局
组件的定位官方描写的比较详细也比较全,我的简单理解为,ECharts.js对于图表里面每个组件和工具都采用了两种尺寸单位和设置固定位置。
一种是比较直接的 像素(px),设置的时间直接以 number 形式填写。比如
title:{ text:'ECharts 数据统计', top:20 }
这里就是设置标题组件的距离上面的高度是20px。
还有一种是安装百分比(%)的形式来设置的,百分比值是 string 类型,需要加上引号。比如
legend:{ data:['访问量','用户量'], left:'50%' }
这里标识legend组件的位置距离左侧的距离是整个图表的50%宽度
另外可以通过固定的值来设置所在位置,比如:
- 可以设置
left: 'center'
,表示水平居中。 - 可以设置
top: 'middle'
,表示垂直居中。
另外针对不同类型的图标还有不同的定位方式。
布局这块可以简单归结为两种,一种是 横向(horizontal)显示,一种是 纵向(vertical)显示。
二、ECharts自适应能力Media Query
Media Query 提供了『随着容器的尺寸改变而改变』的能力。
option = { baseOption: { // 这里是基本的『原子option』。 title: {...}, legend: {...}, series: [{...}, {...}, ...], ... }, media: [ // 这里定义了 media query 的逐条规则。 { query: {...}, // 这里写规则。 option: { // 这里写此规则满足下的option。 legend: {...}, ... } }, { query: {...}, // 第二个规则。 option: { // 第二个规则对应的option。 legend: {...}, ... } }, { // 这条里没有写规则,表示『默认』, option: { // 即所有规则都不满足时,采纳这个option。 legend: {...}, ... } } ] };
上面的例子中,baseOption
、以及 media
每个 option 都是『原子 option』,即普通的含有各组件、系列定义的 option。而由『原子option』组合成的整个 option,我们称为『复合 option』。baseOption
是必然被使用的,此外,满足了某个 query
条件时,对应的 option 会被使用 chart.mergeOption()
来 merge 进去。
多个 query 被满足时的优先级:
注意,可以有多个 query
同时被满足,会都被 mergeOption
,定义在后的后被 merge(即优先级更高)。
默认 query:
如果 media
中有某项不写 query
,则表示『默认值』,即所有规则都不满足时,采纳这个option。
容器大小实时变化时的注意事项:
在不少情况下,并不需要容器DOM节点任意随着拖拽变化大小,而是只是根据不同终端设置几个典型尺寸。
但是如果容器DOM节点需要能任意随着拖拽变化大小,那么目前使用时需要注意这件事:某个配置项,如果在某一个 query option
中出现,那么在其他 query option
中也必须出现,否则不能够回归到原来的状态。(left/right/top/bottom/width/height
不受这个限制。)
『复合 option』 中的 media
不支持 merge
也就是说,当第二(或三、四、五 ...)次 chart.setOption(rawOption)
时,如果 rawOption
是 复合option
(即包含 media
列表),那么新的 rawOption.media
列表不会和老的 media
列表进行 merge,而是简单替代。当然,rawOption.baseOption
仍然会正常和老的 option 进行merge。
其实,很少有场景需要使用『复合 option』来多次 setOption
,而我们推荐的做法是,使用 mediaQuery 时,第一次setOption使用『复合 option』,后面 setOption
时仅使用 『原子 option』,也就是仅仅用 setOption 来改变 baseOption
。
以上是EChart提供的关于移动端小屏幕自适应的方法,我另外提供一种方式
通过JS识别浏览器信息,然后根据所得的信息,设置图表容器的尺寸,然后再结合EChart的media query更好的展示图表
检测是否为移动端的JS
var ismobile = false; var browser = { versions: function () { var u = navigator.userAgent, app = navigator.appVersion; return { trident: u.indexOf('Trident') > -1, //IE内核 presto: u.indexOf('Presto') > -1, //opera内核 webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核 gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核 mobile: !!u.match(/AppleWebKit.*Mobile.*/) || !!u.match(/AppleWebKit/), //是否为移动终端 ios: !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端 android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器 iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者QQHD浏览器 iPad: u.indexOf('iPad') > -1, //是否iPad webApp: u.indexOf('Safari') == -1 //是否web应该程序,没有头部与底部 }; }(), language: (navigator.browserLanguage || navigator.language).toLowerCase() } ismobile = browser.versions.mobile;
这段代码能够识别大部分的移动端设备的浏览器信息,对于一些特殊的浏览器可能会存在缺陷
根据浏览器尺寸,设置图表容器的大小
if (browser.versions.mobile) { window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", hengshuping, false); $("#chartmain").height(pageheight*0.6); $("#chartmain").width(pagewidth * 0.95); } else { $("#chartmain").height("500px"); $("#chartmain").width("700px"); } function hengshuping(){ if(window.orientation==180||window.orientation==0){ $("#chartmain").height($(window).height()-20); $("#chartmain").width("100%"); } if(window.orientation==90||window.orientation==-90){ $("#chartmain").height($(window).height()-20); $("#chartmain").width("100%"); } }
结合EChart的 Media Query 设置图表参数
function init(){ ///折现报表实现代码 var myChart = echarts.init(document.getElementById('chartmain')); option = { baseOption:{ title : { text: '奶牛数字化养殖报表', subtext: '西部电子数据采集' }, tooltip : { trigger: 'axis' }, legend: { data:['每日饲喂量','产奶量'] }, toolbox: { show : true, feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, magicType : {show: true, type: ['line', 'bar', 'stack', 'tiled']}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : true, xAxis : [ { type : 'category', boundaryGap : false, data : ['周一','周二','周三','周四','周五','周六','周日'] } ], yAxis : [ { type : 'value' } ], series : [ { name:'每日饲喂量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[100, 200, 150, 130, 260, 830, 710] }, { name:'产奶量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[30, 182, 216, 156, 390, 300, 356] } ] }, media:[ //media开始 { query:{}, option:{ title : { text: '奶牛数字化养殖报表', subtext: '西部电子数据采集' }, tooltip : { trigger: 'axis' }, legend: { data:['每日饲喂量','产奶量'] }, toolbox: { show : true, feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, magicType : {show: true, type: ['line', 'bar', 'stack', 'tiled']}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : true, xAxis : [ { type : 'category', boundaryGap : false, data : ['周一','周二','周三','周四','周五','周六','周日'] } ], yAxis : [ { type : 'value' } ], series : [ { name:'每日饲喂量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[100, 200, 150, 130, 260, 830, 710] }, { name:'产奶量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[30, 182, 216, 156, 390, 300, 356] } ] } }, { query:{maxWidth:400,ismobile:true}, option:{ title : { text: '奶牛数字化养殖报表', subtext: '西部电子数据采集' }, tooltip : { trigger: 'axis' }, legend: { data:['每日饲喂量','产奶量'], right: 'center', bottom: 0, orient: 'horizontal' }, toolbox: { show : true, orient:'vertical', feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, magicType : {show: true, type: ['line', 'bar', 'stack', 'tiled']}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : true, xAxis : [ { type : 'category', boundaryGap : false, data : ['周一','周二','周三','周四','周五','周六','周日'] } ], yAxis : [ { type : 'value' } ], series : [ { name:'每日饲喂量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[100, 200, 150, 130, 260, 830, 710] }, { name:'产奶量', type:'line', smooth:true, itemStyle: {normal: {areaStyle: {type: 'default'}}}, data:[30, 182, 216, 156, 390, 300, 356] } ] } } //media结束 ] }; myChart.setOption(option); }