最近我一直在作數據可視化的前端工做,我用的最多的繪圖工具是d3。d3有點像photoshop,功能很強大,例子也不少,可是學習成本也不低,作項目是須要較大人力投入的。3月底由在亞馬遜工做的同窗介紹下使用了一下echart,一個由百度前端發起的canvas國產類庫(官網:http://echarts.baidu.com/index.html)。這個echart實際上是在canvas類庫zrender的基礎上作的主題圖庫,優勢有數據驅動,圖例豐富,功能強大,支持數據拖拽重計算,數據區域漫遊,全中文文檔很是過癮。跟一樣是國產的前端腳手架fis同樣(官網http://fis.baidu.com/),都是誠意滿滿的國產套餐,體現了現今國內不俗的前端開發實力。使用它們的感受就像想本身作個平板電腦,去華強北一轉,主板、CPU、屏幕等各類套餐一訂購,東西就嘩嘩地組裝起來了。極其高效,很是適合商業項目開發。並且,即便是爲了研究,用這些也能夠打一個很好的基礎。php
廢話很少說了,看到好東西,第一步是從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; };
固然,作到這一步,雖然實現了需求,但畢竟是照着別人的例子修改,沒有本身創做來的有深度。不過如何本身創做,就超過了本文的範疇,而且也不是這一篇博客所能包含得了的,往後再議。
5.瀏覽器兼容性:
最後必須一提瀏覽器兼容性問題。IE八、IE7瀏覽器不兼容canvas繪圖(IE6請容許我直接無視),爲了實現兼容須要引入excanvas_r3庫。
1
2
3
4
5
|
<
!
--
Le
HTML5
shim
,
for
IE6
-
8
support
of
HTML5
elements
--
>
<
!
--
[
if
lt
IE
9
]
>
<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繪圖錯位
--
>
<
!
--
[
if
lt
IE
9
]
>
<
meta
http
-
equiv
=
"X-UA-Compatible"
content
=
"IE=7"
/
>
<
!
[
endif
]
--
>
|