Commit 89ce1ca9 authored by olau@iola.dk's avatar olau@iola.dk

Introduce step-wise line charts, extending the data model to have (possibly)...

Introduce step-wise line charts, extending the data model to have (possibly) separate data sets for each graph type, and changing the line shadows to include a horizontal component


git-svn-id: https://flot.googlecode.com/svn/trunk@138 1e0a6537-2640-0410-bfb7-f154510ff394
parent c7288df5
...@@ -421,6 +421,10 @@ Customizing the data series ...@@ -421,6 +421,10 @@ Customizing the data series
align: "left" or "center" align: "left" or "center"
} }
lines: {
steps: boolean
}
colors: [ color1, color2, ... ] colors: [ color1, color2, ... ]
shadowSize: number shadowSize: number
...@@ -457,6 +461,10 @@ instance, for time series the unit is milliseconds so 24 * 60 * 60 * ...@@ -457,6 +461,10 @@ instance, for time series the unit is milliseconds so 24 * 60 * 60 *
a bar should be left-aligned (default) or centered on top of the value a bar should be left-aligned (default) or centered on top of the value
it represents. it represents.
For lines, "steps" specifies whether two adjacent data points are
connected with a straight (possibly diagonal) line or with first a
horizontal and then a vertical line.
The "colors" array specifies a default color theme to get colors for The "colors" array specifies a default color theme to get colors for
the data series from. You can specify as many colors as you like, like the data series from. You can specify as many colors as you like, like
this: this:
......
...@@ -40,9 +40,13 @@ Changes: ...@@ -40,9 +40,13 @@ Changes:
- The option legend.margin can now specify horizontal and vertical - The option legend.margin can now specify horizontal and vertical
margins independently (suggestion by someone who's annoyed). margins independently (suggestion by someone who's annoyed).
- Data passed into Flot is now copied to enable further processing - Data passed into Flot is now copied to a new canonical format to
before it hits the drawing routines. As a side-effect, this should enable further processing before it hits the drawing routines. As a
make Flot more robust in the face of bad data (and fixes issue 112). side-effect, this should make Flot more robust in the face of bad
data (and fixes issue 112).
- Step-wise charting: line charts have a new option "steps" that when
set to true connects the points with steps instead of diagonal lines.
Bug fixes: Bug fixes:
...@@ -77,6 +81,9 @@ Bug fixes: ...@@ -77,6 +81,9 @@ Bug fixes:
- Imported SVN version of excanvas and fixed an issue with the newer - Imported SVN version of excanvas and fixed an issue with the newer
version. Hopefully, this will make Flot work with IE8 (nudge by version. Hopefully, this will make Flot work with IE8 (nudge by
Fabien Menager). Fabien Menager).
- Changed the shadow code for lines to hopefully look a bit better
with vertical lines.
Flot 0.5 Flot 0.5
-------- --------
......
...@@ -37,6 +37,10 @@ $(function () { ...@@ -37,6 +37,10 @@ $(function () {
for (var i = 0; i < 14; i += 0.5) for (var i = 0; i < 14; i += 0.5)
d5.push([i, Math.sqrt(i)]); d5.push([i, Math.sqrt(i)]);
var d6 = [];
for (var i = 0; i < 14; i += 0.5 + Math.random())
d6.push([i, Math.sqrt(2*i + Math.sin(i) + 5)]);
$.plot($("#placeholder"), [ $.plot($("#placeholder"), [
{ {
data: d1, data: d1,
...@@ -58,6 +62,10 @@ $(function () { ...@@ -58,6 +62,10 @@ $(function () {
data: d5, data: d5,
lines: { show: true }, lines: { show: true },
points: { show: true } points: { show: true }
},
{
data: d6,
lines: { show: true, steps: true }
} }
]); ]);
}); });
......
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
mode: null, // one of null, "x", "y" or "xy", mode: null, // one of null, "x", "y" or "xy",
color: "#aa0000" color: "#aa0000"
}, },
shadowSize: 4 shadowSize: 3
}, },
canvas = null, // the canvas for the plot itself canvas = null, // the canvas for the plot itself
overlay = null, // canvas for interactive stuff on top of plot overlay = null, // canvas for interactive stuff on top of plot
...@@ -336,6 +336,26 @@ ...@@ -336,6 +336,26 @@
points[k] = x; points[k] = x;
} }
s.bars.datapoints = s.lines.datapoints = s.points.datapoints = s.datapoints;
if (s.lines.show && s.lines.steps) {
var p = [];
// copy, inserting extra points to make steps
for (j = k = 0; j < points.length; j += incr, k += incr) {
if (j > 0
&& points[j - incr] != null
&& points[j] != null
&& points[j - incr + 1] != points[j + 1]) {
p[k] = points[j];
p[k + 1] = points[j - incr + 1];
k += incr;
}
p[k] = points[j];
p[k + 1] = points[j + 1];
}
s.lines.datapoints.points = p;
}
if (s.bars.show) { if (s.bars.show) {
// make sure we got room for the bar // make sure we got room for the bar
var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
...@@ -347,6 +367,7 @@ ...@@ -347,6 +367,7 @@
axisx.datamax = Math.max(axisx.datamax, xmax); axisx.datamax = Math.max(axisx.datamax, xmax);
axisy.datamin = Math.min(axisy.datamin, ymin); axisy.datamin = Math.min(axisy.datamin, ymin);
axisy.datamax = Math.max(axisy.datamax, ymax); axisy.datamax = Math.max(axisy.datamax, ymax);
} }
} }
...@@ -1018,14 +1039,14 @@ ...@@ -1018,14 +1039,14 @@
function insertLabels() { function insertLabels() {
target.find(".tickLabels").remove(); target.find(".tickLabels").remove();
var html = '<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'; var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
function addLabels(axis, labelGenerator) { function addLabels(axis, labelGenerator) {
for (var i = 0; i < axis.ticks.length; ++i) { for (var i = 0; i < axis.ticks.length; ++i) {
var tick = axis.ticks[i]; var tick = axis.ticks[i];
if (!tick.label || tick.v < axis.min || tick.v > axis.max) if (!tick.label || tick.v < axis.min || tick.v > axis.max)
continue; continue;
html += labelGenerator(tick, axis); html.push(labelGenerator(tick, axis));
} }
} }
...@@ -1048,9 +1069,9 @@ ...@@ -1048,9 +1069,9 @@
return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>"; return '<div style="position:absolute;top:' + (plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
}); });
html += '</div>'; html.push('</div>');
target.append(html); target.append(html.join(""));
} }
function drawSeries(series) { function drawSeries(series) {
...@@ -1063,9 +1084,9 @@ ...@@ -1063,9 +1084,9 @@
} }
function drawSeriesLines(series) { function drawSeriesLines(series) {
function plotLine(datapoints, offset, axisx, axisy) { function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
var points = datapoints.points, incr = datapoints.incr, var points = datapoints.points, incr = datapoints.incr,
drawx = null, drawy = null; prevx = null, prevy = null;
ctx.beginPath(); ctx.beginPath();
for (var i = incr; i < points.length; i += incr) { for (var i = incr; i < points.length; i += incr) {
...@@ -1132,12 +1153,12 @@ ...@@ -1132,12 +1153,12 @@
x2 = axisx.max; x2 = axisx.max;
} }
if (drawx != axisx.p2c(x1) || drawy != axisy.p2c(y1) + offset) if (x1 != prevx || y1 != prevy)
ctx.moveTo(axisx.p2c(x1), axisy.p2c(y1) + offset); ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
drawx = axisx.p2c(x2); prevx = x2;
drawy = axisy.p2c(y2) + offset; prevy = y2;
ctx.lineTo(drawx, drawy); ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
} }
ctx.stroke(); ctx.stroke();
} }
...@@ -1285,13 +1306,13 @@ ...@@ -1285,13 +1306,13 @@
sw = series.shadowSize; sw = series.shadowSize;
// FIXME: consider another form of shadow when filling is turned on // FIXME: consider another form of shadow when filling is turned on
if (lw > 0 && sw > 0) { if (lw > 0 && sw > 0) {
// draw shadow in two steps // draw shadow as a thick and thin line with transparency
var w = sw / 2; ctx.lineWidth = sw;
ctx.lineWidth = w;
ctx.strokeStyle = "rgba(0,0,0,0.1)"; ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotLine(series.datapoints, lw/2 + w + w/2, series.xaxis, series.yaxis); var xoffset = 1;
ctx.strokeStyle = "rgba(0,0,0,0.2)"; plotLine(series.lines.datapoints, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis);
plotLine(series.datapoints, lw/2 + w/2, series.xaxis, series.yaxis); ctx.lineWidth = sw/2;
plotLine(series.lines.datapoints, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis);
} }
ctx.lineWidth = lw; ctx.lineWidth = lw;
...@@ -1299,11 +1320,11 @@ ...@@ -1299,11 +1320,11 @@
var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
if (fillStyle) { if (fillStyle) {
ctx.fillStyle = fillStyle; ctx.fillStyle = fillStyle;
plotLineArea(series.datapoints, series.xaxis, series.yaxis); plotLineArea(series.lines.datapoints, series.xaxis, series.yaxis);
} }
if (lw > 0) if (lw > 0)
plotLine(series.datapoints, 0, series.xaxis, series.yaxis); plotLine(series.lines.datapoints, 0, 0, series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
} }
...@@ -1337,17 +1358,17 @@ ...@@ -1337,17 +1358,17 @@
var w = sw / 2; var w = sw / 2;
ctx.lineWidth = w; ctx.lineWidth = w;
ctx.strokeStyle = "rgba(0,0,0,0.1)"; ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotPoints(series.datapoints, radius, null, w + w/2, 2 * Math.PI, plotPoints(series.points.datapoints, radius, null, w + w/2, 2 * Math.PI,
series.xaxis, series.yaxis); series.xaxis, series.yaxis);
ctx.strokeStyle = "rgba(0,0,0,0.2)"; ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotPoints(series.datapoints, radius, null, w/2, 2 * Math.PI, plotPoints(series.points.datapoints, radius, null, w/2, 2 * Math.PI,
series.xaxis, series.yaxis); series.xaxis, series.yaxis);
} }
ctx.lineWidth = lw; ctx.lineWidth = lw;
ctx.strokeStyle = series.color; ctx.strokeStyle = series.color;
plotPoints(series.datapoints, radius, plotPoints(series.points.datapoints, radius,
getFillStyle(series.points, series.color), 0, 2 * Math.PI, getFillStyle(series.points, series.color), 0, 2 * Math.PI,
series.xaxis, series.yaxis); series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
...@@ -1454,7 +1475,7 @@ ...@@ -1454,7 +1475,7 @@
ctx.strokeStyle = series.color; ctx.strokeStyle = series.color;
var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); plotBars(series.points.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
} }
...@@ -1538,7 +1559,6 @@ ...@@ -1538,7 +1559,6 @@
} }
var div = legend.children(); var div = legend.children();
$('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
} }
} }
} }
...@@ -1567,42 +1587,25 @@ ...@@ -1567,42 +1587,25 @@
continue; continue;
var s = series[i], var s = series[i],
points = s.datapoints.points,
incr = s.datapoints.incr,
axisx = s.xaxis, axisx = s.xaxis,
axisy = s.yaxis, axisy = s.yaxis,
points = s.points.datapoints.points,
// precompute some stuff to make the loop faster incr = s.points.datapoints.incr,
mx = axisx.c2p(mouseX), mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
my = axisy.c2p(mouseY), my = axisy.c2p(mouseY),
maxx = maxDistance / axisx.scale, maxx = maxDistance / axisx.scale,
maxy = maxDistance / axisy.scale, maxy = maxDistance / axisy.scale;
checkbar = s.bars.show,
checkpoint = !s.bars.show || s.lines.show || s.points.show,
barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
barRight = barLeft + s.bars.barWidth;
if (s.lines.show || s.points.show) {
for (j = 0; j < points.length; j += incr) { for (j = 0; j < points.length; j += incr) {
var x = points[j], y = points[j + 1]; var x = points[j], y = points[j + 1];
if (x == null) if (x == null)
continue; continue;
if (checkbar) {
// For a bar graph, the cursor must be inside the bar
// and no other point can be nearby
if (!foundPoint && mx >= x + barLeft &&
mx <= x + barRight &&
my >= Math.min(0, y) && my <= Math.max(0, y))
item = [i, j / incr];
}
if (checkpoint) {
// For points and lines, the cursor must be within a // For points and lines, the cursor must be within a
// certain distance to the data point // certain distance to the data point
if (x - mx > maxx || x - mx < -maxx ||
// check bounding box first y - my > maxy || y - my < -maxy)
if ((x - mx > maxx || x - mx < -maxx) ||
(y - my > maxy || y - my < -maxy))
continue; continue;
// We have to calculate distances in pixels, not in // We have to calculate distances in pixels, not in
...@@ -1612,11 +1615,29 @@ ...@@ -1612,11 +1615,29 @@
dist = dx * dx + dy * dy; // no idea in taking sqrt dist = dx * dx + dy * dy; // no idea in taking sqrt
if (dist < lowestDistance) { if (dist < lowestDistance) {
lowestDistance = dist; lowestDistance = dist;
foundPoint = true;
item = [i, j / incr]; item = [i, j / incr];
} }
} }
} }
if (s.bars.show && !item) { // no other point can be nearby
points = s.bars.datapoints.points;
incr = s.bars.datapoints.incr;
var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
barRight = barLeft + s.bars.barWidth;
for (j = 0; j < points.length; j += incr) {
var x = points[j], y = points[j + 1];
if (x == null)
continue;
// For a bar graph, the cursor must be inside the bar
if (mx >= x + barLeft && mx <= x + barRight &&
my >= Math.min(0, y) && my <= Math.max(0, y))
item = [i, j / incr];
}
}
} }
if (item) { if (item) {
......
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