OLD | NEW |
(Empty) | |
| 1 /* Flot plugin for stacking data sets rather than overlyaing them. |
| 2 |
| 3 Copyright (c) 2007-2014 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 : da
tapoints.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] - p
y) * (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] != nu
ll) |
| 152 bottom = qy + (otherpoints[j - otherps + accumulateO
ffset] - 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); |
OLD | NEW |