主要用到echart插件实现趋势图绘制,为了模拟实时效果,并且保证趋势图匀速移动,实现主要思想如下:
1.为保证匀速移动,趋势图的移动以前端时间间隔为准,时间间隔到了,趋势图就移动一次,不管后台数据正常返回与否,有后台返回的数据则显示正常数据,没有正常数据则前端自行填补数据。
2.趋势图x轴显示的时间,是第一次和后台交互,从时间戳解析出来的。最新的时间,就是取第一次请求数据中后台最新的时间,以后的时间,由前端自行计算nextTime,所以第一次与后台的协定,特别重要。
3.初始化整个趋势图后,以后一份份数据请求。后台以“尽最大努力”发送数据位目标,前端indexRight记录最新最近的正常返回数据。
4.cacheData数组用来缓存后台数据,生成趋势图时,参考缓存数据,并通过一些“填空补缺”的机制,绘制趋势图。
5.趋势图为了保证轴两端都有时间,对xAxis 的interval设置函数实现。
趋势图部分:
1 var trendChart = null; 2 var initTrendFlag = false; 3 /**echart反复创建**/ 4 function createTrend(convertedData) { 5 if(convertedData && convertedData[0] && convertedData[1]){ 6 $("#trendContent").attr(‘class‘,‘trendContentStyle‘); 7 var title = ""; 8 var historyOption = { 9 title : { 10 text: title 11 }, 12 tooltip : { 13 trigger: ‘axis‘, 14 formatter:"{b}<br>{c}" 15 }, 16 legend: { 17 show:false, 18 data:[‘‘] 19 }, 20 dataZoom : { 21 show : false, 22 start : 0, 23 end : 100 24 }, 25 xAxis : [ 26 { 27 type : ‘category‘, 28 boundaryGap : false, 29 axisLabel: { 30 show: true, 31 rotate: 0, 32 interval: function(index,data){ 33 if(index == 0|| index == showNum-1 || index % 40 == 0){ 34 return true; 35 }else 36 { 37 return false; 38 } 39 }, 40 onGap: true 41 }, 42 data : convertedData[0] //[0,0,0,0,0,0,0,0,0,0] 43 } 44 ], 45 yAxis : [ 46 { 47 show:false, 48 type : ‘value‘, 49 scale: true, 50 name : ‘价格‘, 51 boundaryGap: [0.2, 0.2], 52 min:0 53 } 54 ], 55 series : [ 56 { 57 itemStyle: {normal: {areaStyle: {type: ‘default‘,color:‘#D8E2F7‘}}}, 58 symbol:‘circle‘, 59 symbolSize:0.02, 60 name:‘‘, 61 smooth:true, 62 type:‘line‘, 63 data:convertedData[1] //[0,0,0,0,0,0,0,0,0,0] 64 } 65 ] 66 }; 67 //切换到有数据清空下,重新set整个option 68 69 trendChart = echarts.init(document.getElementById(‘trendContent‘)); 70 trendChart.setOption(historyOption); 71 }else{ 72 if(trendChart && trendChart.clear){ 73 trendChart.clear();//从有数据切换到无数据 74 } 75 $("#trendContent").attr(‘class‘,‘trendContentStyle nodata‘); 76 } 77 } 78 79 function initTrend(poiid){ 80 cacheData = [];//切换地区,清空上一次缓存数据 81 initTrendFlag = false;//初始化标识为false 82 var url = "/lte/realtimeheatmap/poilinkchart"; 83 var para = { 84 province:condition.provincecode, 85 city:condition.citycode, 86 // poiid:‘g20atblc‘, 87 poiid:poiid, 88 time:"" 89 }; 90 //"/lte/realtimeheatmap/poilinkchart?province=330000&city=330100&poiid=g20atblc&time=2016-07-10+14:50:00" 91 $.ajax({ 92 type: "GET", 93 contentType: "application/json", 94 url: url, 95 data: para, 96 async: false, //需要同步;初始化成功后,趋势图再做其他 97 success: function (data) { 98 //初始化成功,改变indexNow;缓存、更新cacheData,不绘制趋势图 99 // showNum = data.length;//第一次初始化,数据就有可能缺失 100 if(!data || data.length <1){ 101 createTrend(null); 102 return; 103 } 104 var newestTime = data[data.length-1][0]; 105 if(indexRight < newestTime){ 106 indexRight = newestTime;//第一次初始化,indexRight = "" ,一定会成功赋值的 107 } 108 indexNow = indexRight; 109 indexTail = getNextTimeStamp(indexNow,timeInterval,-showNum + 1);//显示[indexTail,indexNow]showNum个数据 110 initCache(data);//用第一次数据,初始化规整的cacheData 111 var convertedData = convertTrendData();//结合cacheData中数据,生成趋势图所需数据 112 createTrend(convertedData); 113 initTrendFlag = true; 114 requestTrendData();//初始化之后,立马发起一次请求 115 }, 116 error: function (XMLHttpRequest, textStatus, errorThrown) { 117 createTrend(null); 118 initTrendFlag = false; 119 } 120 }); 121 } 122 123 /**不断定时调用,发请求,请求最新的数据,来生成趋势图**/ 124 function requestTrendData() { 125 if(!cacheData || cacheData.length < 1) return;//初始化失败 126 // var indexTail = getNextTimeStamp(indexNow,timeInterval,-showNum); 127 if(indexRight < indexTail){ 128 indexRight = indexTail;//整个趋势图显示数据都是前端补齐的 129 }else if(indexRight > indexNow){ 130 indexRight = indexNow;//某一次最新数据超过indexNow 131 } 132 var url = "/lte/realtimeheatmap/poilinkchart"; 133 //"/poilinkchart?province=330000&city=330100&poiid=g20atblc&time=2016-07-10+14:50:00" 134 var para = { 135 province:condition.provincecode, 136 city:condition.citycode, 137 poiid:‘g20atblc‘, 138 time:indexRight 139 }; 140 141 $.ajax({ 142 type: "GET", 143 contentType: "application/json", 144 url: url, 145 data: para, 146 //async: false, //需要异步;不过,存在几个ajax返回同事addData去修改cacheData的情况? 147 success: function (data) { 148 /**拿到数据,填装数组,绘制趋势图,更新indexRight**/ 149 if(!data || data.length <1){ 150 //查无数据,返回 [] 151 return; 152 } 153 var newestTime = data[data.length-1][0]; 154 if(indexRight < newestTime){ 155 indexRight = newestTime;//indexRight只右移;不再次查询之前的 156 } 157 addResponseData(data); //更新cacheData;不绘制趋势图;更新indexTail、indexNow 158 // var convertedData = convertTrendData(cacheData); 159 // createTrend(convertedData); 160 }, 161 error: function (XMLHttpRequest, textStatus, errorThrown) { 162 /**ajax失败,添加测试数据;真实情况ajax失败直接不予处理,时间间隔到了,前端就走**/ 163 // addResponseData([[getNextTimeStamp(indexNow,timeInterval,1),Math.random()*100]]); 164 } 165 }); 166 }
缓存数据部分:
/**趋势图只有第一次初始化成功后,才开始持续走动;在初始化成功的ajax回调里面调用其他函数**/ //记录收到的最新一次的 真实后台数据的时间戳 ,超期了,等于indexTail // 为了填补(连续缺数据的情况),发给后台;如果30分钟的到了,25分钟的还是前端填补的,那直接忽略 var indexRight = "";//在有新数据来到的时候更新 var indexNow = "";//指向趋势图 显示的 最新时间戳;只在前端时间间隔到了的时候更新 var indexTail = "";//指向趋势图 显示的 最早时间戳;只在前端时间间隔到了的时候更新 var timeInterval = 1;//时间间隔,五分钟,可配置 var showNum = 481;//前后端提前协议好,因为第一次初始化的时候,数据长度就不确定;这样保证indexNow和indexTail间距固定 //存储ajax返回的趋势图数据;不存储伪造数据 var cacheData = [];//cacheData的第一个数据cacheData[0][0],不一定对齐显示的最左侧数据indexTail,最后一个数据,也不一定对齐indexNow(可能缓存未来的) //cacheData[0] = [];//先初始化第一个元素为一个空的数组 //var mapCachedata = {};//日期-数据,key-value形式,更好填充? /**构造一个ajax返回的趋势图样例数据**/ /**先假设趋势图显示10分钟数据,时间间隔为1分钟**/ var initData = [ ["2016-07-01 08:30:00",50], ["2016-07-01 08:31:00",40], ["2016-07-01 08:32:00",70], ["2016-07-01 08:33:00",55], ["2016-07-01 08:34:00",55], ["2016-07-01 08:35:00",25], ["2016-07-01 08:36:00",155], ["2016-07-01 08:37:00",15], ["2016-07-01 08:38:00",95], ["2016-07-01 08:39:00",52] ]; /**第一次收到数据后,形成规则的cacheData数组**/ function initCache(resData){ var timeStamp = indexTail; var searchData = null; var flag = false; for(var k=0;k<showNum;k++){ cacheData[k] = []; cacheData[k][0] = timeStamp; cacheData[k][1] = 0; //遍历resData上的数据,如果ajax返回数据里面有,直接填上,如果没有填补之前的 searchData = getSearchData(timeStamp,resData); if(searchData != null){ flag = true;//一旦有一条有一条效数据,后续空缺填补之前的 cacheData[k][1] = searchData; }else if(flag){ //前段自行填补 cacheData[k][1] = cacheData[k-1][1]; } timeStamp = getNextTimeStamp(timeStamp,timeInterval,1); } } /**需要把cacheData从稀疏数组,转化为稠密数组吗? * cacheData前段,在初始化正确后,就一直会保证是非稀疏的 * cacheData[i]为undefined的空位不能剔除掉,有可能有数据会来填补 * **/ function fillUpCache() { if (!cacheData || !cacheData[0] || !cacheData[0][0]) { { //cacheData第一个数据为空时;修改头部为indexTail,0 cacheData = []; cacheData[0] = []; cacheData[0][0] = indexTail; cacheData[0][1] = 0; // cacheData.unshift([indexTail,0]);//在头部插入一个数据;这样会多 } for (var i = 1; i < cacheData.length; i++) { /**这样只能保证数组为没有返回的数据预留了空位的情况下的填充;如果第一次返回数据就是间断的**/ if (cacheData[i] == undefined) { cacheData[i] = []; cacheData[i][0] = getNextTimeStamp(cacheData[i - 1][0], timeInterval, 1);//要确保第一个非空;i从1开始 cacheData[i][1] = cacheData[i - 1][1]; } } } } /**从cacheData取数据给趋势图加载; * 不存在则伪造;填补cacheData * 行列转换,转化时间戳为显示时间形式**/ function convertTrendData(){ var time = []; var ueNum = []; var timeStamp = indexTail; var searchData = null; for(var k=0;k<showNum;k++){ var timeArr = timeStamp.split(" "); if(timeArr && timeArr[1]){ var lastTime = timeStamp.split(" ")[1]; if(lastTime[0] == ‘0‘){ lastTime = lastTime.substring(1); } time[k] = lastTime; }else{ time[k] = timeStamp;//时间戳格式有误;直接赋值整个时间戳 } ueNum[k] = 0; //遍历cacheData上的数据,如果缓存里面有,直接填上,如果没有填补之前的 searchData = getSearchData(timeStamp,cacheData);//缓存上基本有序,按理说,可以优化搜索,不应该每次都去遍历所有 if(searchData != null){ ueNum[k] = searchData; }else{ if(k>0){ ueNum[k] = ueNum[k-1];//不是第一个数据,就取前一个数据的 addResponseData([[timeStamp,ueNum[k]]]);//伪造的数据,也必须纳入缓存 }else{ ueNum[k] = 0;//第一个数据,没有历史,直接给0;第一次初始化cacheData成功后,不会搜索不到 } } timeStamp = getNextTimeStamp(timeStamp,timeInterval,1); } return [time,ueNum]; } /**时间到了,填入之前的数据;并且从头到尾检查数组是否有空缺,有空缺填补上一个**/ /**填装数据,有ajax返回数据需要填充,时间间隔 到了也需要填充;添加新数据到尾部,drop头数据**/ /**ajax返回的数据,是合理时间点,有效数据;存在三种情况:填补(更新)之前数据,填充当前最新数据,或者缓存未来数据**/ function addResponseData(reqData){ /**返回数据是“连续时间点”的**/ var time = null; var data = null; var insertIndex = null; if(reqData && reqData.length > 0){ for(var i=0;i<reqData.length;i++){ if(reqData[i] && reqData[i][0] && reqData[i][1]){ time = reqData[i][0]; data = reqData[i][1]; insertIndex = getIndexByTime(time); if(insertIndex !== null){ cacheData[insertIndex] = []; cacheData[insertIndex][0] = time; cacheData[insertIndex][1] = data; } } } /**添加新数据后,暂时不挪动indexNow等;每次都在前端时钟结束,调用move方法移动**/ } } /**前端计时器到时间间隔,坐标轴移动一个单位,绘制趋势图**/ function trendMove(){ indexNow = getNextTimeStamp(indexNow,timeInterval,1); indexTail = getNextTimeStamp(indexTail,timeInterval,1); if(cacheData && cacheData[0] && cacheData[0][0]){ //如果cacheData首个元素为 undefined 或者[] 则无法删除数据了; /** 在第一次初始化的时候,如果首个元素正常,后续每挪动一步,必有填补,cacheData在indexTail-indexNow之间的区段是非稀疏的数组 * 首个元素就不可能为空了**/ while(getNextTimeStamp(indexTail,timeInterval,-1) > cacheData[0][0]){ cacheData.shift();//删除超期数据 } } var convertedData = convertTrendData();//结合cacheData中数据,生成趋势图所需数据 createTrend(convertedData); } /**初始化的时候,后台需要返回时间戳,例如2016-07-08 7:25 **/ function getNextTimeStamp(currentTime,timeInterval,num){ var d = new Date(currentTime); d.setMinutes(d.getMinutes() + timeInterval * num); var nextStr = getTimeStamp(d)[0];//暂时先用完整时间"2016-07-08 7:25" return nextStr; } /**根据给定时间戳,查找数据应该填入cacheData位置;更新index;跳跃式更新,数组中间的是undefined,数组长度会变长+N**/ function getIndexByTime(timeStamp){ if(timeStamp < indexTail){ //过期数据 return null; }else if(timeStamp <= indexNow){ //趋势图正在显示的历史时间 //折半查找不行,因为可能中间的数据是undefined 无法确定是在哪段 for(var tail = cacheData.length - 1;tail >=0;tail--){ if(cacheData[tail] && cacheData[tail][0] && cacheData[tail][0] === timeStamp){ return tail; } } return null;//数据异常,没匹配上各个时间点,一般不应该出现 }else{ //未来数据 var tempTimeStamp = indexNow; var tempIndex = cacheData.length - 1; while(timeStamp > tempTimeStamp){ tempTimeStamp = getNextTimeStamp(tempTimeStamp,timeInterval,1);//后移一个间隔 tempIndex ++; if(timeStamp === tempTimeStamp){ return tempIndex; } } /**数据间隔应该可以逐步+1 timeInterval吻合,一般不应该返回null;除非本身cacheData时间点“不连续”**/ return null; } } /**为填充趋势图数据,在cacheData中查找数据**/ function getSearchData(timeStamp,cacheData){ //折半查找行不通,虽然有序,但是cacheData可能是稀疏数组 // if(!cacheData || !cacheData.length) return null; for(var index=0;index<cacheData.length;index++){ if(cacheData[index] && cacheData[index][0] && cacheData[index][0] === timeStamp && cacheData[index][1] != undefined){ return cacheData[index][1];//"1a3"这种非法数据没有排除 } } return null;//数据没有缓存在cacheData中 } //将日期对象转化为标准的时间戳以及趋势图横坐标显示形式:["2016-07-08 7:25","7:25"] function getTimeStamp(dateObj){ var strYear = dateObj.getFullYear(); var strMonth = dateObj.getMonth() + 1; if (strMonth < 10) { strMonth = ‘0‘ + strMonth; } var strDay = dateObj.getDate(); if (strDay < 10) { strDay = ‘0‘ + strDay; } var strHour = dateObj.getHours(); if(strHour < 10){ strHour = "0" + strHour; } var strMinute = dateObj.getMinutes(); if(strMinute < 10){ strMinute = ‘0‘ + strMinute; } var last = strHour + ":" + strMinute + ":00"; var all = strYear + ‘-‘ + strMonth + ‘-‘ + strDay + " " + last; return [all,last]; }
时间: 2024-10-03 00:06:28