angularjs自定义折线图+滑动条指令
这篇文章不是一个教程,只是分享一个本人在创建angularjs创建自定义控件的过程,并分享一些理解,由于不熟悉调试angularjs的工具,文中的解决基本是基于本人的推测。
最后实现的效果如上图所示,图中包含一个折线图,一个时间轴,一个滑动条 一块实时显示区域(相应鼠标滑动)。
设置这个指令的初衷在于这部分组件是一个自我完善的部分,有着独立的数据处理与获取逻辑,并且在项目中会多次出现,频数多于5.
angularjs的指令是将模板中的内容添加到指令出现的地方,按照指令配置了,模板可以作为目标Dom的子元素(默认形式),或者替换Dom中的对应的元素。
指令的生效是在ng-controller之前。
angularjs应用在解析dom的时候会先匹配angularjs指令,如果出现指令,那么angularjs会将指令的模板对应插入到dom中并运行指令的初始化方法
比如你定义了一个名字为k,匹配方式为E(代表element)的指令
那么如果在dom中出现
那么angularjs会将指令k的模板插入到标签内部(默认方式),如果指令k的模板为,那么我们会得到如下结果
利用指令的这个性质我们可以封装一些模块化的结构,最后达到精简代码,便于阅读的效果。
当然如果我们只是利用指令的替换功能,我们可以直接使用include指令。指令能带来更多的好处是在与我们可以基于指令封装一些处理逻辑,比如我们定义标签
指令可以定义成
return :{
restrict: 'E',
scope=false;
template:'{{userInput*userInput}}'
}
在其中restrict表明匹配模式 为E 另外可选元素为 A C M
scope 代码作用的模式 false属性表明指令共享父级作用域,可以理解为直接include,但是我们可以在指令内部定义一些函数或者属性
另外可以选选择的属性 true 自己创建一个作用域,然后可以继承父级作用域,这个就于嵌套 controller
还有一种属性为{} 在其中我们可以指定从父级作用域中继承部分属性,实现单向绑定与双向绑定
关于angularjs的指令基本知识就到这里为止
折线图工具我是使用flot库,我们可以在flot上找到相应的源文件,总的来说还是比较好用的,我们中间使用了一些扩展功能,比如显示横纵坐标的名称,或者在鼠标悬浮到数据点上显示标签,这些需要我们现在额外的库,这些库的依赖我们可以在flot的主页上找到链接。
折线图部分遇到的几个问题与解决方案
1数据点的x轴坐标不是一个数值
这个问题flot组件已经帮我们解决了,在折线图x轴坐标声明的时候,我们只要声明坐标的显示方式为categories就可以,flot库会自动为我们按照x坐标输入顺序聚集数据生成横坐标与数值的对应关系,比如 2015-7-1 1:2:20 对应了坐标轴的0号位置
xaxis: {
tickColor: '#dddddd',
mode: 'categories',
},
2x轴坐标过多,我们只需要显示坐标轴的部分关键节点就可以了
坐标轴有一个ticks属性,用来指定坐标轴显示的刻度,可以采用指定坐标轴的个数,指定显示的数组比如 [[1,'嘎嘎'],[4,'哈哈']] 表明显示在第一个和第四个x轴节点的位置显示刻度,并且刻度的名称按照指定的字符串显示。另外我们可以指定函数,这个函数根据x坐标轴有关的信息生成一个数组交给图显示结果.这是我采取的方式
scope.options.xaxis.ticks = translateTick;
//筛选x轴的函数function translateTick(axis){ var backArray = [];var dataset = axis.categories;for (var timestamp in dataset){var time =new Date( timestamp);if(time.getMinutes()%scope.intervalArray[scope.interval][2]==0 &&time.getSeconds()==0 &&time.getHours() % scope.intervalArray[scope.interval][3] == 0){backArray.push([dataset[timestamp],time.Format(scope.intervalArray[scope.interval][4])]);}}return backArray;}
由于我们之前给xaxis定义了 categories, 我们可以通过被注入的形参的categories属性来获取坐标轴值与数值的对应,最后拼接成数组的形式返回
3鼠标在图上悬浮的时候 我们需要让用户知道当前x轴节点下所有线条的y轴的值
这一点我们需要利用flot的悬浮事件,首先在flot折线图的配置属性中开启hoverable属性,让图能产生hoverable事件
grid: {borderColor: '#eeee',borderWidth: 1,hoverable: true,backgroundColor: '#fcfcfc'},然后定义相应监听函数
plotArea.on("plothover", function (event, pos, item) {//console.log("You clicked at " + pos.x + ", " + pos.y);// axis coordinates for other axes, if present, are in pos.x2, pos.x3, ...// if you need global screen coordinates, they are pos.pageX, pos.pageYvar dataset = scope.plot.getData();var dataArray = dataset[0].data;if(dataArray.length >= 1){var index = -1;index = Math.round(pos.x);if(index < 0){index = 0;}if(index > dataArray.length-1){index = dataArray.length-1;}scope.xaxis =new Date(scope.plot.getData()[0].data[index][0]).Format("yyyy-MM-dd hh:mm:ss") ;/*scope.curve1value = scope.plot.getData()[0].data[index][1] ;scope.curve2value = scope.plot.getData()[1].data[index][1] ;*/scope.items = [];var numOfLine = dataset.length;for(var i = 0 ; i < numOfLine ; i++){scope.items.push([ dataset[i].label,dataset[i].data[index][1] ]);}scope.$apply();}});
这部分我们借用了angularjs的动态数据绑定来实现了数据回显。
scope.$apply() 也是一个关键点,由于该事件并不由angularjs管理,所以数值的改变不能触发angular的 digest过程,于是我们需要手动调用作用域的$apply()方法来实现数据同步
下面是滑动条相关的问题
这里使用的滑动条是html5自带的组件
这个组件基本能够满足需求
但是由于这个组件会引起折线图数据的改变,所以我们需要监听用户的输入,也就是我们需要双向数据绑定,这一点就给我带来了很大的玛法,虽然核心原因是我对js的数据复制和angularjs的作用域的概念还是不怎么熟悉
这组件的设计经历了3个过程
1直接硬编码到template中
我认为这个ng-model是绑定到我的指令中的,在指令内部设计属性监听,最后在一个controller中只有一个这个组件的时候行为表现的很好,但是在一个controller中有多个这个组件的时候出现了异常的情况,折现图的坐标轴显示异常,于是我认为ng-model会在父级controller中注册,这是我进的第一个坑
2使用transclude命令由父级创建一个滑动条交给指令注入,然后将滑动条的值通过双向数据绑定传入指令之中。
最后证明这个推论失败了,测试用例的结果表明如果我们通过transclude命令注入,滑动条绑定的model并不在父级controller中,model存在一个子作用域中,在本身没有创建该属性的时候只享有父级controller对应属性的读权限,也就是作用域的继承的结果
3还是硬编码到template中
经过之前的过程,我确定了如果在指令中声明ngModel 那么该数据的作用域还是在指令中,并不是在父级controller中,于是我开始排查别的问题,于是我发现了,在设置折线图的属性的时候 我采用了
aOptions = bOptions;
这样类似的语句,这个就是导致我之前页面显示异常的原因,这样的赋值方式不是值复制而是引用复制,如果用术语说那就是浅拷贝(指针指向地址的拷贝),而不是深拷贝(内容的拷贝),于是我们只需要将浅拷贝修改成深拷贝就可以了。
var deepCopy= function(source) {
var result={};
for (var key in source) {result[key] = typeof source[key]===’object’? deepCopy(source[key]): source[key];} return result;
}
上面的方法是可以借助的。
下面放我的测试代码吧,由于是从项目中扣了点内容,所以可能存在冗余。
model.js
angular.module('hiApp',[]).controller('StorageDetailMonitorOverviewController',['$scope', '$timeout', function($scope, $timeout) {'use strict';// AREA// -----------------------------------$scope.areaData =[]; $scope.areaOptions = {series: {lines: {show: true,fill: 0.2},/*points: {show: true,radius: 4}*/},grid: {borderColor: '#eeee',borderWidth: 1,hoverable: true,backgroundColor: '#fcfcfc'},tooltip: true,tooltipOpts: {content: function (label, x, y) { return x + ' : ' + y; }},xaxis: {tickColor: '#dddddd',mode: 'categories',},yaxis: {min: 0,//max: 1,tickColor: '#dddddd',position: 'left',},shadowSize: 0};$scope.areaData1 = [];$scope.areaOptions1= {series: {lines: {show: true,fill: 0.2},/*points: {show: true,radius: 4}*/},grid: {borderColor: '#eeee',borderWidth: 1,hoverable: true,backgroundColor: '#fcfcfc'},tooltip: true,tooltipOpts: {content: function (label, x, y) { return x + ' : ' + y; }},xaxis: {tickColor: '#dddddd',mode: 'categories',},yaxis: {min: 0,//max: 1,tickColor: '#dddddd',position: 'left',},shadowSize: 0};}]).directive('flot', ['$http','$timeout', function($http,$timeout) {'use strict';return {restrict: 'EA',template: '{{title}}
时间轴 :
{{intervalName}}
{rangeMax}}>{{name[0]}}选择时间 : {{xaxis}}
{{item[0]}} : {{item[1]}}
',scope: {dataset: '=?',options: '=',series: '=',callback: '=',src: '=',xaxislabel:'=',yaxislabel:'=',testa :'=',title :'=',},link: linkFunction};function linkFunction(scope, element, attributes) {var height, plot, plotArea, width;var heightDefault = 220;plot = null;width = attributes.width || '60%';height = attributes.height || heightDefault;plotArea = $($(element.children()[0]).children()[1]);plotArea.css({width: width,height: height});//折线图属性初始化scope.options.legend=scope.options.legend||{};scope.options.xaxis.axisLabel = scope.xaxislabel||"x轴";scope.options.yaxis.axisLabel = scope.yaxislabel||"y轴";//scope.options.legend.container =// $($($(element.children()[0]).children()[1]).children()[1]);scope.options.xaxis.ticks = translateTick;//回显元素和初始化scope.xaxis = "未选择";// id , 映射的url内容 , 与x轴坐标相关的min,与x轴坐标相关的hour,x轴时间的格式scope.intervalArray = [['1周','-7d',60,24,'MM-dd'],['3天','-3d',60,24,'MM-dd'],['24时','-24hour',60,6,'hh:mm'],['12时','-12hour',60,2,'hh:mm'],['1时','-1hour',10,1,'hh:mm']];scope.dataSourceArray = ['data7d','data3d','data24h','data12h','data1h'];scope.interval = 2;scope.rangeMax = scope.intervalArray.length-1;getData();scope.$watch('interval',function(newval){scope.intervalName = scope.intervalArray[newval][0];});//筛选x轴的函数function translateTick(axis){ var backArray = [];var dataset = axis.categories;for (var timestamp in dataset){var time =new Date( timestamp);if(time.getMinutes()%scope.intervalArray[scope.interval][2]==0 &&time.getSeconds()==0 &&time.getHours() % scope.intervalArray[scope.interval][3] == 0){backArray.push([dataset[timestamp],time.Format(scope.intervalArray[scope.interval][4])]);}}return backArray;}//scope.options.xaxis.ticks = translateTick;function init() {var plotObj;if(!scope.dataset || !scope.options ||scope.dataset.length<=0 ||!scope.dataset[0].data) return;//scope.options.xaxis.ticks = translateTick(scope.dataset[0].data); scope.plot = plotObj = $.plot(plotArea, scope.dataset, scope.options);scope.$emit('plotReady', plotObj);if (scope.callback) {scope.callback(plotObj, scope);}return plotObj;}function getData(){$http({method : 'jsonp',url:'./'+scope.dataSourceArray[scope.interval]+'.json?callback=JSON_CALLBACK'}).success(function(data,header,config,status){//响应成功parseJsonpValue(data);}).error(function(data,header,config,status){//处理响应失败});}//解析返回数据function parseJsonpValue(data){var reData = [];if(data.datapoints.length > 0){for(var i = 0 ; i < data.datapoints[0].length-1;i++){reData.push({'label':'label'+i,'data':[]});}for(var i = 0 ; i < data.datapoints.length;i++){var time = new Date(data.datapoints[i][0]*1000).Format("yyyy-MM-dd hh:mm:ss");for(var j = 0 ; j < reData.length ; j ++){if(!data.datapoints[i][j+1]){data.datapoints[i][j+1] = 0;}reData[j].data.push([ time, data.datapoints[i][j+1]]);}}scope.dataset = reData;}}function onDatasetChanged(dataset) {plot = init();return plot;}scope.$watchCollection('dataset', onDatasetChanged, true);function onSrcChanged(src) {if( src ) {$http.get(src).success(function (data) {$timeout(function(){scope.dataset = data;});}).error(function(){$.error('Flot chart: Bad request.');});}}scope.$watch('src', onSrcChanged);function onIntervalChanged(interval){if(interval >= 0 && interval < scope.intervalArray.length){var data ={};if(interval == 1){data = {"targets": ["ceph.cluster.016041a8-e1b3-4b26-a85b-a54824efb189.pool.all.num_read", "ceph.cluster.016041a8-e1b3-4b26-a85b-a54824efb189.pool.all.num_write"], "datapoints": [[1445930100, null, null], [1445931000, 0.07142857142857142, 0.0], [1445931900, 0.0, 0.0], [1445932800, 0.0, 0.0], [1445933700, 0.0, 0.0], [1445934600, 0.0, 0.0], [1445935500, 0.0, 0.0], [1445936400, 0.0, 0.0], [1445937300, 0.0, 0.0], [1445938200, 0.0, 0.0], [1445939100, 0.0, 0.0], [1445940000, 0.0, 0.0], [1445940900, 0.0, 0.0], [1445941800, 0.0, 0.0], [1445942700, 0.0, 0.0], [1445943600, 0.0, 0.0], [1445944500, 0.0, 0.0], [1445945400, 0.0, 0.0], [1445946300, 0.0, 0.0], [1445947200, 0.0, 0.0], [1445948100, 0.0, 0.0], [1445949000, 0.0, 0.0], [1445949900, 0.0, 0.0], [1445950800, 0.0, 0.0], [1445951700, 0.0, 0.0], [1445952600, 0.0, 0.0], [1445953500, 0.0, 0.0], [1445954400, 0.0, 0.0], [1445955300, 0.0, 0.0], [1445956200, 0.0, 0.0], [1445957100, 0.0, 0.0], [1445958000, 0.0, 0.0], [1445958900, 0.0, 0.0], [1445959800, 0.0, 0.0], [1445960700, 0.0, 0.0], [1445961600, 0.0, 0.0], [1445962500, 0.0, 0.0], [1445963400, 0.0, 0.0], [1445964300, 0.0, 0.0], [1445965200, 0.0, 0.0], [1445966100, 0.0, 0.0], [1445967000, 0.0, 0.0], [1445967900, 0.0, 0.0], [1445968800, 0.0, 0.0], [1445969700, 0.0, 0.0], [1445970600, 0.0, 0.0], [1445971500, 0.0, 0.0], [1445972400, 0.0, 0.0], [1445973300, 0.0, 0.0], [1445974200, 0.0, 0.0], [1445975100, 0.0, 0.0], [1445976000, 0.0, 0.0], [1445976900, 0.0, 0.0], [1445977800, 0.0, 0.0], [1445978700, 0.0, 0.0], [1445979600, 0.0, 0.0], [1445980500, 0.0, 0.0], [1445981400, 0.0, 0.0], [1445982300, 0.0, 0.0], [1445983200, 0.0, 0.0], [1445984100, 0.0, 0.0], [1445985000, 0.0, 0.0], [1445985900, 0.0, 0.0], [1445986800, 0.0, 0.0], [1445987700, 0.0, 0.0], [1445988600, 0.0, 0.0], [1445989500, 0.0, 0.0], [1445990400, 0.0, 0.0], [1445991300, 0.0, 0.0], [1445992200, 0.0, 0.0], [1445993100, 0.0, 0.0], [1445994000, 0.0, 0.0], [1445994900, 0.0, 0.0], [1445995800, 0.0, 0.0], [1445996700, 0.0, 0.0], [1445997600, 0.0, 0.0], [1445998500, 0.0, 0.0], [1445999400, 0.0, 0.0], [1446000300, 0.0, 0.0], [1446001200, 0.0, 0.0], [1446002100, 0.0, 0.0], [1446003000, 0.0, 0.0], [1446003900, 0.0, 0.0], [1446004800, 0.0, 0.0], [1446005700, 0.0, 0.0], [1446006600, 0.0, 0.0], [1446007500, 0.0, 0.0], [1446008400, 0.0, 0.0], [1446009300, 0.0, 0.0], [1446010200, 0.0, 0.0], [1446011100, 0.0, 0.0], [1446012000, 0.0, 0.0], [1446012900, 0.0, 0.0], [1446013800, 0.0, 0.0], [1446014700, 0.0, 0.0], [1446015600, 0.0, 0.0], [1446016500, null, null], [1446017400, null, null], [1446018300, null, null], [1446019200, null, null], [1446020100, null, null], [1446021000, null, null], [1446021900, null, null], [1446022800, null, null], [1446023700, null, null], [1446024600, null, null], [1446025500, null, null], [1446026400, null, null], [1446027300, null, null], [1446028200, null, null], [1446029100, null, null], [1446030000, null, null], [1446030900, null, null], [1446031800, null, null], [1446032700, null, null], [1446033600, null, null], [1446034500, null, null], [1446035400, null, null], [1446036300, 0.0, 0.0], [1446037200, 0.0, 0.0], [1446038100, 0.0, 0.0], [1446039000, 0.0, 0.0], [1446039900, 0.0, 0.0], [1446040800, 0.0, 0.0], [1446041700, 0.0, 0.0], [1446042600, 0.0, 0.0], [1446043500, 0.0, 0.0], [1446044400, 0.0, 0.0], [1446045300, 0.0, 0.0], [1446046200, 0.0, 0.0], [1446047100, 0.0, 0.0], [1446048000, 0.0, 0.0], [1446048900, 0.0, 0.0], [1446049800, 0.0, 0.0], [1446050700, 0.0, 0.0], [1446051600, 0.0, 0.0], [1446052500, 0.0, 0.0], [1446053400, 0.0, 0.0], [1446054300, 0.0, 0.0], [1446055200, 0.0, 0.0], [1446056100, 0.0, 0.0], [1446057000, 0.0, 0.0], [1446057900, 0.0, 0.0], [1446058800, 0.0, 0.0], [1446059700, 0.0, 0.0], [1446060600, 0.0, 0.0], [1446061500, 0.0, 0.0], [1446062400, 0.0, 0.0], [1446063300, 0.0, 0.0], [1446064200, 0.0, 0.0], [1446065100, 0.0, 0.0], [1446066000, 0.0, 0.0], [1446066900, 0.0, 0.0], [1446067800, 0.0, 0.0], [1446068700, 0.0, 0.0], [1446069600, 0.0, 0.0], [1446070500, 0.0, 0.0], [1446071400, 0.0, 0.0], [1446072300, 0.0, 0.0], [1446073200, 0.0, 0.0], [1446074100, 0.0, 0.0], [1446075000, 0.0, 0.0], [1446075900, 0.0, 0.0], [1446076800, 0.0, 0.0], [1446077700, 0.0, 0.0], [1446078600, 0.0, 0.0], [1446079500, 0.0, 0.0], [1446080400, 0.0, 0.0], [1446081300, 0.0, 0.0], [1446082200, 0.0, 0.0], [1446083100, 0.0, 0.0], [1446084000, 0.0, 0.0], [1446084900, 0.0, 0.0], [1446085800, 0.0, 0.0], [1446086700, 0.0, 0.0], [1446087600, 0.0, 0.0], [1446088500, 0.0, 0.0], [1446089400, 0
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
