1 /* Flot plugin for selecting regions of a plot.
3 Copyright (c) 2007-2013 IOLA and Ole Laursen.
4 Licensed under the MIT license.
6 The plugin supports these options:
9 mode: null or "x" or "y" or "xy",
11 shape: "round" or "miter" or "bevel",
12 minSize: number of pixels
15 Selection support is enabled by setting the mode to one of "x", "y" or "xy".
16 In "x" mode, the user will only be able to specify the x range, similarly for
17 "y" mode. For "xy", the selection becomes a rectangle where both ranges can be
18 specified. "color" is color of the selection (if you need to change the color
19 later on, you can get to it with plot.getOptions().selection.color). "shape"
20 is the shape of the corners of the selection.
22 "minSize" is the minimum size a selection can be in pixels. This value can
23 be customized to determine the smallest size a selection can be and still
24 have the selection rectangle be displayed. When customizing this value, the
25 fact that it refers to pixels, not axis units must be taken into account.
26 Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
27 minute, setting "minSize" to 1 will not make the minimum selection size 1
28 minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
29 "plotunselected" events from being fired when the user clicks the mouse without
32 When selection support is enabled, a "plotselected" event will be emitted on
33 the DOM element you passed into the plot function. The event handler gets a
34 parameter with the ranges selected on the axes, like this:
36 placeholder.bind( "plotselected", function( event, ranges ) {
37 alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
38 // similar for yaxis - with multiple axes, the extra ones are in
39 // x2axis, x3axis, ...
42 The "plotselected" event is only fired when the user has finished making the
43 selection. A "plotselecting" event is fired during the process with the same
44 parameters as the "plotselected" event, in case you want to know what's
45 happening while it's happening,
47 A "plotunselected" event with no arguments is emitted when the user clicks the
48 mouse to remove the selection. As stated above, setting "minSize" to 0 will
49 destroy this behavior.
51 The plugin allso adds the following methods to the plot object:
53 - setSelection( ranges, preventEvent )
55 Set the selection rectangle. The passed in ranges is on the same form as
56 returned in the "plotselected" event. If the selection mode is "x", you
57 should put in either an xaxis range, if the mode is "y" you need to put in
58 an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
61 setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
63 setSelection will trigger the "plotselected" event when called. If you don't
64 want that to happen, e.g. if you're inside a "plotselected" handler, pass
65 true as the second parameter. If you are using multiple axes, you can
66 specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
67 xaxis, the plugin picks the first one it sees.
69 - clearSelection( preventEvent )
71 Clear the selection rectangle. Pass in true to avoid getting a
72 "plotunselected" event.
76 Returns the current selection in the same format as the "plotselected"
77 event. If there's currently no selection, the function returns null.
84 first: { x: -1, y: -1}, second: { x: -1, y: -1},
89 // FIXME: The drag handling implemented here should be
90 // abstracted out, there's some similar code from a library in
91 // the navigation plugin, this should be massaged a bit to fit
92 // the Flot cases here better and reused. Doing this would
93 // make this plugin much slimmer.
94 var savedhandlers = {};
96 var mouseUpHandler = null;
98 function onMouseMove(e) {
99 if (selection.active) {
102 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
106 function onMouseDown(e) {
107 if (e.which != 1) // only accept left-click
110 // cancel out any text selections
111 document.body.focus();
113 // prevent text selection and drag in old-school browsers
114 if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
115 savedhandlers.onselectstart = document.onselectstart;
116 document.onselectstart = function () { return false; };
118 if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
119 savedhandlers.ondrag = document.ondrag;
120 document.ondrag = function () { return false; };
123 setSelectionPos(selection.first, e);
125 selection.active = true;
127 // this is a bit silly, but we have to use a closure to be
128 // able to whack the same handler again
129 mouseUpHandler = function (e) { onMouseUp(e); };
131 $(document).one("mouseup", mouseUpHandler);
134 function onMouseUp(e) {
135 mouseUpHandler = null;
137 // revert drag stuff for old-school browsers
138 if (document.onselectstart !== undefined)
139 document.onselectstart = savedhandlers.onselectstart;
140 if (document.ondrag !== undefined)
141 document.ondrag = savedhandlers.ondrag;
144 selection.active = false;
147 if (selectionIsSane())
148 triggerSelectedEvent();
150 // this counts as a clear
151 plot.getPlaceholder().trigger("plotunselected", [ ]);
152 plot.getPlaceholder().trigger("plotselecting", [ null ]);
158 function getSelection() {
159 if (!selectionIsSane())
162 if (!selection.show) return null;
164 var r = {}, c1 = selection.first, c2 = selection.second;
165 $.each(plot.getAxes(), function (name, axis) {
167 var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
168 r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
174 function triggerSelectedEvent() {
175 var r = getSelection();
177 plot.getPlaceholder().trigger("plotselected", [ r ]);
179 // backwards-compat stuff, to be removed in future
180 if (r.xaxis && r.yaxis)
181 plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
184 function clamp(min, value, max) {
185 return value < min ? min: (value > max ? max: value);
188 function setSelectionPos(pos, e) {
189 var o = plot.getOptions();
190 var offset = plot.getPlaceholder().offset();
191 var plotOffset = plot.getPlotOffset();
192 pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
193 pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
195 if (o.selection.mode == "y")
196 pos.x = pos == selection.first ? 0 : plot.width();
198 if (o.selection.mode == "x")
199 pos.y = pos == selection.first ? 0 : plot.height();
202 function updateSelection(pos) {
203 if (pos.pageX == null)
206 setSelectionPos(selection.second, pos);
207 if (selectionIsSane()) {
208 selection.show = true;
209 plot.triggerRedrawOverlay();
212 clearSelection(true);
215 function clearSelection(preventEvent) {
216 if (selection.show) {
217 selection.show = false;
218 plot.triggerRedrawOverlay();
220 plot.getPlaceholder().trigger("plotunselected", [ ]);
224 // function taken from markings support in Flot
225 function extractRange(ranges, coord) {
226 var axis, from, to, key, axes = plot.getAxes();
228 for (var k in axes) {
230 if (axis.direction == coord) {
231 key = coord + axis.n + "axis";
232 if (!ranges[key] && axis.n == 1)
233 key = coord + "axis"; // support x1axis as xaxis
235 from = ranges[key].from;
242 // backwards-compat stuff - to be removed in future
244 axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
245 from = ranges[coord + "1"];
246 to = ranges[coord + "2"];
249 // auto-reverse as an added bonus
250 if (from != null && to != null && from > to) {
256 return { from: from, to: to, axis: axis };
259 function setSelection(ranges, preventEvent) {
260 var axis, range, o = plot.getOptions();
262 if (o.selection.mode == "y") {
263 selection.first.x = 0;
264 selection.second.x = plot.width();
267 range = extractRange(ranges, "x");
269 selection.first.x = range.axis.p2c(range.from);
270 selection.second.x = range.axis.p2c(range.to);
273 if (o.selection.mode == "x") {
274 selection.first.y = 0;
275 selection.second.y = plot.height();
278 range = extractRange(ranges, "y");
280 selection.first.y = range.axis.p2c(range.from);
281 selection.second.y = range.axis.p2c(range.to);
284 selection.show = true;
285 plot.triggerRedrawOverlay();
286 if (!preventEvent && selectionIsSane())
287 triggerSelectedEvent();
290 function selectionIsSane() {
291 var minSize = plot.getOptions().selection.minSize;
292 return Math.abs(selection.second.x - selection.first.x) >= minSize &&
293 Math.abs(selection.second.y - selection.first.y) >= minSize;
296 plot.clearSelection = clearSelection;
297 plot.setSelection = setSelection;
298 plot.getSelection = getSelection;
300 plot.hooks.bindEvents.push(function(plot, eventHolder) {
301 var o = plot.getOptions();
302 if (o.selection.mode != null) {
303 eventHolder.mousemove(onMouseMove);
304 eventHolder.mousedown(onMouseDown);
309 plot.hooks.drawOverlay.push(function (plot, ctx) {
311 if (selection.show && selectionIsSane()) {
312 var plotOffset = plot.getPlotOffset();
313 var o = plot.getOptions();
316 ctx.translate(plotOffset.left, plotOffset.top);
318 var c = $.color.parse(o.selection.color);
320 ctx.strokeStyle = c.scale('a', 0.8).toString();
322 ctx.lineJoin = o.selection.shape;
323 ctx.fillStyle = c.scale('a', 0.4).toString();
325 var x = Math.min(selection.first.x, selection.second.x) + 0.5,
326 y = Math.min(selection.first.y, selection.second.y) + 0.5,
327 w = Math.abs(selection.second.x - selection.first.x) - 1,
328 h = Math.abs(selection.second.y - selection.first.y) - 1;
330 ctx.fillRect(x, y, w, h);
331 ctx.strokeRect(x, y, w, h);
337 plot.hooks.shutdown.push(function (plot, eventHolder) {
338 eventHolder.unbind("mousemove", onMouseMove);
339 eventHolder.unbind("mousedown", onMouseDown);
342 $(document).unbind("mouseup", mouseUpHandler);
347 $.plot.plugins.push({
351 mode: null, // one of null, "x", "y" or "xy"
353 shape: "round", // one of "round", "miter", or "bevel"
354 minSize: 5 // minimum number of pixels