1 /* Flot plugin for adding the ability to pan and zoom the plot. |
1 /* Flot plugin for adding the ability to pan and zoom the plot. |
2 |
2 |
3 Copyright (c) 2007-2014 IOLA and Ole Laursen. |
3 Copyright (c) 2007-2014 IOLA and Ole Laursen. |
|
4 Copyright (c) 2016 Ciprian Ceteras. |
|
5 Copyright (c) 2017 Raluca Portase. |
4 Licensed under the MIT license. |
6 Licensed under the MIT license. |
5 |
7 |
6 The default behaviour is double click and scrollwheel up/down to zoom in, drag |
8 */ |
|
9 |
|
10 /** |
|
11 ## jquery.flot.navigate.js |
|
12 |
|
13 This flot plugin is used for adding the ability to pan and zoom the plot. |
|
14 A higher level overview is available at [interactions](interactions.md) documentation. |
|
15 |
|
16 The default behaviour is scrollwheel up/down to zoom in, drag |
7 to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and |
17 to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and |
8 plot.pan( offset ) so you easily can add custom controls. It also fires |
18 plot.pan( offset ) so you easily can add custom controls. It also fires |
9 "plotpan" and "plotzoom" events, useful for synchronizing plots. |
19 "plotpan" and "plotzoom" events, useful for synchronizing plots. |
10 |
20 |
11 The plugin supports these options: |
21 The plugin supports these options: |
12 |
22 ```js |
13 zoom: { |
23 zoom: { |
14 interactive: false |
24 interactive: false, |
15 trigger: "dblclick" // or "click" for single click |
25 active: false, |
16 amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
26 amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
17 } |
27 } |
18 |
28 |
19 pan: { |
29 pan: { |
20 interactive: false |
30 interactive: false, |
21 cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer" |
31 active: false, |
22 frameRate: 20 |
32 cursor: "move", // CSS mouse cursor value used when dragging, e.g. "pointer" |
23 } |
33 frameRate: 60, |
24 |
34 mode: "smart" // enable smart pan mode |
25 xaxis, yaxis, x2axis, y2axis: { |
35 } |
26 zoomRange: null // or [ number, number ] (min range, max range) or false |
36 |
27 panRange: null // or [ number, number ] (min, max) or false |
37 xaxis: { |
28 } |
38 axisZoom: true, //zoom axis when mouse over it is allowed |
29 |
39 plotZoom: true, //zoom axis is allowed for plot zoom |
30 "interactive" enables the built-in drag/click behaviour. If you enable |
40 axisPan: true, //pan axis when mouse over it is allowed |
|
41 plotPan: true //pan axis is allowed for plot pan |
|
42 } |
|
43 |
|
44 yaxis: { |
|
45 axisZoom: true, //zoom axis when mouse over it is allowed |
|
46 plotZoom: true, //zoom axis is allowed for plot zoom |
|
47 axisPan: true, //pan axis when mouse over it is allowed |
|
48 plotPan: true //pan axis is allowed for plot pan |
|
49 } |
|
50 ``` |
|
51 **interactive** enables the built-in drag/click behaviour. If you enable |
31 interactive for pan, then you'll have a basic plot that supports moving |
52 interactive for pan, then you'll have a basic plot that supports moving |
32 around; the same for zoom. |
53 around; the same for zoom. |
33 |
54 |
34 "amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to |
55 **active** is true after a touch tap on plot. This enables plot navigation. |
|
56 Once activated, zoom and pan cannot be deactivated. When the plot becomes active, |
|
57 "plotactivated" event is triggered. |
|
58 |
|
59 **amount** specifies the default amount to zoom in (so 1.5 = 150%) relative to |
35 the current viewport. |
60 the current viewport. |
36 |
61 |
37 "cursor" is a standard CSS mouse cursor string used for visual feedback to the |
62 **cursor** is a standard CSS mouse cursor string used for visual feedback to the |
38 user when dragging. |
63 user when dragging. |
39 |
64 |
40 "frameRate" specifies the maximum number of times per second the plot will |
65 **frameRate** specifies the maximum number of times per second the plot will |
41 update itself while the user is panning around on it (set to null to disable |
66 update itself while the user is panning around on it (set to null to disable |
42 intermediate pans, the plot will then not update until the mouse button is |
67 intermediate pans, the plot will then not update until the mouse button is |
43 released). |
68 released). |
44 |
69 |
45 "zoomRange" is the interval in which zooming can happen, e.g. with zoomRange: |
70 **mode** a string specifies the pan mode for mouse interaction. Accepted values: |
46 [1, 100] the zoom will never scale the axis so that the difference between min |
71 'manual': no pan hint or direction snapping; |
47 and max is smaller than 1 or larger than 100. You can set either end to null |
72 'smart': The graph shows pan hint bar and the pan movement will snap |
48 to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis |
73 to one direction when the drag direction is close to it; |
49 will be disabled. |
74 'smartLock'. The graph shows pan hint bar and the pan movement will always |
50 |
75 snap to a direction that the drag diorection started with. |
51 "panRange" confines the panning to stay within a range, e.g. with panRange: |
|
52 [-10, 20] panning stops at -10 in one end and at 20 in the other. Either can |
|
53 be null, e.g. [-10, null]. If you set panRange to false, panning on that axis |
|
54 will be disabled. |
|
55 |
76 |
56 Example API usage: |
77 Example API usage: |
57 |
78 ```js |
58 plot = $.plot(...); |
79 plot = $.plot(...); |
59 |
80 |
60 // zoom default amount in on the pixel ( 10, 20 ) |
81 // zoom default amount in on the pixel ( 10, 20 ) |
61 plot.zoom({ center: { left: 10, top: 20 } }); |
82 plot.zoom({ center: { left: 10, top: 20 } }); |
62 |
83 |
63 // zoom out again |
84 // zoom out again |
64 plot.zoomOut({ center: { left: 10, top: 20 } }); |
85 plot.zoomOut({ center: { left: 10, top: 20 } }); |
65 |
86 |
66 // zoom 200% in on the pixel (10, 20) |
87 // zoom 200% in on the pixel (10, 20) |
67 plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); |
88 plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); |
68 |
89 |
69 // pan 100 pixels to the left and 20 down |
90 // pan 100 pixels to the left (changing x-range in a positive way) and 20 down |
70 plot.pan({ left: -100, top: 20 }) |
91 plot.pan({ left: -100, top: 20 }) |
|
92 ``` |
71 |
93 |
72 Here, "center" specifies where the center of the zooming should happen. Note |
94 Here, "center" specifies where the center of the zooming should happen. Note |
73 that this is defined in pixel space, not the space of the data points (you can |
95 that this is defined in pixel space, not the space of the data points (you can |
74 use the p2c helpers on the axes in Flot to help you convert between these). |
96 use the p2c helpers on the axes in Flot to help you convert between these). |
75 |
97 |
76 "amount" is the amount to zoom the viewport relative to the current range, so |
98 **amount** is the amount to zoom the viewport relative to the current range, so |
77 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You |
99 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You |
78 can set the default in the options. |
100 can set the default in the options. |
79 |
|
80 */ |
101 */ |
81 |
102 |
82 // First two dependencies, jquery.event.drag.js and |
103 /* eslint-enable */ |
83 // jquery.mousewheel.js, we put them inline here to save people the |
104 (function($) { |
84 // effort of downloading them. |
105 'use strict'; |
85 |
106 |
86 /* |
|
87 jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com) |
|
88 Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt |
|
89 */ |
|
90 (function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery); |
|
91 |
|
92 /* jquery.mousewheel.min.js |
|
93 * Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) |
|
94 * Licensed under the MIT License (LICENSE.txt). |
|
95 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. |
|
96 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. |
|
97 * Thanks to: Seamus Leahy for adding deltaX and deltaY |
|
98 * |
|
99 * Version: 3.0.6 |
|
100 * |
|
101 * Requires: 1.2.2+ |
|
102 */ |
|
103 (function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 (function ($) { |
|
109 var options = { |
107 var options = { |
110 xaxis: { |
|
111 zoomRange: null, // or [number, number] (min range, max range) |
|
112 panRange: null // or [number, number] (min, max) |
|
113 }, |
|
114 zoom: { |
108 zoom: { |
115 interactive: false, |
109 interactive: false, |
116 trigger: "dblclick", // or "click" for single click |
110 active: false, |
117 amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
111 amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
118 }, |
112 }, |
119 pan: { |
113 pan: { |
120 interactive: false, |
114 interactive: false, |
|
115 active: false, |
121 cursor: "move", |
116 cursor: "move", |
122 frameRate: 20 |
117 frameRate: 60, |
|
118 mode: 'smart' |
|
119 }, |
|
120 recenter: { |
|
121 interactive: true |
|
122 }, |
|
123 xaxis: { |
|
124 axisZoom: true, //zoom axis when mouse over it is allowed |
|
125 plotZoom: true, //zoom axis is allowed for plot zoom |
|
126 axisPan: true, //pan axis when mouse over it is allowed |
|
127 plotPan: true //pan axis is allowed for plot pan |
|
128 }, |
|
129 yaxis: { |
|
130 axisZoom: true, |
|
131 plotZoom: true, |
|
132 axisPan: true, |
|
133 plotPan: true |
123 } |
134 } |
124 }; |
135 }; |
125 |
136 |
|
137 var saturated = $.plot.saturated; |
|
138 var browser = $.plot.browser; |
|
139 var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT; |
|
140 var PANHINT_LENGTH_CONSTANT = $.plot.uiConstants.PANHINT_LENGTH_CONSTANT; |
|
141 |
126 function init(plot) { |
142 function init(plot) { |
127 function onZoomClick(e, zoomOut) { |
143 plot.hooks.processOptions.push(initNevigation); |
|
144 } |
|
145 |
|
146 function initNevigation(plot, options) { |
|
147 var panAxes = null; |
|
148 var canDrag = false; |
|
149 var useManualPan = options.pan.mode === 'manual', |
|
150 smartPanLock = options.pan.mode === 'smartLock', |
|
151 useSmartPan = smartPanLock || options.pan.mode === 'smart'; |
|
152 |
|
153 function onZoomClick(e, zoomOut, amount) { |
|
154 var page = browser.getPageXY(e); |
|
155 |
128 var c = plot.offset(); |
156 var c = plot.offset(); |
129 c.left = e.pageX - c.left; |
157 c.left = page.X - c.left; |
130 c.top = e.pageY - c.top; |
158 c.top = page.Y - c.top; |
131 if (zoomOut) |
159 |
132 plot.zoomOut({ center: c }); |
160 var ec = plot.getPlaceholder().offset(); |
133 else |
161 ec.left = page.X - ec.left; |
134 plot.zoom({ center: c }); |
162 ec.top = page.Y - ec.top; |
135 } |
163 |
|
164 var axes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) { |
|
165 var box = axis.box; |
|
166 if (box !== undefined) { |
|
167 return (ec.left > box.left) && (ec.left < box.left + box.width) && |
|
168 (ec.top > box.top) && (ec.top < box.top + box.height); |
|
169 } |
|
170 }); |
|
171 |
|
172 if (axes.length === 0) { |
|
173 axes = undefined; |
|
174 } |
|
175 |
|
176 if (zoomOut) { |
|
177 plot.zoomOut({ |
|
178 center: c, |
|
179 axes: axes, |
|
180 amount: amount |
|
181 }); |
|
182 } else { |
|
183 plot.zoom({ |
|
184 center: c, |
|
185 axes: axes, |
|
186 amount: amount |
|
187 }); |
|
188 } |
|
189 } |
|
190 |
|
191 var prevCursor = 'default', |
|
192 panHint = null, |
|
193 panTimeout = null, |
|
194 plotState, |
|
195 prevDragPosition = { x: 0, y: 0 }, |
|
196 isPanAction = false; |
136 |
197 |
137 function onMouseWheel(e, delta) { |
198 function onMouseWheel(e, delta) { |
138 e.preventDefault(); |
199 var maxAbsoluteDeltaOnMac = 1, |
139 onZoomClick(e, delta < 0); |
200 isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac, |
140 return false; |
201 defaultNonMacScrollAmount = null, |
141 } |
202 macMagicRatio = 50, |
142 |
203 amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount; |
143 var prevCursor = 'default', prevPageX = 0, prevPageY = 0, |
204 |
144 panTimeout = null; |
205 if (isPanAction) { |
|
206 onDragEnd(e); |
|
207 } |
|
208 |
|
209 if (plot.getOptions().zoom.active) { |
|
210 e.preventDefault(); |
|
211 onZoomClick(e, delta < 0, amount); |
|
212 return false; |
|
213 } |
|
214 } |
|
215 |
|
216 plot.navigationState = function(startPageX, startPageY) { |
|
217 var axes = this.getAxes(); |
|
218 var result = {}; |
|
219 Object.keys(axes).forEach(function(axisName) { |
|
220 var axis = axes[axisName]; |
|
221 result[axisName] = { |
|
222 navigationOffset: { below: axis.options.offset.below || 0, |
|
223 above: axis.options.offset.above || 0}, |
|
224 axisMin: axis.min, |
|
225 axisMax: axis.max, |
|
226 diagMode: false |
|
227 } |
|
228 }); |
|
229 |
|
230 result.startPageX = startPageX || 0; |
|
231 result.startPageY = startPageY || 0; |
|
232 return result; |
|
233 } |
|
234 |
|
235 function onMouseDown(e) { |
|
236 canDrag = true; |
|
237 } |
|
238 |
|
239 function onMouseUp(e) { |
|
240 canDrag = false; |
|
241 } |
|
242 |
|
243 function isLeftMouseButtonPressed(e) { |
|
244 return e.button === 0; |
|
245 } |
145 |
246 |
146 function onDragStart(e) { |
247 function onDragStart(e) { |
147 if (e.which != 1) // only accept left-click |
248 if (!canDrag || !isLeftMouseButtonPressed(e)) { |
148 return false; |
249 return false; |
|
250 } |
|
251 |
|
252 isPanAction = true; |
|
253 var page = browser.getPageXY(e); |
|
254 |
|
255 var ec = plot.getPlaceholder().offset(); |
|
256 ec.left = page.X - ec.left; |
|
257 ec.top = page.Y - ec.top; |
|
258 |
|
259 panAxes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) { |
|
260 var box = axis.box; |
|
261 if (box !== undefined) { |
|
262 return (ec.left > box.left) && (ec.left < box.left + box.width) && |
|
263 (ec.top > box.top) && (ec.top < box.top + box.height); |
|
264 } |
|
265 }); |
|
266 |
|
267 if (panAxes.length === 0) { |
|
268 panAxes = undefined; |
|
269 } |
|
270 |
149 var c = plot.getPlaceholder().css('cursor'); |
271 var c = plot.getPlaceholder().css('cursor'); |
150 if (c) |
272 if (c) { |
151 prevCursor = c; |
273 prevCursor = c; |
|
274 } |
|
275 |
152 plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); |
276 plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); |
153 prevPageX = e.pageX; |
277 |
154 prevPageY = e.pageY; |
278 if (useSmartPan) { |
|
279 plotState = plot.navigationState(page.X, page.Y); |
|
280 } else if (useManualPan) { |
|
281 prevDragPosition.x = page.X; |
|
282 prevDragPosition.y = page.Y; |
|
283 } |
155 } |
284 } |
156 |
285 |
157 function onDrag(e) { |
286 function onDrag(e) { |
|
287 if (!isPanAction) { |
|
288 return; |
|
289 } |
|
290 |
|
291 var page = browser.getPageXY(e); |
158 var frameRate = plot.getOptions().pan.frameRate; |
292 var frameRate = plot.getOptions().pan.frameRate; |
159 if (panTimeout || !frameRate) |
293 |
|
294 if (frameRate === -1) { |
|
295 if (useSmartPan) { |
|
296 plot.smartPan({ |
|
297 x: plotState.startPageX - page.X, |
|
298 y: plotState.startPageY - page.Y |
|
299 }, plotState, panAxes, false, smartPanLock); |
|
300 } else if (useManualPan) { |
|
301 plot.pan({ |
|
302 left: prevDragPosition.x - page.X, |
|
303 top: prevDragPosition.y - page.Y, |
|
304 axes: panAxes |
|
305 }); |
|
306 prevDragPosition.x = page.X; |
|
307 prevDragPosition.y = page.Y; |
|
308 } |
160 return; |
309 return; |
161 |
310 } |
162 panTimeout = setTimeout(function () { |
311 |
163 plot.pan({ left: prevPageX - e.pageX, |
312 if (panTimeout || !frameRate) return; |
164 top: prevPageY - e.pageY }); |
313 |
165 prevPageX = e.pageX; |
314 panTimeout = setTimeout(function() { |
166 prevPageY = e.pageY; |
315 if (useSmartPan) { |
167 |
316 plot.smartPan({ |
|
317 x: plotState.startPageX - page.X, |
|
318 y: plotState.startPageY - page.Y |
|
319 }, plotState, panAxes, false, smartPanLock); |
|
320 } else if (useManualPan) { |
|
321 plot.pan({ |
|
322 left: prevDragPosition.x - page.X, |
|
323 top: prevDragPosition.y - page.Y, |
|
324 axes: panAxes |
|
325 }); |
|
326 prevDragPosition.x = page.X; |
|
327 prevDragPosition.y = page.Y; |
|
328 } |
|
329 |
168 panTimeout = null; |
330 panTimeout = null; |
169 }, 1 / frameRate * 1000); |
331 }, 1 / frameRate * 1000); |
170 } |
332 } |
171 |
333 |
172 function onDragEnd(e) { |
334 function onDragEnd(e) { |
|
335 if (!isPanAction) { |
|
336 return; |
|
337 } |
|
338 |
173 if (panTimeout) { |
339 if (panTimeout) { |
174 clearTimeout(panTimeout); |
340 clearTimeout(panTimeout); |
175 panTimeout = null; |
341 panTimeout = null; |
176 } |
342 } |
177 |
343 |
|
344 isPanAction = false; |
|
345 var page = browser.getPageXY(e); |
|
346 |
178 plot.getPlaceholder().css('cursor', prevCursor); |
347 plot.getPlaceholder().css('cursor', prevCursor); |
179 plot.pan({ left: prevPageX - e.pageX, |
348 |
180 top: prevPageY - e.pageY }); |
349 if (useSmartPan) { |
181 } |
350 plot.smartPan({ |
182 |
351 x: plotState.startPageX - page.X, |
|
352 y: plotState.startPageY - page.Y |
|
353 }, plotState, panAxes, false, smartPanLock); |
|
354 plot.smartPan.end(); |
|
355 } else if (useManualPan) { |
|
356 plot.pan({ |
|
357 left: prevDragPosition.x - page.X, |
|
358 top: prevDragPosition.y - page.Y, |
|
359 axes: panAxes |
|
360 }); |
|
361 prevDragPosition.x = 0; |
|
362 prevDragPosition.y = 0; |
|
363 } |
|
364 } |
|
365 |
|
366 function onDblClick(e) { |
|
367 plot.activate(); |
|
368 var o = plot.getOptions() |
|
369 |
|
370 if (!o.recenter.interactive) { return; } |
|
371 |
|
372 var axes = plot.getTouchedAxis(e.clientX, e.clientY), |
|
373 event; |
|
374 |
|
375 plot.recenter({ axes: axes[0] ? axes : null }); |
|
376 |
|
377 if (axes[0]) { |
|
378 event = new $.Event('re-center', { detail: { |
|
379 axisTouched: axes[0] |
|
380 }}); |
|
381 } else { |
|
382 event = new $.Event('re-center', { detail: e }); |
|
383 } |
|
384 plot.getPlaceholder().trigger(event); |
|
385 } |
|
386 |
|
387 function onClick(e) { |
|
388 plot.activate(); |
|
389 |
|
390 if (isPanAction) { |
|
391 onDragEnd(e); |
|
392 } |
|
393 |
|
394 return false; |
|
395 } |
|
396 |
|
397 plot.activate = function() { |
|
398 var o = plot.getOptions(); |
|
399 if (!o.pan.active || !o.zoom.active) { |
|
400 o.pan.active = true; |
|
401 o.zoom.active = true; |
|
402 plot.getPlaceholder().trigger("plotactivated", [plot]); |
|
403 } |
|
404 } |
|
405 |
183 function bindEvents(plot, eventHolder) { |
406 function bindEvents(plot, eventHolder) { |
184 var o = plot.getOptions(); |
407 var o = plot.getOptions(); |
185 if (o.zoom.interactive) { |
408 if (o.zoom.interactive) { |
186 eventHolder[o.zoom.trigger](onZoomClick); |
|
187 eventHolder.mousewheel(onMouseWheel); |
409 eventHolder.mousewheel(onMouseWheel); |
188 } |
410 } |
189 |
411 |
190 if (o.pan.interactive) { |
412 if (o.pan.interactive) { |
191 eventHolder.bind("dragstart", { distance: 10 }, onDragStart); |
413 plot.addEventHandler("dragstart", onDragStart, eventHolder, 0); |
192 eventHolder.bind("drag", onDrag); |
414 plot.addEventHandler("drag", onDrag, eventHolder, 0); |
193 eventHolder.bind("dragend", onDragEnd); |
415 plot.addEventHandler("dragend", onDragEnd, eventHolder, 0); |
194 } |
416 eventHolder.bind("mousedown", onMouseDown); |
195 } |
417 eventHolder.bind("mouseup", onMouseUp); |
196 |
418 } |
197 plot.zoomOut = function (args) { |
419 |
198 if (!args) |
420 eventHolder.dblclick(onDblClick); |
|
421 eventHolder.click(onClick); |
|
422 } |
|
423 |
|
424 plot.zoomOut = function(args) { |
|
425 if (!args) { |
199 args = {}; |
426 args = {}; |
200 |
427 } |
201 if (!args.amount) |
428 |
|
429 if (!args.amount) { |
202 args.amount = plot.getOptions().zoom.amount; |
430 args.amount = plot.getOptions().zoom.amount; |
|
431 } |
203 |
432 |
204 args.amount = 1 / args.amount; |
433 args.amount = 1 / args.amount; |
205 plot.zoom(args); |
434 plot.zoom(args); |
206 }; |
435 }; |
207 |
436 |
208 plot.zoom = function (args) { |
437 plot.zoom = function(args) { |
209 if (!args) |
438 if (!args) { |
210 args = {}; |
439 args = {}; |
211 |
440 } |
|
441 |
212 var c = args.center, |
442 var c = args.center, |
213 amount = args.amount || plot.getOptions().zoom.amount, |
443 amount = args.amount || plot.getOptions().zoom.amount, |
214 w = plot.width(), h = plot.height(); |
444 w = plot.width(), |
215 |
445 h = plot.height(), |
216 if (!c) |
446 axes = args.axes || plot.getAxes(); |
217 c = { left: w / 2, top: h / 2 }; |
447 |
218 |
448 if (!c) { |
|
449 c = { |
|
450 left: w / 2, |
|
451 top: h / 2 |
|
452 }; |
|
453 } |
|
454 |
219 var xf = c.left / w, |
455 var xf = c.left / w, |
220 yf = c.top / h, |
456 yf = c.top / h, |
221 minmax = { |
457 minmax = { |
222 x: { |
458 x: { |
223 min: c.left - xf * w / amount, |
459 min: c.left - xf * w / amount, |
227 min: c.top - yf * h / amount, |
463 min: c.top - yf * h / amount, |
228 max: c.top + (1 - yf) * h / amount |
464 max: c.top + (1 - yf) * h / amount |
229 } |
465 } |
230 }; |
466 }; |
231 |
467 |
232 $.each(plot.getAxes(), function(_, axis) { |
468 for (var key in axes) { |
233 var opts = axis.options, |
469 if (!axes.hasOwnProperty(key)) { |
|
470 continue; |
|
471 } |
|
472 |
|
473 var axis = axes[key], |
|
474 opts = axis.options, |
234 min = minmax[axis.direction].min, |
475 min = minmax[axis.direction].min, |
235 max = minmax[axis.direction].max, |
476 max = minmax[axis.direction].max, |
236 zr = opts.zoomRange, |
477 navigationOffset = axis.options.offset; |
237 pr = opts.panRange; |
478 |
238 |
479 //skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot |
239 if (zr === false) // no zooming on this axis |
480 if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) { |
240 return; |
481 continue; |
241 |
482 } |
242 min = axis.c2p(min); |
483 |
243 max = axis.c2p(max); |
484 min = $.plot.saturated.saturate(axis.c2p(min)); |
|
485 max = $.plot.saturated.saturate(axis.c2p(max)); |
244 if (min > max) { |
486 if (min > max) { |
245 // make sure min < max |
487 // make sure min < max |
246 var tmp = min; |
488 var tmp = min; |
247 min = max; |
489 min = max; |
248 max = tmp; |
490 max = tmp; |
249 } |
491 } |
250 |
492 |
251 //Check that we are in panRange |
493 var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min)); |
252 if (pr) { |
494 var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max)); |
253 if (pr[0] != null && min < pr[0]) { |
495 opts.offset = { below: offsetBelow, above: offsetAbove }; |
254 min = pr[0]; |
496 }; |
255 } |
497 |
256 if (pr[1] != null && max > pr[1]) { |
498 plot.setupGrid(true); |
257 max = pr[1]; |
|
258 } |
|
259 } |
|
260 |
|
261 var range = max - min; |
|
262 if (zr && |
|
263 ((zr[0] != null && range < zr[0] && amount >1) || |
|
264 (zr[1] != null && range > zr[1] && amount <1))) |
|
265 return; |
|
266 |
|
267 opts.min = min; |
|
268 opts.max = max; |
|
269 }); |
|
270 |
|
271 plot.setupGrid(); |
|
272 plot.draw(); |
499 plot.draw(); |
273 |
500 |
274 if (!args.preventEvent) |
501 if (!args.preventEvent) { |
275 plot.getPlaceholder().trigger("plotzoom", [ plot, args ]); |
502 plot.getPlaceholder().trigger("plotzoom", [plot, args]); |
|
503 } |
276 }; |
504 }; |
277 |
505 |
278 plot.pan = function (args) { |
506 plot.pan = function(args) { |
279 var delta = { |
507 var delta = { |
280 x: +args.left, |
508 x: +args.left, |
281 y: +args.top |
509 y: +args.top |
282 }; |
510 }; |
283 |
511 |
284 if (isNaN(delta.x)) |
512 if (isNaN(delta.x)) delta.x = 0; |
285 delta.x = 0; |
513 if (isNaN(delta.y)) delta.y = 0; |
286 if (isNaN(delta.y)) |
514 |
287 delta.y = 0; |
515 $.each(args.axes || plot.getAxes(), function(_, axis) { |
288 |
|
289 $.each(plot.getAxes(), function (_, axis) { |
|
290 var opts = axis.options, |
516 var opts = axis.options, |
291 min, max, d = delta[axis.direction]; |
517 d = delta[axis.direction]; |
292 |
518 |
293 min = axis.c2p(axis.p2c(axis.min) + d), |
519 //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot |
294 max = axis.c2p(axis.p2c(axis.max) + d); |
520 if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) { |
295 |
|
296 var pr = opts.panRange; |
|
297 if (pr === false) // no panning on this axis |
|
298 return; |
521 return; |
299 |
522 } |
300 if (pr) { |
523 |
301 // check whether we hit the wall |
524 if (d !== 0) { |
302 if (pr[0] != null && pr[0] > min) { |
525 var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))), |
303 d = pr[0] - min; |
526 navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max))); |
304 min += d; |
527 |
305 max += d; |
528 if (!isFinite(navigationOffsetBelow)) { |
|
529 navigationOffsetBelow = 0; |
306 } |
530 } |
307 |
531 |
308 if (pr[1] != null && pr[1] < max) { |
532 if (!isFinite(navigationOffsetAbove)) { |
309 d = pr[1] - max; |
533 navigationOffsetAbove = 0; |
310 min += d; |
|
311 max += d; |
|
312 } |
534 } |
313 } |
535 |
314 |
536 opts.offset = { |
315 opts.min = min; |
537 below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)), |
316 opts.max = max; |
538 above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0)) |
|
539 }; |
|
540 } |
317 }); |
541 }); |
318 |
542 |
319 plot.setupGrid(); |
543 plot.setupGrid(true); |
320 plot.draw(); |
544 plot.draw(); |
321 |
545 if (!args.preventEvent) { |
322 if (!args.preventEvent) |
546 plot.getPlaceholder().trigger("plotpan", [plot, args]); |
323 plot.getPlaceholder().trigger("plotpan", [ plot, args ]); |
547 } |
324 }; |
548 }; |
325 |
549 |
|
550 plot.recenter = function(args) { |
|
551 $.each(args.axes || plot.getAxes(), function(_, axis) { |
|
552 if (args.axes) { |
|
553 if (this.direction === 'x') { |
|
554 axis.options.offset = { below: 0 }; |
|
555 } else if (this.direction === 'y') { |
|
556 axis.options.offset = { above: 0 }; |
|
557 } |
|
558 } else { |
|
559 axis.options.offset = { below: 0, above: 0 }; |
|
560 } |
|
561 }); |
|
562 plot.setupGrid(true); |
|
563 plot.draw(); |
|
564 }; |
|
565 |
|
566 var shouldSnap = function(delta) { |
|
567 return (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) || |
|
568 (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT); |
|
569 } |
|
570 |
|
571 // adjust delta so the pan action is constrained on the vertical or horizontal direction |
|
572 // it the movements in the other direction are small |
|
573 var adjustDeltaToSnap = function(delta) { |
|
574 if (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT) { |
|
575 return {x: 0, y: delta.y}; |
|
576 } |
|
577 |
|
578 if (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) { |
|
579 return {x: delta.x, y: 0}; |
|
580 } |
|
581 |
|
582 return delta; |
|
583 } |
|
584 |
|
585 var lockedDirection = null; |
|
586 var lockDeltaDirection = function(delta) { |
|
587 if (!lockedDirection && Math.max(Math.abs(delta.x), Math.abs(delta.y)) >= SNAPPING_CONSTANT) { |
|
588 lockedDirection = Math.abs(delta.x) < Math.abs(delta.y) ? 'y' : 'x'; |
|
589 } |
|
590 |
|
591 switch (lockedDirection) { |
|
592 case 'x': |
|
593 return { x: delta.x, y: 0 }; |
|
594 case 'y': |
|
595 return { x: 0, y: delta.y }; |
|
596 default: |
|
597 return { x: 0, y: 0 }; |
|
598 } |
|
599 } |
|
600 |
|
601 var isDiagonalMode = function(delta) { |
|
602 if (Math.abs(delta.x) > 0 && Math.abs(delta.y) > 0) { |
|
603 return true; |
|
604 } |
|
605 return false; |
|
606 } |
|
607 |
|
608 var restoreAxisOffset = function(axes, initialState, delta) { |
|
609 var axis; |
|
610 Object.keys(axes).forEach(function(axisName) { |
|
611 axis = axes[axisName]; |
|
612 if (delta[axis.direction] === 0) { |
|
613 axis.options.offset.below = initialState[axisName].navigationOffset.below; |
|
614 axis.options.offset.above = initialState[axisName].navigationOffset.above; |
|
615 } |
|
616 }); |
|
617 } |
|
618 |
|
619 var prevDelta = { x: 0, y: 0 }; |
|
620 plot.smartPan = function(delta, initialState, panAxes, preventEvent, smartLock) { |
|
621 var snap = smartLock ? true : shouldSnap(delta), |
|
622 axes = plot.getAxes(), |
|
623 opts; |
|
624 delta = smartLock ? lockDeltaDirection(delta) : adjustDeltaToSnap(delta); |
|
625 |
|
626 if (isDiagonalMode(delta)) { |
|
627 initialState.diagMode = true; |
|
628 } |
|
629 |
|
630 if (snap && initialState.diagMode === true) { |
|
631 initialState.diagMode = false; |
|
632 restoreAxisOffset(axes, initialState, delta); |
|
633 } |
|
634 |
|
635 if (snap) { |
|
636 panHint = { |
|
637 start: { |
|
638 x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left, |
|
639 y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top |
|
640 }, |
|
641 end: { |
|
642 x: initialState.startPageX - delta.x - plot.offset().left + plot.getPlotOffset().left, |
|
643 y: initialState.startPageY - delta.y - plot.offset().top + plot.getPlotOffset().top |
|
644 } |
|
645 } |
|
646 } else { |
|
647 panHint = { |
|
648 start: { |
|
649 x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left, |
|
650 y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top |
|
651 }, |
|
652 end: false |
|
653 } |
|
654 } |
|
655 |
|
656 if (isNaN(delta.x)) delta.x = 0; |
|
657 if (isNaN(delta.y)) delta.y = 0; |
|
658 |
|
659 if (panAxes) { |
|
660 axes = panAxes; |
|
661 } |
|
662 |
|
663 var axis, axisMin, axisMax, p, d; |
|
664 Object.keys(axes).forEach(function(axisName) { |
|
665 axis = axes[axisName]; |
|
666 axisMin = axis.min; |
|
667 axisMax = axis.max; |
|
668 opts = axis.options; |
|
669 |
|
670 d = delta[axis.direction]; |
|
671 p = prevDelta[axis.direction]; |
|
672 |
|
673 //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot |
|
674 if ((!opts.axisPan && panAxes) || (!panAxes && !opts.plotPan)) { |
|
675 return; |
|
676 } |
|
677 |
|
678 if (d !== 0) { |
|
679 var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axisMin) - (p - d)) - axis.c2p(axis.p2c(axisMin))), |
|
680 navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axisMax) - (p - d)) - axis.c2p(axis.p2c(axisMax))); |
|
681 |
|
682 if (!isFinite(navigationOffsetBelow)) { |
|
683 navigationOffsetBelow = 0; |
|
684 } |
|
685 |
|
686 if (!isFinite(navigationOffsetAbove)) { |
|
687 navigationOffsetAbove = 0; |
|
688 } |
|
689 |
|
690 axis.options.offset.below = saturated.saturate(navigationOffsetBelow + (axis.options.offset.below || 0)); |
|
691 axis.options.offset.above = saturated.saturate(navigationOffsetAbove + (axis.options.offset.above || 0)); |
|
692 } |
|
693 }); |
|
694 |
|
695 prevDelta = delta; |
|
696 plot.setupGrid(true); |
|
697 plot.draw(); |
|
698 |
|
699 if (!preventEvent) { |
|
700 plot.getPlaceholder().trigger("plotpan", [plot, delta, panAxes, initialState]); |
|
701 } |
|
702 }; |
|
703 |
|
704 plot.smartPan.end = function() { |
|
705 panHint = null; |
|
706 lockedDirection = null; |
|
707 prevDelta = { x: 0, y: 0 }; |
|
708 plot.triggerRedrawOverlay(); |
|
709 } |
|
710 |
326 function shutdown(plot, eventHolder) { |
711 function shutdown(plot, eventHolder) { |
327 eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); |
|
328 eventHolder.unbind("mousewheel", onMouseWheel); |
712 eventHolder.unbind("mousewheel", onMouseWheel); |
|
713 eventHolder.unbind("mousedown", onMouseDown); |
|
714 eventHolder.unbind("mouseup", onMouseUp); |
329 eventHolder.unbind("dragstart", onDragStart); |
715 eventHolder.unbind("dragstart", onDragStart); |
330 eventHolder.unbind("drag", onDrag); |
716 eventHolder.unbind("drag", onDrag); |
331 eventHolder.unbind("dragend", onDragEnd); |
717 eventHolder.unbind("dragend", onDragEnd); |
332 if (panTimeout) |
718 eventHolder.unbind("dblclick", onDblClick); |
333 clearTimeout(panTimeout); |
719 eventHolder.unbind("click", onClick); |
334 } |
720 |
335 |
721 if (panTimeout) clearTimeout(panTimeout); |
|
722 } |
|
723 |
|
724 function drawOverlay(plot, ctx) { |
|
725 if (panHint) { |
|
726 ctx.strokeStyle = 'rgba(96, 160, 208, 0.7)'; |
|
727 ctx.lineWidth = 2; |
|
728 ctx.lineJoin = "round"; |
|
729 var startx = Math.round(panHint.start.x), |
|
730 starty = Math.round(panHint.start.y), |
|
731 endx, endy; |
|
732 |
|
733 if (panAxes) { |
|
734 if (panAxes[0].direction === 'x') { |
|
735 endy = Math.round(panHint.start.y); |
|
736 endx = Math.round(panHint.end.x); |
|
737 } else if (panAxes[0].direction === 'y') { |
|
738 endx = Math.round(panHint.start.x); |
|
739 endy = Math.round(panHint.end.y); |
|
740 } |
|
741 } else { |
|
742 endx = Math.round(panHint.end.x); |
|
743 endy = Math.round(panHint.end.y); |
|
744 } |
|
745 |
|
746 ctx.beginPath(); |
|
747 |
|
748 if (panHint.end === false) { |
|
749 ctx.moveTo(startx, starty - PANHINT_LENGTH_CONSTANT); |
|
750 ctx.lineTo(startx, starty + PANHINT_LENGTH_CONSTANT); |
|
751 |
|
752 ctx.moveTo(startx + PANHINT_LENGTH_CONSTANT, starty); |
|
753 ctx.lineTo(startx - PANHINT_LENGTH_CONSTANT, starty); |
|
754 } else { |
|
755 var dirX = starty === endy; |
|
756 |
|
757 ctx.moveTo(startx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty - (dirX ? PANHINT_LENGTH_CONSTANT : 0)); |
|
758 ctx.lineTo(startx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty + (dirX ? PANHINT_LENGTH_CONSTANT : 0)); |
|
759 |
|
760 ctx.moveTo(startx, starty); |
|
761 ctx.lineTo(endx, endy); |
|
762 |
|
763 ctx.moveTo(endx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy - (dirX ? PANHINT_LENGTH_CONSTANT : 0)); |
|
764 ctx.lineTo(endx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy + (dirX ? PANHINT_LENGTH_CONSTANT : 0)); |
|
765 } |
|
766 |
|
767 ctx.stroke(); |
|
768 } |
|
769 } |
|
770 |
|
771 plot.getTouchedAxis = function(touchPointX, touchPointY) { |
|
772 var ec = plot.getPlaceholder().offset(); |
|
773 ec.left = touchPointX - ec.left; |
|
774 ec.top = touchPointY - ec.top; |
|
775 |
|
776 var axis = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) { |
|
777 var box = axis.box; |
|
778 if (box !== undefined) { |
|
779 return (ec.left > box.left) && (ec.left < box.left + box.width) && |
|
780 (ec.top > box.top) && (ec.top < box.top + box.height); |
|
781 } |
|
782 }); |
|
783 |
|
784 return axis; |
|
785 } |
|
786 |
|
787 plot.hooks.drawOverlay.push(drawOverlay); |
336 plot.hooks.bindEvents.push(bindEvents); |
788 plot.hooks.bindEvents.push(bindEvents); |
337 plot.hooks.shutdown.push(shutdown); |
789 plot.hooks.shutdown.push(shutdown); |
338 } |
790 } |
339 |
791 |
340 $.plot.plugins.push({ |
792 $.plot.plugins.push({ |
341 init: init, |
793 init: init, |
342 options: options, |
794 options: options, |
343 name: 'navigate', |
795 name: 'navigate', |
344 version: '1.3' |
796 version: '1.3' |