Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / web / public_php / ams / js / jquery.flot.stack.js
bloba31d5dc9b5863a68b59a3357b8c1ab6be4b9b863
1 /*
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
17   series: {
18     stack: null or true or key (number/string)
19   }
21 or specify it for a specific series
23   $.plot($("#placeholder"), [{ data: [ ... ], stack: true }])
24   
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).
34 (function ($) {
35     var options = {
36         series: { stack: null } // or number/string
37     };
38     
39     function init(plot) {
40         function findMatchingSeries(s, allseries) {
41             var res = null
42             for (var i = 0; i < allseries.length; ++i) {
43                 if (s == allseries[i])
44                     break;
45                 
46                 if (allseries[i].stack == s.stack)
47                     res = allseries[i];
48             }
49             
50             return res;
51         }
52         
53         function stackData(plot, s, datapoints) {
54             if (s.stack == null)
55                 return;
57             var other = findMatchingSeries(s, plot.getData());
58             if (!other)
59                 return;
61             var ps = datapoints.pointsize,
62                 points = datapoints.points,
63                 otherps = other.datapoints.pointsize,
64                 otherpoints = other.datapoints.points,
65                 newpoints = [],
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,
71                 fromgap = true,
72                 keyOffset = horizontal ? 1 : 0,
73                 accumulateOffset = horizontal ? 0 : 1,
74                 i = 0, j = 0, l;
76             while (true) {
77                 if (i >= points.length)
78                     break;
80                 l = newpoints.length;
82                 if (points[i] == null) {
83                     // copy gaps
84                     for (m = 0; m < ps; ++m)
85                         newpoints.push(points[i + m]);
86                     i += ps;
87                 }
88                 else if (j >= otherpoints.length) {
89                     // for lines, we can't use the rest of the points
90                     if (!withlines) {
91                         for (m = 0; m < ps; ++m)
92                             newpoints.push(points[i + m]);
93                     }
94                     i += ps;
95                 }
96                 else if (otherpoints[j] == null) {
97                     // oops, got a gap
98                     for (m = 0; m < ps; ++m)
99                         newpoints.push(null);
100                     fromgap = true;
101                     j += otherps;
102                 }
103                 else {
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];
109                     bottom = 0;
111                     if (px == qx) {
112                         for (m = 0; m < ps; ++m)
113                             newpoints.push(points[i + m]);
115                         newpoints[l + accumulateOffset] += qy;
116                         bottom = qy;
117                         
118                         i += ps;
119                         j += otherps;
120                     }
121                     else if (px > qx) {
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);
126                             newpoints.push(qx);
127                             newpoints.push(intery + qy);
128                             for (m = 2; m < ps; ++m)
129                                 newpoints.push(points[i + m]);
130                             bottom = qy; 
131                         }
133                         j += otherps;
134                     }
135                     else { // px < qx
136                         if (fromgap && withlines) {
137                             // if we come from a gap, we just skip this point
138                             i += ps;
139                             continue;
140                         }
141                             
142                         for (m = 0; m < ps; ++m)
143                             newpoints.push(points[i + m]);
144                         
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;
151                         
152                         i += ps;
153                     }
155                     fromgap = false;
156                     
157                     if (l != newpoints.length && withbottom)
158                         newpoints[l + 2] += bottom;
159                 }
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];
169                 }
170             }
172             datapoints.points = newpoints;
173         }
174         
175         plot.hooks.processDatapoints.push(stackData);
176     }
177     
178     $.plot.plugins.push({
179         init: init,
180         options: options,
181         name: 'stack',
182         version: '1.2'
183     });
184 })(jQuery);