minor fixes
[sgn.git] / js / flot / selection.js
blob7f7b32694bda98c39b5f4af4ef54835c72d41dbb
1 /*
2 Flot plugin for selecting regions.
4 The plugin defines the following options:
6   selection: {
7     mode: null or "x" or "y" or "xy",
8     color: color
9   }
11 Selection support is enabled by setting the mode to one of "x", "y" or
12 "xy". In "x" mode, the user will only be able to specify the x range,
13 similarly for "y" mode. For "xy", the selection becomes a rectangle
14 where both ranges can be specified. "color" is color of the selection
15 (if you need to change the color later on, you can get to it with
16 plot.getOptions().selection.color).
18 When selection support is enabled, a "plotselected" event will be
19 emitted on the DOM element you passed into the plot function. The
20 event handler gets a parameter with the ranges selected on the axes,
21 like this:
23   placeholder.bind("plotselected", function(event, ranges) {
24     alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
25     // similar for yaxis - with multiple axes, the extra ones are in
26     // x2axis, x3axis, ...
27   });
29 The "plotselected" event is only fired when the user has finished
30 making the selection. A "plotselecting" event is fired during the
31 process with the same parameters as the "plotselected" event, in case
32 you want to know what's happening while it's happening,
34 A "plotunselected" event with no arguments is emitted when the user
35 clicks the mouse to remove the selection.
37 The plugin allso adds the following methods to the plot object:
39 - setSelection(ranges, preventEvent)
41   Set the selection rectangle. The passed in ranges is on the same
42   form as returned in the "plotselected" event. If the selection mode
43   is "x", you should put in either an xaxis range, if the mode is "y"
44   you need to put in an yaxis range and both xaxis and yaxis if the
45   selection mode is "xy", like this:
47     setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
49   setSelection will trigger the "plotselected" event when called. If
50   you don't want that to happen, e.g. if you're inside a
51   "plotselected" handler, pass true as the second parameter. If you
52   are using multiple axes, you can specify the ranges on any of those,
53   e.g. as x2axis/x3axis/... instead of xaxis, the plugin picks the
54   first one it sees.
55   
56 - clearSelection(preventEvent)
58   Clear the selection rectangle. Pass in true to avoid getting a
59   "plotunselected" event.
61 - getSelection()
63   Returns the current selection in the same format as the
64   "plotselected" event. If there's currently no selection, the
65   function returns null.
69 (function ($) {
70     function init(plot) {
71         var selection = {
72                 first: { x: -1, y: -1}, second: { x: -1, y: -1},
73                 show: false,
74                 active: false
75             };
77         // FIXME: The drag handling implemented here should be
78         // abstracted out, there's some similar code from a library in
79         // the navigation plugin, this should be massaged a bit to fit
80         // the Flot cases here better and reused. Doing this would
81         // make this plugin much slimmer.
82         var savedhandlers = {};
84         var mouseUpHandler = null;
85         
86         function onMouseMove(e) {
87             if (selection.active) {
88                 updateSelection(e);
89                 
90                 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
91             }
92         }
94         function onMouseDown(e) {
95             if (e.which != 1)  // only accept left-click
96                 return;
97             
98             // cancel out any text selections
99             document.body.focus();
101             // prevent text selection and drag in old-school browsers
102             if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
103                 savedhandlers.onselectstart = document.onselectstart;
104                 document.onselectstart = function () { return false; };
105             }
106             if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
107                 savedhandlers.ondrag = document.ondrag;
108                 document.ondrag = function () { return false; };
109             }
111             setSelectionPos(selection.first, e);
113             selection.active = true;
115             // this is a bit silly, but we have to use a closure to be
116             // able to whack the same handler again
117             mouseUpHandler = function (e) { onMouseUp(e); };
118             
119             $(document).one("mouseup", mouseUpHandler);
120         }
122         function onMouseUp(e) {
123             mouseUpHandler = null;
124             
125             // revert drag stuff for old-school browsers
126             if (document.onselectstart !== undefined)
127                 document.onselectstart = savedhandlers.onselectstart;
128             if (document.ondrag !== undefined)
129                 document.ondrag = savedhandlers.ondrag;
131             // no more dragging
132             selection.active = false;
133             updateSelection(e);
135             if (selectionIsSane())
136                 triggerSelectedEvent();
137             else {
138                 // this counts as a clear
139                 plot.getPlaceholder().trigger("plotunselected", [ ]);
140                 plot.getPlaceholder().trigger("plotselecting", [ null ]);
141             }
143             return false;
144         }
146         function getSelection() {
147             if (!selectionIsSane())
148                 return null;
150             var r = {}, c1 = selection.first, c2 = selection.second;
151             $.each(plot.getAxes(), function (name, axis) {
152                 if (axis.used) {
153                     var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); 
154                     r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
155                 }
156             });
157             return r;
158         }
160         function triggerSelectedEvent() {
161             var r = getSelection();
163             plot.getPlaceholder().trigger("plotselected", [ r ]);
165             // backwards-compat stuff, to be removed in future
166             if (r.xaxis && r.yaxis)
167                 plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
168         }
170         function clamp(min, value, max) {
171             return value < min ? min: (value > max ? max: value);
172         }
174         function setSelectionPos(pos, e) {
175             var o = plot.getOptions();
176             var offset = plot.getPlaceholder().offset();
177             var plotOffset = plot.getPlotOffset();
178             pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
179             pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
181             if (o.selection.mode == "y")
182                 pos.x = pos == selection.first ? 0 : plot.width();
184             if (o.selection.mode == "x")
185                 pos.y = pos == selection.first ? 0 : plot.height();
186         }
188         function updateSelection(pos) {
189             if (pos.pageX == null)
190                 return;
192             setSelectionPos(selection.second, pos);
193             if (selectionIsSane()) {
194                 selection.show = true;
195                 plot.triggerRedrawOverlay();
196             }
197             else
198                 clearSelection(true);
199         }
201         function clearSelection(preventEvent) {
202             if (selection.show) {
203                 selection.show = false;
204                 plot.triggerRedrawOverlay();
205                 if (!preventEvent)
206                     plot.getPlaceholder().trigger("plotunselected", [ ]);
207             }
208         }
210         // function taken from markings support in Flot
211         function extractRange(ranges, coord) {
212             var axis, from, to, key, axes = plot.getAxes();
214             for (var k in axes) {
215                 axis = axes[k];
216                 if (axis.direction == coord) {
217                     key = coord + axis.n + "axis";
218                     if (!ranges[key] && axis.n == 1)
219                         key = coord + "axis"; // support x1axis as xaxis
220                     if (ranges[key]) {
221                         from = ranges[key].from;
222                         to = ranges[key].to;
223                         break;
224                     }
225                 }
226             }
228             // backwards-compat stuff - to be removed in future
229             if (!ranges[key]) {
230                 axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
231                 from = ranges[coord + "1"];
232                 to = ranges[coord + "2"];
233             }
235             // auto-reverse as an added bonus
236             if (from != null && to != null && from > to) {
237                 var tmp = from;
238                 from = to;
239                 to = tmp;
240             }
241             
242             return { from: from, to: to, axis: axis };
243         }
244         
245         function setSelection(ranges, preventEvent) {
246             var axis, range, o = plot.getOptions();
248             if (o.selection.mode == "y") {
249                 selection.first.x = 0;
250                 selection.second.x = plot.width();
251             }
252             else {
253                 range = extractRange(ranges, "x");
255                 selection.first.x = range.axis.p2c(range.from);
256                 selection.second.x = range.axis.p2c(range.to);
257             }
259             if (o.selection.mode == "x") {
260                 selection.first.y = 0;
261                 selection.second.y = plot.height();
262             }
263             else {
264                 range = extractRange(ranges, "y");
266                 selection.first.y = range.axis.p2c(range.from);
267                 selection.second.y = range.axis.p2c(range.to);
268             }
270             selection.show = true;
271             plot.triggerRedrawOverlay();
272             if (!preventEvent && selectionIsSane())
273                 triggerSelectedEvent();
274         }
276         function selectionIsSane() {
277             var minSize = 5;
278             return Math.abs(selection.second.x - selection.first.x) >= minSize &&
279                 Math.abs(selection.second.y - selection.first.y) >= minSize;
280         }
282         plot.clearSelection = clearSelection;
283         plot.setSelection = setSelection;
284         plot.getSelection = getSelection;
286         plot.hooks.bindEvents.push(function(plot, eventHolder) {
287             var o = plot.getOptions();
288             if (o.selection.mode != null) {
289                 eventHolder.mousemove(onMouseMove);
290                 eventHolder.mousedown(onMouseDown);
291             }
292         });
295         plot.hooks.drawOverlay.push(function (plot, ctx) {
296             // draw selection
297             if (selection.show && selectionIsSane()) {
298                 var plotOffset = plot.getPlotOffset();
299                 var o = plot.getOptions();
301                 ctx.save();
302                 ctx.translate(plotOffset.left, plotOffset.top);
304                 var c = $.color.parse(o.selection.color);
306                 ctx.strokeStyle = c.scale('a', 0.8).toString();
307                 ctx.lineWidth = 1;
308                 ctx.lineJoin = "round";
309                 ctx.fillStyle = c.scale('a', 0.4).toString();
311                 var x = Math.min(selection.first.x, selection.second.x),
312                     y = Math.min(selection.first.y, selection.second.y),
313                     w = Math.abs(selection.second.x - selection.first.x),
314                     h = Math.abs(selection.second.y - selection.first.y);
316                 ctx.fillRect(x, y, w, h);
317                 ctx.strokeRect(x, y, w, h);
319                 ctx.restore();
320             }
321         });
322         
323         plot.hooks.shutdown.push(function (plot, eventHolder) {
324             eventHolder.unbind("mousemove", onMouseMove);
325             eventHolder.unbind("mousedown", onMouseDown);
326             
327             if (mouseUpHandler)
328                 $(document).unbind("mouseup", mouseUpHandler);
329         });
331     }
333     $.plot.plugins.push({
334         init: init,
335         options: {
336             selection: {
337                 mode: null, // one of null, "x", "y" or "xy"
338                 color: "#e8cfac"
339             }
340         },
341         name: 'selection',
342         version: '1.1'
343     });
344 })(jQuery);