]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
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); |