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

Removed work-around for slow jQuery mousemove events (closing issue

134), add new hooks and new API for interactive plugins, move
crosshair support to plugin


git-svn-id: https://flot.googlecode.com/svn/trunk@159 1e0a6537-2640-0410-bfb7-f154510ff394
parent 1f1866e6
......@@ -204,7 +204,7 @@ specified, the plot will furthermore extend the axis end-point to the
nearest whole tick. The default value is "null" for the x axis and
0.02 for the y axis which seems appropriate for most cases.
"labelWidth" and "labelHeight" specifies the maximum size of the tick
"labelWidth" and "labelHeight" specifies a fixed size of the tick
labels in pixels. They're useful in case you need to align several
plots.
......@@ -662,20 +662,6 @@ A "plotunselected" event with no arguments is emitted when the user
clicks the mouse to remove the selection.
Customizing the crosshair
=========================
crosshair: {
mode: null or "x" or "y" or "xy"
color: color
}
You can enable crosshairs, thin lines, that follow the mouse by
setting the mode to one of "x", "y" or "xy". The "x" mode enables a
vertical crosshair that lets you trace the values on the x axis, "y"
enables a horizontal crosshair and "xy" enables them both.
Specifying gradients
====================
......@@ -730,19 +716,11 @@ can call:
Clear the selection rectangle. Pass in true to avoid getting a
"plotunselected" event.
- getSelection()
- setCrosshair(pos)
Set the position of the crosshair. Note that this is cleared if
the user moves the mouse. "pos" should be on the form { x: xpos,
y: ypos } (or x2 and y2 if you're using the secondary axes), which
is coincidentally the same format as what you get from a "plothover"
event. If "pos" is null, the crosshair is cleared.
- clearCrosshair()
Clear the crosshair.
Returns the current selection in the same format as the
"plotselected" event. If there's currently no selection, it
returns null.
- highlight(series, datapoint)
......@@ -787,7 +765,28 @@ can call:
- draw()
Redraws the canvas.
Redraws the plot canvas.
- triggerRedrawOverlay()
Schedules an update of an overlay canvas used for drawing
interactive things like the selection and point highlights. This
is mostly useful for writing plugins. The redraw doesn't happen
immediately, instead a timer is set to catch multiple successive
redraws (e.g. from a mousemove).
- width()/height()
Gets the width and height of the plotting area inside the grid.
This is smaller than the canvas or placeholder dimensions as some
extra space is needed (e.g. for labels).
- offset()
Returns the offset of the plotting area inside the grid relative
to the document, useful for instance for calculating mouse
positions (event.pageX/Y minus this offset is the pixel position
inside the plot).
There are also some members that let you peek inside the internal
......@@ -851,7 +850,7 @@ Here's an overview of the phases Flot goes through:
1. Plugin initialization, parsing options
2. Constructing the canvas used for drawing
2. Constructing the canvases used for drawing
3. Set data: parsing data specification, calculating colors,
copying raw data points into internal format,
......@@ -871,7 +870,7 @@ sub-object on the Plot object with the names mentioned below, e.g.
var plot = $.plot(...);
function f() { alert("hello!")};
function f(plot, series, datapoints) { alert("hello!")};
plot.hooks.processDatapoints.push(f);
......@@ -886,6 +885,7 @@ Currently available hooks (when in doubt, check the Flot source):
Called after Flot has parsed and merged options. Rarely useful, but
does allow customizations beyond simple merging of default values.
- processRawData [phase 3]
function(plot, series, data, datapoints)
......@@ -895,6 +895,7 @@ Currently available hooks (when in doubt, check the Flot source):
points and sets datapoints.pointsize to the size of the points,
Flot will skip the copying/normalization step for this series.
- processDatapoints [phase 3]
function(plot, series, datapoints)
......@@ -916,6 +917,51 @@ Currently available hooks (when in doubt, check the Flot source):
doesn't check it or do any normalization on it afterwards.
- bindEvents [phase 6]
function(plot, eventHolder)
Called after Flot has setup its event handlers. Should set any
necessary event handlers on eventHolder, a jQuery object with the
canvas, e.g.
function (plot, eventHolder) {
eventHolder.mousedown(function (e) {
alert("You pressed the mouse at " + e.pageX + " " + e.pageY);
});
}
Interesting events include click, mousemove, mouseup/down. You can
use all jQuery events. Usually, the event handlers will update the
state by drawing something (add a drawOverlay hook and call
triggerRedrawOverlay) or firing an externally visible event for
user code. See the crosshair plugin for an example.
Currently, eventHolder actually contains both the static canvas
used for the plot itself and the overlay canvas used for
interactive features because some versions of IE get the stacking
order wrong. The hook only gets one event, though (either for the
overlay or for the static canvas).
- drawOverlay [phase 7]
function (plot, canvascontext)
The drawOverlay hook is used for interactive things that need a
canvas to draw on. The model currently used by Flot works the way
that an extra overlay canvas is positioned on top of the static
canvas. This overlay is cleared and then completely redrawn
whenever something interesting happens. This hook is called when
the overlay canvas is to be redrawn.
"canvascontext" is the 2D context of the overlay canvas. You can
use this to draw things. You'll most likely need some of the
metrics computed by Flot, e.g. plot.width()/plot.height(). See the
crosshair plugin for an example.
Plugins
-------
......
......@@ -34,10 +34,6 @@ Changes:
- A new "plotselecting" event is now emitted while the user is making
a selection.
- Added a new crosshairs feature for tracing the mouse position on the
axes, enable with crosshair { mode: "x"} (see the new tracking
example for a use).
- The "plothover" event is now emitted immediately instead of at most
10 times per second, you'll have to put in a setTimeout yourself if
you're doing something really expensive on this event.
......@@ -95,6 +91,10 @@ Changes:
them summed. This is useful for drawing additive/cumulative graphs
with bars and (currently unfilled) lines.
- Crosshairs plugin: trace the mouse position on the axes, enable with
crosshair: { mode: "x"} (see the new tracking example for a use).
Bug fixes:
- Fixed two corner-case bugs when drawing filled curves (report and
......
......@@ -28,7 +28,9 @@ support for VML which excanvas is relying on. It appears that some
stripped down versions used for test environments on virtual machines
lack the VML support.
Also note that you need at least jQuery 1.2.6.
Also note that you need at least jQuery 1.2.6 (but at least 1.3.2 is
recommended for interactive charts because of performance improvements
in event handling).
Basic usage
......
......@@ -7,6 +7,7 @@
<!--[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.crosshair.js"></script>
</head>
<body>
<h1>Flot Examples</h1>
......@@ -23,6 +24,7 @@
<p id="hoverdata"></p>
<script id="source" language="javascript" type="text/javascript">
var plot;
$(function () {
var sin = [], cos = [];
for (var i = 0; i < 14; i += 0.1) {
......@@ -30,7 +32,7 @@ $(function () {
cos.push([i, Math.cos(i)]);
}
var plot = $.plot($("#placeholder"),
plot = $.plot($("#placeholder"),
[ { data: sin, label: "sin(x) = -0.00"},
{ data: cos, label: "cos(x) = -0.00" } ], {
series: {
......
/*
Flot plugin for showing a crosshair, thin lines, when the mouse hovers
over the plot.
crosshair: {
mode: null or "x" or "y" or "xy"
color: color
}
Set the mode to one of "x", "y" or "xy". The "x" mode enables a
vertical crosshair that lets you trace the values on the x axis, "y"
enables a horizontal crosshair and "xy" enables them both. "color" is
the color of the crosshair (default is "rgba(170, 0, 0, 0.80)")
The plugin also adds two public methods:
- setCrosshair(pos)
Set the position of the crosshair. Note that this is cleared if
the user moves the mouse. "pos" should be on the form { x: xpos,
y: ypos } (or x2 and y2 if you're using the secondary axes), which
is coincidentally the same format as what you get from a "plothover"
event. If "pos" is null, the crosshair is cleared.
- clearCrosshair()
Clear the crosshair.
*/
(function ($) {
var options = {
crosshair: {
mode: null, // one of null, "x", "y" or "xy",
color: "rgba(170, 0, 0, 0.80)"
}
};
function init(plot) {
// position of crosshair in pixels
var crosshair = { x: -1, y: -1 };
plot.setCrosshair = function setCrosshair(pos) {
if (!pos)
crosshair.x = -1;
else {
var axes = plot.getAxes();
crosshair.x = Math.max(0, Math.min(pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plot.width()));
crosshair.y = Math.max(0, Math.min(pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plot.height()));
}
plot.triggerRedrawOverlay();
};
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
plot.hooks.bindEvents.push(function (plot, eventHolder) {
if (!plot.getOptions().crosshair.mode)
return;
eventHolder.mouseout(function () {
if (crosshair.x != -1) {
crosshair.x = -1;
plot.triggerRedrawOverlay();
}
});
eventHolder.mousemove(function (e) {
if (!plot.getSelection()) {
var offset = plot.offset();
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
plot.triggerRedrawOverlay();
}
else
crosshair.x = -1; // hide the crosshair while selecting
});
});
plot.hooks.drawOverlay.push(function (plot, ctx) {
var c = plot.getOptions().crosshair;
if (!c.mode)
return;
var plotOffset = plot.getPlotOffset();
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
if (crosshair.x != -1) {
ctx.strokeStyle = c.color;
ctx.lineWidth = 1;
ctx.lineJoin = "round";
ctx.beginPath();
if (c.mode.indexOf("x") != -1) {
ctx.moveTo(crosshair.x, 0);
ctx.lineTo(crosshair.x, plot.height());
}
if (c.mode.indexOf("y") != -1) {
ctx.moveTo(0, crosshair.y);
ctx.lineTo(plot.width(), crosshair.y);
}
ctx.stroke();
}
ctx.restore();
});
}
$.plot.plugins.push({
init: init,
options: options,
name: 'crosshair',
version: '1.0'
});
})(jQuery);
......@@ -98,10 +98,6 @@
selection: {
mode: null, // one of null, "x", "y" or "xy"
color: "#e8cfac"
},
crosshair: {
mode: null, // one of null, "x", "y" or "xy",
color: "#aa0000"
}
},
canvas = null, // the canvas for the plot itself
......@@ -115,7 +111,9 @@
hooks = {
processOptions: [],
processRawData: [],
processDatapoints: []
processDatapoints: [],
bindEvents: [],
drawOverlay: []
},
plot = this,
// dedicated to storing data for buggy standard compliance cases
......@@ -127,15 +125,23 @@
plot.draw = draw;
plot.clearSelection = clearSelection;
plot.setSelection = setSelection;
plot.getSelection = getSelection;
plot.getCanvas = function() { return canvas; };
plot.getPlotOffset = function() { return plotOffset; };
plot.width = function () { return plotWidth; }
plot.height = function () { return plotHeight; }
plot.offset = function () {
var o = eventHolder.offset();
o.left += plotOffset.left;
o.top += plotOffset.top;
return o;
};
plot.getData = function() { return series; };
plot.getAxes = function() { return axes; };
plot.getOptions = function() { return options; };
plot.setCrosshair = setCrosshair;
plot.clearCrosshair = function () { setCrosshair(null); };
plot.highlight = highlight;
plot.unhighlight = unhighlight;
plot.triggerRedrawOverlay = triggerRedrawOverlay;
// public attributes
plot.hooks = hooks;
......@@ -494,6 +500,7 @@
// overlay canvas for interactive features
overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(target).get(0);
octx = overlay.getContext("2d");
octx.stroke();
}
function bindEvents() {
......@@ -502,22 +509,17 @@
eventHolder = $([overlay, canvas]);
// bind events
if (options.selection.mode != null || options.crosshair.mode != null
|| options.grid.hoverable) {
// FIXME: temp. work-around until jQuery bug 4398 is fixed
eventHolder.each(function () {
this.onmousemove = onMouseMove;
});
if (options.selection.mode != null
|| options.grid.hoverable)
eventHolder.mousemove(onMouseMove);
if (options.selection.mode != null)
eventHolder.mousedown(onMouseDown);
}
if (options.crosshair.mode != null)
eventHolder.mouseout(onMouseOut);
if (options.grid.clickable)
eventHolder.click(onClick);
executeHooks(hooks.bindEvents, [eventHolder]);
}
function setupGrid() {
......@@ -1695,8 +1697,9 @@
var lastMousePos = { pageX: null, pageY: null },
selection = {
first: { x: -1, y: -1}, second: { x: -1, y: -1},
show: false, active: false },
crosshair = { pos: { x: -1, y: -1 } },
show: false,
active: false
},
highlights = [],
clickIsMouseUp = false,
redrawTimeout = null,
......@@ -1779,34 +1782,16 @@
return null;
}
function onMouseMove(ev) {
// FIXME: temp. work-around until jQuery bug 4398 is fixed
var e = ev || window.event;
if (e.pageX == null && e.clientX != null) {
var de = document.documentElement, b = document.body;
lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0) - (de.clientLeft || 0);
lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0) - (de.clientTop || 0);
}
else {
function onMouseMove(e) {
lastMousePos.pageX = e.pageX;
lastMousePos.pageY = e.pageY;
}
if (options.grid.hoverable)
triggerClickHoverEvent("plothover", lastMousePos,
function (s) { return s["hoverable"] != false; });
if (options.crosshair.mode != null) {
if (!selection.active) {
setPositionFromEvent(crosshair.pos, lastMousePos);
triggerRedrawOverlay();
}
else
crosshair.pos.x = -1; // hide the crosshair while selecting
}
if (selection.active) {
target.trigger("plotselecting", [ selectionIsSane() ? getSelectionForEvent() : null ]);
target.trigger("plotselecting", [ getSelection() ]);
updateSelection(lastMousePos);
}
......@@ -1836,13 +1821,6 @@
$(document).one("mouseup", onSelectionMouseUp);
}
function onMouseOut(ev) {
if (options.crosshair.mode != null && crosshair.pos.x != -1) {
crosshair.pos.x = -1;
triggerRedrawOverlay();
}
}
function onClick(e) {
if (clickIsMouseUp) {
clickIsMouseUp = false;
......@@ -1908,13 +1886,13 @@
function triggerRedrawOverlay() {
if (!redrawTimeout)
redrawTimeout = setTimeout(redrawOverlay, 30);
redrawTimeout = setTimeout(drawOverlay, 30);
}
function redrawOverlay() {
function drawOverlay() {
redrawTimeout = null;
// redraw highlights
// draw highlights
octx.save();
octx.clearRect(0, 0, canvasWidth, canvasHeight);
octx.translate(plotOffset.left, plotOffset.top);
......@@ -1929,7 +1907,7 @@
drawPointHighlight(hi.series, hi.point);
}
// redraw selection
// draw selection
if (selection.show && selectionIsSane()) {
octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
......@@ -1944,27 +1922,9 @@
octx.fillRect(x, y, w, h);
octx.strokeRect(x, y, w, h);
}
// redraw crosshair
var pos = crosshair.pos, mode = options.crosshair.mode;
if (mode != null && pos.x != -1) {
octx.strokeStyle = parseColor(options.crosshair.color).scale(null, null, null, 0.8).toString();
octx.lineWidth = 1;
ctx.lineJoin = "round";
octx.beginPath();
if (mode.indexOf("x") != -1) {
octx.moveTo(pos.x, 0);
octx.lineTo(pos.x, plotHeight);
}
if (mode.indexOf("y") != -1) {
octx.moveTo(0, pos.y);
octx.lineTo(plotWidth, pos.y);
}
octx.stroke();
}
octx.restore();
executeHooks(hooks.drawOverlay, [octx]);
}
function highlight(s, point, auto) {
......@@ -2039,23 +1999,10 @@
0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
}
function setPositionFromEvent(pos, e) {
var offset = eventHolder.offset();
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth);
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight);
}
function setCrosshair(pos) {
if (pos == null)
crosshair.pos.x = -1;
else {
crosshair.pos.x = clamp(0, pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plotWidth);
crosshair.pos.y = clamp(0, pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plotHeight);
}
triggerRedrawOverlay();
}
function getSelection() {
if (!selectionIsSane())
return null;
function getSelectionForEvent() {
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),
......@@ -2074,7 +2021,7 @@
}
function triggerSelectedEvent() {
var r = getSelectionForEvent();
var r = getSelection();
target.trigger("plotselected", [ r ]);
......@@ -2108,7 +2055,9 @@
}
function setSelectionPos(pos, e) {
setPositionFromEvent(pos, e);
var offset = eventHolder.offset();
pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth);
pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight);
if (options.selection.mode == "y") {
if (pos == selection.first)
......
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