模拟实时动态趋势

  主要用到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];
}

 

posted @ 2016-07-20 21:22  jht_newbie  阅读(770)  评论(0编辑  收藏  举报