jquery.flot.touchNavigate.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. /* global jQuery */
  2. (function($) {
  3. 'use strict';
  4. var options = {
  5. zoom: {
  6. enableTouch: false
  7. },
  8. pan: {
  9. enableTouch: false,
  10. touchMode: 'manual'
  11. },
  12. recenter: {
  13. enableTouch: true
  14. }
  15. };
  16. var ZOOM_DISTANCE_MARGIN = $.plot.uiConstants.ZOOM_DISTANCE_MARGIN;
  17. function init(plot) {
  18. plot.hooks.processOptions.push(initTouchNavigation);
  19. }
  20. function initTouchNavigation(plot, options) {
  21. var gestureState = {
  22. zoomEnable: false,
  23. prevDistance: null,
  24. prevTapTime: 0,
  25. prevPanPosition: { x: 0, y: 0 },
  26. prevTapPosition: { x: 0, y: 0 }
  27. },
  28. navigationState = {
  29. prevTouchedAxis: 'none',
  30. currentTouchedAxis: 'none',
  31. touchedAxis: null,
  32. navigationConstraint: 'unconstrained',
  33. initialState: null,
  34. },
  35. useManualPan = options.pan.interactive && options.pan.touchMode === 'manual',
  36. smartPanLock = options.pan.touchMode === 'smartLock',
  37. useSmartPan = options.pan.interactive && (smartPanLock || options.pan.touchMode === 'smart'),
  38. pan, pinch, doubleTap;
  39. function bindEvents(plot, eventHolder) {
  40. var o = plot.getOptions();
  41. if (o.zoom.interactive && o.zoom.enableTouch) {
  42. eventHolder[0].addEventListener('pinchstart', pinch.start, false);
  43. eventHolder[0].addEventListener('pinchdrag', pinch.drag, false);
  44. eventHolder[0].addEventListener('pinchend', pinch.end, false);
  45. }
  46. if (o.pan.interactive && o.pan.enableTouch) {
  47. eventHolder[0].addEventListener('panstart', pan.start, false);
  48. eventHolder[0].addEventListener('pandrag', pan.drag, false);
  49. eventHolder[0].addEventListener('panend', pan.end, false);
  50. }
  51. if ((o.recenter.interactive && o.recenter.enableTouch)) {
  52. eventHolder[0].addEventListener('doubletap', doubleTap.recenterPlot, false);
  53. }
  54. }
  55. function shutdown(plot, eventHolder) {
  56. eventHolder[0].removeEventListener('panstart', pan.start);
  57. eventHolder[0].removeEventListener('pandrag', pan.drag);
  58. eventHolder[0].removeEventListener('panend', pan.end);
  59. eventHolder[0].removeEventListener('pinchstart', pinch.start);
  60. eventHolder[0].removeEventListener('pinchdrag', pinch.drag);
  61. eventHolder[0].removeEventListener('pinchend', pinch.end);
  62. eventHolder[0].removeEventListener('doubletap', doubleTap.recenterPlot);
  63. }
  64. pan = {
  65. start: function(e) {
  66. presetNavigationState(e, 'pan', gestureState);
  67. updateData(e, 'pan', gestureState, navigationState);
  68. if (useSmartPan) {
  69. var point = getPoint(e, 'pan');
  70. navigationState.initialState = plot.navigationState(point.x, point.y);
  71. }
  72. },
  73. drag: function(e) {
  74. presetNavigationState(e, 'pan', gestureState);
  75. if (useSmartPan) {
  76. var point = getPoint(e, 'pan');
  77. plot.smartPan({
  78. x: navigationState.initialState.startPageX - point.x,
  79. y: navigationState.initialState.startPageY - point.y
  80. }, navigationState.initialState, navigationState.touchedAxis, false, smartPanLock);
  81. } else if (useManualPan) {
  82. plot.pan({
  83. left: -delta(e, 'pan', gestureState).x,
  84. top: -delta(e, 'pan', gestureState).y,
  85. axes: navigationState.touchedAxis
  86. });
  87. updatePrevPanPosition(e, 'pan', gestureState, navigationState);
  88. }
  89. },
  90. end: function(e) {
  91. presetNavigationState(e, 'pan', gestureState);
  92. if (useSmartPan) {
  93. plot.smartPan.end();
  94. }
  95. if (wasPinchEvent(e, gestureState)) {
  96. updateprevPanPosition(e, 'pan', gestureState, navigationState);
  97. }
  98. }
  99. };
  100. var pinchDragTimeout;
  101. pinch = {
  102. start: function(e) {
  103. if (pinchDragTimeout) {
  104. clearTimeout(pinchDragTimeout);
  105. pinchDragTimeout = null;
  106. }
  107. presetNavigationState(e, 'pinch', gestureState);
  108. setPrevDistance(e, gestureState);
  109. updateData(e, 'pinch', gestureState, navigationState);
  110. },
  111. drag: function(e) {
  112. if (pinchDragTimeout) {
  113. return;
  114. }
  115. pinchDragTimeout = setTimeout(function() {
  116. presetNavigationState(e, 'pinch', gestureState);
  117. plot.pan({
  118. left: -delta(e, 'pinch', gestureState).x,
  119. top: -delta(e, 'pinch', gestureState).y,
  120. axes: navigationState.touchedAxis
  121. });
  122. updatePrevPanPosition(e, 'pinch', gestureState, navigationState);
  123. var dist = pinchDistance(e);
  124. if (gestureState.zoomEnable || Math.abs(dist - gestureState.prevDistance) > ZOOM_DISTANCE_MARGIN) {
  125. zoomPlot(plot, e, gestureState, navigationState);
  126. //activate zoom mode
  127. gestureState.zoomEnable = true;
  128. }
  129. pinchDragTimeout = null;
  130. }, 1000 / 60);
  131. },
  132. end: function(e) {
  133. if (pinchDragTimeout) {
  134. clearTimeout(pinchDragTimeout);
  135. pinchDragTimeout = null;
  136. }
  137. presetNavigationState(e, 'pinch', gestureState);
  138. gestureState.prevDistance = null;
  139. }
  140. };
  141. doubleTap = {
  142. recenterPlot: function(e) {
  143. if (e && e.detail && e.detail.type === 'touchstart') {
  144. // only do not recenter for touch start;
  145. recenterPlotOnDoubleTap(plot, e, gestureState, navigationState);
  146. }
  147. }
  148. };
  149. if (options.pan.enableTouch === true || options.zoom.enableTouch === true) {
  150. plot.hooks.bindEvents.push(bindEvents);
  151. plot.hooks.shutdown.push(shutdown);
  152. }
  153. function presetNavigationState(e, gesture, gestureState) {
  154. navigationState.touchedAxis = getAxis(plot, e, gesture, navigationState);
  155. if (noAxisTouched(navigationState)) {
  156. navigationState.navigationConstraint = 'unconstrained';
  157. } else {
  158. navigationState.navigationConstraint = 'axisConstrained';
  159. }
  160. }
  161. }
  162. $.plot.plugins.push({
  163. init: init,
  164. options: options,
  165. name: 'navigateTouch',
  166. version: '0.3'
  167. });
  168. function recenterPlotOnDoubleTap(plot, e, gestureState, navigationState) {
  169. checkAxesForDoubleTap(plot, e, navigationState);
  170. if ((navigationState.currentTouchedAxis === 'x' && navigationState.prevTouchedAxis === 'x') ||
  171. (navigationState.currentTouchedAxis === 'y' && navigationState.prevTouchedAxis === 'y') ||
  172. (navigationState.currentTouchedAxis === 'none' && navigationState.prevTouchedAxis === 'none')) {
  173. var event;
  174. plot.recenter({ axes: navigationState.touchedAxis });
  175. if (navigationState.touchedAxis) {
  176. event = new $.Event('re-center', { detail: { axisTouched: navigationState.touchedAxis } });
  177. } else {
  178. event = new $.Event('re-center', { detail: e });
  179. }
  180. plot.getPlaceholder().trigger(event);
  181. }
  182. }
  183. function checkAxesForDoubleTap(plot, e, navigationState) {
  184. var axis = plot.getTouchedAxis(e.detail.firstTouch.x, e.detail.firstTouch.y);
  185. if (axis[0] !== undefined) {
  186. navigationState.prevTouchedAxis = axis[0].direction;
  187. }
  188. axis = plot.getTouchedAxis(e.detail.secondTouch.x, e.detail.secondTouch.y);
  189. if (axis[0] !== undefined) {
  190. navigationState.touchedAxis = axis;
  191. navigationState.currentTouchedAxis = axis[0].direction;
  192. }
  193. if (noAxisTouched(navigationState)) {
  194. navigationState.touchedAxis = null;
  195. navigationState.prevTouchedAxis = 'none';
  196. navigationState.currentTouchedAxis = 'none';
  197. }
  198. }
  199. function zoomPlot(plot, e, gestureState, navigationState) {
  200. var offset = plot.offset(),
  201. center = {
  202. left: 0,
  203. top: 0
  204. },
  205. zoomAmount = pinchDistance(e) / gestureState.prevDistance,
  206. dist = pinchDistance(e);
  207. center.left = getPoint(e, 'pinch').x - offset.left;
  208. center.top = getPoint(e, 'pinch').y - offset.top;
  209. // send the computed touched axis to the zoom function so that it only zooms on that one
  210. plot.zoom({
  211. center: center,
  212. amount: zoomAmount,
  213. axes: navigationState.touchedAxis
  214. });
  215. gestureState.prevDistance = dist;
  216. }
  217. function wasPinchEvent(e, gestureState) {
  218. return (gestureState.zoomEnable && e.detail.touches.length === 1);
  219. }
  220. function getAxis(plot, e, gesture, navigationState) {
  221. if (e.type === 'pinchstart') {
  222. var axisTouch1 = plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
  223. var axisTouch2 = plot.getTouchedAxis(e.detail.touches[1].pageX, e.detail.touches[1].pageY);
  224. if (axisTouch1.length === axisTouch2.length && axisTouch1.toString() === axisTouch2.toString()) {
  225. return axisTouch1;
  226. }
  227. } else if (e.type === 'panstart') {
  228. return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
  229. } else if (e.type === 'pinchend') {
  230. //update axis since instead on pinch, a pan event is made
  231. return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
  232. } else {
  233. return navigationState.touchedAxis;
  234. }
  235. }
  236. function noAxisTouched(navigationState) {
  237. return (!navigationState.touchedAxis || navigationState.touchedAxis.length === 0);
  238. }
  239. function setPrevDistance(e, gestureState) {
  240. gestureState.prevDistance = pinchDistance(e);
  241. }
  242. function updateData(e, gesture, gestureState, navigationState) {
  243. var axisDir,
  244. point = getPoint(e, gesture);
  245. switch (navigationState.navigationConstraint) {
  246. case 'unconstrained':
  247. navigationState.touchedAxis = null;
  248. gestureState.prevTapPosition = {
  249. x: gestureState.prevPanPosition.x,
  250. y: gestureState.prevPanPosition.y
  251. };
  252. gestureState.prevPanPosition = {
  253. x: point.x,
  254. y: point.y
  255. };
  256. break;
  257. case 'axisConstrained':
  258. axisDir = navigationState.touchedAxis[0].direction;
  259. navigationState.currentTouchedAxis = axisDir;
  260. gestureState.prevTapPosition[axisDir] = gestureState.prevPanPosition[axisDir];
  261. gestureState.prevPanPosition[axisDir] = point[axisDir];
  262. break;
  263. default:
  264. break;
  265. }
  266. }
  267. function distance(x1, y1, x2, y2) {
  268. return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
  269. }
  270. function pinchDistance(e) {
  271. var t1 = e.detail.touches[0],
  272. t2 = e.detail.touches[1];
  273. return distance(t1.pageX, t1.pageY, t2.pageX, t2.pageY);
  274. }
  275. function updatePrevPanPosition(e, gesture, gestureState, navigationState) {
  276. var point = getPoint(e, gesture);
  277. switch (navigationState.navigationConstraint) {
  278. case 'unconstrained':
  279. gestureState.prevPanPosition.x = point.x;
  280. gestureState.prevPanPosition.y = point.y;
  281. break;
  282. case 'axisConstrained':
  283. gestureState.prevPanPosition[navigationState.currentTouchedAxis] =
  284. point[navigationState.currentTouchedAxis];
  285. break;
  286. default:
  287. break;
  288. }
  289. }
  290. function delta(e, gesture, gestureState) {
  291. var point = getPoint(e, gesture);
  292. return {
  293. x: point.x - gestureState.prevPanPosition.x,
  294. y: point.y - gestureState.prevPanPosition.y
  295. }
  296. }
  297. function getPoint(e, gesture) {
  298. if (gesture === 'pinch') {
  299. return {
  300. x: (e.detail.touches[0].pageX + e.detail.touches[1].pageX) / 2,
  301. y: (e.detail.touches[0].pageY + e.detail.touches[1].pageY) / 2
  302. }
  303. } else {
  304. return {
  305. x: e.detail.touches[0].pageX,
  306. y: e.detail.touches[0].pageY
  307. }
  308. }
  309. }
  310. })(jQuery);