UNPKG

8.03 kBJavaScriptView Raw
1/* Flot plugin for stacking data sets rather than overlaying them.
2
3Copyright (c) 2007-2014 IOLA and Ole Laursen.
4Licensed under the MIT license.
5
6The plugin assumes the data is sorted on x (or y if stacking horizontally).
7For line charts, it is assumed that if a line has an undefined gap (from a
8null point), then the line above it should have the same gap - insert zeros
9instead of "null" if you want another behaviour. This also holds for the start
10and end of the chart. Note that stacking a mix of positive and negative values
11in most instances doesn't make sense (so it looks weird).
12
13Two or more series are stacked when their "stack" attribute is set to the same
14key (which can be any number or string or just "true"). To specify the default
15stack, you can set the stack option like this:
16
17 series: {
18 stack: null/false, true, or a key (number/string)
19 }
20
21You can also specify it for a single series, like this:
22
23 $.plot( $("#placeholder"), [{
24 data: [ ... ],
25 stack: true
26 }])
27
28The 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
31Internally, the plugin modifies the datapoints in each series, adding an
32offset to the y value. For line series, extra data points are inserted through
33interpolation. If there's a second y value, it's also adjusted (e.g for bar
34charts 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]) break;
48
49 if (allseries[i].stack === s.stack) {
50 res = allseries[i];
51 }
52 }
53
54 return res;
55 }
56
57 function addBottomPoints (s, datapoints) {
58 var formattedPoints = [];
59 for (var i = 0; i < datapoints.points.length; i += 2) {
60 formattedPoints.push(datapoints.points[i]);
61 formattedPoints.push(datapoints.points[i + 1]);
62 formattedPoints.push(0);
63 }
64
65 datapoints.format.push({
66 x: false,
67 y: true,
68 number: true,
69 required: false,
70 computeRange: s.yaxis.options.autoScale !== 'none',
71 defaultValue: 0
72 });
73 datapoints.points = formattedPoints;
74 datapoints.pointsize = 3;
75 }
76
77 function stackData(plot, s, datapoints) {
78 if (s.stack == null || s.stack === false) return;
79
80 var needsBottom = s.bars.show || (s.lines.show && s.lines.fill);
81 var hasBottom = datapoints.pointsize > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y);
82 // Series data is missing bottom points - need to format
83 if (needsBottom && !hasBottom) {
84 addBottomPoints(s, datapoints);
85 }
86
87 var other = findMatchingSeries(s, plot.getData());
88 if (!other) return;
89
90 var ps = datapoints.pointsize,
91 points = datapoints.points,
92 otherps = other.datapoints.pointsize,
93 otherpoints = other.datapoints.points,
94 newpoints = [],
95 px, py, intery, qx, qy, bottom,
96 withlines = s.lines.show,
97 horizontal = s.bars.horizontal,
98 withsteps = withlines && s.lines.steps,
99 fromgap = true,
100 keyOffset = horizontal ? 1 : 0,
101 accumulateOffset = horizontal ? 0 : 1,
102 i = 0, j = 0, l, m;
103
104 while (true) {
105 if (i >= points.length) break;
106
107 l = newpoints.length;
108
109 if (points[i] == null) {
110 // copy gaps
111 for (m = 0; m < ps; ++m) {
112 newpoints.push(points[i + m]);
113 }
114
115 i += ps;
116 } else if (j >= otherpoints.length) {
117 // for lines, we can't use the rest of the points
118 if (!withlines) {
119 for (m = 0; m < ps; ++m) {
120 newpoints.push(points[i + m]);
121 }
122 }
123
124 i += ps;
125 } else if (otherpoints[j] == null) {
126 // oops, got a gap
127 for (m = 0; m < ps; ++m) {
128 newpoints.push(null);
129 }
130
131 fromgap = true;
132 j += otherps;
133 } else {
134 // cases where we actually got two points
135 px = points[i + keyOffset];
136 py = points[i + accumulateOffset];
137 qx = otherpoints[j + keyOffset];
138 qy = otherpoints[j + accumulateOffset];
139 bottom = 0;
140
141 if (px === qx) {
142 for (m = 0; m < ps; ++m) {
143 newpoints.push(points[i + m]);
144 }
145
146 newpoints[l + accumulateOffset] += qy;
147 bottom = qy;
148
149 i += ps;
150 j += otherps;
151 } else if (px > qx) {
152 // we got past point below, might need to
153 // insert interpolated extra point
154 if (withlines && i > 0 && points[i - ps] != null) {
155 intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
156 newpoints.push(qx);
157 newpoints.push(intery + qy);
158 for (m = 2; m < ps; ++m) {
159 newpoints.push(points[i + m]);
160 }
161
162 bottom = qy;
163 }
164
165 j += otherps;
166 } else { // px < qx
167 if (fromgap && withlines) {
168 // if we come from a gap, we just skip this point
169 i += ps;
170 continue;
171 }
172
173 for (m = 0; m < ps; ++m) {
174 newpoints.push(points[i + m]);
175 }
176
177 // we might be able to interpolate a point below,
178 // this can give us a better y
179 if (withlines && j > 0 && otherpoints[j - otherps] != null) {
180 bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
181 }
182
183 newpoints[l + accumulateOffset] += bottom;
184
185 i += ps;
186 }
187
188 fromgap = false;
189
190 if (l !== newpoints.length && needsBottom) {
191 newpoints[l + 2] += bottom;
192 }
193 }
194
195 // maintain the line steps invariant
196 if (withsteps && l !== newpoints.length && l > 0 &&
197 newpoints[l] !== null &&
198 newpoints[l] !== newpoints[l - ps] &&
199 newpoints[l + 1] !== newpoints[l - ps + 1]) {
200 for (m = 0; m < ps; ++m) {
201 newpoints[l + ps + m] = newpoints[l + m];
202 }
203
204 newpoints[l + 1] = newpoints[l - ps + 1];
205 }
206 }
207
208 datapoints.points = newpoints;
209 }
210
211 plot.hooks.processDatapoints.push(stackData);
212 }
213
214 $.plot.plugins.push({
215 init: init,
216 options: options,
217 name: 'stack',
218 version: '1.2'
219 });
220})(jQuery);