Search code examples
javascriptdygraphs

dygraph rolling average for data with holes


Dygraphs options provide 'rollPeriod' to support rolling averages and 'stepPlot' to support step plots. When set together when some data is missing in between, they give very unexpected results. For example, attached image link shows graph for original data (rollPeriod=1) and rollPeriod=5. (https://i.sstatic.net/yRAgv.jpg)

At 40,000 for example, the rolling average must be zero. But, dygraphs takes average of last 5 datapoints instead of last 5 seconds.

Is it possible to get rolling average that maintains notion of time rather than data points. Thanks in advance !

PS- Sorry for image link. SO won't allow me to directly post images due to lack of reputation. :(


Solution

  • Due to lack of this functionality, I implemented it by myself. I am putting the code here for someone in similar situation. Code uses internal function extractSeries_ in dygraph library and Queue.js. Use with extreme caution !

      function calcAvg_(minDate, maxDate, dispData){
        var windowSize = Math.round((maxDate-minDate)/100);
        if(windowSize <= 1){ 
            return dispData;
        }
        var energy = 0;
        var lastS = new Queue();
    
        var series = dispData;
    
        var lastAvg = 0;
        // Initially lastS elements are all 0
        // lastS shall always be maintained of windowSize.
        // Every enqueue after initialization in lastS shall be matched by a dequeue
        for(j=0; j<windowSize; j++){
            lastS.enqueue(0);
        }
    
        var avg_series = [];
    
        var prevTime = minDate - windowSize;
        var prevVal = 0;
        avg_series.push([prevTime, prevVal]);
    
        //console.log( "calcAvg_ min: " + minDate + " max: " + maxDate + " win: " + windowSize );
        for(j=0; j<series.length; j++){
            var time = series[j][0];
            var value = series[j].slice(1);
            if(time > minDate){
                var k = 0;
                while(k < windowSize && prevTime + k < time){
                    var tail = lastS.dequeue();
                    lastS.enqueue(prevVal);
                    lastAvg = lastAvg + (prevVal - tail)/windowSize;
                    avg_series.push([prevTime+k, lastAvg]);
                    k++;
                }
            }
            prevTime = time;
            prevVal = value;
            if(time > maxDate){
                break;
            }
        }
        if(j == series.length){
            //console.log("Fix last value");
            var k = 0;
            while(k < windowSize && prevTime + k < maxDate){
                var tail = lastS.dequeue();
                lastS.enqueue(prevVal);
                lastAvg = lastAvg + (prevVal - tail)/windowSize;
                avg_series.push([prevTime+k, lastAvg]);
                k++;
            }
        }
        //console.log(avg_series);
        avg_series.push([maxDate, 0]);
        return avg_series;
      }
    
      var blockRedraw = false;
      myDrawCallback_ = function(gs, initial) {
        if (blockRedraw) return;
        blockRedraw = true;
        var range = gs.xAxisRange();
        var yrange = gs.yAxisRange();
        var series = calcAvg_(range[0], range[1], 
                        gs.extractSeries_(gs.rawData_, 0, false));
        gs.updateOptions( { 
            dateWindow: range, 
            valueRange: yrange, 
            file: series } );
        blockRedraw = false;
      }