| /* |
| Flot plugin for computing bottoms for filled line and bar charts. |
| |
| The case: you've got two series that you want to fill the area |
| between. In Flot terms, you need to use one as the fill bottom of the |
| other. You can specify the bottom of each data point as the third |
| coordinate manually, or you can use this plugin to compute it for you. |
| |
| In order to name the other series, you need to give it an id, like this |
| |
| var dataset = [ |
| { data: [ ... ], id: "foo" } , // use default bottom |
| { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom |
| ]; |
| |
| $.plot($("#placeholder"), dataset, { line: { show: true, fill: true }}); |
| |
| As a convenience, if the id given is a number that doesn't appear as |
| an id in the series, it is interpreted as the index in the array |
| instead (so fillBetween: 0 can also mean the first series). |
| |
| Internally, the plugin modifies the datapoints in each series. For |
| line series, extra data points might be inserted through |
| interpolation. Note that at points where the bottom line is not |
| defined (due to a null point or start/end of line), the current line |
| will show a gap too. The algorithm comes from the jquery.flot.stack.js |
| plugin, possibly some code could be shared. |
| */ |
| |
| (function ($) { |
| var options = { |
| series: { fillBetween: null } // or number |
| }; |
| |
| function init(plot) { |
| function findBottomSeries(s, allseries) { |
| var i; |
| for (i = 0; i < allseries.length; ++i) { |
| if (allseries[i].id == s.fillBetween) |
| return allseries[i]; |
| } |
| |
| if (typeof s.fillBetween == "number") { |
| i = s.fillBetween; |
| |
| if (i < 0 || i >= allseries.length) |
| return null; |
| |
| return allseries[i]; |
| } |
| |
| return null; |
| } |
| |
| function computeFillBottoms(plot, s, datapoints) { |
| if (s.fillBetween == null) |
| return; |
| |
| var other = findBottomSeries(s, plot.getData()); |
| if (!other) |
| return; |
| |
| var ps = datapoints.pointsize, |
| points = datapoints.points, |
| otherps = other.datapoints.pointsize, |
| otherpoints = other.datapoints.points, |
| newpoints = [], |
| px, py, intery, qx, qy, bottom, |
| withlines = s.lines.show, |
| withbottom = ps > 2 && datapoints.format[2].y, |
| withsteps = withlines && s.lines.steps, |
| fromgap = true, |
| i = 0, j = 0, l; |
| |
| while (true) { |
| if (i >= points.length) |
| break; |
| |
| l = newpoints.length; |
| |
| if (points[i] == null) { |
| // copy gaps |
| for (m = 0; m < ps; ++m) |
| newpoints.push(points[i + m]); |
| i += ps; |
| } |
| else if (j >= otherpoints.length) { |
| // for lines, we can't use the rest of the points |
| if (!withlines) { |
| for (m = 0; m < ps; ++m) |
| newpoints.push(points[i + m]); |
| } |
| i += ps; |
| } |
| else if (otherpoints[j] == null) { |
| // oops, got a gap |
| for (m = 0; m < ps; ++m) |
| newpoints.push(null); |
| fromgap = true; |
| j += otherps; |
| } |
| else { |
| // cases where we actually got two points |
| px = points[i]; |
| py = points[i + 1]; |
| qx = otherpoints[j]; |
| qy = otherpoints[j + 1]; |
| bottom = 0; |
| |
| if (px == qx) { |
| for (m = 0; m < ps; ++m) |
| newpoints.push(points[i + m]); |
| |
| //newpoints[l + 1] += qy; |
| bottom = qy; |
| |
| i += ps; |
| j += otherps; |
| } |
| else if (px > qx) { |
| // we got past point below, might need to |
| // insert interpolated extra point |
| if (withlines && i > 0 && points[i - ps] != null) { |
| intery = py + (points[i - ps + 1] - py) * (qx - px) / (points[i - ps] - px); |
| newpoints.push(qx); |
| newpoints.push(intery) |
| for (m = 2; m < ps; ++m) |
| newpoints.push(points[i + m]); |
| bottom = qy; |
| } |
| |
| j += otherps; |
| } |
| else { // px < qx |
| if (fromgap && withlines) { |
| // if we come from a gap, we just skip this point |
| i += ps; |
| continue; |
| } |
| |
| for (m = 0; m < ps; ++m) |
| newpoints.push(points[i + m]); |
| |
| // we might be able to interpolate a point below, |
| // this can give us a better y |
| if (withlines && j > 0 && otherpoints[j - otherps] != null) |
| bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx); |
| |
| //newpoints[l + 1] += bottom; |
| |
| i += ps; |
| } |
| |
| fromgap = false; |
| |
| if (l != newpoints.length && withbottom) |
| newpoints[l + 2] = bottom; |
| } |
| |
| // maintain the line steps invariant |
| if (withsteps && l != newpoints.length && l > 0 |
| && newpoints[l] != null |
| && newpoints[l] != newpoints[l - ps] |
| && newpoints[l + 1] != newpoints[l - ps + 1]) { |
| for (m = 0; m < ps; ++m) |
| newpoints[l + ps + m] = newpoints[l + m]; |
| newpoints[l + 1] = newpoints[l - ps + 1]; |
| } |
| } |
| |
| datapoints.points = newpoints; |
| } |
| |
| plot.hooks.processDatapoints.push(computeFillBottoms); |
| } |
| |
| $.plot.plugins.push({ |
| init: init, |
| options: options, |
| name: 'fillbetween', |
| version: '1.0' |
| }); |
| })(jQuery); |