hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /* Flot plugin for selecting regions of a plot.
A 2
3 Copyright (c) 2007-2013 IOLA and Ole Laursen.
4 Licensed under the MIT license.
5
6 The plugin supports these options:
7
8 selection: {
9     mode: null or "x" or "y" or "xy",
10     color: color,
11     shape: "round" or "miter" or "bevel",
12     minSize: number of pixels
13 }
14
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.
21
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
30 dragging.
31
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:
35
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, ...
40     });
41
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,
46
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.
50
51 The plugin allso adds the following methods to the plot object:
52
53 - setSelection( ranges, preventEvent )
54
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
59   this:
60
61     setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
62
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.
68
69 - clearSelection( preventEvent )
70
71   Clear the selection rectangle. Pass in true to avoid getting a
72   "plotunselected" event.
73
74 - getSelection()
75
76   Returns the current selection in the same format as the "plotselected"
77   event. If there's currently no selection, the function returns null.
78
79 */
80
81 (function ($) {
82     function init(plot) {
83         var selection = {
84                 first: { x: -1, y: -1}, second: { x: -1, y: -1},
85                 show: false,
86                 active: false
87             };
88
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 = {};
95
96         var mouseUpHandler = null;
97         
98         function onMouseMove(e) {
99             if (selection.active) {
100                 updateSelection(e);
101                 
102                 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
103             }
104         }
105
106         function onMouseDown(e) {
107             if (e.which != 1)  // only accept left-click
108                 return;
109             
110             // cancel out any text selections
111             document.body.focus();
112
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; };
117             }
118             if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
119                 savedhandlers.ondrag = document.ondrag;
120                 document.ondrag = function () { return false; };
121             }
122
123             setSelectionPos(selection.first, e);
124
125             selection.active = true;
126
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); };
130             
131             $(document).one("mouseup", mouseUpHandler);
132         }
133
134         function onMouseUp(e) {
135             mouseUpHandler = null;
136             
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;
142
143             // no more dragging
144             selection.active = false;
145             updateSelection(e);
146
147             if (selectionIsSane())
148                 triggerSelectedEvent();
149             else {
150                 // this counts as a clear
151                 plot.getPlaceholder().trigger("plotunselected", [ ]);
152                 plot.getPlaceholder().trigger("plotselecting", [ null ]);
153             }
154
155             return false;
156         }
157
158         function getSelection() {
159             if (!selectionIsSane())
160                 return null;
161             
162             if (!selection.show) return null;
163
164             var r = {}, c1 = selection.first, c2 = selection.second;
165             $.each(plot.getAxes(), function (name, axis) {
166                 if (axis.used) {
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) };
169                 }
170             });
171             return r;
172         }
173
174         function triggerSelectedEvent() {
175             var r = getSelection();
176
177             plot.getPlaceholder().trigger("plotselected", [ r ]);
178
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 } ]);
182         }
183
184         function clamp(min, value, max) {
185             return value < min ? min: (value > max ? max: value);
186         }
187
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());
194
195             if (o.selection.mode == "y")
196                 pos.x = pos == selection.first ? 0 : plot.width();
197
198             if (o.selection.mode == "x")
199                 pos.y = pos == selection.first ? 0 : plot.height();
200         }
201
202         function updateSelection(pos) {
203             if (pos.pageX == null)
204                 return;
205
206             setSelectionPos(selection.second, pos);
207             if (selectionIsSane()) {
208                 selection.show = true;
209                 plot.triggerRedrawOverlay();
210             }
211             else
212                 clearSelection(true);
213         }
214
215         function clearSelection(preventEvent) {
216             if (selection.show) {
217                 selection.show = false;
218                 plot.triggerRedrawOverlay();
219                 if (!preventEvent)
220                     plot.getPlaceholder().trigger("plotunselected", [ ]);
221             }
222         }
223
224         // function taken from markings support in Flot
225         function extractRange(ranges, coord) {
226             var axis, from, to, key, axes = plot.getAxes();
227
228             for (var k in axes) {
229                 axis = axes[k];
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
234                     if (ranges[key]) {
235                         from = ranges[key].from;
236                         to = ranges[key].to;
237                         break;
238                     }
239                 }
240             }
241
242             // backwards-compat stuff - to be removed in future
243             if (!ranges[key]) {
244                 axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
245                 from = ranges[coord + "1"];
246                 to = ranges[coord + "2"];
247             }
248
249             // auto-reverse as an added bonus
250             if (from != null && to != null && from > to) {
251                 var tmp = from;
252                 from = to;
253                 to = tmp;
254             }
255             
256             return { from: from, to: to, axis: axis };
257         }
258         
259         function setSelection(ranges, preventEvent) {
260             var axis, range, o = plot.getOptions();
261
262             if (o.selection.mode == "y") {
263                 selection.first.x = 0;
264                 selection.second.x = plot.width();
265             }
266             else {
267                 range = extractRange(ranges, "x");
268
269                 selection.first.x = range.axis.p2c(range.from);
270                 selection.second.x = range.axis.p2c(range.to);
271             }
272
273             if (o.selection.mode == "x") {
274                 selection.first.y = 0;
275                 selection.second.y = plot.height();
276             }
277             else {
278                 range = extractRange(ranges, "y");
279
280                 selection.first.y = range.axis.p2c(range.from);
281                 selection.second.y = range.axis.p2c(range.to);
282             }
283
284             selection.show = true;
285             plot.triggerRedrawOverlay();
286             if (!preventEvent && selectionIsSane())
287                 triggerSelectedEvent();
288         }
289
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;
294         }
295
296         plot.clearSelection = clearSelection;
297         plot.setSelection = setSelection;
298         plot.getSelection = getSelection;
299
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);
305             }
306         });
307
308
309         plot.hooks.drawOverlay.push(function (plot, ctx) {
310             // draw selection
311             if (selection.show && selectionIsSane()) {
312                 var plotOffset = plot.getPlotOffset();
313                 var o = plot.getOptions();
314
315                 ctx.save();
316                 ctx.translate(plotOffset.left, plotOffset.top);
317
318                 var c = $.color.parse(o.selection.color);
319
320                 ctx.strokeStyle = c.scale('a', 0.8).toString();
321                 ctx.lineWidth = 1;
322                 ctx.lineJoin = o.selection.shape;
323                 ctx.fillStyle = c.scale('a', 0.4).toString();
324
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;
329
330                 ctx.fillRect(x, y, w, h);
331                 ctx.strokeRect(x, y, w, h);
332
333                 ctx.restore();
334             }
335         });
336         
337         plot.hooks.shutdown.push(function (plot, eventHolder) {
338             eventHolder.unbind("mousemove", onMouseMove);
339             eventHolder.unbind("mousedown", onMouseDown);
340             
341             if (mouseUpHandler)
342                 $(document).unbind("mouseup", mouseUpHandler);
343         });
344
345     }
346
347     $.plot.plugins.push({
348         init: init,
349         options: {
350             selection: {
351                 mode: null, // one of null, "x", "y" or "xy"
352                 color: "#e8cfac",
353                 shape: "round", // one of "round", "miter", or "bevel"
354                 minSize: 5 // minimum number of pixels
355             }
356         },
357         name: 'selection',
358         version: '1.1'
359     });
360 })(jQuery);