Commit 5d2883f6 authored by olau@iola.dk's avatar olau@iola.dk

Preliminary support for highlighting points, still needs polishing

git-svn-id: https://flot.googlecode.com/svn/trunk@75 1e0a6537-2640-0410-bfb7-f154510ff394
parent 4bc191f0
...@@ -78,11 +78,13 @@ ...@@ -78,11 +78,13 @@
tickColor: "#dddddd", // color used for the ticks tickColor: "#dddddd", // color used for the ticks
labelMargin: 3, // in pixels labelMargin: 3, // in pixels
borderWidth: 2, borderWidth: 2,
coloredAreas: null, // array of { x1, y1, x2, y2 } or fn: plot area -> areas
coloredAreasColor: "#f4f4f4",
// interactive stuff
clickable: false, clickable: false,
hoverable: false, hoverable: false,
mouseCatchingArea: 30, mouseCatchingArea: 30, // FIXME
coloredAreas: null, // array of { x1, y1, x2, y2 } or fn: plot area -> areas autoHighlight: true, // highlight in case mouse is near
coloredAreasColor: "#f4f4f4"
}, },
selection: { selection: {
mode: null, // one of null, "x", "y" or "xy" mode: null, // one of null, "x", "y" or "xy"
...@@ -90,7 +92,9 @@ ...@@ -90,7 +92,9 @@
}, },
shadowSize: 4 shadowSize: 4
}; };
var canvas = null, overlay = null, eventHolder = null, var canvas = null, // the canvas for the plot itself
overlay = null, // canvas for interactive stuff on top of plot
eventHolder = null, // jQuery object that events should be bound to
ctx = null, octx = null, ctx = null, octx = null,
target = target_, target = target_,
xaxis = {}, yaxis = {}, xaxis = {}, yaxis = {},
...@@ -110,6 +114,8 @@ ...@@ -110,6 +114,8 @@
this.getPlotOffset = function() { return plotOffset; }; this.getPlotOffset = function() { return plotOffset; };
this.getData = function() { return series; }; this.getData = function() { return series; };
this.getAxes = function() { return { xaxis: xaxis, yaxis: yaxis, x2axis: x2axis, y2axis: y2axis }; }; this.getAxes = function() { return { xaxis: xaxis, yaxis: yaxis, x2axis: x2axis, y2axis: y2axis }; };
this.highlight = highlight;
this.unhighlight = unhighlight;
// initialize // initialize
parseOptions(options_); parseOptions(options_);
...@@ -1475,11 +1481,17 @@ ...@@ -1475,11 +1481,17 @@
} }
} }
var lastMousePos = { pageX: null, pageY: null };
var selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} }; // interactive features
var prevSelection = null;
var selectionInterval = null; var lastMousePos = { pageX: null, pageY: null },
var ignoreClick = false; selection = {
first: { x: -1, y: -1}, second: { x: -1, y: -1},
show: false, active: false },
highlights = [],
clickIsMouseUp = false,
redrawTimeout = null,
hoverTimeout = null;
// Returns the data item the mouse is over, or null if none is found // Returns the data item the mouse is over, or null if none is found
function findNearbyItem(mouseX, mouseY) { function findNearbyItem(mouseX, mouseY) {
...@@ -1492,6 +1504,7 @@ ...@@ -1492,6 +1504,7 @@
axisx = series[i].xaxis, axisx = series[i].xaxis,
axisy = series[i].yaxis; axisy = series[i].yaxis;
// precompute some stuff to make the loop fast
var mx = axisx.c2p(mouseX), my = axisy.c2p(mouseY), var mx = axisx.c2p(mouseX), my = axisy.c2p(mouseY),
maxx = maxDistance / axisx.scale, maxx = maxDistance / axisx.scale,
maxy = maxDistance / axisy.scale; maxy = maxDistance / axisy.scale;
...@@ -1499,14 +1512,15 @@ ...@@ -1499,14 +1512,15 @@
if (data[j] == null) if (data[j] == null)
continue; continue;
// We have to calculate distances in pixels, not in // first check whether we're too far away
// data units, because the scale of the axes may be different
var x = data[j][0], y = data[j][1]; var x = data[j][0], y = data[j][1];
if (x - mx > maxx || x - mx < -maxx) if (x - mx > maxx || x - mx < -maxx)
continue; // Don't bother, we're too far continue;
if (y - my > maxy || y - my < -maxy) if (y - my > maxy || y - my < -maxy)
continue; continue;
// We have to calculate distances in pixels, not in
// data units, because the scale of the axes may be different
var dx = Math.abs(xaxis.p2c(x) - mouseX), var dx = Math.abs(xaxis.p2c(x) - mouseX),
dy = Math.abs(yaxis.p2c(y) - mouseY); dy = Math.abs(yaxis.p2c(y) - mouseY);
var dist = dx * dx + dy * dy; var dist = dx * dx + dy * dy;
...@@ -1523,8 +1537,6 @@ ...@@ -1523,8 +1537,6 @@
return item; return item;
} }
var hoverTimeout = null;
function onMouseMove(ev) { function onMouseMove(ev) {
// FIXME: temp. work-around until jQuery bug 1871 is fixed // FIXME: temp. work-around until jQuery bug 1871 is fixed
var e = ev || window.event; var e = ev || window.event;
...@@ -1539,7 +1551,10 @@ ...@@ -1539,7 +1551,10 @@
} }
if (options.grid.hoverable && !hoverTimeout) if (options.grid.hoverable && !hoverTimeout)
hoverTimeout = setTimeout(emitHoverEvent, 100); hoverTimeout = setTimeout(onHover, 100);
if (selection.active)
updateSelection(lastMousePos);
} }
function onMouseDown(e) { function onMouseDown(e) {
...@@ -1561,23 +1576,21 @@ ...@@ -1561,23 +1576,21 @@
setSelectionPos(selection.first, e); setSelectionPos(selection.first, e);
if (selectionInterval != null)
clearInterval(selectionInterval);
lastMousePos.pageX = null; lastMousePos.pageX = null;
selectionInterval = setInterval(updateSelectionOnMouseMove, 200); selection.active = true;
$(document).one("mouseup", onSelectionMouseUp); $(document).one("mouseup", onSelectionMouseUp);
} }
function onClick(e) { function onClick(e) {
if (ignoreClick) { if (clickIsMouseUp) {
ignoreClick = false; clickIsMouseUp = false;
return; return;
} }
triggerClickHoverEvent("plotclick", e); triggerClickHoverEvent("plotclick", e);
} }
function emitHoverEvent() { function onHover() {
triggerClickHoverEvent("plothover", lastMousePos); triggerClickHoverEvent("plothover", lastMousePos);
hoverTimeout = null; hoverTimeout = null;
} }
...@@ -1602,33 +1615,118 @@ ...@@ -1602,33 +1615,118 @@
item = findNearbyItem(canvasX, canvasY); item = findNearbyItem(canvasX, canvasY);
if (item) { if (item) {
// fill in mouse pos for any listeners out there
item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
if (options.grid.autoHighlight) {
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.auto)
unhighlight(h.series, h.point);
}
highlight(item.series, item.datapoint, true);
}
} }
target.trigger(eventname, [ pos, item ]); target.trigger(eventname, [ pos, item ]);
} }
function triggerSelectedEvent() { function triggerRedrawOverlay() {
var x1, x2, y1, y2; if (!redrawTimeout)
redrawTimeout = setTimeout(redrawOverlay, 100);
}
function redrawOverlay() {
redrawTimeout = null;
// redraw highlights
octx.save();
octx.clearRect(0, 0, canvasWidth, canvasHeight);
octx.translate(plotOffset.left, plotOffset.top);
if (selection.first.x <= selection.second.x) { var i, h;
x1 = selection.first.x; for (i = 0; i < highlights.length; ++i) {
x2 = selection.second.x; h = highlights[i];
drawPointHighlight(h.series, h.point);
} }
else { octx.restore();
x1 = selection.second.x;
x2 = selection.first.x; // redraw selection
if (selection.show && selectionIsSane()) {
octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
ctx.lineJoin = "round";
octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
var x = Math.min(selection.first.x, selection.second.x),
y = Math.min(selection.first.y, selection.second.y),
w = Math.abs(selection.second.x - selection.first.x),
h = Math.abs(selection.second.y - selection.first.y);
octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);
octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);
} }
}
function highlight(s, point, auto) {
if (typeof s == "number")
s = series[s];
var i = indexOfHighlight(s, point);
if (i == -1) {
highlights.push({ series: s, point: point, auto: auto });
if (selection.first.y >= selection.second.y) { triggerRedrawOverlay();
y1 = selection.first.y;
y2 = selection.second.y;
} }
else { }
y1 = selection.second.y;
y2 = selection.first.y; function unhighlight(s, point) {
if (typeof s == "number")
s = series[s];
var i = indexOfHighlight(s, point);
if (i != -1) {
highlights.splice(i, 1);
triggerRedrawOverlay();
} }
}
function indexOfHighlight(s, p) {
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.series == s && h.point[0] == p[0]
&& h.point[1] == p[1])
return i;
}
return -1;
}
function drawPointHighlight(series, point) {
var x = point[0], y = point[1],
axisx = series.xaxis, axisy = series.yaxis;
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
return;
octx.lineWidth = series.points.lineWidth;
octx.strokeStyle = series.color;
var radius = series.points.lineWidth + series.points.radius;
octx.beginPath();
octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true);
octx.stroke();
}
function triggerSelectedEvent() {
var x1 = Math.min(selection.first.x, selection.second.x),
x2 = Math.max(selection.first.x, selection.second.x),
y1 = Math.max(selection.first.y, selection.second.y),
y2 = Math.min(selection.first.y, selection.second.y);
var r = {}; var r = {};
if (xaxis.used) if (xaxis.used)
...@@ -1648,30 +1746,23 @@ ...@@ -1648,30 +1746,23 @@
} }
function onSelectionMouseUp(e) { function onSelectionMouseUp(e) {
// revert drag stuff for old-school browsers
if (document.onselectstart !== undefined) if (document.onselectstart !== undefined)
document.onselectstart = workarounds.onselectstart; document.onselectstart = workarounds.onselectstart;
if (document.ondrag !== undefined) if (document.ondrag !== undefined)
document.ondrag = workarounds.ondrag; document.ondrag = workarounds.ondrag;
if (selectionInterval != null) { // no more draggy-dee-drag
clearInterval(selectionInterval); selection.active = false;
selectionInterval = null; updateSelection(e);
}
setSelectionPos(selection.second, e);
clearSelection();
if (!selectionIsSane() || e.which != 1)
return false;
drawSelection();
triggerSelectedEvent(); triggerSelectedEvent();
ignoreClick = true; clickIsMouseUp = true;
return false; return false;
} }
function setSelectionPos(pos, e) { function setSelectionPos(pos, e) {
var offset = $(overlay).offset(); var offset = eventHolder.offset();
if (options.selection.mode == "y") { if (options.selection.mode == "y") {
if (pos == selection.first) if (pos == selection.first)
pos.x = 0; pos.x = 0;
...@@ -1695,36 +1786,27 @@ ...@@ -1695,36 +1786,27 @@
} }
} }
function updateSelectionOnMouseMove() { function updateSelection(pos) {
if (lastMousePos.pageX == null) if (pos.pageX == null)
return; return;
setSelectionPos(selection.second, lastMousePos); setSelectionPos(selection.second, pos);
clearSelection(); if (selectionIsSane()) {
if (selectionIsSane()) selection.show = true;
drawSelection(); triggerRedrawOverlay();
}
else
clearSelection();
} }
function clearSelection() { function clearSelection() {
if (prevSelection == null) if (selection.show) {
return; selection.show = false;
triggerRedrawOverlay();
var x = Math.min(prevSelection.first.x, prevSelection.second.x), }
y = Math.min(prevSelection.first.y, prevSelection.second.y),
w = Math.abs(prevSelection.second.x - prevSelection.first.x),
h = Math.abs(prevSelection.second.y - prevSelection.first.y);
octx.clearRect(x + plotOffset.left - octx.lineWidth,
y + plotOffset.top - octx.lineWidth,
w + octx.lineWidth*2,
h + octx.lineWidth*2);
prevSelection = null;
} }
function setSelection(ranges) { function setSelection(ranges) {
clearSelection();
var axis, from, to; var axis, from, to;
if (options.selection.mode == "y") { if (options.selection.mode == "y") {
...@@ -1779,37 +1861,11 @@ ...@@ -1779,37 +1861,11 @@
selection.second.y = axis.p2c(to); selection.second.y = axis.p2c(to);
} }
drawSelection(); selection.show = true;
triggerRedrawOverlay();
triggerSelectedEvent(); triggerSelectedEvent();
} }
function drawSelection() {
if (prevSelection != null &&
selection.first.x == prevSelection.first.x &&
selection.first.y == prevSelection.first.y &&
selection.second.x == prevSelection.second.x &&
selection.second.y == prevSelection.second.y)
return;
octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
ctx.lineJoin = "round";
octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
prevSelection = { first: { x: selection.first.x,
y: selection.first.y },
second: { x: selection.second.x,
y: selection.second.y } };
var x = Math.min(selection.first.x, selection.second.x),
y = Math.min(selection.first.y, selection.second.y),
w = Math.abs(selection.second.x - selection.first.x),
h = Math.abs(selection.second.y - selection.first.y);
octx.fillRect(x + plotOffset.left, y + plotOffset.top, w, h);
octx.strokeRect(x + plotOffset.left, y + plotOffset.top, w, h);
}
function selectionIsSane() { function selectionIsSane() {
var minSize = 5; var minSize = 5;
return Math.abs(selection.second.x - selection.first.x) >= minSize && return Math.abs(selection.second.x - selection.first.x) >= minSize &&
......
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