sigma
Version:
A JavaScript library aimed at visualizing graphs of thousands of nodes and edges.
284 lines (283 loc) • 12.3 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
var captor_1 = __importStar(require("./captor"));
/**
* Constants.
*/
var DRAG_TIMEOUT = 100;
var DRAGGED_EVENTS_TOLERANCE = 3;
var MOUSE_INERTIA_DURATION = 200;
var MOUSE_INERTIA_RATIO = 3;
var MOUSE_ZOOM_DURATION = 250;
var ZOOMING_RATIO = 1.7;
var DOUBLE_CLICK_TIMEOUT = 300;
var DOUBLE_CLICK_ZOOMING_RATIO = 2.2;
var DOUBLE_CLICK_ZOOMING_DURATION = 200;
/**
* Mouse captor class.
*
* @constructor
*/
var MouseCaptor = /** @class */ (function (_super) {
__extends(MouseCaptor, _super);
function MouseCaptor(container, renderer) {
var _this = _super.call(this, container, renderer) || this;
// State
_this.enabled = true;
_this.draggedEvents = 0;
_this.downStartTime = null;
_this.lastMouseX = null;
_this.lastMouseY = null;
_this.isMouseDown = false;
_this.isMoving = false;
_this.movingTimeout = null;
_this.startCameraState = null;
_this.clicks = 0;
_this.doubleClickTimeout = null;
_this.currentWheelDirection = 0;
// Binding methods
_this.handleClick = _this.handleClick.bind(_this);
_this.handleRightClick = _this.handleRightClick.bind(_this);
_this.handleDown = _this.handleDown.bind(_this);
_this.handleUp = _this.handleUp.bind(_this);
_this.handleMove = _this.handleMove.bind(_this);
_this.handleWheel = _this.handleWheel.bind(_this);
_this.handleOut = _this.handleOut.bind(_this);
// Binding events
container.addEventListener("click", _this.handleClick, false);
container.addEventListener("contextmenu", _this.handleRightClick, false);
container.addEventListener("mousedown", _this.handleDown, false);
container.addEventListener("wheel", _this.handleWheel, false);
container.addEventListener("mouseout", _this.handleOut, false);
document.addEventListener("mousemove", _this.handleMove, false);
document.addEventListener("mouseup", _this.handleUp, false);
return _this;
}
MouseCaptor.prototype.kill = function () {
var container = this.container;
container.removeEventListener("click", this.handleClick);
container.removeEventListener("contextmenu", this.handleRightClick);
container.removeEventListener("mousedown", this.handleDown);
container.removeEventListener("wheel", this.handleWheel);
container.removeEventListener("mouseout", this.handleOut);
document.removeEventListener("mousemove", this.handleMove);
document.removeEventListener("mouseup", this.handleUp);
};
MouseCaptor.prototype.handleClick = function (e) {
var _this = this;
if (!this.enabled)
return;
this.clicks++;
if (this.clicks === 2) {
this.clicks = 0;
if (typeof this.doubleClickTimeout === "number") {
clearTimeout(this.doubleClickTimeout);
this.doubleClickTimeout = null;
}
return this.handleDoubleClick(e);
}
setTimeout(function () {
_this.clicks = 0;
_this.doubleClickTimeout = null;
}, DOUBLE_CLICK_TIMEOUT);
// NOTE: this is here to prevent click events on drag
if (this.draggedEvents < DRAGGED_EVENTS_TOLERANCE)
this.emit("click", (0, captor_1.getMouseCoords)(e, this.container));
};
MouseCaptor.prototype.handleRightClick = function (e) {
if (!this.enabled)
return;
this.emit("rightClick", (0, captor_1.getMouseCoords)(e, this.container));
};
MouseCaptor.prototype.handleDoubleClick = function (e) {
if (!this.enabled)
return;
e.preventDefault();
e.stopPropagation();
var mouseCoords = (0, captor_1.getMouseCoords)(e, this.container);
this.emit("doubleClick", mouseCoords);
if (mouseCoords.sigmaDefaultPrevented)
return;
// default behavior
var camera = this.renderer.getCamera();
var newRatio = camera.getBoundedRatio(camera.getState().ratio / DOUBLE_CLICK_ZOOMING_RATIO);
camera.animate(this.renderer.getViewportZoomedState((0, captor_1.getPosition)(e, this.container), newRatio), {
easing: "quadraticInOut",
duration: DOUBLE_CLICK_ZOOMING_DURATION,
});
};
MouseCaptor.prototype.handleDown = function (e) {
if (!this.enabled)
return;
// We only start dragging on left button
if (e.button === 0) {
this.startCameraState = this.renderer.getCamera().getState();
var _a = (0, captor_1.getPosition)(e, this.container), x = _a.x, y = _a.y;
this.lastMouseX = x;
this.lastMouseY = y;
this.draggedEvents = 0;
this.downStartTime = Date.now();
this.isMouseDown = true;
}
this.emit("mousedown", (0, captor_1.getMouseCoords)(e, this.container));
};
MouseCaptor.prototype.handleUp = function (e) {
var _this = this;
if (!this.enabled || !this.isMouseDown)
return;
var camera = this.renderer.getCamera();
this.isMouseDown = false;
if (typeof this.movingTimeout === "number") {
clearTimeout(this.movingTimeout);
this.movingTimeout = null;
}
var _a = (0, captor_1.getPosition)(e, this.container), x = _a.x, y = _a.y;
var cameraState = camera.getState(), previousCameraState = camera.getPreviousState() || { x: 0, y: 0 };
if (this.isMoving) {
camera.animate({
x: cameraState.x + MOUSE_INERTIA_RATIO * (cameraState.x - previousCameraState.x),
y: cameraState.y + MOUSE_INERTIA_RATIO * (cameraState.y - previousCameraState.y),
}, {
duration: MOUSE_INERTIA_DURATION,
easing: "quadraticOut",
});
}
else if (this.lastMouseX !== x || this.lastMouseY !== y) {
camera.setState({
x: cameraState.x,
y: cameraState.y,
});
}
this.isMoving = false;
setTimeout(function () {
_this.draggedEvents = 0;
// NOTE: this refresh is here to make sure `hideEdgesOnMove` can work
// when someone releases camera pan drag after having stopped moving.
// See commit: https://github.com/jacomyal/sigma.js/commit/cfd9197f70319109db6b675dd7c82be493ca95a2
// See also issue: https://github.com/jacomyal/sigma.js/issues/1290
// It could be possible to render instead of scheduling a refresh but for
// now it seems good enough.
_this.renderer.refresh();
}, 0);
this.emit("mouseup", (0, captor_1.getMouseCoords)(e, this.container));
};
MouseCaptor.prototype.handleMove = function (e) {
var _this = this;
if (!this.enabled)
return;
var mouseCoords = (0, captor_1.getMouseCoords)(e, this.container);
// Always trigger a "mousemovebody" event, so that it is possible to develop
// a drag-and-drop effect that works even when the mouse is out of the
// container:
this.emit("mousemovebody", mouseCoords);
// Only trigger the "mousemove" event when the mouse is actually hovering
// the container, to avoid weirdly hovering nodes and/or edges when the
// mouse is not hover the container:
if (e.target === this.container) {
this.emit("mousemove", mouseCoords);
}
if (mouseCoords.sigmaDefaultPrevented)
return;
// Handle the case when "isMouseDown" all the time, to allow dragging the
// stage while the mouse is not hover the container:
if (this.isMouseDown) {
this.isMoving = true;
this.draggedEvents++;
if (typeof this.movingTimeout === "number") {
clearTimeout(this.movingTimeout);
}
this.movingTimeout = window.setTimeout(function () {
_this.movingTimeout = null;
_this.isMoving = false;
}, DRAG_TIMEOUT);
var camera = this.renderer.getCamera();
var _a = (0, captor_1.getPosition)(e, this.container), eX = _a.x, eY = _a.y;
var lastMouse = this.renderer.viewportToFramedGraph({
x: this.lastMouseX,
y: this.lastMouseY,
});
var mouse = this.renderer.viewportToFramedGraph({ x: eX, y: eY });
var offsetX = lastMouse.x - mouse.x, offsetY = lastMouse.y - mouse.y;
var cameraState = camera.getState();
var x = cameraState.x + offsetX, y = cameraState.y + offsetY;
camera.setState({ x: x, y: y });
this.lastMouseX = eX;
this.lastMouseY = eY;
e.preventDefault();
e.stopPropagation();
}
};
MouseCaptor.prototype.handleWheel = function (e) {
var _this = this;
if (!this.enabled)
return;
e.preventDefault();
e.stopPropagation();
var delta = (0, captor_1.getWheelDelta)(e);
if (!delta)
return;
var wheelCoords = (0, captor_1.getWheelCoords)(e, this.container);
this.emit("wheel", wheelCoords);
if (wheelCoords.sigmaDefaultPrevented)
return;
// Default behavior
var ratioDiff = delta > 0 ? 1 / ZOOMING_RATIO : ZOOMING_RATIO;
var camera = this.renderer.getCamera();
var newRatio = camera.getBoundedRatio(camera.getState().ratio * ratioDiff);
var wheelDirection = delta > 0 ? 1 : -1;
var now = Date.now();
// Cancel events that are too close too each other and in the same direction:
if (this.currentWheelDirection === wheelDirection &&
this.lastWheelTriggerTime &&
now - this.lastWheelTriggerTime < MOUSE_ZOOM_DURATION / 5) {
return;
}
camera.animate(this.renderer.getViewportZoomedState((0, captor_1.getPosition)(e, this.container), newRatio), {
easing: "quadraticOut",
duration: MOUSE_ZOOM_DURATION,
}, function () {
_this.currentWheelDirection = 0;
});
this.currentWheelDirection = wheelDirection;
this.lastWheelTriggerTime = now;
};
MouseCaptor.prototype.handleOut = function () {
// TODO: dispatch event
};
return MouseCaptor;
}(captor_1.default));
exports.default = MouseCaptor;