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

Refactor to copy data instead of using user-provied data array directly

git-svn-id: https://flot.googlecode.com/svn/trunk@137 1e0a6537-2640-0410-bfb7-f154510ff394
parent 8bc315f1
...@@ -39,7 +39,12 @@ Changes: ...@@ -39,7 +39,12 @@ 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
before it hits the drawing routines. As a side-effect, this should
make Flot more robust in the face of bad data (and fixes issue 112).
Bug fixes: Bug fixes:
- Fixed two corner-case bugs when drawing filled curves (report and - Fixed two corner-case bugs when drawing filled curves (report and
......
...@@ -61,10 +61,11 @@ ...@@ -61,10 +61,11 @@
}, },
lines: { lines: {
// we don't put in show: false so we can see // we don't put in show: false so we can see
// whether the user actively disabled lines // whether lines were actively disabled
lineWidth: 2, // in pixels lineWidth: 2, // in pixels
fill: false, fill: false,
fillColor: null fillColor: null,
steps: false
}, },
bars: { bars: {
show: false, show: false,
...@@ -269,7 +270,7 @@ ...@@ -269,7 +270,7 @@
function processData() { function processData() {
var topSentry = Number.POSITIVE_INFINITY, var topSentry = Number.POSITIVE_INFINITY,
bottomSentry = Number.NEGATIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY,
axis; axis, i, j, k;
for (axis in axes) { for (axis in axes) {
axes[axis].datamin = topSentry; axes[axis].datamin = topSentry;
...@@ -279,51 +280,73 @@ ...@@ -279,51 +280,73 @@
axes[axis].used = false; axes[axis].used = false;
} }
for (var i = 0; i < series.length; ++i) { for (i = 0; i < series.length; ++i) {
var data = series[i].data, var s = series[i];
axisx = series[i].xaxis,
axisy = series[i].yaxis, s.datapoints = { points: [], incr: 2 };
mindelta = 0, maxdelta = 0;
var data = s.data,
points = s.datapoints.points,
incr = s.datapoints.incr,
axisx = s.xaxis, axisy = s.yaxis,
xmin = topSentry, xmax = bottomSentry,
ymin = topSentry, ymax = bottomSentry;
// determine the increment
// examine data to find out how to copy
/*
for (j = 0; j < data.length; ++j) {
}*/
if (series[i].bars.show) {
// make sure we got room for the bar
mindelta = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2;
maxdelta = mindelta + series[i].bars.barWidth;
}
axisx.used = axisy.used = true; axisx.used = axisy.used = true;
for (var j = 0; j < data.length; ++j) { for (j = k = 0; j < data.length; ++j, k += incr) {
if (data[j] == null) var x = null, y = null;
continue;
var x = data[j][0], y = data[j][1];
if (data[j] != null) {
x = data[j][0];
y = data[j][1];
}
// convert to number // convert to number
if (x != null && !isNaN(x = +x)) { if (x != null && !isNaN(x = +x)) {
if (x + mindelta < axisx.datamin) if (x < xmin)
axisx.datamin = x + mindelta; xmin = x;
if (x + maxdelta > axisx.datamax) if (x > xmax)
axisx.datamax = x + maxdelta; xmax = x
} }
else
x = null;
if (y != null && !isNaN(y = +y)) { if (y != null && !isNaN(y = +y)) {
if (y < axisy.datamin) if (y < ymin)
axisy.datamin = y; ymin = y;
if (y > axisy.datamax) if (y > ymax)
axisy.datamax = y; ymax = y;
} }
else
if (x == null || y == null || isNaN(x) || isNaN(y)) y = null;
data[j] = null; // mark this point as invalid
}
}
for (axis in axes) { if (x == null || y == null)
if (axes[axis].datamin == topSentry) x = y = null; // make sure everything is cleared
axes[axis].datamin = 0;
if (axes[axis].datamax == bottomSentry) points[k + 1] = y;
axes[axis].datamax = 1; points[k] = x;
}
if (s.bars.show) {
// make sure we got room for the bar
var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
xmin += delta;
xmax += delta + s.bars.barWidth;
}
axisx.datamin = Math.min(axisx.datamin, xmin);
axisx.datamax = Math.max(axisx.datamax, xmax);
axisy.datamin = Math.min(axisy.datamin, ymin);
axisy.datamax = Math.max(axisy.datamax, ymax);
} }
} }
...@@ -404,9 +427,15 @@ ...@@ -404,9 +427,15 @@
} }
function setRange(axis, axisOptions) { function setRange(axis, axisOptions) {
var min = axisOptions.min != null ? +axisOptions.min : axis.datamin; var min = axisOptions.min != null ? +axisOptions.min : axis.datamin,
var max = axisOptions.max != null ? +axisOptions.max : axis.datamax; max = axisOptions.max != null ? +axisOptions.max : axis.datamax;
// degenerate case
if (min == Number.POSITIVE_INFINITY)
min = 0;
if (max == Number.NEGATIVE_INFINITY)
max = 1;
if (max - min == 0.0) { if (max - min == 0.0) {
// degenerate case // degenerate case
var widen = max == 0 ? 1 : 0.01; var widen = max == 0 ? 1 : 0.01;
...@@ -1034,19 +1063,17 @@ ...@@ -1034,19 +1063,17 @@
} }
function drawSeriesLines(series) { function drawSeriesLines(series) {
function plotLine(data, offset, axisx, axisy) { function plotLine(datapoints, offset, axisx, axisy) {
var prev, cur = null, drawx = null, drawy = null; var points = datapoints.points, incr = datapoints.incr,
drawx = null, drawy = null;
ctx.beginPath(); ctx.beginPath();
for (var i = 0; i < data.length; ++i) { for (var i = incr; i < points.length; i += incr) {
prev = cur; var x1 = points[i - incr], y1 = points[i - incr + 1],
cur = data[i]; x2 = points[i], y2 = points[i + 1];
if (prev == null || cur == null)
continue;
var x1 = prev[0], y1 = prev[1], if (x1 == null || x2 == null)
x2 = cur[0], y2 = cur[1]; continue;
// clip with ymin // clip with ymin
if (y1 <= y2 && y1 < axisy.min) { if (y1 <= y2 && y1 < axisy.min) {
...@@ -1115,19 +1142,16 @@ ...@@ -1115,19 +1142,16 @@
ctx.stroke(); ctx.stroke();
} }
function plotLineArea(data, axisx, axisy) { function plotLineArea(datapoints, axisx, axisy) {
var prev, cur = null; var points = datapoints.points, incr = datapoints.incr,
bottom = Math.min(Math.max(0, axisy.min), axisy.max),
top, lastX = 0, areaOpen = false;
var bottom = Math.min(Math.max(0, axisy.min), axisy.max); for (var i = incr; i < points.length; i += incr) {
var top, lastX = 0; var x1 = points[i - incr], y1 = points[i - incr + 1],
x2 = points[i], y2 = points[i + 1];
var areaOpen = false;
if (areaOpen && x1 != null && x2 == null) {
for (var i = 0; i < data.length; ++i) {
prev = cur;
cur = data[i];
if (areaOpen && prev != null && cur == null) {
// close area // close area
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
ctx.fill(); ctx.fill();
...@@ -1135,11 +1159,8 @@ ...@@ -1135,11 +1159,8 @@
continue; continue;
} }
if (prev == null || cur == null) if (x1 == null || x2 == null)
continue; continue;
var x1 = prev[0], y1 = prev[1],
x2 = cur[0], y2 = cur[1];
// clip x values // clip x values
...@@ -1268,9 +1289,9 @@ ...@@ -1268,9 +1289,9 @@
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)";
plotLine(series.data, lw/2 + w + w/2, series.xaxis, series.yaxis); plotLine(series.datapoints, lw/2 + w + w/2, series.xaxis, series.yaxis);
ctx.strokeStyle = "rgba(0,0,0,0.2)"; ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotLine(series.data, lw/2 + w/2, series.xaxis, series.yaxis); plotLine(series.datapoints, lw/2 + w/2, series.xaxis, series.yaxis);
} }
ctx.lineWidth = lw; ctx.lineWidth = lw;
...@@ -1278,26 +1299,25 @@ ...@@ -1278,26 +1299,25 @@
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.data, series.xaxis, series.yaxis); plotLineArea(series.datapoints, series.xaxis, series.yaxis);
} }
if (lw > 0) if (lw > 0)
plotLine(series.data, 0, series.xaxis, series.yaxis); plotLine(series.datapoints, 0, series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
} }
function drawSeriesPoints(series) { function drawSeriesPoints(series) {
function plotPoints(data, radius, fillStyle, axisx, axisy) { function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
for (var i = 0; i < data.length; ++i) { var points = datapoints.points, incr = datapoints.incr;
if (data[i] == null)
continue; for (var i = 0; i < points.length; i += incr) {
var x = points[i], y = points[i + 1];
var x = data[i][0], y = data[i][1]; if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
continue; continue;
ctx.beginPath(); ctx.beginPath();
ctx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true); ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, true);
if (fillStyle) { if (fillStyle) {
ctx.fillStyle = fillStyle; ctx.fillStyle = fillStyle;
ctx.fill(); ctx.fill();
...@@ -1305,43 +1325,30 @@ ...@@ -1305,43 +1325,30 @@
ctx.stroke(); ctx.stroke();
} }
} }
function plotPointShadows(data, offset, radius, axisx, axisy) {
for (var i = 0; i < data.length; ++i) {
if (data[i] == null)
continue;
var x = data[i][0], y = data[i][1];
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
continue;
ctx.beginPath();
ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, Math.PI, false);
ctx.stroke();
}
}
ctx.save(); ctx.save();
ctx.translate(plotOffset.left, plotOffset.top); ctx.translate(plotOffset.left, plotOffset.top);
var lw = series.lines.lineWidth, var lw = series.lines.lineWidth,
sw = series.shadowSize; sw = series.shadowSize,
radius = series.points.radius;
if (lw > 0 && sw > 0) { if (lw > 0 && sw > 0) {
// draw shadow in two steps // draw shadow in two steps
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)";
plotPointShadows(series.data, w + w/2, plotPoints(series.datapoints, radius, null, w + w/2, 2 * Math.PI,
series.points.radius, series.xaxis, series.yaxis); series.xaxis, series.yaxis);
ctx.strokeStyle = "rgba(0,0,0,0.2)"; ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotPointShadows(series.data, w/2, plotPoints(series.datapoints, radius, null, w/2, 2 * Math.PI,
series.points.radius, series.xaxis, series.yaxis); series.xaxis, series.yaxis);
} }
ctx.lineWidth = series.points.lineWidth; ctx.lineWidth = lw;
ctx.strokeStyle = series.color; ctx.strokeStyle = series.color;
plotPoints(series.data, series.points.radius, plotPoints(series.datapoints, radius,
getFillStyle(series.points, series.color), getFillStyle(series.points, series.color), 0, 2 * Math.PI,
series.xaxis, series.yaxis); series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
} }
...@@ -1428,11 +1435,13 @@ ...@@ -1428,11 +1435,13 @@
} }
function drawSeriesBars(series) { function drawSeriesBars(series) {
function plotBars(data, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
for (var i = 0; i < data.length; i++) { var points = datapoints.points, incr = datapoints.incr;
if (data[i] == null)
for (var i = 0; i < points.length; i += incr) {
if (points[i] == null)
continue; continue;
drawBar(data[i][0], data[i][1], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx); drawBar(points[i], points[i + 1], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx);
} }
} }
...@@ -1441,26 +1450,11 @@ ...@@ -1441,26 +1450,11 @@
ctx.lineJoin = "round"; ctx.lineJoin = "round";
// FIXME: figure out a way to add shadows (for instance along the right edge) // FIXME: figure out a way to add shadows (for instance along the right edge)
/*
var bw = series.bars.barWidth;
var lw = series.bars.lineWidth;
var sw = series.shadowSize;
if (sw > 0) {
// draw shadow in two steps
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotBars(series.data, bw, lw/2 + sw/2 + ctx.lineWidth/2, false);
ctx.lineWidth = sw / 2;
ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotBars(series.data, bw, lw/2 + ctx.lineWidth/2, false);
}*/
ctx.lineWidth = series.bars.lineWidth; ctx.lineWidth = series.bars.lineWidth;
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.data, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
ctx.restore(); ctx.restore();
} }
...@@ -1566,37 +1560,32 @@ ...@@ -1566,37 +1560,32 @@
function findNearbyItem(mouseX, mouseY, seriesFilter) { function findNearbyItem(mouseX, mouseY, seriesFilter) {
var maxDistance = options.grid.mouseActiveRadius, var maxDistance = options.grid.mouseActiveRadius,
lowestDistance = maxDistance * maxDistance + 1, lowestDistance = maxDistance * maxDistance + 1,
item = null, foundPoint = false; item = null, foundPoint = false, i, j;
function result(i, j) {
return { datapoint: series[i].data[j],
dataIndex: j,
series: series[i],
seriesIndex: i };
}
for (var i = 0; i < series.length; ++i) { for (var i = 0; i < series.length; ++i) {
if (!seriesFilter(series[i])) if (!seriesFilter(series[i]))
continue; continue;
var data = series[i].data, var s = series[i],
axisx = series[i].xaxis, points = s.datapoints.points,
axisy = series[i].yaxis, incr = s.datapoints.incr,
axisx = s.xaxis,
axisy = s.yaxis,
// precompute some stuff to make the loop faster // precompute some stuff to make the loop faster
mx = axisx.c2p(mouseX), mx = axisx.c2p(mouseX),
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 = series[i].bars.show, checkbar = s.bars.show,
checkpoint = !(series[i].bars.show && !(series[i].lines.show || series[i].points.show)), checkpoint = !s.bars.show || s.lines.show || s.points.show,
barLeft = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2, barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
barRight = barLeft + series[i].bars.barWidth; barRight = barLeft + s.bars.barWidth;
for (var j = 0; j < data.length; ++j) {
if (data[j] == null) for (j = 0; j < points.length; j += incr) {
var x = points[j], y = points[j + 1];
if (x == null)
continue; continue;
var x = data[j][0], y = data[j][1];
if (checkbar) { if (checkbar) {
// For a bar graph, the cursor must be inside the bar // For a bar graph, the cursor must be inside the bar
...@@ -1604,7 +1593,7 @@ ...@@ -1604,7 +1593,7 @@
if (!foundPoint && mx >= x + barLeft && if (!foundPoint && mx >= x + barLeft &&
mx <= x + barRight && mx <= x + barRight &&
my >= Math.min(0, y) && my <= Math.max(0, y)) my >= Math.min(0, y) && my <= Math.max(0, y))
item = result(i, j); item = [i, j / incr];
} }
if (checkpoint) { if (checkpoint) {
...@@ -1617,20 +1606,30 @@ ...@@ -1617,20 +1606,30 @@
continue; continue;
// We have to calculate distances in pixels, not in // We have to calculate distances in pixels, not in
// data units, because the scale of the axes may be different // data units, because the scales of the axes may be different
var dx = Math.abs(axisx.p2c(x) - mouseX), var dx = Math.abs(axisx.p2c(x) - mouseX),
dy = Math.abs(axisy.p2c(y) - mouseY), dy = Math.abs(axisy.p2c(y) - mouseY),
dist = dx * dx + dy * dy; dist = dx * dx + dy * dy; // no idea in taking sqrt
if (dist < lowestDistance) { if (dist < lowestDistance) {
lowestDistance = dist; lowestDistance = dist;
foundPoint = true; foundPoint = true;
item = result(i, j); item = [i, j / incr];
} }
} }
} }
} }
return item; if (item) {
i = item[0];
j = item[1];
return { datapoint: series[i].data[j],
dataIndex: j,
series: series[i],
seriesIndex: i }
}
return null;
} }
function onMouseMove(ev) { function onMouseMove(ev) {
......
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