jquery.flot.navigate.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. /* Flot plugin for adding the ability to pan and zoom the plot.
  2. Copyright (c) 2007-2014 IOLA and Ole Laursen.
  3. Copyright (c) 2016 Ciprian Ceteras.
  4. Copyright (c) 2017 Raluca Portase.
  5. Licensed under the MIT license.
  6. */
  7. /**
  8. ## jquery.flot.navigate.js
  9. This flot plugin is used for adding the ability to pan and zoom the plot.
  10. A higher level overview is available at [interactions](interactions.md) documentation.
  11. The default behaviour is scrollwheel up/down to zoom in, drag
  12. to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
  13. plot.pan( offset ) so you easily can add custom controls. It also fires
  14. "plotpan" and "plotzoom" events, useful for synchronizing plots.
  15. The plugin supports these options:
  16. ```js
  17. zoom: {
  18. interactive: false,
  19. active: false,
  20. amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
  21. }
  22. pan: {
  23. interactive: false,
  24. active: false,
  25. cursor: "move", // CSS mouse cursor value used when dragging, e.g. "pointer"
  26. frameRate: 60,
  27. mode: "smart" // enable smart pan mode
  28. }
  29. xaxis: {
  30. axisZoom: true, //zoom axis when mouse over it is allowed
  31. plotZoom: true, //zoom axis is allowed for plot zoom
  32. axisPan: true, //pan axis when mouse over it is allowed
  33. plotPan: true //pan axis is allowed for plot pan
  34. }
  35. yaxis: {
  36. axisZoom: true, //zoom axis when mouse over it is allowed
  37. plotZoom: true, //zoom axis is allowed for plot zoom
  38. axisPan: true, //pan axis when mouse over it is allowed
  39. plotPan: true //pan axis is allowed for plot pan
  40. }
  41. ```
  42. **interactive** enables the built-in drag/click behaviour. If you enable
  43. interactive for pan, then you'll have a basic plot that supports moving
  44. around; the same for zoom.
  45. **active** is true after a touch tap on plot. This enables plot navigation.
  46. Once activated, zoom and pan cannot be deactivated. When the plot becomes active,
  47. "plotactivated" event is triggered.
  48. **amount** specifies the default amount to zoom in (so 1.5 = 150%) relative to
  49. the current viewport.
  50. **cursor** is a standard CSS mouse cursor string used for visual feedback to the
  51. user when dragging.
  52. **frameRate** specifies the maximum number of times per second the plot will
  53. update itself while the user is panning around on it (set to null to disable
  54. intermediate pans, the plot will then not update until the mouse button is
  55. released).
  56. **mode** a string specifies the pan mode for mouse interaction. Accepted values:
  57. 'manual': no pan hint or direction snapping;
  58. 'smart': The graph shows pan hint bar and the pan movement will snap
  59. to one direction when the drag direction is close to it;
  60. 'smartLock'. The graph shows pan hint bar and the pan movement will always
  61. snap to a direction that the drag diorection started with.
  62. Example API usage:
  63. ```js
  64. plot = $.plot(...);
  65. // zoom default amount in on the pixel ( 10, 20 )
  66. plot.zoom({ center: { left: 10, top: 20 } });
  67. // zoom out again
  68. plot.zoomOut({ center: { left: 10, top: 20 } });
  69. // zoom 200% in on the pixel (10, 20)
  70. plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
  71. // pan 100 pixels to the left (changing x-range in a positive way) and 20 down
  72. plot.pan({ left: -100, top: 20 })
  73. ```
  74. Here, "center" specifies where the center of the zooming should happen. Note
  75. that this is defined in pixel space, not the space of the data points (you can
  76. use the p2c helpers on the axes in Flot to help you convert between these).
  77. **amount** is the amount to zoom the viewport relative to the current range, so
  78. 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
  79. can set the default in the options.
  80. */
  81. /* eslint-enable */
  82. (function($) {
  83. 'use strict';
  84. var options = {
  85. zoom: {
  86. interactive: false,
  87. active: false,
  88. amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
  89. },
  90. pan: {
  91. interactive: false,
  92. active: false,
  93. cursor: "move",
  94. frameRate: 60,
  95. mode: 'smart'
  96. },
  97. recenter: {
  98. interactive: true
  99. },
  100. xaxis: {
  101. axisZoom: true, //zoom axis when mouse over it is allowed
  102. plotZoom: true, //zoom axis is allowed for plot zoom
  103. axisPan: true, //pan axis when mouse over it is allowed
  104. plotPan: true //pan axis is allowed for plot pan
  105. },
  106. yaxis: {
  107. axisZoom: true,
  108. plotZoom: true,
  109. axisPan: true,
  110. plotPan: true
  111. }
  112. };
  113. var saturated = $.plot.saturated;
  114. var browser = $.plot.browser;
  115. var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
  116. var PANHINT_LENGTH_CONSTANT = $.plot.uiConstants.PANHINT_LENGTH_CONSTANT;
  117. function init(plot) {
  118. plot.hooks.processOptions.push(initNevigation);
  119. }
  120. function initNevigation(plot, options) {
  121. var panAxes = null;
  122. var canDrag = false;
  123. var useManualPan = options.pan.mode === 'manual',
  124. smartPanLock = options.pan.mode === 'smartLock',
  125. useSmartPan = smartPanLock || options.pan.mode === 'smart';
  126. function onZoomClick(e, zoomOut, amount) {
  127. var page = browser.getPageXY(e);
  128. var c = plot.offset();
  129. c.left = page.X - c.left;
  130. c.top = page.Y - c.top;
  131. var ec = plot.getPlaceholder().offset();
  132. ec.left = page.X - ec.left;
  133. ec.top = page.Y - ec.top;
  134. var axes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
  135. var box = axis.box;
  136. if (box !== undefined) {
  137. return (ec.left > box.left) && (ec.left < box.left + box.width) &&
  138. (ec.top > box.top) && (ec.top < box.top + box.height);
  139. }
  140. });
  141. if (axes.length === 0) {
  142. axes = undefined;
  143. }
  144. if (zoomOut) {
  145. plot.zoomOut({
  146. center: c,
  147. axes: axes,
  148. amount: amount
  149. });
  150. } else {
  151. plot.zoom({
  152. center: c,
  153. axes: axes,
  154. amount: amount
  155. });
  156. }
  157. }
  158. var prevCursor = 'default',
  159. panHint = null,
  160. panTimeout = null,
  161. plotState,
  162. prevDragPosition = { x: 0, y: 0 },
  163. isPanAction = false;
  164. function onMouseWheel(e, delta) {
  165. var maxAbsoluteDeltaOnMac = 1,
  166. isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac,
  167. defaultNonMacScrollAmount = null,
  168. macMagicRatio = 50,
  169. amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount;
  170. if (isPanAction) {
  171. onDragEnd(e);
  172. }
  173. if (plot.getOptions().zoom.active) {
  174. e.preventDefault();
  175. onZoomClick(e, delta < 0, amount);
  176. return false;
  177. }
  178. }
  179. plot.navigationState = function(startPageX, startPageY) {
  180. var axes = this.getAxes();
  181. var result = {};
  182. Object.keys(axes).forEach(function(axisName) {
  183. var axis = axes[axisName];
  184. result[axisName] = {
  185. navigationOffset: { below: axis.options.offset.below || 0,
  186. above: axis.options.offset.above || 0},
  187. axisMin: axis.min,
  188. axisMax: axis.max,
  189. diagMode: false
  190. }
  191. });
  192. result.startPageX = startPageX || 0;
  193. result.startPageY = startPageY || 0;
  194. return result;
  195. }
  196. function onMouseDown(e) {
  197. canDrag = true;
  198. }
  199. function onMouseUp(e) {
  200. canDrag = false;
  201. }
  202. function isLeftMouseButtonPressed(e) {
  203. return e.button === 0;
  204. }
  205. function onDragStart(e) {
  206. if (!canDrag || !isLeftMouseButtonPressed(e)) {
  207. return false;
  208. }
  209. isPanAction = true;
  210. var page = browser.getPageXY(e);
  211. var ec = plot.getPlaceholder().offset();
  212. ec.left = page.X - ec.left;
  213. ec.top = page.Y - ec.top;
  214. panAxes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
  215. var box = axis.box;
  216. if (box !== undefined) {
  217. return (ec.left > box.left) && (ec.left < box.left + box.width) &&
  218. (ec.top > box.top) && (ec.top < box.top + box.height);
  219. }
  220. });
  221. if (panAxes.length === 0) {
  222. panAxes = undefined;
  223. }
  224. var c = plot.getPlaceholder().css('cursor');
  225. if (c) {
  226. prevCursor = c;
  227. }
  228. plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
  229. if (useSmartPan) {
  230. plotState = plot.navigationState(page.X, page.Y);
  231. } else if (useManualPan) {
  232. prevDragPosition.x = page.X;
  233. prevDragPosition.y = page.Y;
  234. }
  235. }
  236. function onDrag(e) {
  237. if (!isPanAction) {
  238. return;
  239. }
  240. var page = browser.getPageXY(e);
  241. var frameRate = plot.getOptions().pan.frameRate;
  242. if (frameRate === -1) {
  243. if (useSmartPan) {
  244. plot.smartPan({
  245. x: plotState.startPageX - page.X,
  246. y: plotState.startPageY - page.Y
  247. }, plotState, panAxes, false, smartPanLock);
  248. } else if (useManualPan) {
  249. plot.pan({
  250. left: prevDragPosition.x - page.X,
  251. top: prevDragPosition.y - page.Y,
  252. axes: panAxes
  253. });
  254. prevDragPosition.x = page.X;
  255. prevDragPosition.y = page.Y;
  256. }
  257. return;
  258. }
  259. if (panTimeout || !frameRate) return;
  260. panTimeout = setTimeout(function() {
  261. if (useSmartPan) {
  262. plot.smartPan({
  263. x: plotState.startPageX - page.X,
  264. y: plotState.startPageY - page.Y
  265. }, plotState, panAxes, false, smartPanLock);
  266. } else if (useManualPan) {
  267. plot.pan({
  268. left: prevDragPosition.x - page.X,
  269. top: prevDragPosition.y - page.Y,
  270. axes: panAxes
  271. });
  272. prevDragPosition.x = page.X;
  273. prevDragPosition.y = page.Y;
  274. }
  275. panTimeout = null;
  276. }, 1 / frameRate * 1000);
  277. }
  278. function onDragEnd(e) {
  279. if (!isPanAction) {
  280. return;
  281. }
  282. if (panTimeout) {
  283. clearTimeout(panTimeout);
  284. panTimeout = null;
  285. }
  286. isPanAction = false;
  287. var page = browser.getPageXY(e);
  288. plot.getPlaceholder().css('cursor', prevCursor);
  289. if (useSmartPan) {
  290. plot.smartPan({
  291. x: plotState.startPageX - page.X,
  292. y: plotState.startPageY - page.Y
  293. }, plotState, panAxes, false, smartPanLock);
  294. plot.smartPan.end();
  295. } else if (useManualPan) {
  296. plot.pan({
  297. left: prevDragPosition.x - page.X,
  298. top: prevDragPosition.y - page.Y,
  299. axes: panAxes
  300. });
  301. prevDragPosition.x = 0;
  302. prevDragPosition.y = 0;
  303. }
  304. }
  305. function onDblClick(e) {
  306. plot.activate();
  307. var o = plot.getOptions()
  308. if (!o.recenter.interactive) { return; }
  309. var axes = plot.getTouchedAxis(e.clientX, e.clientY),
  310. event;
  311. plot.recenter({ axes: axes[0] ? axes : null });
  312. if (axes[0]) {
  313. event = new $.Event('re-center', { detail: {
  314. axisTouched: axes[0]
  315. }});
  316. } else {
  317. event = new $.Event('re-center', { detail: e });
  318. }
  319. plot.getPlaceholder().trigger(event);
  320. }
  321. function onClick(e) {
  322. plot.activate();
  323. if (isPanAction) {
  324. onDragEnd(e);
  325. }
  326. return false;
  327. }
  328. plot.activate = function() {
  329. var o = plot.getOptions();
  330. if (!o.pan.active || !o.zoom.active) {
  331. o.pan.active = true;
  332. o.zoom.active = true;
  333. plot.getPlaceholder().trigger("plotactivated", [plot]);
  334. }
  335. }
  336. function bindEvents(plot, eventHolder) {
  337. var o = plot.getOptions();
  338. if (o.zoom.interactive) {
  339. eventHolder.mousewheel(onMouseWheel);
  340. }
  341. if (o.pan.interactive) {
  342. plot.addEventHandler("dragstart", onDragStart, eventHolder, 0);
  343. plot.addEventHandler("drag", onDrag, eventHolder, 0);
  344. plot.addEventHandler("dragend", onDragEnd, eventHolder, 0);
  345. eventHolder.bind("mousedown", onMouseDown);
  346. eventHolder.bind("mouseup", onMouseUp);
  347. }
  348. eventHolder.dblclick(onDblClick);
  349. eventHolder.click(onClick);
  350. }
  351. plot.zoomOut = function(args) {
  352. if (!args) {
  353. args = {};
  354. }
  355. if (!args.amount) {
  356. args.amount = plot.getOptions().zoom.amount;
  357. }
  358. args.amount = 1 / args.amount;
  359. plot.zoom(args);
  360. };
  361. plot.zoom = function(args) {
  362. if (!args) {
  363. args = {};
  364. }
  365. var c = args.center,
  366. amount = args.amount || plot.getOptions().zoom.amount,
  367. w = plot.width(),
  368. h = plot.height(),
  369. axes = args.axes || plot.getAxes();
  370. if (!c) {
  371. c = {
  372. left: w / 2,
  373. top: h / 2
  374. };
  375. }
  376. var xf = c.left / w,
  377. yf = c.top / h,
  378. minmax = {
  379. x: {
  380. min: c.left - xf * w / amount,
  381. max: c.left + (1 - xf) * w / amount
  382. },
  383. y: {
  384. min: c.top - yf * h / amount,
  385. max: c.top + (1 - yf) * h / amount
  386. }
  387. };
  388. for (var key in axes) {
  389. if (!axes.hasOwnProperty(key)) {
  390. continue;
  391. }
  392. var axis = axes[key],
  393. opts = axis.options,
  394. min = minmax[axis.direction].min,
  395. max = minmax[axis.direction].max,
  396. navigationOffset = axis.options.offset;
  397. //skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot
  398. if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) {
  399. continue;
  400. }
  401. min = $.plot.saturated.saturate(axis.c2p(min));
  402. max = $.plot.saturated.saturate(axis.c2p(max));
  403. if (min > max) {
  404. // make sure min < max
  405. var tmp = min;
  406. min = max;
  407. max = tmp;
  408. }
  409. var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min));
  410. var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max));
  411. opts.offset = { below: offsetBelow, above: offsetAbove };
  412. };
  413. plot.setupGrid(true);
  414. plot.draw();
  415. if (!args.preventEvent) {
  416. plot.getPlaceholder().trigger("plotzoom", [plot, args]);
  417. }
  418. };
  419. plot.pan = function(args) {
  420. var delta = {
  421. x: +args.left,
  422. y: +args.top
  423. };
  424. if (isNaN(delta.x)) delta.x = 0;
  425. if (isNaN(delta.y)) delta.y = 0;
  426. $.each(args.axes || plot.getAxes(), function(_, axis) {
  427. var opts = axis.options,
  428. d = delta[axis.direction];
  429. //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
  430. if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) {
  431. return;
  432. }
  433. if (d !== 0) {
  434. var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))),
  435. navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max)));
  436. if (!isFinite(navigationOffsetBelow)) {
  437. navigationOffsetBelow = 0;
  438. }
  439. if (!isFinite(navigationOffsetAbove)) {
  440. navigationOffsetAbove = 0;
  441. }
  442. opts.offset = {
  443. below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)),
  444. above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0))
  445. };
  446. }
  447. });
  448. plot.setupGrid(true);
  449. plot.draw();
  450. if (!args.preventEvent) {
  451. plot.getPlaceholder().trigger("plotpan", [plot, args]);
  452. }
  453. };
  454. plot.recenter = function(args) {
  455. $.each(args.axes || plot.getAxes(), function(_, axis) {
  456. if (args.axes) {
  457. if (this.direction === 'x') {
  458. axis.options.offset = { below: 0 };
  459. } else if (this.direction === 'y') {
  460. axis.options.offset = { above: 0 };
  461. }
  462. } else {
  463. axis.options.offset = { below: 0, above: 0 };
  464. }
  465. });
  466. plot.setupGrid(true);
  467. plot.draw();
  468. };
  469. var shouldSnap = function(delta) {
  470. return (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) ||
  471. (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT);
  472. }
  473. // adjust delta so the pan action is constrained on the vertical or horizontal direction
  474. // it the movements in the other direction are small
  475. var adjustDeltaToSnap = function(delta) {
  476. if (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT) {
  477. return {x: 0, y: delta.y};
  478. }
  479. if (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) {
  480. return {x: delta.x, y: 0};
  481. }
  482. return delta;
  483. }
  484. var lockedDirection = null;
  485. var lockDeltaDirection = function(delta) {
  486. if (!lockedDirection && Math.max(Math.abs(delta.x), Math.abs(delta.y)) >= SNAPPING_CONSTANT) {
  487. lockedDirection = Math.abs(delta.x) < Math.abs(delta.y) ? 'y' : 'x';
  488. }
  489. switch (lockedDirection) {
  490. case 'x':
  491. return { x: delta.x, y: 0 };
  492. case 'y':
  493. return { x: 0, y: delta.y };
  494. default:
  495. return { x: 0, y: 0 };
  496. }
  497. }
  498. var isDiagonalMode = function(delta) {
  499. if (Math.abs(delta.x) > 0 && Math.abs(delta.y) > 0) {
  500. return true;
  501. }
  502. return false;
  503. }
  504. var restoreAxisOffset = function(axes, initialState, delta) {
  505. var axis;
  506. Object.keys(axes).forEach(function(axisName) {
  507. axis = axes[axisName];
  508. if (delta[axis.direction] === 0) {
  509. axis.options.offset.below = initialState[axisName].navigationOffset.below;
  510. axis.options.offset.above = initialState[axisName].navigationOffset.above;
  511. }
  512. });
  513. }
  514. var prevDelta = { x: 0, y: 0 };
  515. plot.smartPan = function(delta, initialState, panAxes, preventEvent, smartLock) {
  516. var snap = smartLock ? true : shouldSnap(delta),
  517. axes = plot.getAxes(),
  518. opts;
  519. delta = smartLock ? lockDeltaDirection(delta) : adjustDeltaToSnap(delta);
  520. if (isDiagonalMode(delta)) {
  521. initialState.diagMode = true;
  522. }
  523. if (snap && initialState.diagMode === true) {
  524. initialState.diagMode = false;
  525. restoreAxisOffset(axes, initialState, delta);
  526. }
  527. if (snap) {
  528. panHint = {
  529. start: {
  530. x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
  531. y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
  532. },
  533. end: {
  534. x: initialState.startPageX - delta.x - plot.offset().left + plot.getPlotOffset().left,
  535. y: initialState.startPageY - delta.y - plot.offset().top + plot.getPlotOffset().top
  536. }
  537. }
  538. } else {
  539. panHint = {
  540. start: {
  541. x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
  542. y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
  543. },
  544. end: false
  545. }
  546. }
  547. if (isNaN(delta.x)) delta.x = 0;
  548. if (isNaN(delta.y)) delta.y = 0;
  549. if (panAxes) {
  550. axes = panAxes;
  551. }
  552. var axis, axisMin, axisMax, p, d;
  553. Object.keys(axes).forEach(function(axisName) {
  554. axis = axes[axisName];
  555. axisMin = axis.min;
  556. axisMax = axis.max;
  557. opts = axis.options;
  558. d = delta[axis.direction];
  559. p = prevDelta[axis.direction];
  560. //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
  561. if ((!opts.axisPan && panAxes) || (!panAxes && !opts.plotPan)) {
  562. return;
  563. }
  564. if (d !== 0) {
  565. var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axisMin) - (p - d)) - axis.c2p(axis.p2c(axisMin))),
  566. navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axisMax) - (p - d)) - axis.c2p(axis.p2c(axisMax)));
  567. if (!isFinite(navigationOffsetBelow)) {
  568. navigationOffsetBelow = 0;
  569. }
  570. if (!isFinite(navigationOffsetAbove)) {
  571. navigationOffsetAbove = 0;
  572. }
  573. axis.options.offset.below = saturated.saturate(navigationOffsetBelow + (axis.options.offset.below || 0));
  574. axis.options.offset.above = saturated.saturate(navigationOffsetAbove + (axis.options.offset.above || 0));
  575. }
  576. });
  577. prevDelta = delta;
  578. plot.setupGrid(true);
  579. plot.draw();
  580. if (!preventEvent) {
  581. plot.getPlaceholder().trigger("plotpan", [plot, delta, panAxes, initialState]);
  582. }
  583. };
  584. plot.smartPan.end = function() {
  585. panHint = null;
  586. lockedDirection = null;
  587. prevDelta = { x: 0, y: 0 };
  588. plot.triggerRedrawOverlay();
  589. }
  590. function shutdown(plot, eventHolder) {
  591. eventHolder.unbind("mousewheel", onMouseWheel);
  592. eventHolder.unbind("mousedown", onMouseDown);
  593. eventHolder.unbind("mouseup", onMouseUp);
  594. eventHolder.unbind("dragstart", onDragStart);
  595. eventHolder.unbind("drag", onDrag);
  596. eventHolder.unbind("dragend", onDragEnd);
  597. eventHolder.unbind("dblclick", onDblClick);
  598. eventHolder.unbind("click", onClick);
  599. if (panTimeout) clearTimeout(panTimeout);
  600. }
  601. function drawOverlay(plot, ctx) {
  602. if (panHint) {
  603. ctx.strokeStyle = 'rgba(96, 160, 208, 0.7)';
  604. ctx.lineWidth = 2;
  605. ctx.lineJoin = "round";
  606. var startx = Math.round(panHint.start.x),
  607. starty = Math.round(panHint.start.y),
  608. endx, endy;
  609. if (panAxes) {
  610. if (panAxes[0].direction === 'x') {
  611. endy = Math.round(panHint.start.y);
  612. endx = Math.round(panHint.end.x);
  613. } else if (panAxes[0].direction === 'y') {
  614. endx = Math.round(panHint.start.x);
  615. endy = Math.round(panHint.end.y);
  616. }
  617. } else {
  618. endx = Math.round(panHint.end.x);
  619. endy = Math.round(panHint.end.y);
  620. }
  621. ctx.beginPath();
  622. if (panHint.end === false) {
  623. ctx.moveTo(startx, starty - PANHINT_LENGTH_CONSTANT);
  624. ctx.lineTo(startx, starty + PANHINT_LENGTH_CONSTANT);
  625. ctx.moveTo(startx + PANHINT_LENGTH_CONSTANT, starty);
  626. ctx.lineTo(startx - PANHINT_LENGTH_CONSTANT, starty);
  627. } else {
  628. var dirX = starty === endy;
  629. ctx.moveTo(startx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
  630. ctx.lineTo(startx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
  631. ctx.moveTo(startx, starty);
  632. ctx.lineTo(endx, endy);
  633. ctx.moveTo(endx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
  634. ctx.lineTo(endx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
  635. }
  636. ctx.stroke();
  637. }
  638. }
  639. plot.getTouchedAxis = function(touchPointX, touchPointY) {
  640. var ec = plot.getPlaceholder().offset();
  641. ec.left = touchPointX - ec.left;
  642. ec.top = touchPointY - ec.top;
  643. var axis = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
  644. var box = axis.box;
  645. if (box !== undefined) {
  646. return (ec.left > box.left) && (ec.left < box.left + box.width) &&
  647. (ec.top > box.top) && (ec.top < box.top + box.height);
  648. }
  649. });
  650. return axis;
  651. }
  652. plot.hooks.drawOverlay.push(drawOverlay);
  653. plot.hooks.bindEvents.push(bindEvents);
  654. plot.hooks.shutdown.push(shutdown);
  655. }
  656. $.plot.plugins.push({
  657. init: init,
  658. options: options,
  659. name: 'navigate',
  660. version: '1.3'
  661. });
  662. })(jQuery);