UNPKG

sigma

Version:

A JavaScript library dedicated to graph drawing.

381 lines (380 loc) 13.5 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 __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; 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 }); /** * Sigma.js Camera Class * ====================== * * Class designed to store camera information & used to update it. * @module */ var events_1 = require("events"); var animate_1 = require("../utils/animate"); var easings_1 = __importDefault(require("../utils/easings")); var utils_1 = require("../utils"); /** * Defaults. */ var DEFAULT_ZOOMING_RATIO = 1.5; // TODO: animate options = number polymorphism? // TODO: pan, zoom, unzoom, reset, rotate, zoomTo // TODO: add width / height to camera and add #.resize // TODO: bind camera to renderer rather than sigma // TODO: add #.graphToDisplay, #.displayToGraph, batch methods later /** * Camera class * * @constructor */ var Camera = /** @class */ (function (_super) { __extends(Camera, _super); function Camera() { var _this = _super.call(this) || this; _this.x = 0.5; _this.y = 0.5; _this.angle = 0; _this.ratio = 1; _this.nextFrame = null; _this.enabled = true; // State _this.previousState = _this.getState(); return _this; } /** * Static method used to create a Camera object with a given state. * * @param state * @return {Camera} */ Camera.from = function (state) { var camera = new Camera(); return camera.setState(state); }; /** * Method used to enable the camera. * * @return {Camera} */ Camera.prototype.enable = function () { this.enabled = true; return this; }; /** * Method used to disable the camera. * * @return {Camera} */ Camera.prototype.disable = function () { this.enabled = false; return this; }; /** * Method used to retrieve the camera's current state. * * @return {object} */ Camera.prototype.getState = function () { return { x: this.x, y: this.y, angle: this.angle, ratio: this.ratio, }; }; /** * Method used to retrieve the camera's previous state. * * @return {object} */ Camera.prototype.getPreviousState = function () { var state = this.previousState; return { x: state.x, y: state.y, angle: state.angle, ratio: state.ratio, }; }; /** * Method used to check whether the camera is currently being animated. * * @return {boolean} */ Camera.prototype.isAnimated = function () { return !!this.nextFrame; }; /** * Method returning the coordinates of a point from the framed graph system to the * viewport system. * * @param {object} dimensions - Dimensions of the viewport. * @param {object} coordinates - Coordinates of the point. * @return {object} - The point coordinates in the viewport. */ Camera.prototype.framedGraphToViewport = function (dimensions, coordinates) { var smallestDimension = Math.min(dimensions.width, dimensions.height); var dx = smallestDimension / dimensions.width; var dy = smallestDimension / dimensions.height; var ratio = this.ratio / smallestDimension; // Align with center of the graph: var x1 = (coordinates.x - this.x) / ratio; var y1 = (this.y - coordinates.y) / ratio; // Rotate: var x2 = x1 * Math.cos(this.angle) - y1 * Math.sin(this.angle); var y2 = y1 * Math.cos(this.angle) + x1 * Math.sin(this.angle); return { // Translate to center of screen x: x2 + smallestDimension / 2 / dx, y: y2 + smallestDimension / 2 / dy, }; }; /** * Method returning the coordinates of a point from the viewport system to the * framed graph system. * * @param {object} dimensions - Dimensions of the viewport. * @param {object} coordinates - Coordinates of the point. * @return {object} - The point coordinates in the graph frame. */ Camera.prototype.viewportToFramedGraph = function (dimensions, coordinates) { var _a; var smallestDimension = Math.min(dimensions.width, dimensions.height); var dx = smallestDimension / dimensions.width; var dy = smallestDimension / dimensions.height; var ratio = this.ratio / smallestDimension; // Align with center of the graph: var x = coordinates.x - smallestDimension / 2 / dx; var y = coordinates.y - smallestDimension / 2 / dy; // Rotate: _a = __read([ x * Math.cos(-this.angle) - y * Math.sin(-this.angle), y * Math.cos(-this.angle) + x * Math.sin(-this.angle), ], 2), x = _a[0], y = _a[1]; return { x: x * ratio + this.x, y: -y * ratio + this.y, }; }; /** * Method returning the abstract rectangle containing the graph according * to the camera's state. * * @return {object} - The view's rectangle. */ Camera.prototype.viewRectangle = function (dimensions) { // TODO: reduce relative margin? var marginX = (0 * dimensions.width) / 8, marginY = (0 * dimensions.height) / 8; var p1 = this.viewportToFramedGraph(dimensions, { x: 0 - marginX, y: 0 - marginY }), p2 = this.viewportToFramedGraph(dimensions, { x: dimensions.width + marginX, y: 0 - marginY }), h = this.viewportToFramedGraph(dimensions, { x: 0, y: dimensions.height + marginY }); return { x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, height: p2.y - h.y, }; }; /** * Method used to set the camera's state. * * @param {object} state - New state. * @return {Camera} */ Camera.prototype.setState = function (state) { if (!this.enabled) return this; // TODO: validations // TODO: update by function // Keeping track of last state this.previousState = this.getState(); if (state.x) this.x = state.x; if (state.y) this.y = state.y; if (state.angle) this.angle = state.angle; if (state.ratio) this.ratio = state.ratio; // Emitting // TODO: don't emit if nothing changed? this.emit("updated", this.getState()); return this; }; /** * Method used to (un)zoom, while preserving the position of a viewport point. * Used for instance to * * @param viewportTarget * @param dimensions * @param ratio * @return {CameraState} */ Camera.prototype.getViewportZoomedState = function (viewportTarget, dimensions, ratio) { // TODO: handle max zoom var ratioDiff = ratio / this.ratio; var center = { x: dimensions.width / 2, y: dimensions.height / 2, }; var graphMousePosition = this.viewportToFramedGraph(dimensions, viewportTarget); var graphCenterPosition = this.viewportToFramedGraph(dimensions, center); return __assign(__assign({}, this.getState()), { x: (graphMousePosition.x - graphCenterPosition.x) * (1 - ratioDiff) + this.x, y: (graphMousePosition.y - graphCenterPosition.y) * (1 - ratioDiff) + this.y, ratio: ratio }); }; /** * Method used to animate the camera. * * @param {object} state - State to reach eventually. * @param {object} opts - Options: * @param {number} duration - Duration of the animation. * @param {string | number => number} easing - Easing function or name of an existing one * @param {function} callback - Callback */ Camera.prototype.animate = function (state, opts, callback) { var _this = this; if (!this.enabled) return; var options = Object.assign({}, animate_1.ANIMATE_DEFAULTS, opts); var easing = typeof options.easing === "function" ? options.easing : easings_1.default[options.easing]; // State var start = Date.now(), initialState = this.getState(); // Function performing the animation var fn = function () { var t = (Date.now() - start) / options.duration; // The animation is over: if (t >= 1) { _this.nextFrame = null; _this.setState(state); if (_this.animationCallback) { _this.animationCallback.call(null); _this.animationCallback = undefined; } return; } var coefficient = easing(t); var newState = {}; if (state.x) newState.x = initialState.x + (state.x - initialState.x) * coefficient; if (state.y) newState.y = initialState.y + (state.y - initialState.y) * coefficient; if (state.angle) newState.angle = initialState.angle + (state.angle - initialState.angle) * coefficient; if (state.ratio) newState.ratio = initialState.ratio + (state.ratio - initialState.ratio) * coefficient; _this.setState(newState); _this.nextFrame = utils_1.requestFrame(fn); }; if (this.nextFrame) { utils_1.cancelFrame(this.nextFrame); if (this.animationCallback) this.animationCallback.call(null); this.nextFrame = utils_1.requestFrame(fn); } else { fn(); } this.animationCallback = callback; }; /** * Method used to zoom the camera. * * @param {number|object} factorOrOptions - Factor or options. * @return {function} */ Camera.prototype.animatedZoom = function (factorOrOptions) { if (!factorOrOptions) { this.animate({ ratio: this.ratio / DEFAULT_ZOOMING_RATIO }); } else { if (typeof factorOrOptions === "number") return this.animate({ ratio: this.ratio / factorOrOptions }); else this.animate({ ratio: this.ratio / (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO), }, factorOrOptions); } }; /** * Method used to unzoom the camera. * * @param {number|object} factorOrOptions - Factor or options. */ Camera.prototype.animatedUnzoom = function (factorOrOptions) { if (!factorOrOptions) { this.animate({ ratio: this.ratio * DEFAULT_ZOOMING_RATIO }); } else { if (typeof factorOrOptions === "number") return this.animate({ ratio: this.ratio * factorOrOptions }); else this.animate({ ratio: this.ratio * (factorOrOptions.factor || DEFAULT_ZOOMING_RATIO), }, factorOrOptions); } }; /** * Method used to reset the camera. * * @param {object} options - Options. */ Camera.prototype.animatedReset = function (options) { this.animate({ x: 0.5, y: 0.5, ratio: 1, angle: 0, }, options); }; /** * Returns a new Camera instance, with the same state as the current camera. * * @return {Camera} */ Camera.prototype.copy = function () { return Camera.from(this.getState()); }; return Camera; }(events_1.EventEmitter)); exports.default = Camera;