UNPKG

gojs

Version:

Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams

168 lines (157 loc) 6.62 kB
"use strict"; /* * Copyright (C) 1998-2020 by Northwoods Software Corporation. All Rights Reserved. */ /* * This is an extension and not part of the main GoJS library. * Note that the API for this class may change with any version, even point releases. * If you intend to use an extension in production, you should copy the code to your own source directory. * Extensions can be found in the GoJS kit under the extensions or extensionsTS folders. * See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information. */ /** * @constructor * @extends RotatingTool * @class * A custom tool for rotating multiple objects at a time. When more than one * part is selected, rotates all parts, revolving them about their collective center. * If the control key is held down during rotation, rotates all parts individually. * <p> * Caution: this only works for Groups that do *not* have a Placeholder. */ function RotateMultipleTool() { go.RotatingTool.call(this); this.name = "RotateMultiple"; // holds references to all selected non-Link Parts and their offset & angles this._initialInfo = null; // initial angle when rotating as a whole this._initialAngle = 0; // rotation point of selection this._centerPoint = null; } go.Diagram.inherit(RotateMultipleTool, go.RotatingTool); /** * Calls RotatingTool.doActivate, and then remembers the center point of the collection, * and the initial distances and angles of selected parts to the center. * @this {RotateMultipleTool} */ RotateMultipleTool.prototype.doActivate = function() { go.RotatingTool.prototype.doActivate.call(this); var diagram = this.diagram; // center point of the collection this._centerPoint = diagram.computePartsBounds(diagram.selection).center; // remember the angle relative to the center point when rotating the whole collection this._initialAngle = this._centerPoint.directionPoint(diagram.lastInput.documentPoint); // remember initial angle and distance for each Part var infos = new go.Map(/*go.Part, PartInfo*/); var tool = this; diagram.selection.each(function(part) { tool.walkTree(part, infos); }); this._initialInfo = infos; } /** * @ignore * @param {Part} part * @param {Map} infos */ RotateMultipleTool.prototype.walkTree = function(part, infos) { if (part === null || part instanceof go.Link) return; // distance from _centerPoint to locationSpot of part var dist = Math.sqrt(this._centerPoint.distanceSquaredPoint(part.location)); // calculate initial relative angle var dir = this._centerPoint.directionPoint(part.location); // saves part-angle combination in array infos.add(part, new PartInfo(dir, dist, part.rotateObject.angle)); // recurse into Groups if (part instanceof go.Group) { var it = part.memberParts.iterator; while (it.next()) this.walkTree(it.value, infos); } }; /** * @ignore * Internal class that remembers a Part's offset & angle. */ function PartInfo(placementAngle, distance, rotationAngle) { this.placementAngle = placementAngle * (Math.PI / 180); // in radians this.distance = distance; this.rotationAngle = rotationAngle; // in degrees } /** * Clean up any references to Parts. * @this {RotateMultipleTool} */ RotateMultipleTool.prototype.doDeactivate = function() { this._initialInfo = null; go.RotatingTool.prototype.doDeactivate.call(this); }; /** * Overrides rotatingTool.rotate to rotate all selected objects about their collective center. * When the control key is held down while rotating, all selected objects are rotated individually. * @this {RotateMultipleTool} * @param {number} newangle */ RotateMultipleTool.prototype.rotate = function(newangle) { var diagram = this.diagram; var e = diagram.lastInput; // when rotating individual parts, remember the original angle difference var angleDiff = newangle - this.adornedObject.part.rotateObject.angle; var tool = this; this._initialInfo.each(function(kvp) { var part = kvp.key; if (part instanceof go.Link) return; // only Nodes and simple Parts var partInfo = kvp.value; // rotate every selected non-Link Part // find information about the part set in RotateMultipleTool.initialInformation if (e.control || e.meta) { if (tool.adornedObject.part === part) { part.rotateObject.angle = newangle; } else { part.rotateObject.angle += angleDiff; } } else { var radAngle = newangle * (Math.PI / 180); // converts the angle traveled from degrees to radians // calculate the part's x-y location relative to the central rotation point var offsetX = partInfo.distance * Math.cos(radAngle + partInfo.placementAngle); var offsetY = partInfo.distance * Math.sin(radAngle + partInfo.placementAngle); // move part part.location = new go.Point(tool._centerPoint.x + offsetX, tool._centerPoint.y + offsetY); // rotate part part.rotateObject.angle = partInfo.rotationAngle + newangle; } }); } /** * This override needs to calculate the desired angle with different rotation points, * depending on whether we are rotating the whole selection as one, or Parts individually. * @this {RotateMultipleTool} * @param {Point} newPoint in document coordinates */ RotateMultipleTool.prototype.computeRotate = function(newPoint) { var diagram = this.diagram; var angle; var e = diagram.lastInput; if (e.control || e.meta) { // relative to the center of the Node whose handle we are rotating var part = this.adornedObject.part; var rotationPoint = part.getDocumentPoint(part.locationSpot); angle = rotationPoint.directionPoint(newPoint); } else { // relative to the center of the whole selection angle = this._centerPoint.directionPoint(newPoint) - this._initialAngle; } if (angle >= 360) angle -= 360; else if (angle < 0) angle += 360; var interval = Math.min(Math.abs(this.snapAngleMultiple), 180); var epsilon = Math.min(Math.abs(this.snapAngleEpsilon), interval / 2); // if it's close to a multiple of INTERVAL degrees, make it exactly so if (!diagram.lastInput.shift && interval > 0 && epsilon > 0) { if (angle % interval < epsilon) { angle = Math.floor(angle / interval) * interval; } else if (angle % interval > interval - epsilon) { angle = (Math.floor(angle / interval) + 1) * interval; } if (angle >= 360) angle -= 360; else if (angle < 0) angle += 360; } return angle; };