Graph database Analysis of the Steam Network
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
11 KiB

  1. ;(function(undefined) {
  2. 'use strict';
  3. if (typeof sigma === 'undefined')
  4. throw 'sigma is not declared';
  5. // Initialize packages:
  6. sigma.utils.pkg('sigma.captors');
  7. /**
  8. * The user inputs default captor. It deals with mouse events, keyboards
  9. * events and touch events.
  10. *
  11. * @param {DOMElement} target The DOM element where the listeners will be
  12. * bound.
  13. * @param {camera} camera The camera related to the target.
  14. * @param {configurable} settings The settings function.
  15. * @return {sigma.captor} The fresh new captor instance.
  16. */
  17. sigma.captors.touch = function(target, camera, settings) {
  18. var _self = this,
  19. _target = target,
  20. _camera = camera,
  21. _settings = settings,
  22. // CAMERA MANAGEMENT:
  23. // ******************
  24. // The camera position when the user starts dragging:
  25. _startCameraX,
  26. _startCameraY,
  27. _startCameraAngle,
  28. _startCameraRatio,
  29. // The latest stage position:
  30. _lastCameraX,
  31. _lastCameraY,
  32. _lastCameraAngle,
  33. _lastCameraRatio,
  34. // TOUCH MANAGEMENT:
  35. // *****************
  36. // Touches that are down:
  37. _downTouches = [],
  38. _startTouchX0,
  39. _startTouchY0,
  40. _startTouchX1,
  41. _startTouchY1,
  42. _startTouchAngle,
  43. _startTouchDistance,
  44. _touchMode,
  45. _isMoving,
  46. _doubleTap,
  47. _movingTimeoutId;
  48. sigma.classes.dispatcher.extend(this);
  49. sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler);
  50. _target.addEventListener('touchstart', _handleStart, false);
  51. _target.addEventListener('touchend', _handleLeave, false);
  52. _target.addEventListener('touchcancel', _handleLeave, false);
  53. _target.addEventListener('touchleave', _handleLeave, false);
  54. _target.addEventListener('touchmove', _handleMove, false);
  55. function position(e) {
  56. var offset = sigma.utils.getOffset(_target);
  57. return {
  58. x: e.pageX - offset.left,
  59. y: e.pageY - offset.top
  60. };
  61. }
  62. /**
  63. * This method unbinds every handlers that makes the captor work.
  64. */
  65. this.kill = function() {
  66. sigma.utils.unbindDoubleClick(_target, 'touchstart');
  67. _target.addEventListener('touchstart', _handleStart);
  68. _target.addEventListener('touchend', _handleLeave);
  69. _target.addEventListener('touchcancel', _handleLeave);
  70. _target.addEventListener('touchleave', _handleLeave);
  71. _target.addEventListener('touchmove', _handleMove);
  72. };
  73. // TOUCH EVENTS:
  74. // *************
  75. /**
  76. * The handler listening to the 'touchstart' event. It will set the touch
  77. * mode ("_touchMode") and start observing the user touch moves.
  78. *
  79. * @param {event} e A touch event.
  80. */
  81. function _handleStart(e) {
  82. if (_settings('touchEnabled')) {
  83. var x0,
  84. x1,
  85. y0,
  86. y1,
  87. pos0,
  88. pos1;
  89. _downTouches = e.touches;
  90. switch (_downTouches.length) {
  91. case 1:
  92. _camera.isMoving = true;
  93. _touchMode = 1;
  94. _startCameraX = _camera.x;
  95. _startCameraY = _camera.y;
  96. _lastCameraX = _camera.x;
  97. _lastCameraY = _camera.y;
  98. pos0 = position(_downTouches[0]);
  99. _startTouchX0 = pos0.x;
  100. _startTouchY0 = pos0.y;
  101. break;
  102. case 2:
  103. _camera.isMoving = true;
  104. _touchMode = 2;
  105. pos0 = position(_downTouches[0]);
  106. pos1 = position(_downTouches[1]);
  107. x0 = pos0.x;
  108. y0 = pos0.y;
  109. x1 = pos1.x;
  110. y1 = pos1.y;
  111. _lastCameraX = _camera.x;
  112. _lastCameraY = _camera.y;
  113. _startCameraAngle = _camera.angle;
  114. _startCameraRatio = _camera.ratio;
  115. _startCameraX = _camera.x;
  116. _startCameraY = _camera.y;
  117. _startTouchX0 = x0;
  118. _startTouchY0 = y0;
  119. _startTouchX1 = x1;
  120. _startTouchY1 = y1;
  121. _startTouchAngle = Math.atan2(
  122. _startTouchY1 - _startTouchY0,
  123. _startTouchX1 - _startTouchX0
  124. );
  125. _startTouchDistance = Math.sqrt(
  126. (_startTouchY1 - _startTouchY0) *
  127. (_startTouchY1 - _startTouchY0) +
  128. (_startTouchX1 - _startTouchX0) *
  129. (_startTouchX1 - _startTouchX0)
  130. );
  131. e.preventDefault();
  132. return false;
  133. }
  134. }
  135. }
  136. /**
  137. * The handler listening to the 'touchend', 'touchcancel' and 'touchleave'
  138. * event. It will update the touch mode if there are still at least one
  139. * finger, and stop dragging else.
  140. *
  141. * @param {event} e A touch event.
  142. */
  143. function _handleLeave(e) {
  144. if (_settings('touchEnabled')) {
  145. _downTouches = e.touches;
  146. var inertiaRatio = _settings('touchInertiaRatio');
  147. if (_movingTimeoutId) {
  148. _isMoving = false;
  149. clearTimeout(_movingTimeoutId);
  150. }
  151. switch (_touchMode) {
  152. case 2:
  153. if (e.touches.length === 1) {
  154. _handleStart(e);
  155. e.preventDefault();
  156. break;
  157. }
  158. /* falls through */
  159. case 1:
  160. _camera.isMoving = false;
  161. _self.dispatchEvent('stopDrag');
  162. if (_isMoving) {
  163. _doubleTap = false;
  164. sigma.misc.animation.camera(
  165. _camera,
  166. {
  167. x: _camera.x +
  168. inertiaRatio * (_camera.x - _lastCameraX),
  169. y: _camera.y +
  170. inertiaRatio * (_camera.y - _lastCameraY)
  171. },
  172. {
  173. easing: 'quadraticOut',
  174. duration: _settings('touchInertiaDuration')
  175. }
  176. );
  177. }
  178. _isMoving = false;
  179. _touchMode = 0;
  180. break;
  181. }
  182. }
  183. }
  184. /**
  185. * The handler listening to the 'touchmove' event. It will effectively drag
  186. * the graph, and eventually zooms and turn it if the user is using two
  187. * fingers.
  188. *
  189. * @param {event} e A touch event.
  190. */
  191. function _handleMove(e) {
  192. if (!_doubleTap && _settings('touchEnabled')) {
  193. var x0,
  194. x1,
  195. y0,
  196. y1,
  197. cos,
  198. sin,
  199. end,
  200. pos0,
  201. pos1,
  202. diff,
  203. start,
  204. dAngle,
  205. dRatio,
  206. newStageX,
  207. newStageY,
  208. newStageRatio,
  209. newStageAngle;
  210. _downTouches = e.touches;
  211. _isMoving = true;
  212. if (_movingTimeoutId)
  213. clearTimeout(_movingTimeoutId);
  214. _movingTimeoutId = setTimeout(function() {
  215. _isMoving = false;
  216. }, _settings('dragTimeout'));
  217. switch (_touchMode) {
  218. case 1:
  219. pos0 = position(_downTouches[0]);
  220. x0 = pos0.x;
  221. y0 = pos0.y;
  222. diff = _camera.cameraPosition(
  223. x0 - _startTouchX0,
  224. y0 - _startTouchY0,
  225. true
  226. );
  227. newStageX = _startCameraX - diff.x;
  228. newStageY = _startCameraY - diff.y;
  229. if (newStageX !== _camera.x || newStageY !== _camera.y) {
  230. _lastCameraX = _camera.x;
  231. _lastCameraY = _camera.y;
  232. _camera.goTo({
  233. x: newStageX,
  234. y: newStageY
  235. });
  236. _self.dispatchEvent('mousemove',
  237. sigma.utils.mouseCoords(e, pos0.x, pos0.y));
  238. _self.dispatchEvent('drag');
  239. }
  240. break;
  241. case 2:
  242. pos0 = position(_downTouches[0]);
  243. pos1 = position(_downTouches[1]);
  244. x0 = pos0.x;
  245. y0 = pos0.y;
  246. x1 = pos1.x;
  247. y1 = pos1.y;
  248. start = _camera.cameraPosition(
  249. (_startTouchX0 + _startTouchX1) / 2 -
  250. sigma.utils.getCenter(e).x,
  251. (_startTouchY0 + _startTouchY1) / 2 -
  252. sigma.utils.getCenter(e).y,
  253. true
  254. );
  255. end = _camera.cameraPosition(
  256. (x0 + x1) / 2 - sigma.utils.getCenter(e).x,
  257. (y0 + y1) / 2 - sigma.utils.getCenter(e).y,
  258. true
  259. );
  260. dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle;
  261. dRatio = Math.sqrt(
  262. (y1 - y0) * (y1 - y0) + (x1 - x0) * (x1 - x0)
  263. ) / _startTouchDistance;
  264. // Translation:
  265. x0 = start.x;
  266. y0 = start.y;
  267. // Homothetic transformation:
  268. newStageRatio = _startCameraRatio / dRatio;
  269. x0 = x0 * dRatio;
  270. y0 = y0 * dRatio;
  271. // Rotation:
  272. newStageAngle = _startCameraAngle - dAngle;
  273. cos = Math.cos(-dAngle);
  274. sin = Math.sin(-dAngle);
  275. x1 = x0 * cos + y0 * sin;
  276. y1 = y0 * cos - x0 * sin;
  277. x0 = x1;
  278. y0 = y1;
  279. // Finalize:
  280. newStageX = x0 - end.x + _startCameraX;
  281. newStageY = y0 - end.y + _startCameraY;
  282. if (
  283. newStageRatio !== _camera.ratio ||
  284. newStageAngle !== _camera.angle ||
  285. newStageX !== _camera.x ||
  286. newStageY !== _camera.y
  287. ) {
  288. _lastCameraX = _camera.x;
  289. _lastCameraY = _camera.y;
  290. _lastCameraAngle = _camera.angle;
  291. _lastCameraRatio = _camera.ratio;
  292. _camera.goTo({
  293. x: newStageX,
  294. y: newStageY,
  295. angle: newStageAngle,
  296. ratio: newStageRatio
  297. });
  298. _self.dispatchEvent('drag');
  299. }
  300. break;
  301. }
  302. e.preventDefault();
  303. return false;
  304. }
  305. }
  306. /**
  307. * The handler listening to the double tap custom event. It will
  308. * basically zoom into the graph.
  309. *
  310. * @param {event} e A touch event.
  311. */
  312. function _doubleTapHandler(e) {
  313. var pos,
  314. ratio,
  315. animation;
  316. if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) {
  317. _doubleTap = true;
  318. ratio = 1 / _settings('doubleClickZoomingRatio');
  319. pos = position(e.touches[0]);
  320. _self.dispatchEvent('doubleclick',
  321. sigma.utils.mouseCoords(e, pos.x, pos.y));
  322. if (_settings('doubleClickEnabled')) {
  323. pos = _camera.cameraPosition(
  324. pos.x - sigma.utils.getCenter(e).x,
  325. pos.y - sigma.utils.getCenter(e).y,
  326. true
  327. );
  328. animation = {
  329. duration: _settings('doubleClickZoomDuration'),
  330. onComplete: function() {
  331. _doubleTap = false;
  332. }
  333. };
  334. sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
  335. }
  336. if (e.preventDefault)
  337. e.preventDefault();
  338. else
  339. e.returnValue = false;
  340. e.stopPropagation();
  341. return false;
  342. }
  343. }
  344. };
  345. }).call(this);