1 |
|
2 |
|
3 |
|
4 | (function($) {
|
5 | 'use strict';
|
6 |
|
7 | var options = {
|
8 | propagateSupportedGesture: false
|
9 | };
|
10 |
|
11 | function init(plot) {
|
12 | plot.hooks.processOptions.push(initTouchNavigation);
|
13 | }
|
14 |
|
15 | function initTouchNavigation(plot, options) {
|
16 | var gestureState = {
|
17 | twoTouches: false,
|
18 | currentTapStart: { x: 0, y: 0 },
|
19 | currentTapEnd: { x: 0, y: 0 },
|
20 | prevTap: { x: 0, y: 0 },
|
21 | currentTap: { x: 0, y: 0 },
|
22 | interceptedLongTap: false,
|
23 | isUnsupportedGesture: false,
|
24 | prevTapTime: null,
|
25 | tapStartTime: null,
|
26 | longTapTriggerId: null
|
27 | },
|
28 | maxDistanceBetweenTaps = 20,
|
29 | maxIntervalBetweenTaps = 500,
|
30 | maxLongTapDistance = 20,
|
31 | minLongTapDuration = 1500,
|
32 | pressedTapDuration = 125,
|
33 | mainEventHolder;
|
34 |
|
35 | function interpretGestures(e) {
|
36 | var o = plot.getOptions();
|
37 |
|
38 | if (!o.pan.active && !o.zoom.active) {
|
39 | return;
|
40 | }
|
41 |
|
42 | updateOnMultipleTouches(e);
|
43 | mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));
|
44 |
|
45 | if (isPinchEvent(e)) {
|
46 | executeAction(e, 'pinch');
|
47 | } else {
|
48 | executeAction(e, 'pan');
|
49 | if (!wasPinchEvent(e)) {
|
50 | if (isDoubleTap(e)) {
|
51 | executeAction(e, 'doubleTap');
|
52 | }
|
53 | executeAction(e, 'tap');
|
54 | executeAction(e, 'longTap');
|
55 | }
|
56 | }
|
57 | }
|
58 |
|
59 | function executeAction(e, gesture) {
|
60 | switch (gesture) {
|
61 | case 'pan':
|
62 | pan[e.type](e);
|
63 | break;
|
64 | case 'pinch':
|
65 | pinch[e.type](e);
|
66 | break;
|
67 | case 'doubleTap':
|
68 | doubleTap.onDoubleTap(e);
|
69 | break;
|
70 | case 'longTap':
|
71 | longTap[e.type](e);
|
72 | break;
|
73 | case 'tap':
|
74 | tap[e.type](e);
|
75 | break;
|
76 | }
|
77 | }
|
78 |
|
79 | function bindEvents(plot, eventHolder) {
|
80 | mainEventHolder = eventHolder[0];
|
81 | eventHolder[0].addEventListener('touchstart', interpretGestures, false);
|
82 | eventHolder[0].addEventListener('touchmove', interpretGestures, false);
|
83 | eventHolder[0].addEventListener('touchend', interpretGestures, false);
|
84 | }
|
85 |
|
86 | function shutdown(plot, eventHolder) {
|
87 | eventHolder[0].removeEventListener('touchstart', interpretGestures);
|
88 | eventHolder[0].removeEventListener('touchmove', interpretGestures);
|
89 | eventHolder[0].removeEventListener('touchend', interpretGestures);
|
90 | if (gestureState.longTapTriggerId) {
|
91 | clearTimeout(gestureState.longTapTriggerId);
|
92 | gestureState.longTapTriggerId = null;
|
93 | }
|
94 | }
|
95 |
|
96 | var pan = {
|
97 | touchstart: function(e) {
|
98 | updatePrevForDoubleTap();
|
99 | updateCurrentForDoubleTap(e);
|
100 | updateStateForLongTapStart(e);
|
101 |
|
102 | mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
|
103 | },
|
104 |
|
105 | touchmove: function(e) {
|
106 | preventEventBehaviors(e);
|
107 |
|
108 | updateCurrentForDoubleTap(e);
|
109 | updateStateForLongTapEnd(e);
|
110 |
|
111 | if (!gestureState.isUnsupportedGesture) {
|
112 | mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));
|
113 | }
|
114 | },
|
115 |
|
116 | touchend: function(e) {
|
117 | preventEventBehaviors(e);
|
118 |
|
119 | if (wasPinchEvent(e)) {
|
120 | mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));
|
121 | mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
|
122 | } else if (noTouchActive(e)) {
|
123 | mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));
|
124 | }
|
125 | }
|
126 | };
|
127 |
|
128 | var pinch = {
|
129 | touchstart: function(e) {
|
130 | mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));
|
131 | },
|
132 |
|
133 | touchmove: function(e) {
|
134 | preventEventBehaviors(e);
|
135 | gestureState.twoTouches = isPinchEvent(e);
|
136 | if (!gestureState.isUnsupportedGesture) {
|
137 | mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));
|
138 | }
|
139 | },
|
140 |
|
141 | touchend: function(e) {
|
142 | preventEventBehaviors(e);
|
143 | }
|
144 | };
|
145 |
|
146 | var doubleTap = {
|
147 | onDoubleTap: function(e) {
|
148 | preventEventBehaviors(e);
|
149 | mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));
|
150 | }
|
151 | };
|
152 |
|
153 | var longTap = {
|
154 | touchstart: function(e) {
|
155 | longTap.waitForLongTap(e);
|
156 | },
|
157 |
|
158 | touchmove: function(e) {
|
159 | },
|
160 |
|
161 | touchend: function(e) {
|
162 | if (gestureState.longTapTriggerId) {
|
163 | clearTimeout(gestureState.longTapTriggerId);
|
164 | gestureState.longTapTriggerId = null;
|
165 | }
|
166 | },
|
167 |
|
168 | isLongTap: function(e) {
|
169 | var currentTime = new Date().getTime(),
|
170 | tapDuration = currentTime - gestureState.tapStartTime;
|
171 | if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
|
172 | if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
|
173 | gestureState.interceptedLongTap = true;
|
174 | return true;
|
175 | }
|
176 | }
|
177 | return false;
|
178 | },
|
179 |
|
180 | waitForLongTap: function(e) {
|
181 | var longTapTrigger = function() {
|
182 | if (longTap.isLongTap(e)) {
|
183 | mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));
|
184 | }
|
185 | gestureState.longTapTriggerId = null;
|
186 | };
|
187 | if (!gestureState.longTapTriggerId) {
|
188 | gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
|
189 | }
|
190 | }
|
191 | };
|
192 |
|
193 | var tap = {
|
194 | touchstart: function(e) {
|
195 | gestureState.tapStartTime = new Date().getTime();
|
196 | },
|
197 |
|
198 | touchmove: function(e) {
|
199 | },
|
200 |
|
201 | touchend: function(e) {
|
202 | if (tap.isTap(e)) {
|
203 | mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));
|
204 | preventEventBehaviors(e);
|
205 | }
|
206 | },
|
207 |
|
208 | isTap: function(e) {
|
209 | var currentTime = new Date().getTime(),
|
210 | tapDuration = currentTime - gestureState.tapStartTime;
|
211 | if (tapDuration <= pressedTapDuration) {
|
212 | if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
|
213 | return true;
|
214 | }
|
215 | }
|
216 | return false;
|
217 | }
|
218 | };
|
219 |
|
220 | if (options.pan.enableTouch === true || options.zoom.enableTouch) {
|
221 | plot.hooks.bindEvents.push(bindEvents);
|
222 | plot.hooks.shutdown.push(shutdown);
|
223 | };
|
224 |
|
225 | function updatePrevForDoubleTap() {
|
226 | gestureState.prevTap = {
|
227 | x: gestureState.currentTap.x,
|
228 | y: gestureState.currentTap.y
|
229 | };
|
230 | };
|
231 |
|
232 | function updateCurrentForDoubleTap(e) {
|
233 | gestureState.currentTap = {
|
234 | x: e.touches[0].pageX,
|
235 | y: e.touches[0].pageY
|
236 | };
|
237 | }
|
238 |
|
239 | function updateStateForLongTapStart(e) {
|
240 | gestureState.tapStartTime = new Date().getTime();
|
241 | gestureState.interceptedLongTap = false;
|
242 | gestureState.currentTapStart = {
|
243 | x: e.touches[0].pageX,
|
244 | y: e.touches[0].pageY
|
245 | };
|
246 | gestureState.currentTapEnd = {
|
247 | x: e.touches[0].pageX,
|
248 | y: e.touches[0].pageY
|
249 | };
|
250 | };
|
251 |
|
252 | function updateStateForLongTapEnd(e) {
|
253 | gestureState.currentTapEnd = {
|
254 | x: e.touches[0].pageX,
|
255 | y: e.touches[0].pageY
|
256 | };
|
257 | };
|
258 |
|
259 | function isDoubleTap(e) {
|
260 | var currentTime = new Date().getTime(),
|
261 | intervalBetweenTaps = currentTime - gestureState.prevTapTime;
|
262 |
|
263 | if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
|
264 | if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
|
265 | e.firstTouch = gestureState.prevTap;
|
266 | e.secondTouch = gestureState.currentTap;
|
267 | return true;
|
268 | }
|
269 | }
|
270 | gestureState.prevTapTime = currentTime;
|
271 | return false;
|
272 | }
|
273 |
|
274 | function preventEventBehaviors(e) {
|
275 | if (!gestureState.isUnsupportedGesture) {
|
276 | e.preventDefault();
|
277 | if (!plot.getOptions().propagateSupportedGesture) {
|
278 | e.stopPropagation();
|
279 | }
|
280 | }
|
281 | }
|
282 |
|
283 | function distance(x1, y1, x2, y2) {
|
284 | return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
285 | }
|
286 |
|
287 | function noTouchActive(e) {
|
288 | return (e.touches && e.touches.length === 0);
|
289 | }
|
290 |
|
291 | function wasPinchEvent(e) {
|
292 | return (gestureState.twoTouches && e.touches.length === 1);
|
293 | }
|
294 |
|
295 | function updateOnMultipleTouches(e) {
|
296 | if (e.touches.length >= 3) {
|
297 | gestureState.isUnsupportedGesture = true;
|
298 | } else {
|
299 | gestureState.isUnsupportedGesture = false;
|
300 | }
|
301 | }
|
302 |
|
303 | function isPinchEvent(e) {
|
304 | if (e.touches && e.touches.length >= 2) {
|
305 | if (e.touches[0].target === plot.getEventHolder() &&
|
306 | e.touches[1].target === plot.getEventHolder()) {
|
307 | return true;
|
308 | }
|
309 | }
|
310 | return false;
|
311 | }
|
312 | }
|
313 |
|
314 | $.plot.plugins.push({
|
315 | init: init,
|
316 | options: options,
|
317 | name: 'navigateTouch',
|
318 | version: '0.3'
|
319 | });
|
320 | })(jQuery);
|