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:
- The option legend.margin can now specify horizontal and vertical
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:
- Fixed two corner-case bugs when drawing filled curves (report and
......
......@@ -61,10 +61,11 @@
},
lines: {
// 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
fill: false,
fillColor: null
fillColor: null,
steps: false
},
bars: {
show: false,
......@@ -269,7 +270,7 @@
function processData() {
var topSentry = Number.POSITIVE_INFINITY,
bottomSentry = Number.NEGATIVE_INFINITY,
axis;
axis, i, j, k;
for (axis in axes) {
axes[axis].datamin = topSentry;
......@@ -279,51 +280,73 @@
axes[axis].used = false;
}
for (var i = 0; i < series.length; ++i) {
var data = series[i].data,
axisx = series[i].xaxis,
axisy = series[i].yaxis,
mindelta = 0, maxdelta = 0;
for (i = 0; i < series.length; ++i) {
var s = series[i];
s.datapoints = { points: [], incr: 2 };
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;
for (var j = 0; j < data.length; ++j) {
if (data[j] == null)
continue;
var x = data[j][0], y = data[j][1];
for (j = k = 0; j < data.length; ++j, k += incr) {
var x = null, y = null;
if (data[j] != null) {
x = data[j][0];
y = data[j][1];
}
// convert to number
if (x != null && !isNaN(x = +x)) {
if (x + mindelta < axisx.datamin)
axisx.datamin = x + mindelta;
if (x + maxdelta > axisx.datamax)
axisx.datamax = x + maxdelta;
if (x < xmin)
xmin = x;
if (x > xmax)
xmax = x
}
else
x = null;
if (y != null && !isNaN(y = +y)) {
if (y < axisy.datamin)
axisy.datamin = y;
if (y > axisy.datamax)
axisy.datamax = y;
if (y < ymin)
ymin = y;
if (y > ymax)
ymax = y;
}
if (x == null || y == null || isNaN(x) || isNaN(y))
data[j] = null; // mark this point as invalid
}
}
else
y = null;
for (axis in axes) {
if (axes[axis].datamin == topSentry)
axes[axis].datamin = 0;
if (axes[axis].datamax == bottomSentry)
axes[axis].datamax = 1;
if (x == null || y == null)
x = y = null; // make sure everything is cleared
points[k + 1] = y;
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 @@
}
function setRange(axis, axisOptions) {
var min = axisOptions.min != null ? +axisOptions.min : axis.datamin;
var max = axisOptions.max != null ? +axisOptions.max : axis.datamax;
var min = axisOptions.min != null ? +axisOptions.min : axis.datamin,
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) {
// degenerate case
var widen = max == 0 ? 1 : 0.01;
......@@ -1034,19 +1063,17 @@
}
function drawSeriesLines(series) {
function plotLine(data, offset, axisx, axisy) {
var prev, cur = null, drawx = null, drawy = null;
function plotLine(datapoints, offset, axisx, axisy) {
var points = datapoints.points, incr = datapoints.incr,
drawx = null, drawy = null;
ctx.beginPath();
for (var i = 0; i < data.length; ++i) {
prev = cur;
cur = data[i];
if (prev == null || cur == null)
continue;
for (var i = incr; i < points.length; i += incr) {
var x1 = points[i - incr], y1 = points[i - incr + 1],
x2 = points[i], y2 = points[i + 1];
var x1 = prev[0], y1 = prev[1],
x2 = cur[0], y2 = cur[1];
if (x1 == null || x2 == null)
continue;
// clip with ymin
if (y1 <= y2 && y1 < axisy.min) {
......@@ -1115,19 +1142,16 @@
ctx.stroke();
}
function plotLineArea(data, axisx, axisy) {
var prev, cur = null;
function plotLineArea(datapoints, axisx, axisy) {
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);
var top, lastX = 0;
var areaOpen = false;
for (var i = 0; i < data.length; ++i) {
prev = cur;
cur = data[i];
if (areaOpen && prev != null && cur == null) {
for (var i = incr; i < points.length; i += incr) {
var x1 = points[i - incr], y1 = points[i - incr + 1],
x2 = points[i], y2 = points[i + 1];
if (areaOpen && x1 != null && x2 == null) {
// close area
ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
ctx.fill();
......@@ -1135,11 +1159,8 @@
continue;
}
if (prev == null || cur == null)
if (x1 == null || x2 == null)
continue;
var x1 = prev[0], y1 = prev[1],
x2 = cur[0], y2 = cur[1];
// clip x values
......@@ -1268,9 +1289,9 @@
var w = sw / 2;
ctx.lineWidth = w;
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)";
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;
......@@ -1278,26 +1299,25 @@
var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
if (fillStyle) {
ctx.fillStyle = fillStyle;
plotLineArea(series.data, series.xaxis, series.yaxis);
plotLineArea(series.datapoints, series.xaxis, series.yaxis);
}
if (lw > 0)
plotLine(series.data, 0, series.xaxis, series.yaxis);
plotLine(series.datapoints, 0, series.xaxis, series.yaxis);
ctx.restore();
}
function drawSeriesPoints(series) {
function plotPoints(data, radius, fillStyle, 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)
function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
var points = datapoints.points, incr = datapoints.incr;
for (var i = 0; i < points.length; i += incr) {
var x = points[i], y = points[i + 1];
if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
continue;
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) {
ctx.fillStyle = fillStyle;
ctx.fill();
......@@ -1305,43 +1325,30 @@
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.translate(plotOffset.left, plotOffset.top);
var lw = series.lines.lineWidth,
sw = series.shadowSize;
sw = series.shadowSize,
radius = series.points.radius;
if (lw > 0 && sw > 0) {
// draw shadow in two steps
var w = sw / 2;
ctx.lineWidth = w;
ctx.strokeStyle = "rgba(0,0,0,0.1)";
plotPointShadows(series.data, w + w/2,
series.points.radius, series.xaxis, series.yaxis);
plotPoints(series.datapoints, radius, null, w + w/2, 2 * Math.PI,
series.xaxis, series.yaxis);
ctx.strokeStyle = "rgba(0,0,0,0.2)";
plotPointShadows(series.data, w/2,
series.points.radius, series.xaxis, series.yaxis);
plotPoints(series.datapoints, radius, null, w/2, 2 * Math.PI,
series.xaxis, series.yaxis);
}
ctx.lineWidth = series.points.lineWidth;
ctx.lineWidth = lw;
ctx.strokeStyle = series.color;
plotPoints(series.data, series.points.radius,
getFillStyle(series.points, series.color),
plotPoints(series.datapoints, radius,
getFillStyle(series.points, series.color), 0, 2 * Math.PI,
series.xaxis, series.yaxis);
ctx.restore();
}
......@@ -1428,11 +1435,13 @@
}
function drawSeriesBars(series) {
function plotBars(data, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
for (var i = 0; i < data.length; i++) {
if (data[i] == null)
function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
var points = datapoints.points, incr = datapoints.incr;
for (var i = 0; i < points.length; i += incr) {
if (points[i] == null)
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 @@
ctx.lineJoin = "round";
// 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.strokeStyle = series.color;
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;
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();
}
......@@ -1566,37 +1560,32 @@
function findNearbyItem(mouseX, mouseY, seriesFilter) {
var maxDistance = options.grid.mouseActiveRadius,
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) {
if (!seriesFilter(series[i]))
continue;
var data = series[i].data,
axisx = series[i].xaxis,
axisy = series[i].yaxis,
var s = series[i],
points = s.datapoints.points,
incr = s.datapoints.incr,
axisx = s.xaxis,
axisy = s.yaxis,
// precompute some stuff to make the loop faster
mx = axisx.c2p(mouseX),
my = axisy.c2p(mouseY),
maxx = maxDistance / axisx.scale,
maxy = maxDistance / axisy.scale,
checkbar = series[i].bars.show,
checkpoint = !(series[i].bars.show && !(series[i].lines.show || series[i].points.show)),
barLeft = series[i].bars.align == "left" ? 0 : -series[i].bars.barWidth/2,
barRight = barLeft + series[i].bars.barWidth;
for (var j = 0; j < data.length; ++j) {
if (data[j] == null)
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;
for (j = 0; j < points.length; j += incr) {
var x = points[j], y = points[j + 1];
if (x == null)
continue;
var x = data[j][0], y = data[j][1];
if (checkbar) {
// For a bar graph, the cursor must be inside the bar
......@@ -1604,7 +1593,7 @@
if (!foundPoint && mx >= x + barLeft &&
mx <= x + barRight &&
my >= Math.min(0, y) && my <= Math.max(0, y))
item = result(i, j);
item = [i, j / incr];
}
if (checkpoint) {
......@@ -1617,20 +1606,30 @@
continue;
// 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),
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) {
lowestDistance = dist;
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) {
......
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