sigma
Version:
A JavaScript library dedicated to graph drawing.
245 lines (244 loc) • 11.4 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var captor_1 = __importStar(require("./captor"));
var camera_1 = __importDefault(require("../camera"));
var DRAG_TIMEOUT = 200;
var TOUCH_INERTIA_RATIO = 3;
var TOUCH_INERTIA_DURATION = 200;
/**
* Touch captor class.
*
* @constructor
*/
var TouchCaptor = /** @class */ (function (_super) {
__extends(TouchCaptor, _super);
function TouchCaptor(container, camera) {
var _this = _super.call(this, container, camera) || this;
_this.enabled = true;
_this.isMoving = false;
_this.touchMode = 0; // number of touches down
// Binding methods:
_this.handleStart = _this.handleStart.bind(_this);
_this.handleLeave = _this.handleLeave.bind(_this);
_this.handleMove = _this.handleMove.bind(_this);
// Binding events
container.addEventListener("touchstart", _this.handleStart, false);
container.addEventListener("touchend", _this.handleLeave, false);
container.addEventListener("touchcancel", _this.handleLeave, false);
container.addEventListener("touchmove", _this.handleMove, false);
return _this;
}
TouchCaptor.prototype.kill = function () {
var container = this.container;
container.removeEventListener("touchstart", this.handleStart);
container.removeEventListener("touchend", this.handleLeave);
container.removeEventListener("touchcancel", this.handleLeave);
container.removeEventListener("touchmove", this.handleMove);
};
TouchCaptor.prototype.getDimensions = function () {
return {
width: this.container.offsetWidth,
height: this.container.offsetHeight,
};
};
TouchCaptor.prototype.dispatchRelatedMouseEvent = function (type, e, position, emitter) {
var mousePosition = position || captor_1.getPosition(e.touches[0]);
var mouseEvent = new MouseEvent(type, {
clientX: mousePosition.x,
clientY: mousePosition.y,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
});
(emitter || this.container).dispatchEvent(mouseEvent);
};
TouchCaptor.prototype.handleStart = function (e) {
if (!this.enabled)
return;
// Prevent default to avoid default browser behaviors...
e.preventDefault();
// ...but simulate mouse behavior anyway, to get the MouseCaptor working as well:
if (e.touches.length === 1)
this.dispatchRelatedMouseEvent("mousedown", e);
var touches = captor_1.getTouchesArray(e.touches);
this.isMoving = true;
this.touchMode = touches.length;
this.startCameraState = this.camera.getState();
this.startTouchesPositions = touches.map(captor_1.getPosition);
this.lastTouchesPositions = this.startTouchesPositions;
// When there are two touches down, let's record distance and angle as well:
if (this.touchMode === 2) {
var _a = __read(this.startTouchesPositions, 2), _b = _a[0], x0 = _b.x, y0 = _b.y, _c = _a[1], x1 = _c.x, y1 = _c.y;
this.startTouchesAngle = Math.atan2(y1 - y0, x1 - x0);
this.startTouchesDistance = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2));
}
this.emit("touchdown", captor_1.getTouchCoords(e));
};
TouchCaptor.prototype.handleLeave = function (e) {
if (!this.enabled)
return;
// Prevent default to avoid default browser behaviors...
e.preventDefault();
// ...but simulate mouse behavior anyway, to get the MouseCaptor working as well:
if (e.touches.length === 0 && this.lastTouchesPositions && this.lastTouchesPositions.length) {
this.dispatchRelatedMouseEvent("mouseup", e, this.lastTouchesPositions[0], document);
this.dispatchRelatedMouseEvent("click", e, this.lastTouchesPositions[0]);
}
if (this.movingTimeout) {
this.isMoving = false;
clearTimeout(this.movingTimeout);
}
switch (this.touchMode) {
case 2:
if (e.touches.length === 1) {
this.handleStart(e);
e.preventDefault();
break;
}
/* falls through */
case 1:
// TODO
// Dispatch event
if (this.isMoving) {
var cameraState = this.camera.getState(), previousCameraState = this.camera.getPreviousState();
this.camera.animate({
x: cameraState.x + TOUCH_INERTIA_RATIO * (cameraState.x - previousCameraState.x),
y: cameraState.y + TOUCH_INERTIA_RATIO * (cameraState.y - previousCameraState.y),
}, {
duration: TOUCH_INERTIA_DURATION,
easing: "quadraticOut",
});
}
this.isMoving = false;
this.touchMode = 0;
break;
}
this.emit("touchup", captor_1.getTouchCoords(e));
};
TouchCaptor.prototype.handleMove = function (e) {
var _a;
var _this = this;
if (!this.enabled)
return;
// Prevent default to avoid default browser behaviors...
e.preventDefault();
// ...but simulate mouse behavior anyway, to get the MouseCaptor working as well:
if (e.touches.length === 1)
this.dispatchRelatedMouseEvent("mousemove", e);
var startCameraState = this.startCameraState;
var touches = captor_1.getTouchesArray(e.touches);
var touchesPositions = touches.map(captor_1.getPosition);
this.lastTouchesPositions = touchesPositions;
this.isMoving = true;
if (this.movingTimeout)
clearTimeout(this.movingTimeout);
this.movingTimeout = window.setTimeout(function () {
_this.isMoving = false;
}, DRAG_TIMEOUT);
switch (this.touchMode) {
case 1: {
var _b = this.camera.viewportToFramedGraph(this.getDimensions(), (this.startTouchesPositions || [])[0]), xStart = _b.x, yStart = _b.y;
var _c = this.camera.viewportToFramedGraph(this.getDimensions(), touchesPositions[0]), x = _c.x, y = _c.y;
this.camera.setState({
x: startCameraState.x + xStart - x,
y: startCameraState.y + yStart - y,
});
break;
}
case 2: {
/**
* Here is the thinking here:
*
* 1. We can find the new angle and ratio, by comparing the vector from "touch one" to "touch two" at the start
* of the d'n'd and now
*
* 2. We can use `Camera#viewportToGraph` inside formula to retrieve the new camera position, using the graph
* position of a touch at the beginning of the d'n'd (using `startCamera.viewportToGraph`) and the viewport
* position of this same touch now
*/
var newCameraState = {};
var _d = touchesPositions[0], x0 = _d.x, y0 = _d.y;
var _e = touchesPositions[1], x1 = _e.x, y1 = _e.y;
var angleDiff = Math.atan2(y1 - y0, x1 - x0) - this.startTouchesAngle;
var ratioDiff = Math.hypot(y1 - y0, x1 - x0) / this.startTouchesDistance;
// 1.
newCameraState.ratio = startCameraState.ratio / ratioDiff;
newCameraState.angle = startCameraState.angle + angleDiff;
// 2.
var dimensions = this.getDimensions();
var touchGraphPosition = camera_1.default.from(startCameraState).viewportToFramedGraph(dimensions, (this.startTouchesPositions || [])[0]);
var smallestDimension = Math.min(dimensions.width, dimensions.height);
var dx = smallestDimension / dimensions.width;
var dy = smallestDimension / dimensions.height;
var ratio = newCameraState.ratio / smallestDimension;
// Align with center of the graph:
var x = x0 - smallestDimension / 2 / dx;
var y = y0 - smallestDimension / 2 / dy;
// Rotate:
_a = __read([
x * Math.cos(-newCameraState.angle) - y * Math.sin(-newCameraState.angle),
y * Math.cos(-newCameraState.angle) + x * Math.sin(-newCameraState.angle),
], 2), x = _a[0], y = _a[1];
newCameraState.x = touchGraphPosition.x - x * ratio;
newCameraState.y = touchGraphPosition.y + y * ratio;
this.camera.setState(newCameraState);
break;
}
}
this.emit("touchmove", captor_1.getTouchCoords(e));
};
return TouchCaptor;
}(captor_1.default));
exports.default = TouchCaptor;