1 /* Flot plugin for adding the ability to pan and zoom the plot. |
|
2 |
|
3 Copyright (c) 2007-2014 IOLA and Ole Laursen. |
|
4 Copyright (c) 2016 Ciprian Ceteras. |
|
5 Copyright (c) 2017 Raluca Portase. |
|
6 Licensed under the MIT license. |
|
7 |
|
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 |
|
17 to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and |
|
18 plot.pan( offset ) so you easily can add custom controls. It also fires |
|
19 "plotpan" and "plotzoom" events, useful for synchronizing plots. |
|
20 |
|
21 The plugin supports these options: |
|
22 ```js |
|
23 zoom: { |
|
24 interactive: false, |
|
25 active: false, |
|
26 amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
|
27 } |
|
28 |
|
29 pan: { |
|
30 interactive: false, |
|
31 active: false, |
|
32 cursor: "move", // CSS mouse cursor value used when dragging, e.g. "pointer" |
|
33 frameRate: 60, |
|
34 mode: "smart" // enable smart pan mode |
|
35 } |
|
36 |
|
37 xaxis: { |
|
38 axisZoom: true, //zoom axis when mouse over it is allowed |
|
39 plotZoom: true, //zoom axis is allowed for plot zoom |
|
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 |
|
52 interactive for pan, then you'll have a basic plot that supports moving |
|
53 around; the same for zoom. |
|
54 |
|
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 |
|
60 the current viewport. |
|
61 |
|
62 **cursor** is a standard CSS mouse cursor string used for visual feedback to the |
|
63 user when dragging. |
|
64 |
|
65 **frameRate** specifies the maximum number of times per second the plot will |
|
66 update itself while the user is panning around on it (set to null to disable |
|
67 intermediate pans, the plot will then not update until the mouse button is |
|
68 released). |
|
69 |
|
70 **mode** a string specifies the pan mode for mouse interaction. Accepted values: |
|
71 'manual': no pan hint or direction snapping; |
|
72 'smart': The graph shows pan hint bar and the pan movement will snap |
|
73 to one direction when the drag direction is close to it; |
|
74 'smartLock'. The graph shows pan hint bar and the pan movement will always |
|
75 snap to a direction that the drag diorection started with. |
|
76 |
|
77 Example API usage: |
|
78 ```js |
|
79 plot = $.plot(...); |
|
80 |
|
81 // zoom default amount in on the pixel ( 10, 20 ) |
|
82 plot.zoom({ center: { left: 10, top: 20 } }); |
|
83 |
|
84 // zoom out again |
|
85 plot.zoomOut({ center: { left: 10, top: 20 } }); |
|
86 |
|
87 // zoom 200% in on the pixel (10, 20) |
|
88 plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); |
|
89 |
|
90 // pan 100 pixels to the left (changing x-range in a positive way) and 20 down |
|
91 plot.pan({ left: -100, top: 20 }) |
|
92 ``` |
|
93 |
|
94 Here, "center" specifies where the center of the zooming should happen. Note |
|
95 that this is defined in pixel space, not the space of the data points (you can |
|
96 use the p2c helpers on the axes in Flot to help you convert between these). |
|
97 |
|
98 **amount** is the amount to zoom the viewport relative to the current range, so |
|
99 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You |
|
100 can set the default in the options. |
|
101 */ |
|
102 |
|
103 /* eslint-enable */ |
|
104 (function($) { |
|
105 'use strict'; |
|
106 |
|
107 var options = { |
|
108 zoom: { |
|
109 interactive: false, |
|
110 active: false, |
|
111 amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out) |
|
112 }, |
|
113 pan: { |
|
114 interactive: false, |
|
115 active: false, |
|
116 cursor: "move", |
|
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 |
|
134 } |
|
135 }; |
|
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 |
|
142 function init(plot) { |
|
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 |
|
156 var c = plot.offset(); |
|
157 c.left = page.X - c.left; |
|
158 c.top = page.Y - c.top; |
|
159 |
|
160 var ec = plot.getPlaceholder().offset(); |
|
161 ec.left = page.X - ec.left; |
|
162 ec.top = page.Y - ec.top; |
|
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; |
|
197 |
|
198 function onMouseWheel(e, delta) { |
|
199 var maxAbsoluteDeltaOnMac = 1, |
|
200 isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac, |
|
201 defaultNonMacScrollAmount = null, |
|
202 macMagicRatio = 50, |
|
203 amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount; |
|
204 |
|
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 } |
|
246 |
|
247 function onDragStart(e) { |
|
248 if (!canDrag || !isLeftMouseButtonPressed(e)) { |
|
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 |
|
271 var c = plot.getPlaceholder().css('cursor'); |
|
272 if (c) { |
|
273 prevCursor = c; |
|
274 } |
|
275 |
|
276 plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); |
|
277 |
|
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 } |
|
284 } |
|
285 |
|
286 function onDrag(e) { |
|
287 if (!isPanAction) { |
|
288 return; |
|
289 } |
|
290 |
|
291 var page = browser.getPageXY(e); |
|
292 var frameRate = plot.getOptions().pan.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 } |
|
309 return; |
|
310 } |
|
311 |
|
312 if (panTimeout || !frameRate) return; |
|
313 |
|
314 panTimeout = setTimeout(function() { |
|
315 if (useSmartPan) { |
|
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 |
|
330 panTimeout = null; |
|
331 }, 1 / frameRate * 1000); |
|
332 } |
|
333 |
|
334 function onDragEnd(e) { |
|
335 if (!isPanAction) { |
|
336 return; |
|
337 } |
|
338 |
|
339 if (panTimeout) { |
|
340 clearTimeout(panTimeout); |
|
341 panTimeout = null; |
|
342 } |
|
343 |
|
344 isPanAction = false; |
|
345 var page = browser.getPageXY(e); |
|
346 |
|
347 plot.getPlaceholder().css('cursor', prevCursor); |
|
348 |
|
349 if (useSmartPan) { |
|
350 plot.smartPan({ |
|
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 |
|
406 function bindEvents(plot, eventHolder) { |
|
407 var o = plot.getOptions(); |
|
408 if (o.zoom.interactive) { |
|
409 eventHolder.mousewheel(onMouseWheel); |
|
410 } |
|
411 |
|
412 if (o.pan.interactive) { |
|
413 plot.addEventHandler("dragstart", onDragStart, eventHolder, 0); |
|
414 plot.addEventHandler("drag", onDrag, eventHolder, 0); |
|
415 plot.addEventHandler("dragend", onDragEnd, eventHolder, 0); |
|
416 eventHolder.bind("mousedown", onMouseDown); |
|
417 eventHolder.bind("mouseup", onMouseUp); |
|
418 } |
|
419 |
|
420 eventHolder.dblclick(onDblClick); |
|
421 eventHolder.click(onClick); |
|
422 } |
|
423 |
|
424 plot.zoomOut = function(args) { |
|
425 if (!args) { |
|
426 args = {}; |
|
427 } |
|
428 |
|
429 if (!args.amount) { |
|
430 args.amount = plot.getOptions().zoom.amount; |
|
431 } |
|
432 |
|
433 args.amount = 1 / args.amount; |
|
434 plot.zoom(args); |
|
435 }; |
|
436 |
|
437 plot.zoom = function(args) { |
|
438 if (!args) { |
|
439 args = {}; |
|
440 } |
|
441 |
|
442 var c = args.center, |
|
443 amount = args.amount || plot.getOptions().zoom.amount, |
|
444 w = plot.width(), |
|
445 h = plot.height(), |
|
446 axes = args.axes || plot.getAxes(); |
|
447 |
|
448 if (!c) { |
|
449 c = { |
|
450 left: w / 2, |
|
451 top: h / 2 |
|
452 }; |
|
453 } |
|
454 |
|
455 var xf = c.left / w, |
|
456 yf = c.top / h, |
|
457 minmax = { |
|
458 x: { |
|
459 min: c.left - xf * w / amount, |
|
460 max: c.left + (1 - xf) * w / amount |
|
461 }, |
|
462 y: { |
|
463 min: c.top - yf * h / amount, |
|
464 max: c.top + (1 - yf) * h / amount |
|
465 } |
|
466 }; |
|
467 |
|
468 for (var key in axes) { |
|
469 if (!axes.hasOwnProperty(key)) { |
|
470 continue; |
|
471 } |
|
472 |
|
473 var axis = axes[key], |
|
474 opts = axis.options, |
|
475 min = minmax[axis.direction].min, |
|
476 max = minmax[axis.direction].max, |
|
477 navigationOffset = axis.options.offset; |
|
478 |
|
479 //skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot |
|
480 if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) { |
|
481 continue; |
|
482 } |
|
483 |
|
484 min = $.plot.saturated.saturate(axis.c2p(min)); |
|
485 max = $.plot.saturated.saturate(axis.c2p(max)); |
|
486 if (min > max) { |
|
487 // make sure min < max |
|
488 var tmp = min; |
|
489 min = max; |
|
490 max = tmp; |
|
491 } |
|
492 |
|
493 var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min)); |
|
494 var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max)); |
|
495 opts.offset = { below: offsetBelow, above: offsetAbove }; |
|
496 }; |
|
497 |
|
498 plot.setupGrid(true); |
|
499 plot.draw(); |
|
500 |
|
501 if (!args.preventEvent) { |
|
502 plot.getPlaceholder().trigger("plotzoom", [plot, args]); |
|
503 } |
|
504 }; |
|
505 |
|
506 plot.pan = function(args) { |
|
507 var delta = { |
|
508 x: +args.left, |
|
509 y: +args.top |
|
510 }; |
|
511 |
|
512 if (isNaN(delta.x)) delta.x = 0; |
|
513 if (isNaN(delta.y)) delta.y = 0; |
|
514 |
|
515 $.each(args.axes || plot.getAxes(), function(_, axis) { |
|
516 var opts = axis.options, |
|
517 d = delta[axis.direction]; |
|
518 |
|
519 //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot |
|
520 if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) { |
|
521 return; |
|
522 } |
|
523 |
|
524 if (d !== 0) { |
|
525 var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))), |
|
526 navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max))); |
|
527 |
|
528 if (!isFinite(navigationOffsetBelow)) { |
|
529 navigationOffsetBelow = 0; |
|
530 } |
|
531 |
|
532 if (!isFinite(navigationOffsetAbove)) { |
|
533 navigationOffsetAbove = 0; |
|
534 } |
|
535 |
|
536 opts.offset = { |
|
537 below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)), |
|
538 above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0)) |
|
539 }; |
|
540 } |
|
541 }); |
|
542 |
|
543 plot.setupGrid(true); |
|
544 plot.draw(); |
|
545 if (!args.preventEvent) { |
|
546 plot.getPlaceholder().trigger("plotpan", [plot, args]); |
|
547 } |
|
548 }; |
|
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 |
|
711 function shutdown(plot, eventHolder) { |
|
712 eventHolder.unbind("mousewheel", onMouseWheel); |
|
713 eventHolder.unbind("mousedown", onMouseDown); |
|
714 eventHolder.unbind("mouseup", onMouseUp); |
|
715 eventHolder.unbind("dragstart", onDragStart); |
|
716 eventHolder.unbind("drag", onDrag); |
|
717 eventHolder.unbind("dragend", onDragEnd); |
|
718 eventHolder.unbind("dblclick", onDblClick); |
|
719 eventHolder.unbind("click", onClick); |
|
720 |
|
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); |
|
788 plot.hooks.bindEvents.push(bindEvents); |
|
789 plot.hooks.shutdown.push(shutdown); |
|
790 } |
|
791 |
|
792 $.plot.plugins.push({ |
|
793 init: init, |
|
794 options: options, |
|
795 name: 'navigate', |
|
796 version: '1.3' |
|
797 }); |
|
798 })(jQuery); |
|