Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/* Flot plugin for selecting regions of a plot.
2
 
3
Copyright (c) 2007-2014 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);