UNPKG

28.1 kBJavaScriptView Raw
1/* Flot plugin for adding the ability to pan and zoom the plot.
2
3Copyright (c) 2007-2014 IOLA and Ole Laursen.
4Copyright (c) 2016 Ciprian Ceteras.
5Copyright (c) 2017 Raluca Portase.
6Licensed under the MIT license.
7
8*/
9
10/**
11## jquery.flot.navigate.js
12
13This flot plugin is used for adding the ability to pan and zoom the plot.
14A higher level overview is available at [interactions](interactions.md) documentation.
15
16The default behaviour is scrollwheel up/down to zoom in, drag
17to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
18plot.pan( offset ) so you easily can add custom controls. It also fires
19"plotpan" and "plotzoom" events, useful for synchronizing plots.
20
21The 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
52interactive for pan, then you'll have a basic plot that supports moving
53around; the same for zoom.
54
55**active** is true after a touch tap on plot. This enables plot navigation.
56Once 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
60the current viewport.
61
62**cursor** is a standard CSS mouse cursor string used for visual feedback to the
63user when dragging.
64
65**frameRate** specifies the maximum number of times per second the plot will
66update itself while the user is panning around on it (set to null to disable
67intermediate pans, the plot will then not update until the mouse button is
68released).
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
73to one direction when the drag direction is close to it;
74'smartLock'. The graph shows pan hint bar and the pan movement will always
75snap to a direction that the drag diorection started with.
76
77Example 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
94Here, "center" specifies where the center of the zooming should happen. Note
95that this is defined in pixel space, not the space of the data points (you can
96use 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
991 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
100can 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);