2 Flot plugin for stacking data sets, i.e. putting them on top of each
3 other, for accumulative graphs.
5 The plugin assumes the data is sorted on x (or y if stacking
6 horizontally). For line charts, it is assumed that if a line has an
7 undefined gap (from a null point), then the line above it should have
8 the same gap - insert zeros instead of "null" if you want another
9 behaviour. This also holds for the start and end of the chart. Note
10 that stacking a mix of positive and negative values in most instances
11 doesn't make sense (so it looks weird).
13 Two or more series are stacked when their "stack" attribute is set to
14 the same key (which can be any number or string or just "true"). To
15 specify the default stack, you can set
18 stack: null or true or key (number/string)
21 or specify it for a specific series
23 $.plot($("#placeholder"), [{ data: [ ... ], stack: true }])
25 The stacking order is determined by the order of the data series in
26 the array (later series end up on top of the previous).
28 Internally, the plugin modifies the datapoints in each series, adding
29 an offset to the y value. For line series, extra data points are
30 inserted through interpolation. If there's a second y value, it's also
31 adjusted (e.g for bar charts or filled areas).
36 series: { stack: null } // or number/string
40 function findMatchingSeries(s, allseries) {
42 for (var i = 0; i < allseries.length; ++i) {
43 if (s == allseries[i])
46 if (allseries[i].stack == s.stack)
53 function stackData(plot, s, datapoints) {
57 var other = findMatchingSeries(s, plot.getData());
61 var ps = datapoints.pointsize,
62 points = datapoints.points,
63 otherps = other.datapoints.pointsize,
64 otherpoints = other.datapoints.points,
66 px, py, intery, qx, qy, bottom,
67 withlines = s.lines.show,
68 horizontal = s.bars.horizontal,
69 withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
70 withsteps = withlines && s.lines.steps,
72 keyOffset = horizontal ? 1 : 0,
73 accumulateOffset = horizontal ? 0 : 1,
77 if (i >= points.length)
82 if (points[i] == null) {
84 for (m = 0; m < ps; ++m)
85 newpoints.push(points[i + m]);
88 else if (j >= otherpoints.length) {
89 // for lines, we can't use the rest of the points
91 for (m = 0; m < ps; ++m)
92 newpoints.push(points[i + m]);
96 else if (otherpoints[j] == null) {
98 for (m = 0; m < ps; ++m)
104 // cases where we actually got two points
105 px = points[i + keyOffset];
106 py = points[i + accumulateOffset];
107 qx = otherpoints[j + keyOffset];
108 qy = otherpoints[j + accumulateOffset];
112 for (m = 0; m < ps; ++m)
113 newpoints.push(points[i + m]);
115 newpoints[l + accumulateOffset] += qy;
122 // we got past point below, might need to
123 // insert interpolated extra point
124 if (withlines && i > 0 && points[i - ps] != null) {
125 intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
127 newpoints.push(intery + qy);
128 for (m = 2; m < ps; ++m)
129 newpoints.push(points[i + m]);
136 if (fromgap && withlines) {
137 // if we come from a gap, we just skip this point
142 for (m = 0; m < ps; ++m)
143 newpoints.push(points[i + m]);
145 // we might be able to interpolate a point below,
146 // this can give us a better y
147 if (withlines && j > 0 && otherpoints[j - otherps] != null)
148 bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
150 newpoints[l + accumulateOffset] += bottom;
157 if (l != newpoints.length && withbottom)
158 newpoints[l + 2] += bottom;
161 // maintain the line steps invariant
162 if (withsteps && l != newpoints.length && l > 0
163 && newpoints[l] != null
164 && newpoints[l] != newpoints[l - ps]
165 && newpoints[l + 1] != newpoints[l - ps + 1]) {
166 for (m = 0; m < ps; ++m)
167 newpoints[l + ps + m] = newpoints[l + m];
168 newpoints[l + 1] = newpoints[l - ps + 1];
172 datapoints.points = newpoints;
175 plot.hooks.processDatapoints.push(stackData);
178 $.plot.plugins.push({