minor fixes
[sgn.git] / js / flot / navigate.js
blobf2b97603c321bcb9dc1ebee45e61a7c19cdd46f3
1 /*
2 Flot plugin for adding panning and zooming capabilities to a plot.
4 The default behaviour is double click and scrollwheel up/down to zoom
5 in, drag to pan. The plugin defines plot.zoom({ center }),
6 plot.zoomOut() and plot.pan(offset) so you easily can add custom
7 controls. It also fires a "plotpan" and "plotzoom" event when
8 something happens, useful for synchronizing plots.
10 Options:
12   zoom: {
13     interactive: false
14     trigger: "dblclick" // or "click" for single click
15     amount: 1.5         // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
16   }
17   
18   pan: {
19     interactive: false
20     cursor: "move"      // CSS mouse cursor value used when dragging, e.g. "pointer"
21     frameRate: 20
22   }
24   xaxis, yaxis, x2axis, y2axis: {
25     zoomRange: null  // or [number, number] (min range, max range) or false
26     panRange: null   // or [number, number] (min, max) or false
27   }
28   
29 "interactive" enables the built-in drag/click behaviour. If you enable
30 interactive for pan, then you'll have a basic plot that supports
31 moving around; the same for zoom.
33 "amount" specifies the default amount to zoom in (so 1.5 = 150%)
34 relative to the current viewport.
36 "cursor" is a standard CSS mouse cursor string used for visual
37 feedback to the user when dragging.
39 "frameRate" specifies the maximum number of times per second the plot
40 will update itself while the user is panning around on it (set to null
41 to disable intermediate pans, the plot will then not update until the
42 mouse button is released).
44 "zoomRange" is the interval in which zooming can happen, e.g. with
45 zoomRange: [1, 100] the zoom will never scale the axis so that the
46 difference between min and max is smaller than 1 or larger than 100.
47 You can set either end to null to ignore, e.g. [1, null]. If you set
48 zoomRange to false, zooming on that axis will be disabled.
50 "panRange" confines the panning to stay within a range, e.g. with
51 panRange: [-10, 20] panning stops at -10 in one end and at 20 in the
52 other. Either can be null, e.g. [-10, null]. If you set
53 panRange to false, panning on that axis will be disabled.
55 Example API usage:
57   plot = $.plot(...);
58   
59   // zoom default amount in on the pixel (10, 20) 
60   plot.zoom({ center: { left: 10, top: 20 } });
62   // zoom out again
63   plot.zoomOut({ center: { left: 10, top: 20 } });
65   // zoom 200% in on the pixel (10, 20) 
66   plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
67   
68   // pan 100 pixels to the left and 20 down
69   plot.pan({ left: -100, top: 20 })
71 Here, "center" specifies where the center of the zooming should
72 happen. Note that this is defined in pixel space, not the space of the
73 data points (you can use the p2c helpers on the axes in Flot to help
74 you convert between these).
76 "amount" is the amount to zoom the viewport relative to the current
77 range, so 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is
78 70% (zoom out). You can set the default in the options.
79   
83 // First two dependencies, jquery.event.drag.js and
84 // jquery.mousewheel.js, we put them inline here to save people the
85 // effort of downloading them.
88 jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)  
89 Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
91 (function(E){E.fn.drag=function(L,K,J){if(K){this.bind("dragstart",L)}if(J){this.bind("dragend",J)}return !L?this.trigger("drag"):this.bind("drag",K?K:L)};var A=E.event,B=A.special,F=B.drag={not:":input",distance:0,which:1,dragging:false,setup:function(J){J=E.extend({distance:F.distance,which:F.which,not:F.not},J||{});J.distance=I(J.distance);A.add(this,"mousedown",H,J);if(this.attachEvent){this.attachEvent("ondragstart",D)}},teardown:function(){A.remove(this,"mousedown",H);if(this===F.dragging){F.dragging=F.proxy=false}G(this,true);if(this.detachEvent){this.detachEvent("ondragstart",D)}}};B.dragstart=B.dragend={setup:function(){},teardown:function(){}};function H(L){var K=this,J,M=L.data||{};if(M.elem){K=L.dragTarget=M.elem;L.dragProxy=F.proxy||K;L.cursorOffsetX=M.pageX-M.left;L.cursorOffsetY=M.pageY-M.top;L.offsetX=L.pageX-L.cursorOffsetX;L.offsetY=L.pageY-L.cursorOffsetY}else{if(F.dragging||(M.which>0&&L.which!=M.which)||E(L.target).is(M.not)){return }}switch(L.type){case"mousedown":E.extend(M,E(K).offset(),{elem:K,target:L.target,pageX:L.pageX,pageY:L.pageY});A.add(document,"mousemove mouseup",H,M);G(K,false);F.dragging=null;return false;case !F.dragging&&"mousemove":if(I(L.pageX-M.pageX)+I(L.pageY-M.pageY)<M.distance){break}L.target=M.target;J=C(L,"dragstart",K);if(J!==false){F.dragging=K;F.proxy=L.dragProxy=E(J||K)[0]}case"mousemove":if(F.dragging){J=C(L,"drag",K);if(B.drop){B.drop.allowed=(J!==false);B.drop.handler(L)}if(J!==false){break}L.type="mouseup"}case"mouseup":A.remove(document,"mousemove mouseup",H);if(F.dragging){if(B.drop){B.drop.handler(L)}C(L,"dragend",K)}G(K,true);F.dragging=F.proxy=M.elem=false;break}return true}function C(M,K,L){M.type=K;var J=E.event.handle.call(L,M);return J===false?false:J||M.result}function I(J){return Math.pow(J,2)}function D(){return(F.dragging===false)}function G(K,J){if(!K){return }K.unselectable=J?"off":"on";K.onselectstart=function(){return J};if(K.style){K.style.MozUserSelect=J?"":"none"}}})(jQuery);
94 /* jquery.mousewheel.min.js
95  * Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
96  * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
97  * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
98  * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
99  * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
101  * Version: 3.0.2
102  * 
103  * Requires: 1.2.2+
104  */
105 (function(c){var a=["DOMMouseScroll","mousewheel"];c.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var d=a.length;d;){this.addEventListener(a[--d],b,false)}}else{this.onmousewheel=b}},teardown:function(){if(this.removeEventListener){for(var d=a.length;d;){this.removeEventListener(a[--d],b,false)}}else{this.onmousewheel=null}}};c.fn.extend({mousewheel:function(d){return d?this.bind("mousewheel",d):this.trigger("mousewheel")},unmousewheel:function(d){return this.unbind("mousewheel",d)}});function b(f){var d=[].slice.call(arguments,1),g=0,e=true;f=c.event.fix(f||window.event);f.type="mousewheel";if(f.wheelDelta){g=f.wheelDelta/120}if(f.detail){g=-f.detail/3}d.unshift(f,g);return c.event.handle.apply(this,d)}})(jQuery);
110 (function ($) {
111     var options = {
112         xaxis: {
113             zoomRange: null, // or [number, number] (min range, max range)
114             panRange: null // or [number, number] (min, max)
115         },
116         zoom: {
117             interactive: false,
118             trigger: "dblclick", // or "click" for single click
119             amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
120         },
121         pan: {
122             interactive: false,
123             cursor: "move",
124             frameRate: 20
125         }
126     };
128     function init(plot) {
129         function onZoomClick(e, zoomOut) {
130             var c = plot.offset();
131             c.left = e.pageX - c.left;
132             c.top = e.pageY - c.top;
133             if (zoomOut)
134                 plot.zoomOut({ center: c });
135             else
136                 plot.zoom({ center: c });
137         }
139         function onMouseWheel(e, delta) {
140             onZoomClick(e, delta < 0);
141             return false;
142         }
143         
144         var prevCursor = 'default', prevPageX = 0, prevPageY = 0,
145             panTimeout = null;
147         function onDragStart(e) {
148             if (e.which != 1)  // only accept left-click
149                 return false;
150             var c = plot.getPlaceholder().css('cursor');
151             if (c)
152                 prevCursor = c;
153             plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
154             prevPageX = e.pageX;
155             prevPageY = e.pageY;
156         }
157         
158         function onDrag(e) {
159             var frameRate = plot.getOptions().pan.frameRate;
160             if (panTimeout || !frameRate)
161                 return;
163             panTimeout = setTimeout(function () {
164                 plot.pan({ left: prevPageX - e.pageX,
165                            top: prevPageY - e.pageY });
166                 prevPageX = e.pageX;
167                 prevPageY = e.pageY;
168                                                     
169                 panTimeout = null;
170             }, 1 / frameRate * 1000);
171         }
173         function onDragEnd(e) {
174             if (panTimeout) {
175                 clearTimeout(panTimeout);
176                 panTimeout = null;
177             }
178                     
179             plot.getPlaceholder().css('cursor', prevCursor);
180             plot.pan({ left: prevPageX - e.pageX,
181                        top: prevPageY - e.pageY });
182         }
183         
184         function bindEvents(plot, eventHolder) {
185             var o = plot.getOptions();
186             if (o.zoom.interactive) {
187                 eventHolder[o.zoom.trigger](onZoomClick);
188                 eventHolder.mousewheel(onMouseWheel);
189             }
191             if (o.pan.interactive) {
192                 eventHolder.bind("dragstart", { distance: 10 }, onDragStart);
193                 eventHolder.bind("drag", onDrag);
194                 eventHolder.bind("dragend", onDragEnd);
195             }
196         }
198         plot.zoomOut = function (args) {
199             if (!args)
200                 args = {};
201             
202             if (!args.amount)
203                 args.amount = plot.getOptions().zoom.amount
205             args.amount = 1 / args.amount;
206             plot.zoom(args);
207         }
208         
209         plot.zoom = function (args) {
210             if (!args)
211                 args = {};
212             
213             var c = args.center,
214                 amount = args.amount || plot.getOptions().zoom.amount,
215                 w = plot.width(), h = plot.height();
217             if (!c)
218                 c = { left: w / 2, top: h / 2 };
219                 
220             var xf = c.left / w,
221                 yf = c.top / h,
222                 minmax = {
223                     x: {
224                         min: c.left - xf * w / amount,
225                         max: c.left + (1 - xf) * w / amount
226                     },
227                     y: {
228                         min: c.top - yf * h / amount,
229                         max: c.top + (1 - yf) * h / amount
230                     }
231                 };
233             $.each(plot.getAxes(), function(_, axis) {
234                 var opts = axis.options,
235                     min = minmax[axis.direction].min,
236                     max = minmax[axis.direction].max,
237                     zr = opts.zoomRange;
239                 if (zr === false) // no zooming on this axis
240                     return;
241                     
242                 min = axis.c2p(min);
243                 max = axis.c2p(max);
244                 if (min > max) {
245                     // make sure min < max
246                     var tmp = min;
247                     min = max;
248                     max = tmp;
249                 }
251                 var range = max - min;
252                 if (zr &&
253                     ((zr[0] != null && range < zr[0]) ||
254                      (zr[1] != null && range > zr[1])))
255                     return;
256             
257                 opts.min = min;
258                 opts.max = max;
259             });
260             
261             plot.setupGrid();
262             plot.draw();
263             
264             if (!args.preventEvent)
265                 plot.getPlaceholder().trigger("plotzoom", [ plot ]);
266         }
268         plot.pan = function (args) {
269             var delta = {
270                 x: +args.left,
271                 y: +args.top
272             };
274             if (isNaN(delta.x))
275                 delta.x = 0;
276             if (isNaN(delta.y))
277                 delta.y = 0;
279             $.each(plot.getAxes(), function (_, axis) {
280                 var opts = axis.options,
281                     min, max, d = delta[axis.direction];
283                 min = axis.c2p(axis.p2c(axis.min) + d),
284                 max = axis.c2p(axis.p2c(axis.max) + d);
286                 var pr = opts.panRange;
287                 if (pr === false) // no panning on this axis
288                     return;
289                 
290                 if (pr) {
291                     // check whether we hit the wall
292                     if (pr[0] != null && pr[0] > min) {
293                         d = pr[0] - min;
294                         min += d;
295                         max += d;
296                     }
297                     
298                     if (pr[1] != null && pr[1] < max) {
299                         d = pr[1] - max;
300                         min += d;
301                         max += d;
302                     }
303                 }
304                 
305                 opts.min = min;
306                 opts.max = max;
307             });
308             
309             plot.setupGrid();
310             plot.draw();
311             
312             if (!args.preventEvent)
313                 plot.getPlaceholder().trigger("plotpan", [ plot ]);
314         }
316         function shutdown(plot, eventHolder) {
317             eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick);
318             eventHolder.unbind("mousewheel", onMouseWheel);
319             eventHolder.unbind("dragstart", onDragStart);
320             eventHolder.unbind("drag", onDrag);
321             eventHolder.unbind("dragend", onDragEnd);
322             if (panTimeout)
323                 clearTimeout(panTimeout);
324         }
325         
326         plot.hooks.bindEvents.push(bindEvents);
327         plot.hooks.shutdown.push(shutdown);
328     }
329     
330     $.plot.plugins.push({
331         init: init,
332         options: options,
333         name: 'navigate',
334         version: '1.3'
335     });
336 })(jQuery);