;(function(undefined) {
|
|
'use strict';
|
|
|
|
if (typeof sigma === 'undefined')
|
|
throw 'sigma is not declared';
|
|
|
|
// Initialize packages:
|
|
sigma.utils.pkg('sigma.captors');
|
|
|
|
/**
|
|
* The user inputs default captor. It deals with mouse events, keyboards
|
|
* events and touch events.
|
|
*
|
|
* @param {DOMElement} target The DOM element where the listeners will be
|
|
* bound.
|
|
* @param {camera} camera The camera related to the target.
|
|
* @param {configurable} settings The settings function.
|
|
* @return {sigma.captor} The fresh new captor instance.
|
|
*/
|
|
sigma.captors.touch = function(target, camera, settings) {
|
|
var _self = this,
|
|
_target = target,
|
|
_camera = camera,
|
|
_settings = settings,
|
|
|
|
// CAMERA MANAGEMENT:
|
|
// ******************
|
|
// The camera position when the user starts dragging:
|
|
_startCameraX,
|
|
_startCameraY,
|
|
_startCameraAngle,
|
|
_startCameraRatio,
|
|
|
|
// The latest stage position:
|
|
_lastCameraX,
|
|
_lastCameraY,
|
|
_lastCameraAngle,
|
|
_lastCameraRatio,
|
|
|
|
// TOUCH MANAGEMENT:
|
|
// *****************
|
|
// Touches that are down:
|
|
_downTouches = [],
|
|
|
|
_startTouchX0,
|
|
_startTouchY0,
|
|
_startTouchX1,
|
|
_startTouchY1,
|
|
_startTouchAngle,
|
|
_startTouchDistance,
|
|
|
|
_touchMode,
|
|
|
|
_isMoving,
|
|
_doubleTap,
|
|
_movingTimeoutId;
|
|
|
|
sigma.classes.dispatcher.extend(this);
|
|
|
|
sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler);
|
|
_target.addEventListener('touchstart', _handleStart, false);
|
|
_target.addEventListener('touchend', _handleLeave, false);
|
|
_target.addEventListener('touchcancel', _handleLeave, false);
|
|
_target.addEventListener('touchleave', _handleLeave, false);
|
|
_target.addEventListener('touchmove', _handleMove, false);
|
|
|
|
function position(e) {
|
|
var offset = sigma.utils.getOffset(_target);
|
|
|
|
return {
|
|
x: e.pageX - offset.left,
|
|
y: e.pageY - offset.top
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method unbinds every handlers that makes the captor work.
|
|
*/
|
|
this.kill = function() {
|
|
sigma.utils.unbindDoubleClick(_target, 'touchstart');
|
|
_target.addEventListener('touchstart', _handleStart);
|
|
_target.addEventListener('touchend', _handleLeave);
|
|
_target.addEventListener('touchcancel', _handleLeave);
|
|
_target.addEventListener('touchleave', _handleLeave);
|
|
_target.addEventListener('touchmove', _handleMove);
|
|
};
|
|
|
|
// TOUCH EVENTS:
|
|
// *************
|
|
/**
|
|
* The handler listening to the 'touchstart' event. It will set the touch
|
|
* mode ("_touchMode") and start observing the user touch moves.
|
|
*
|
|
* @param {event} e A touch event.
|
|
*/
|
|
function _handleStart(e) {
|
|
if (_settings('touchEnabled')) {
|
|
var x0,
|
|
x1,
|
|
y0,
|
|
y1,
|
|
pos0,
|
|
pos1;
|
|
|
|
_downTouches = e.touches;
|
|
|
|
switch (_downTouches.length) {
|
|
case 1:
|
|
_camera.isMoving = true;
|
|
_touchMode = 1;
|
|
|
|
_startCameraX = _camera.x;
|
|
_startCameraY = _camera.y;
|
|
|
|
_lastCameraX = _camera.x;
|
|
_lastCameraY = _camera.y;
|
|
|
|
pos0 = position(_downTouches[0]);
|
|
_startTouchX0 = pos0.x;
|
|
_startTouchY0 = pos0.y;
|
|
|
|
break;
|
|
case 2:
|
|
_camera.isMoving = true;
|
|
_touchMode = 2;
|
|
|
|
pos0 = position(_downTouches[0]);
|
|
pos1 = position(_downTouches[1]);
|
|
x0 = pos0.x;
|
|
y0 = pos0.y;
|
|
x1 = pos1.x;
|
|
y1 = pos1.y;
|
|
|
|
_lastCameraX = _camera.x;
|
|
_lastCameraY = _camera.y;
|
|
|
|
_startCameraAngle = _camera.angle;
|
|
_startCameraRatio = _camera.ratio;
|
|
|
|
_startCameraX = _camera.x;
|
|
_startCameraY = _camera.y;
|
|
|
|
_startTouchX0 = x0;
|
|
_startTouchY0 = y0;
|
|
_startTouchX1 = x1;
|
|
_startTouchY1 = y1;
|
|
|
|
_startTouchAngle = Math.atan2(
|
|
_startTouchY1 - _startTouchY0,
|
|
_startTouchX1 - _startTouchX0
|
|
);
|
|
_startTouchDistance = Math.sqrt(
|
|
(_startTouchY1 - _startTouchY0) *
|
|
(_startTouchY1 - _startTouchY0) +
|
|
(_startTouchX1 - _startTouchX0) *
|
|
(_startTouchX1 - _startTouchX0)
|
|
);
|
|
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The handler listening to the 'touchend', 'touchcancel' and 'touchleave'
|
|
* event. It will update the touch mode if there are still at least one
|
|
* finger, and stop dragging else.
|
|
*
|
|
* @param {event} e A touch event.
|
|
*/
|
|
function _handleLeave(e) {
|
|
if (_settings('touchEnabled')) {
|
|
_downTouches = e.touches;
|
|
var inertiaRatio = _settings('touchInertiaRatio');
|
|
|
|
if (_movingTimeoutId) {
|
|
_isMoving = false;
|
|
clearTimeout(_movingTimeoutId);
|
|
}
|
|
|
|
switch (_touchMode) {
|
|
case 2:
|
|
if (e.touches.length === 1) {
|
|
_handleStart(e);
|
|
|
|
e.preventDefault();
|
|
break;
|
|
}
|
|
/* falls through */
|
|
case 1:
|
|
_camera.isMoving = false;
|
|
_self.dispatchEvent('stopDrag');
|
|
|
|
if (_isMoving) {
|
|
_doubleTap = false;
|
|
sigma.misc.animation.camera(
|
|
_camera,
|
|
{
|
|
x: _camera.x +
|
|
inertiaRatio * (_camera.x - _lastCameraX),
|
|
y: _camera.y +
|
|
inertiaRatio * (_camera.y - _lastCameraY)
|
|
},
|
|
{
|
|
easing: 'quadraticOut',
|
|
duration: _settings('touchInertiaDuration')
|
|
}
|
|
);
|
|
}
|
|
|
|
_isMoving = false;
|
|
_touchMode = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The handler listening to the 'touchmove' event. It will effectively drag
|
|
* the graph, and eventually zooms and turn it if the user is using two
|
|
* fingers.
|
|
*
|
|
* @param {event} e A touch event.
|
|
*/
|
|
function _handleMove(e) {
|
|
if (!_doubleTap && _settings('touchEnabled')) {
|
|
var x0,
|
|
x1,
|
|
y0,
|
|
y1,
|
|
cos,
|
|
sin,
|
|
end,
|
|
pos0,
|
|
pos1,
|
|
diff,
|
|
start,
|
|
dAngle,
|
|
dRatio,
|
|
newStageX,
|
|
newStageY,
|
|
newStageRatio,
|
|
newStageAngle;
|
|
|
|
_downTouches = e.touches;
|
|
_isMoving = true;
|
|
|
|
if (_movingTimeoutId)
|
|
clearTimeout(_movingTimeoutId);
|
|
|
|
_movingTimeoutId = setTimeout(function() {
|
|
_isMoving = false;
|
|
}, _settings('dragTimeout'));
|
|
|
|
switch (_touchMode) {
|
|
case 1:
|
|
pos0 = position(_downTouches[0]);
|
|
x0 = pos0.x;
|
|
y0 = pos0.y;
|
|
|
|
diff = _camera.cameraPosition(
|
|
x0 - _startTouchX0,
|
|
y0 - _startTouchY0,
|
|
true
|
|
);
|
|
|
|
newStageX = _startCameraX - diff.x;
|
|
newStageY = _startCameraY - diff.y;
|
|
|
|
if (newStageX !== _camera.x || newStageY !== _camera.y) {
|
|
_lastCameraX = _camera.x;
|
|
_lastCameraY = _camera.y;
|
|
|
|
_camera.goTo({
|
|
x: newStageX,
|
|
y: newStageY
|
|
});
|
|
|
|
_self.dispatchEvent('mousemove',
|
|
sigma.utils.mouseCoords(e, pos0.x, pos0.y));
|
|
|
|
_self.dispatchEvent('drag');
|
|
}
|
|
break;
|
|
case 2:
|
|
pos0 = position(_downTouches[0]);
|
|
pos1 = position(_downTouches[1]);
|
|
x0 = pos0.x;
|
|
y0 = pos0.y;
|
|
x1 = pos1.x;
|
|
y1 = pos1.y;
|
|
|
|
start = _camera.cameraPosition(
|
|
(_startTouchX0 + _startTouchX1) / 2 -
|
|
sigma.utils.getCenter(e).x,
|
|
(_startTouchY0 + _startTouchY1) / 2 -
|
|
sigma.utils.getCenter(e).y,
|
|
true
|
|
);
|
|
end = _camera.cameraPosition(
|
|
(x0 + x1) / 2 - sigma.utils.getCenter(e).x,
|
|
(y0 + y1) / 2 - sigma.utils.getCenter(e).y,
|
|
true
|
|
);
|
|
|
|
dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle;
|
|
dRatio = Math.sqrt(
|
|
(y1 - y0) * (y1 - y0) + (x1 - x0) * (x1 - x0)
|
|
) / _startTouchDistance;
|
|
|
|
// Translation:
|
|
x0 = start.x;
|
|
y0 = start.y;
|
|
|
|
// Homothetic transformation:
|
|
newStageRatio = _startCameraRatio / dRatio;
|
|
x0 = x0 * dRatio;
|
|
y0 = y0 * dRatio;
|
|
|
|
// Rotation:
|
|
newStageAngle = _startCameraAngle - dAngle;
|
|
cos = Math.cos(-dAngle);
|
|
sin = Math.sin(-dAngle);
|
|
x1 = x0 * cos + y0 * sin;
|
|
y1 = y0 * cos - x0 * sin;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
|
|
// Finalize:
|
|
newStageX = x0 - end.x + _startCameraX;
|
|
newStageY = y0 - end.y + _startCameraY;
|
|
|
|
if (
|
|
newStageRatio !== _camera.ratio ||
|
|
newStageAngle !== _camera.angle ||
|
|
newStageX !== _camera.x ||
|
|
newStageY !== _camera.y
|
|
) {
|
|
_lastCameraX = _camera.x;
|
|
_lastCameraY = _camera.y;
|
|
_lastCameraAngle = _camera.angle;
|
|
_lastCameraRatio = _camera.ratio;
|
|
|
|
_camera.goTo({
|
|
x: newStageX,
|
|
y: newStageY,
|
|
angle: newStageAngle,
|
|
ratio: newStageRatio
|
|
});
|
|
|
|
_self.dispatchEvent('drag');
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The handler listening to the double tap custom event. It will
|
|
* basically zoom into the graph.
|
|
*
|
|
* @param {event} e A touch event.
|
|
*/
|
|
function _doubleTapHandler(e) {
|
|
var pos,
|
|
ratio,
|
|
animation;
|
|
|
|
if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) {
|
|
_doubleTap = true;
|
|
|
|
ratio = 1 / _settings('doubleClickZoomingRatio');
|
|
|
|
pos = position(e.touches[0]);
|
|
_self.dispatchEvent('doubleclick',
|
|
sigma.utils.mouseCoords(e, pos.x, pos.y));
|
|
|
|
if (_settings('doubleClickEnabled')) {
|
|
pos = _camera.cameraPosition(
|
|
pos.x - sigma.utils.getCenter(e).x,
|
|
pos.y - sigma.utils.getCenter(e).y,
|
|
true
|
|
);
|
|
|
|
animation = {
|
|
duration: _settings('doubleClickZoomDuration'),
|
|
onComplete: function() {
|
|
_doubleTap = false;
|
|
}
|
|
};
|
|
|
|
sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation);
|
|
}
|
|
|
|
if (e.preventDefault)
|
|
e.preventDefault();
|
|
else
|
|
e.returnValue = false;
|
|
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
}).call(this);
|