最近我一直在作数据可视化的前端工做,我用的最多的绘图工具是d3。d3有点像photoshop,功能很强大,例子也不少,可是学习成本也不低,作项目是须要较大人力投入的。3月底由在亚马逊工做的同窗介绍下使用了一下echart,一个由百度前端发起的canvas国产类库(官网:http://echarts.baidu.com/index.html)。这个echart实际上是在canvas类库zrender的基础上作的主题图库,优势有数据驱动,图例丰富,功能强大,支持数据拖拽重计算,数据区域漫游,全中文文档很是过瘾。跟一样是国产的前端脚手架fis同样(官网http://fis.baidu.com/),都是诚意满满的国产套餐,体现了现今国内不俗的前端开发实力。使用它们的感受就像想本身作个平板电脑,去华强北一转,主板、CPU、屏幕等各类套餐一订购,东西就哗哗地组装起来了。极其高效,很是适合商业项目开发。并且,即便是为了研究,用这些也能够打一个很好的基础。php
Echart图表库层次关系html
废话很少说了,看到好东西,第一步是从github上把相关文件全下下来,而后到build目录翻箱倒柜把东西找齐。新建目录以下:前端
echarts-1.3.8
—-zrender //zrender是echart依赖的绘图库,官网要求下载,可是目前个人程序中并无直接引用它,能够说普通状况下echart能够本身独立运行
——–zrender.js
—-excanvas_r3 //excanvas是实现IE7,8兼容canvas绘图的利器,实现了大部分canvas的API,在绘图方面其核心是经过IE的VML去实现的,效率较低
——–excanvas.js
—-echarts.js //echarts主程序,包含除map之外全部的主题图库。注意这个是压缩过的,而且只能经过requirejs或者esl.js模块化加载;想用标签或sea.js加载请用echarts-plain.js
—-echarts-orginal.js //没有压缩过的echarts.js
—-echarts-map.js //echarts的map主题图库html5
这个echart是百度前端作的,他们推荐使用模块式开发。好在我以前的项目,就是采用requirejs + angularjs开发的。因此引入比较容易。git
首先,在requirejs的入口配置文件main.js里加上echart:angularjs
require.config({ baseUrl:'application/views/frontEnd/build/' ,paths:{ //这里省略若干配置信息... //echart及其组件 ,echarts: 'lib/echarts-1.3.8/echarts' ,"echarts/chart/line": 'lib/echarts-1.3.8/echarts' ,"echarts/chart/bar": 'lib/echarts-1.3.8/echarts' ,'echarts/chart/scatter': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/k': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/pie': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/radar': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/map': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/chord': 'lib/echarts-1.3.8/echarts' ,'echarts/chart/force': 'lib/echarts-1.3.8/echarts' ,zrender: 'lib/echarts-1.3.8/zrender/zrender' } ,priority:[ 'angular' ] //,urlArgs:'v=1.1' });
而后,在要引入echart的具体页面控制js文件里,加载相关依赖。github
define([ 'echarts', 'echarts/chart/pie', 'd3' ], function (ec) { function common_chart_staff_assess_ctrl($http, $scope) { // angularjs的控制器.... } } return common_chart_staff_assess_ctrl; });
以上两步,熟悉dojo或者requirejs的朋友应该都毫无压力,可是仍是有不少朋友没用过这些,因此仍是有必要说一下的。ajax
接着就是把官网的例子放在本身的页面里实现。这里我选取了一个特别的千层饼图,点击这里去官网查看原图。bootstrap
那么咱们把例子里的静态数据(option对象)搬运到咱们本身的JS文件中,而后仿照官网的例子写一个渲染&刷新函数canvas
//渲染&刷新函数 $scope.refresh = function(option,isBtnRefresh){ if (isBtnRefresh) { needRefresh = true; if (needRefresh) { myChart.showLoading(); setTimeout($scope.refresh(option), 500); } return; } needRefresh = false; if (myChart && myChart.dispose) { myChart.dispose(); } myChart = ec.init(domMain); window.onresize = myChart.resize; myChart.setOption(option, true); domMessage.innerHTML = ''; }; //测试数据 $scope.option = { title : { text: '浏览器占比变化', subtext: '纯属虚构', x:'right', y:'bottom' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, legend: { orient : 'vertical', x : 'left', data:['Chrome','Firefox','Safari','IE9+','IE8-'] }, toolbox: { show : true, feature : { mark : {show: true}, dataView : {show: true, readOnly: false}, restore : {show: true}, saveAsImage : {show: true} } }, calculable : false, series : (function(){ var series = []; for (var i = 0; i < 30; i++) { series.push({ name:'浏览器(数据纯属虚构)', type:'pie', itemStyle : {normal : { label : {show : i > 28}, labelLine : {show : i > 28, length:20} }}, radius : [i * 4 + 40, i * 4 + 43], data:[ {value: i * 128 + 80, name:'Chrome'}, {value: i * 64 + 160, name:'Firefox'}, {value: i * 32 + 320, name:'Safari'}, {value: i * 16 + 640, name:'IE9+'}, {value: i * 8 + 1280, name:'IE8-'} ] }) } series[0].markPoint = { symbol:'emptyCircle', symbolSize:series[0].radius[0], effect:{show:true,scaleSize:12,color:'rgba(250,225,50,0.8)',shadowBlur:10,period:30}, data:[{x:'50%',y:'50%'}] }; return series; })() }; setTimeout(function(){ var _ZR = myChart.getZrender(); // 补充千层饼 _ZR.addShape({ shape : 'text', style : { x : _ZR.getWidth() / 2, y : _ZR.getHeight() / 2, color: '#666', text : '恶梦的过去', textAlign : 'center' } }); _ZR.addShape({ shape : 'text', style : { x : _ZR.getWidth() / 2 + 200, y : _ZR.getHeight() / 2, brushType:'fill', color: 'orange', text : '美好的将来', textAlign : 'left', textFont:'normal 20px 微软雅黑' } }); _ZR.refresh(); }, 2000);
而后在页面上找个div,显示echart就能够了。
不过请注意必定要给这个div设置宽度高度,不然图显示不出来不要怪我。
<section class="span12"> <div id="graph" class="graph chart-area" style="height:500px"></div> </section>
固然,仅仅停留在引用别人的例子是不能让我满意的。
首先,要进行数据绑定。
这里细分为3步:
//设置真实数据格式 $scope.default_option = { title : { text: '故障类型时序变化年轮图', subtext: '本图由内向外展现了各类故障类型的出现频率所占百分比,及其随时间变化的规律', x:'right', y:'bottom' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, legend: { orient : 'vertical', x : 'left', selected: $scope.faultnameSelected ,data:$scope.faultname }, toolbox: { show : true, feature : { mark : true, dataView : {readOnly: false}, restore : true, saveAsImage : true } }, calculable : false, series : {} }; $scope.ajaxChartData = function(dataname){ myChart.showLoading({ text: '正在努力的读取数据中...', //loading话术 }); $http.post("index.php/main/readfaultnum", null).success(function(alldata){ console.log(alldata); var option = $scope.default_option; option.series = $scope.setData(alldata); $scope.refresh(option,true); $scope.order = dataname; myChart.hideLoading(); }).error(function(){ domMessage.innerHTML = '网络故障,获取数据失败'; }); };
5.进一步定制
虽然我使用了官网的例子实现了需求,可是我还有些本身的想法。
官网的千层饼图中,表明时间的年轮宽度是固定的。可是咱们从后台读取的数据(以月为颗粒度)在时间上是变化的,随着系统使用时间增加而增加。这就致使一个问题,就是在只有一个月时,年轮只有小小一个,有50个月时,年轮多到超过了显示范围。这样很不美观,而且丧失了必定的可用性。
我决定要对其进行优化。当使用月份不多致使数据不多的时候,年轮会很宽;当使用月份不少致使数据不少的时候,年轮会很细——这样就能够同时避免数据量小的时候不美观和数据量大的时候丧失可用性的问题了。固然,若是数据量过大,年轮就会过细,一样会丧失可用性。所以咱们要设置一个最大数据量的阈值,把超过的部分砍掉。
解决方案就是要对根据数据量对年轮宽度进行缩放,也就是使用比例尺函数。在echart里我暂时没有找到这样的比例尺函数。固然,线性比例尺很简单,函数能够本身写。可是其余类型的比例尺缩放就稍微要一些技巧了。好在以前我一直是使用d3类库来作可视化的,我知道d3里有这样的比例尺函数能够很容易地解决个人问题。那就是d3.scale对象,其中包含线性比例尺、平方比例尺、指数比例尺、集合比例尺,彻底能够知足须要。我能够从开源的d3库中把比例尺函数抽取出来,也引入到这个页面。根据实际数据的测试结果,我选择了平方比例尺。那么接下来就很好写了:
var maxTime = 36; //本千层饼图最多显示60个月的数据(最多显示60个圈) //原始数据的加工工厂函数 $scope.setData = function(data){ //这是D3的平方比例尺函数,用于根据数据大小缩放环的宽度 var rScale = d3.scale.sqrt() .domain([maxTime, 1]) .range([3, 30]) .nice(); console.log(rScale(1), rScale(12), rScale(36)) //若是数据量超过60(即有60个月),则删除60个月以前的数据,只显示最近60个月的内容 if(data.length > maxTime){ data.splice(0, maxTime); } //通过D3比例尺计算的环的宽度 var R = rScale(data.length); console.log(R); for(var i=0; i<data.length; i++){ data[i]['type'] = 'pie'; data[i]['radius'] = [i * R + 40, i * R + 40 + R]; //if(typeof($scope.times[i])!= null) data[i]['name'] = $scope.times[i]; data[i]['itemStyle'] = {normal : { label : {show : i > (data.length-2)}, labelLine : { show : i > (data.length-2), length:40, color : '#f0f', width : 10, type : 'dotted' } }}; } //显示中央文字 setTimeout(function(){ var myDate = new Date(); var myMouth = myDate.getMonth()+1; var myYear = myDate.getFullYear(); //获取完整的年份(4位,1970-????) // 补充千层饼中央的说明文字 var _ZR = myChart.getZrender(); _ZR.addShape({ shape : 'text', style : { x : _ZR.getWidth() / 2, y : _ZR.getHeight() / 2, color: 'orange', text : data.length + '个月前', textAlign : 'center' } }); _ZR.addShape({ shape : 'text', style : { x : _ZR.getWidth() / 2 + data.length * R +50, y : _ZR.getHeight() / 2, color: 'orange', strokeColor: 'pink', text : '今日 ' + myYear + "年" + myMouth + "月" , textAlign : 'left', textFont:'normal 14px 微软雅黑' } }); _ZR.refresh(); }, 500); return data; };
只有4个月数据时的年轮图
30个月的测试数据的年轮图
固然,作到这一步,虽然实现了需求,但毕竟是照着别人的例子修改,没有本身创做来的有深度。不过如何本身创做,就超过了本文的范畴,而且也不是这一篇博客所能包含得了的,往后再议。
5.浏览器兼容性:
最后必须一提浏览器兼容性问题。IE八、IE7浏览器不兼容canvas绘图(IE6请容许我直接无视),为了实现兼容须要引入excanvas_r3库。
1
2
3
4
5
|
<!--Le HTML5 shim,forIE6-8supportofHTML5elements-->
<!--[ifltIE9]>
<script src="application/views/frontEnd/build/lib/html5shiv.js"></script>
<script src="application/views/frontEnd/build/lib/echarts-1.3.8/excanvas/excanvas.js"></script>
<![endif]-->
|
有时IE8中绘图错位,加入如下代码启用IE7兼容模式便可解决:
1
2
3
4
|
<!--解决IE8中canvas绘图错位-->
<!--[iflt IE9]>
<meta http-equiv="X-UA-Compatible"content="IE=7"/>
<![endif]-->
|