UNPKG

sigma

Version:

A JavaScript library aimed at visualizing graphs of thousands of nodes and edges.

284 lines (283 loc) 12.3 kB
"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;