Commit c3a34cd6 authored by olau@iola.dk's avatar olau@iola.dk

Support for specifying a bottom for each point for line charts when

filling them, this means that an arbitrary bottom can be used
instead of just the x axis (based on patches patiently provided by
Roman V. Prikhodchenko).

New fillbetween plugin that can compute a bottom for a series from
another series, useful for filling areas between lines (see new
example percentiles.html for a use case).

More predictable handling of gaps for the stacking plugin, now all
undefined ranges are skipped.

Fixed problem with plugins adding options to the series objects.

Fixed a problem introduced in 0.6 with specifying a gradient with {
brightness: x, opacity: y }.


git-svn-id: https://flot.googlecode.com/svn/trunk@229 1e0a6537-2640-0410-bfb7-f154510ff394
parent 7cbc1416
...@@ -52,8 +52,9 @@ drawing. As a special case, a null value for lines is interpreted as a ...@@ -52,8 +52,9 @@ drawing. As a special case, a null value for lines is interpreted as a
line segment end, i.e. the points before and after the null value are line segment end, i.e. the points before and after the null value are
not connected. not connected.
Lines and points take two coordinates. For bars, you can specify a Lines and points take two coordinates. For filled lines and bars, you
third coordinate which is the bottom of the bar (defaults to 0). can specify a third coordinate which is the bottom of the filled
area/bar (defaults to 0).
The format of a single series object is as follows: The format of a single series object is as follows:
...@@ -479,7 +480,7 @@ The most important options are "lines", "points" and "bars" that ...@@ -479,7 +480,7 @@ The most important options are "lines", "points" and "bars" that
specify whether and how lines, points and bars should be shown for specify whether and how lines, points and bars should be shown for
each data series. In case you don't specify anything at all, Flot will each data series. In case you don't specify anything at all, Flot will
default to showing lines (you can turn this off with default to showing lines (you can turn this off with
lines: { show: false}). You can specify the various types lines: { show: false }). You can specify the various types
independently of each other, and Flot will happily draw each of them independently of each other, and Flot will happily draw each of them
in turn (this is probably only useful for lines and points), e.g. in turn (this is probably only useful for lines and points), e.g.
......
Flot x.x Flot x.x
-------- --------
Changes:
- Support for specifying a bottom for each point for line charts when
filling them, this means that an arbitrary bottom can be used
instead of just the x axis (based on patches patiently provided by
Roman V. Prikhodchenko).
- New fillbetween plugin that can compute a bottom for a series from
another series, useful for filling areas between lines (see new
example percentiles.html for a use case).
- More predictable handling of gaps for the stacking plugin, now all
undefined ranges are skipped.
Bug fixes: Bug fixes:
...@@ -8,7 +20,9 @@ Bug fixes: ...@@ -8,7 +20,9 @@ Bug fixes:
(reported by ragingchikn, issue 242). (reported by ragingchikn, issue 242).
- Fixed problem with ticks and the border (based on patch from - Fixed problem with ticks and the border (based on patch from
ultimatehustler69, issue 236). ultimatehustler69, issue 236).
- Fixed problem with plugins adding options to the series objects.
- Fixed a problem introduced in 0.6 with specifying a gradient with {
brightness: x, opacity: y }.
Flot 0.6 Flot 0.6
-------- --------
......
...@@ -29,13 +29,14 @@ ...@@ -29,13 +29,14 @@
<li><a href="navigate.html">Panning and zooming</a> (with navigation plugin)</li> <li><a href="navigate.html">Panning and zooming</a> (with navigation plugin)</li>
</ul> </ul>
<p>Some more esoteric features:</p> <p>Various features:</p>
<ul> <ul>
<li><a href="time.html">Plotting time series</a> and <a href="visitors.html">visitors per day with zooming and weekends</a> (with selection plugin)</li> <li><a href="time.html">Plotting time series</a> and <a href="visitors.html">visitors per day with zooming and weekends</a> (with selection plugin)</li>
<li><a href="dual-axis.html">Dual axis support</a></li> <li><a href="dual-axis.html">Dual axis support</a></li>
<li><a href="thresholding.html">Thresholding the data</a> (with threshold plugin)</li> <li><a href="thresholding.html">Thresholding the data</a> (with threshold plugin)</li>
<li><a href="stacking.html">Stacked charts</a> (with stacking plugin)</li> <li><a href="stacking.html">Stacked charts</a> (with stacking plugin)</li>
<li><a href="percentiles.html">Using filled areas to plot percentiles</a> (with fillbetween plugin)</li>
<li><a href="tracking.html">Tracking curves with crosshair</a> (with crosshair plugin)</li> <li><a href="tracking.html">Tracking curves with crosshair</a> (with crosshair plugin)</li>
<li><a href="image.html">Plotting prerendered images</a> (with image plugin)</li> <li><a href="image.html">Plotting prerendered images</a> (with image plugin)</li>
<li><a href="pie.html">Pie charts</a> (with pie plugin)</li> <li><a href="pie.html">Pie charts</a> (with pie plugin)</li>
......
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Flot Examples</title>
<link href="layout.css" rel="stylesheet" type="text/css"></link>
<!--[if IE]><script language="javascript" type="text/javascript" src="../excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="../jquery.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
<script language="javascript" type="text/javascript" src="../jquery.flot.fillbetween.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
<div id="placeholder" style="width:600px;height:400px;"></div>
<p>Height in centimeters of individuals from the US (2003-2006) as function of
age in years (source: <a href="http://www.cdc.gov/nchs/data/nhsr/nhsr010.pdf">CDC</a>).
The 15%-85%, 25%-75% and 50% percentiles are indicated.</p>
<p>For each point of a filled curve, you can specify an arbitrary
bottom. As this example illustrates, this can be useful for
plotting percentiles. If you have the data sets available without
appropriate fill bottoms, you can use the fillbetween plugin to
compute the data point bottoms automatically.</p>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var males = {'15%': [[2, 88.0], [3, 93.3], [4, 102.0], [5, 108.5], [6, 115.7], [7, 115.6], [8, 124.6], [9, 130.3], [10, 134.3], [11, 141.4], [12, 146.5], [13, 151.7], [14, 159.9], [15, 165.4], [16, 167.8], [17, 168.7], [18, 169.5], [19, 168.0]], '90%': [[2, 96.8], [3, 105.2], [4, 113.9], [5, 120.8], [6, 127.0], [7, 133.1], [8, 139.1], [9, 143.9], [10, 151.3], [11, 161.1], [12, 164.8], [13, 173.5], [14, 179.0], [15, 182.0], [16, 186.9], [17, 185.2], [18, 186.3], [19, 186.6]], '25%': [[2, 89.2], [3, 94.9], [4, 104.4], [5, 111.4], [6, 117.5], [7, 120.2], [8, 127.1], [9, 132.9], [10, 136.8], [11, 144.4], [12, 149.5], [13, 154.1], [14, 163.1], [15, 169.2], [16, 170.4], [17, 171.2], [18, 172.4], [19, 170.8]], '10%': [[2, 86.9], [3, 92.6], [4, 99.9], [5, 107.0], [6, 114.0], [7, 113.5], [8, 123.6], [9, 129.2], [10, 133.0], [11, 140.6], [12, 145.2], [13, 149.7], [14, 158.4], [15, 163.5], [16, 166.9], [17, 167.5], [18, 167.1], [19, 165.3]], 'mean': [[2, 91.9], [3, 98.5], [4, 107.1], [5, 114.4], [6, 120.6], [7, 124.7], [8, 131.1], [9, 136.8], [10, 142.3], [11, 150.0], [12, 154.7], [13, 161.9], [14, 168.7], [15, 173.6], [16, 175.9], [17, 176.6], [18, 176.8], [19, 176.7]], '75%': [[2, 94.5], [3, 102.1], [4, 110.8], [5, 117.9], [6, 124.0], [7, 129.3], [8, 134.6], [9, 141.4], [10, 147.0], [11, 156.1], [12, 160.3], [13, 168.3], [14, 174.7], [15, 178.0], [16, 180.2], [17, 181.7], [18, 181.3], [19, 182.5]], '85%': [[2, 96.2], [3, 103.8], [4, 111.8], [5, 119.6], [6, 125.6], [7, 131.5], [8, 138.0], [9, 143.3], [10, 149.3], [11, 159.8], [12, 162.5], [13, 171.3], [14, 177.5], [15, 180.2], [16, 183.8], [17, 183.4], [18, 183.5], [19, 185.5]], '50%': [[2, 91.9], [3, 98.2], [4, 106.8], [5, 114.6], [6, 120.8], [7, 125.2], [8, 130.3], [9, 137.1], [10, 141.5], [11, 149.4], [12, 153.9], [13, 162.2], [14, 169.0], [15, 174.8], [16, 176.0], [17, 176.8], [18, 176.4], [19, 177.4]]};
var females = {'15%': [[2, 84.8], [3, 93.7], [4, 100.6], [5, 105.8], [6, 113.3], [7, 119.3], [8, 124.3], [9, 131.4], [10, 136.9], [11, 143.8], [12, 149.4], [13, 151.2], [14, 152.3], [15, 155.9], [16, 154.7], [17, 157.0], [18, 156.1], [19, 155.4]], '90%': [[2, 95.6], [3, 104.1], [4, 111.9], [5, 119.6], [6, 127.6], [7, 133.1], [8, 138.7], [9, 147.1], [10, 152.8], [11, 161.3], [12, 166.6], [13, 167.9], [14, 169.3], [15, 170.1], [16, 172.4], [17, 169.2], [18, 171.1], [19, 172.4]], '25%': [[2, 87.2], [3, 95.9], [4, 101.9], [5, 107.4], [6, 114.8], [7, 121.4], [8, 126.8], [9, 133.4], [10, 138.6], [11, 146.2], [12, 152.0], [13, 153.8], [14, 155.7], [15, 158.4], [16, 157.0], [17, 158.5], [18, 158.4], [19, 158.1]], '10%': [[2, 84.0], [3, 91.9], [4, 99.2], [5, 105.2], [6, 112.7], [7, 118.0], [8, 123.3], [9, 130.2], [10, 135.0], [11, 141.1], [12, 148.3], [13, 150.0], [14, 150.7], [15, 154.3], [16, 153.6], [17, 155.6], [18, 154.7], [19, 153.1]], 'mean': [[2, 90.2], [3, 98.3], [4, 105.2], [5, 112.2], [6, 119.0], [7, 125.8], [8, 131.3], [9, 138.6], [10, 144.2], [11, 151.3], [12, 156.7], [13, 158.6], [14, 160.5], [15, 162.1], [16, 162.9], [17, 162.2], [18, 163.0], [19, 163.1]], '75%': [[2, 93.2], [3, 101.5], [4, 107.9], [5, 116.6], [6, 122.8], [7, 129.3], [8, 135.2], [9, 143.7], [10, 148.7], [11, 156.9], [12, 160.8], [13, 163.0], [14, 165.0], [15, 165.8], [16, 168.7], [17, 166.2], [18, 167.6], [19, 168.0]], '85%': [[2, 94.5], [3, 102.8], [4, 110.4], [5, 119.0], [6, 125.7], [7, 131.5], [8, 137.9], [9, 146.0], [10, 151.3], [11, 159.9], [12, 164.0], [13, 166.5], [14, 167.5], [15, 168.5], [16, 171.5], [17, 168.0], [18, 169.8], [19, 170.3]], '50%': [[2, 90.2], [3, 98.1], [4, 105.2], [5, 111.7], [6, 118.2], [7, 125.6], [8, 130.5], [9, 138.3], [10, 143.7], [11, 151.4], [12, 156.7], [13, 157.7], [14, 161.0], [15, 162.0], [16, 162.8], [17, 162.2], [18, 162.8], [19, 163.3]]};
var dataset = [
{ label: 'Female mean', data: females['mean'], lines: { show: true }, color: "rgb(255,50,50)" },
{ id: 'f15%', data: females['15%'], lines: { show: true, lineWidth: 0, fill: false }, color: "rgb(255,50,50)" },
{ id: 'f25%', data: females['25%'], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(255,50,50)", fillBetween: 'f15%' },
{ id: 'f50%', data: females['50%'], lines: { show: true, lineWidth: 0.5, fill: 0.4, shadowSize: 0 }, color: "rgb(255,50,50)", fillBetween: 'f25%' },
{ id: 'f75%', data: females['75%'], lines: { show: true, lineWidth: 0, fill: 0.4 }, color: "rgb(255,50,50)", fillBetween: 'f50%' },
{ id: 'f85%', data: females['85%'], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(255,50,50)", fillBetween: 'f75%' },
{ label: 'Male mean', data: males['mean'], lines: { show: true }, color: "rgb(50,50,255)" },
{ id: 'm15%', data: males['15%'], lines: { show: true, lineWidth: 0, fill: false }, color: "rgb(50,50,255)" },
{ id: 'm25%', data: males['25%'], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(50,50,255)", fillBetween: 'm15%' },
{ id: 'm50%', data: males['50%'], lines: { show: true, lineWidth: 0.5, fill: 0.4, shadowSize: 0 }, color: "rgb(50,50,255)", fillBetween: 'm25%' },
{ id: 'm75%', data: males['75%'], lines: { show: true, lineWidth: 0, fill: 0.4 }, color: "rgb(50,50,255)", fillBetween: 'm50%' },
{ id: 'm85%', data: males['85%'], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(50,50,255)", fillBetween: 'm75%' }
]
$.plot($("#placeholder"), dataset, {
xaxis: { tickDecimals: 0 },
yaxis: { tickFormatter: function (v) { return v + " cm"; } },
legend: { position: 'se' }
});
});
</script>
</body>
</html>
...@@ -50,7 +50,7 @@ $(function () { ...@@ -50,7 +50,7 @@ $(function () {
$.plot($("#placeholder"), [ d1, d2, d3 ], { $.plot($("#placeholder"), [ d1, d2, d3 ], {
series: { series: {
stack: stack, stack: stack,
lines: { show: lines, steps: steps }, lines: { show: lines, fill: true, steps: steps },
bars: { show: bars, barWidth: 0.6 } bars: { show: bars, barWidth: 0.6 }
} }
}); });
......
/*
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);
...@@ -330,7 +330,7 @@ ...@@ -330,7 +330,7 @@
if (s.lines.show == null) { if (s.lines.show == null) {
var v, show = true; var v, show = true;
for (v in s) for (v in s)
if (s[v].show) { if (s[v] && s[v].show) {
show = false; show = false;
break; break;
} }
...@@ -382,7 +382,7 @@ ...@@ -382,7 +382,7 @@
format.push({ x: true, number: true, required: true }); format.push({ x: true, number: true, required: true });
format.push({ y: true, number: true, required: true }); format.push({ y: true, number: true, required: true });
if (s.bars.show) if (s.bars.show || (s.lines.show && s.lines.fill))
format.push({ y: true, number: true, required: false, defaultValue: 0 }); format.push({ y: true, number: true, required: false, defaultValue: 0 });
s.datapoints.format = format; s.datapoints.format = format;
...@@ -1364,18 +1364,40 @@ ...@@ -1364,18 +1364,40 @@
var points = datapoints.points, var points = datapoints.points,
ps = datapoints.pointsize, ps = datapoints.pointsize,
bottom = Math.min(Math.max(0, axisy.min), axisy.max), bottom = Math.min(Math.max(0, axisy.min), axisy.max),
top, lastX = 0, areaOpen = false; i = 0, top, areaOpen = false,
ypos = 1, segmentStart = 0, segmentEnd = 0;
for (var i = ps; i < points.length; i += ps) {
var x1 = points[i - ps], y1 = points[i - ps + 1], // we process each segment in two turns, first forward
x2 = points[i], y2 = points[i + 1]; // direction to sketch out top, then once we hit the
// end we go backwards to sketch the bottom
if (areaOpen && x1 != null && x2 == null) { while (true) {
// close area if (ps > 0 && i > points.length + ps)
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); break;
ctx.fill();
areaOpen = false; i += ps; // ps is negative if going backwards
continue;
var x1 = points[i - ps],
y1 = points[i - ps + ypos],
x2 = points[i], y2 = points[i + ypos];
if (areaOpen) {
if (ps > 0 && x1 != null && x2 == null) {
// at turning point
segmentEnd = i;
ps = -ps;
ypos = 2;
continue;
}
if (ps < 0 && i == segmentStart + ps) {
// done with the reverse sweep
ctx.fill();
areaOpen = false;
ps = -ps;
ypos = 1;
i = segmentStart = segmentEnd + ps;
continue;
}
} }
if (x1 == null || x2 == null) if (x1 == null || x2 == null)
...@@ -1422,22 +1444,22 @@ ...@@ -1422,22 +1444,22 @@
if (y1 >= axisy.max && y2 >= axisy.max) { if (y1 >= axisy.max && y2 >= axisy.max) {
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
lastX = x2;
continue; continue;
} }
else if (y1 <= axisy.min && y2 <= axisy.min) { else if (y1 <= axisy.min && y2 <= axisy.min) {
ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
lastX = x2;
continue; continue;
} }
// else it's a bit more complicated, there might // else it's a bit more complicated, there might
// be two rectangles and two triangles we need to fill // be a flat maxed out rectangle first, then a
// in; to find these keep track of the current x values // triangular cutout or reverse; to find these
// keep track of the current x values
var x1old = x1, x2old = x2; var x1old = x1, x2old = x2;
// and clip the y values, without shortcutting // clip the y values, without shortcutting, we
// go through all cases in turn
// clip with ymin // clip with ymin
if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
...@@ -1459,43 +1481,27 @@ ...@@ -1459,43 +1481,27 @@
y2 = axisy.max; y2 = axisy.max;
} }
// if the x value was changed we got a rectangle // if the x value was changed we got a rectangle
// to fill // to fill
if (x1 != x1old) { if (x1 != x1old) {
if (y1 <= axisy.min) ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
top = axisy.min; // it goes to (x1, y1), but we fill that below
else
top = axisy.max;
ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
} }
// fill the triangles // fill triangular section, this sometimes result
// in redundant points if (x1, y1) hasn't changed
// from previous line to, but we just ignore that
ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
// fill the other rectangle if it's there // fill the other rectangle if it's there
if (x2 != x2old) { if (x2 != x2old) {
if (y2 <= axisy.min) ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
top = axisy.min; ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
else
top = axisy.max;
ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
} }
lastX = Math.max(x2, x2old);
}
if (areaOpen) {
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
ctx.fill();
} }
} }
ctx.save(); ctx.save();
ctx.translate(plotOffset.left, plotOffset.top); ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineJoin = "round"; ctx.lineJoin = "round";
...@@ -2039,9 +2045,12 @@ ...@@ -2039,9 +2045,12 @@
for (var i = 0, l = spec.colors.length; i < l; ++i) { for (var i = 0, l = spec.colors.length; i < l; ++i) {
var c = spec.colors[i]; var c = spec.colors[i];
if (typeof c != "string") { if (typeof c != "string") {
c = $.color.parse(defaultColor).scale('rgb', c.brightness); var co = $.color.parse(defaultColor);
c.a *= c.opacity; if (c.brightness != null)
c = c.toString(); co = co.scale('rgb', c.brightness)
if (c.opacity != null)
co.a *= c.opacity;
c = co.toString();
} }
gradient.addColorStop(i / (l - 1), c); gradient.addColorStop(i / (l - 1), c);
} }
......
/* /*
Flot plugin for stacking data sets, i.e. putting them on top of each Flot plugin for stacking data sets, i.e. putting them on top of each
other, for accumulative graphs. Note that the plugin assumes the data other, for accumulative graphs.
is sorted on x. Also note that stacking a mix of positive and negative
The plugin assumes the data is sorted on x. For line charts, it is
assumed that if a line has an undefined gap (from a null point), then
the line above it should have the same gap - insert zeros instead of
"null" if you want another behaviour. This also holds for the start
and end of the chart. Note that stacking a mix of positive and negative
values in most instances doesn't make sense (so it looks weird). values in most instances doesn't make sense (so it looks weird).
Two or more series are stacked when their "stack" attribute is set to Two or more series are stacked when their "stack" attribute is set to
...@@ -14,15 +19,15 @@ specify the default stack, you can set ...@@ -14,15 +19,15 @@ specify the default stack, you can set
or specify it for a specific series or specify it for a specific series
$.plot($("#placeholder"), [{ data: [ ... ], stack: true ]) $.plot($("#placeholder"), [{ data: [ ... ], stack: true }])
The stacking order is determined by the order of the data series in The stacking order is determined by the order of the data series in
the array (later series end up on top of the previous). the array (later series end up on top of the previous).
Internally, the plugin modifies the datapoints in each series, adding Internally, the plugin modifies the datapoints in each series, adding
an offset to the y value. For line series, extra data points are an offset to the y value. For line series, extra data points are
inserted through interpolation. For bar charts, the second y value is inserted through interpolation. If there's a second y value, it's also
also adjusted. adjusted (e.g for bar charts or filled areas).
*/ */
(function ($) { (function ($) {
...@@ -51,15 +56,17 @@ also adjusted. ...@@ -51,15 +56,17 @@ also adjusted.
var other = findMatchingSeries(s, plot.getData()); var other = findMatchingSeries(s, plot.getData());
if (!other) if (!other)
return; return;
var ps = datapoints.pointsize, var ps = datapoints.pointsize,
points = datapoints.points, points = datapoints.points,
otherps = other.datapoints.pointsize, otherps = other.datapoints.pointsize,
otherpoints = other.datapoints.points, otherpoints = other.datapoints.points,
newpoints = [], newpoints = [],
px, py, intery, qx, qy, bottom, px, py, intery, qx, qy, bottom,
withlines = s.lines.show, withbars = s.bars.show, withlines = s.lines.show,
withbottom = ps > 2 && datapoints.format[2].y,
withsteps = withlines && s.lines.steps, withsteps = withlines && s.lines.steps,
fromgap = true,
i = 0, j = 0, l; i = 0, j = 0, l;
while (true) { while (true) {
...@@ -68,14 +75,27 @@ also adjusted. ...@@ -68,14 +75,27 @@ also adjusted.
l = newpoints.length; l = newpoints.length;
if (j >= otherpoints.length if (points[i] == null) {
|| otherpoints[j] == null // copy gaps
|| points[i] == null) {
// degenerate cases
for (m = 0; m < ps; ++m) for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]); newpoints.push(points[i + m]);
i += ps; 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 { else {
// cases where we actually got two points // cases where we actually got two points
px = points[i]; px = points[i];
...@@ -108,21 +128,29 @@ also adjusted. ...@@ -108,21 +128,29 @@ also adjusted.
j += otherps; j += otherps;
} }
else { 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) for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]); newpoints.push(points[i + m]);
// we might be able to interpolate a point below, // we might be able to interpolate a point below,
// this can give us a better y // this can give us a better y
if (withlines && j > 0 && otherpoints[j - ps] != null) if (withlines && j > 0 && otherpoints[j - otherps] != null)
bottom = qy + (otherpoints[j - ps + 1] - qy) * (px - qx) / (otherpoints[j - ps] - qx); bottom = qy + (otherpoints[j - otherps + 1] - qy) * (px - qx) / (otherpoints[j - otherps] - qx);
newpoints[l + 1] += bottom; newpoints[l + 1] += bottom;
i += ps; i += ps;
} }
fromgap = false;
if (l != newpoints.length && withbars) if (l != newpoints.length && withbottom)
newpoints[l + 2] += bottom; newpoints[l + 2] += bottom;
} }
...@@ -136,7 +164,7 @@ also adjusted. ...@@ -136,7 +164,7 @@ also adjusted.
newpoints[l + 1] = newpoints[l - ps + 1]; newpoints[l + 1] = newpoints[l - ps + 1];
} }
} }
datapoints.points = newpoints; datapoints.points = newpoints;
} }
...@@ -147,6 +175,6 @@ also adjusted. ...@@ -147,6 +175,6 @@ also adjusted.
init: init, init: init,
options: options, options: options,
name: 'stack', name: 'stack',
version: '1.0' version: '1.1'
}); });
})(jQuery); })(jQuery);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment