/* Flot plugin for stacking data sets rather than overlyaing them. 
 | 
  
 | 
Copyright (c) 2007-2014 IOLA and Ole Laursen. 
 | 
Licensed under the MIT license. 
 | 
  
 | 
The plugin assumes the data is sorted on x (or y if stacking horizontally). 
 | 
For line charts, it is assumed that if a line has an undefined gap (from a 
 | 
null point), then the line above it should have the same gap - insert zeros 
 | 
instead of "null" if you want another behaviour. This also holds for the start 
 | 
and end of the chart. Note that stacking a mix of positive and negative values 
 | 
in most instances doesn't make sense (so it looks weird). 
 | 
  
 | 
Two or more series are stacked when their "stack" attribute is set to the same 
 | 
key (which can be any number or string or just "true"). To specify the default 
 | 
stack, you can set the stack option like this: 
 | 
  
 | 
    series: { 
 | 
        stack: null/false, true, or a key (number/string) 
 | 
    } 
 | 
  
 | 
You can also specify it for a single series, like this: 
 | 
  
 | 
    $.plot( $("#placeholder"), [{ 
 | 
        data: [ ... ], 
 | 
        stack: true 
 | 
    }]) 
 | 
  
 | 
The stacking order is determined by the order of the data series in the array 
 | 
(later series end up on top of the previous). 
 | 
  
 | 
Internally, the plugin modifies the datapoints in each series, adding an 
 | 
offset to the y value. For line series, extra data points are inserted through 
 | 
interpolation. If there's a second y value, it's also adjusted (e.g for bar 
 | 
charts or filled areas). 
 | 
  
 | 
*/ 
 | 
  
 | 
(function ($) { 
 | 
    var options = { 
 | 
        series: { stack: null } // or number/string 
 | 
    }; 
 | 
     
 | 
    function init(plot) { 
 | 
        function findMatchingSeries(s, allseries) { 
 | 
            var res = null; 
 | 
            for (var i = 0; i < allseries.length; ++i) { 
 | 
                if (s == allseries[i]) 
 | 
                    break; 
 | 
                 
 | 
                if (allseries[i].stack == s.stack) 
 | 
                    res = allseries[i]; 
 | 
            } 
 | 
             
 | 
            return res; 
 | 
        } 
 | 
         
 | 
        function stackData(plot, s, datapoints) { 
 | 
            if (s.stack == null || s.stack === false) 
 | 
                return; 
 | 
  
 | 
            var other = findMatchingSeries(s, plot.getData()); 
 | 
            if (!other) 
 | 
                return; 
 | 
  
 | 
            var ps = datapoints.pointsize, 
 | 
                points = datapoints.points, 
 | 
                otherps = other.datapoints.pointsize, 
 | 
                otherpoints = other.datapoints.points, 
 | 
                newpoints = [], 
 | 
                px, py, intery, qx, qy, bottom, 
 | 
                withlines = s.lines.show, 
 | 
                horizontal = s.bars.horizontal, 
 | 
                withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), 
 | 
                withsteps = withlines && s.lines.steps, 
 | 
                fromgap = true, 
 | 
                keyOffset = horizontal ? 1 : 0, 
 | 
                accumulateOffset = horizontal ? 0 : 1, 
 | 
                i = 0, j = 0, l, m; 
 | 
  
 | 
            while (true) { 
 | 
                if (i >= points.length) 
 | 
                    break; 
 | 
  
 | 
                l = newpoints.length; 
 | 
  
 | 
                if (points[i] == null) { 
 | 
                    // copy gaps 
 | 
                    for (m = 0; m < ps; ++m) 
 | 
                        newpoints.push(points[i + m]); 
 | 
                    i += ps; 
 | 
                } 
 | 
                else if (j >= otherpoints.length) { 
 | 
                    // for lines, we can't use the rest of the points 
 | 
                    if (!withlines) { 
 | 
                        for (m = 0; m < ps; ++m) 
 | 
                            newpoints.push(points[i + m]); 
 | 
                    } 
 | 
                    i += ps; 
 | 
                } 
 | 
                else if (otherpoints[j] == null) { 
 | 
                    // oops, got a gap 
 | 
                    for (m = 0; m < ps; ++m) 
 | 
                        newpoints.push(null); 
 | 
                    fromgap = true; 
 | 
                    j += otherps; 
 | 
                } 
 | 
                else { 
 | 
                    // cases where we actually got two points 
 | 
                    px = points[i + keyOffset]; 
 | 
                    py = points[i + accumulateOffset]; 
 | 
                    qx = otherpoints[j + keyOffset]; 
 | 
                    qy = otherpoints[j + accumulateOffset]; 
 | 
                    bottom = 0; 
 | 
  
 | 
                    if (px == qx) { 
 | 
                        for (m = 0; m < ps; ++m) 
 | 
                            newpoints.push(points[i + m]); 
 | 
  
 | 
                        newpoints[l + accumulateOffset] += qy; 
 | 
                        bottom = qy; 
 | 
                         
 | 
                        i += ps; 
 | 
                        j += otherps; 
 | 
                    } 
 | 
                    else if (px > qx) { 
 | 
                        // we got past point below, might need to 
 | 
                        // insert interpolated extra point 
 | 
                        if (withlines && i > 0 && points[i - ps] != null) { 
 | 
                            intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); 
 | 
                            newpoints.push(qx); 
 | 
                            newpoints.push(intery + qy); 
 | 
                            for (m = 2; m < ps; ++m) 
 | 
                                newpoints.push(points[i + m]); 
 | 
                            bottom = qy;  
 | 
                        } 
 | 
  
 | 
                        j += otherps; 
 | 
                    } 
 | 
                    else { // px < qx 
 | 
                        if (fromgap && withlines) { 
 | 
                            // if we come from a gap, we just skip this point 
 | 
                            i += ps; 
 | 
                            continue; 
 | 
                        } 
 | 
                             
 | 
                        for (m = 0; m < ps; ++m) 
 | 
                            newpoints.push(points[i + m]); 
 | 
                         
 | 
                        // we might be able to interpolate a point below, 
 | 
                        // this can give us a better y 
 | 
                        if (withlines && j > 0 && otherpoints[j - otherps] != null) 
 | 
                            bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); 
 | 
  
 | 
                        newpoints[l + accumulateOffset] += bottom; 
 | 
                         
 | 
                        i += ps; 
 | 
                    } 
 | 
  
 | 
                    fromgap = false; 
 | 
                     
 | 
                    if (l != newpoints.length && withbottom) 
 | 
                        newpoints[l + 2] += bottom; 
 | 
                } 
 | 
  
 | 
                // maintain the line steps invariant 
 | 
                if (withsteps && l != newpoints.length && l > 0 
 | 
                    && newpoints[l] != null 
 | 
                    && newpoints[l] != newpoints[l - ps] 
 | 
                    && newpoints[l + 1] != newpoints[l - ps + 1]) { 
 | 
                    for (m = 0; m < ps; ++m) 
 | 
                        newpoints[l + ps + m] = newpoints[l + m]; 
 | 
                    newpoints[l + 1] = newpoints[l - ps + 1]; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            datapoints.points = newpoints; 
 | 
        } 
 | 
         
 | 
        plot.hooks.processDatapoints.push(stackData); 
 | 
    } 
 | 
     
 | 
    $.plot.plugins.push({ 
 | 
        init: init, 
 | 
        options: options, 
 | 
        name: 'stack', 
 | 
        version: '1.2' 
 | 
    }); 
 | 
})(jQuery); 
 |