Project

General

Profile

1
/* Flot plugin for stacking data sets rather than overlyaing them.
2

    
3
Copyright (c) 2007-2013 IOLA and Ole Laursen.
4
Licensed under the MIT license.
5

    
6
The plugin assumes the data is sorted on x (or y if stacking horizontally).
7
For line charts, it is assumed that if a line has an undefined gap (from a
8
null point), then the line above it should have the same gap - insert zeros
9
instead of "null" if you want another behaviour. This also holds for the start
10
and end of the chart. Note that stacking a mix of positive and negative values
11
in most instances doesn't make sense (so it looks weird).
12

    
13
Two or more series are stacked when their "stack" attribute is set to the same
14
key (which can be any number or string or just "true"). To specify the default
15
stack, you can set the stack option like this:
16

    
17
	series: {
18
		stack: null/false, true, or a key (number/string)
19
	}
20

    
21
You can also specify it for a single series, like this:
22

    
23
	$.plot( $("#placeholder"), [{
24
		data: [ ... ],
25
		stack: true
26
	}])
27

    
28
The stacking order is determined by the order of the data series in the array
29
(later series end up on top of the previous).
30

    
31
Internally, the plugin modifies the datapoints in each series, adding an
32
offset to the y value. For line series, extra data points are inserted through
33
interpolation. If there's a second y value, it's also adjusted (e.g for bar
34
charts or filled areas).
35

    
36
*/
37

    
38
(function ($) {
39
    var options = {
40
        series: { stack: null } // or number/string
41
    };
42
    
43
    function init(plot) {
44
        function findMatchingSeries(s, allseries) {
45
            var res = null;
46
            for (var i = 0; i < allseries.length; ++i) {
47
                if (s == allseries[i])
48
                    break;
49
                
50
                if (allseries[i].stack == s.stack)
51
                    res = allseries[i];
52
            }
53
            
54
            return res;
55
        }
56
        
57
        function stackData(plot, s, datapoints) {
58
            if (s.stack == null || s.stack === false)
59
                return;
60

    
61
            var other = findMatchingSeries(s, plot.getData());
62
            if (!other)
63
                return;
64

    
65
            var ps = datapoints.pointsize,
66
                points = datapoints.points,
67
                otherps = other.datapoints.pointsize,
68
                otherpoints = other.datapoints.points,
69
                newpoints = [],
70
                px, py, intery, qx, qy, bottom,
71
                withlines = s.lines.show,
72
                horizontal = s.bars.horizontal,
73
                withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
74
                withsteps = withlines && s.lines.steps,
75
                fromgap = true,
76
                keyOffset = horizontal ? 1 : 0,
77
                accumulateOffset = horizontal ? 0 : 1,
78
                i = 0, j = 0, l, m;
79

    
80
            while (true) {
81
                if (i >= points.length)
82
                    break;
83

    
84
                l = newpoints.length;
85

    
86
                if (points[i] == null) {
87
                    // copy gaps
88
                    for (m = 0; m < ps; ++m)
89
                        newpoints.push(points[i + m]);
90
                    i += ps;
91
                }
92
                else if (j >= otherpoints.length) {
93
                    // for lines, we can't use the rest of the points
94
                    if (!withlines) {
95
                        for (m = 0; m < ps; ++m)
96
                            newpoints.push(points[i + m]);
97
                    }
98
                    i += ps;
99
                }
100
                else if (otherpoints[j] == null) {
101
                    // oops, got a gap
102
                    for (m = 0; m < ps; ++m)
103
                        newpoints.push(null);
104
                    fromgap = true;
105
                    j += otherps;
106
                }
107
                else {
108
                    // cases where we actually got two points
109
                    px = points[i + keyOffset];
110
                    py = points[i + accumulateOffset];
111
                    qx = otherpoints[j + keyOffset];
112
                    qy = otherpoints[j + accumulateOffset];
113
                    bottom = 0;
114

    
115
                    if (px == qx) {
116
                        for (m = 0; m < ps; ++m)
117
                            newpoints.push(points[i + m]);
118

    
119
                        newpoints[l + accumulateOffset] += qy;
120
                        bottom = qy;
121
                        
122
                        i += ps;
123
                        j += otherps;
124
                    }
125
                    else if (px > qx) {
126
                        // we got past point below, might need to
127
                        // insert interpolated extra point
128
                        if (withlines && i > 0 && points[i - ps] != null) {
129
                            intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
130
                            newpoints.push(qx);
131
                            newpoints.push(intery + qy);
132
                            for (m = 2; m < ps; ++m)
133
                                newpoints.push(points[i + m]);
134
                            bottom = qy; 
135
                        }
136

    
137
                        j += otherps;
138
                    }
139
                    else { // px < qx
140
                        if (fromgap && withlines) {
141
                            // if we come from a gap, we just skip this point
142
                            i += ps;
143
                            continue;
144
                        }
145
                            
146
                        for (m = 0; m < ps; ++m)
147
                            newpoints.push(points[i + m]);
148
                        
149
                        // we might be able to interpolate a point below,
150
                        // this can give us a better y
151
                        if (withlines && j > 0 && otherpoints[j - otherps] != null)
152
                            bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
153

    
154
                        newpoints[l + accumulateOffset] += bottom;
155
                        
156
                        i += ps;
157
                    }
158

    
159
                    fromgap = false;
160
                    
161
                    if (l != newpoints.length && withbottom)
162
                        newpoints[l + 2] += bottom;
163
                }
164

    
165
                // maintain the line steps invariant
166
                if (withsteps && l != newpoints.length && l > 0
167
                    && newpoints[l] != null
168
                    && newpoints[l] != newpoints[l - ps]
169
                    && newpoints[l + 1] != newpoints[l - ps + 1]) {
170
                    for (m = 0; m < ps; ++m)
171
                        newpoints[l + ps + m] = newpoints[l + m];
172
                    newpoints[l + 1] = newpoints[l - ps + 1];
173
                }
174
            }
175

    
176
            datapoints.points = newpoints;
177
        }
178
        
179
        plot.hooks.processDatapoints.push(stackData);
180
    }
181
    
182
    $.plot.plugins.push({
183
        init: init,
184
        options: options,
185
        name: 'stack',
186
        version: '1.2'
187
    });
188
})(jQuery);
(26-26/34)