sprotty
Version:
A next-gen framework for graphical views
413 lines • 19.7 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (c) 2019-2020 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractEdgeRouter = exports.DefaultAnchors = exports.Side = void 0;
const inversify_1 = require("inversify");
const geometry_1 = require("sprotty-protocol/lib/utils/geometry");
const smodel_utils_1 = require("../../base/model/smodel-utils");
const model_1 = require("./model");
const anchor_1 = require("./anchor");
const model_2 = require("./model");
var Side;
(function (Side) {
Side[Side["RIGHT"] = 0] = "RIGHT";
Side[Side["LEFT"] = 1] = "LEFT";
Side[Side["TOP"] = 2] = "TOP";
Side[Side["BOTTOM"] = 3] = "BOTTOM";
})(Side || (exports.Side = Side = {}));
class DefaultAnchors {
constructor(element, edgeParent, kind) {
this.element = element;
this.kind = kind;
const bounds = element.bounds;
this.bounds = (0, smodel_utils_1.translateBounds)(bounds, element.parent, edgeParent);
this.left = { x: this.bounds.x, y: this.bounds.y + 0.5 * this.bounds.height, kind };
this.right = { x: this.bounds.x + this.bounds.width, y: this.bounds.y + 0.5 * this.bounds.height, kind };
this.top = { x: this.bounds.x + 0.5 * this.bounds.width, y: this.bounds.y, kind };
this.bottom = { x: this.bounds.x + 0.5 * this.bounds.width, y: this.bounds.y + this.bounds.height, kind };
}
get(side) {
return this[Side[side].toLowerCase()];
}
getNearestSide(point) {
const leftDistance = geometry_1.Point.euclideanDistance(point, this.left);
const rightDistance = geometry_1.Point.euclideanDistance(point, this.right);
const topDistance = geometry_1.Point.euclideanDistance(point, this.top);
const bottomDistance = geometry_1.Point.euclideanDistance(point, this.bottom);
let currentNearestSide = Side.LEFT;
let currentMinDist = leftDistance;
if (rightDistance < currentMinDist) {
currentMinDist = rightDistance;
currentNearestSide = Side.RIGHT;
}
if (topDistance < currentMinDist) {
currentMinDist = topDistance;
currentNearestSide = Side.TOP;
}
if (bottomDistance < currentMinDist) {
currentMinDist = bottomDistance;
currentNearestSide = Side.BOTTOM;
}
return currentNearestSide;
}
}
exports.DefaultAnchors = DefaultAnchors;
let AbstractEdgeRouter = class AbstractEdgeRouter {
findOrthogonalIntersection(edge, point) {
const calcOrthogonalIntersectionForSegment = (p1, p2) => {
// Calculate the direction vector d of the edge and vector pq from p1 to point q
const d = geometry_1.Point.subtract(p2, p1);
const pq = geometry_1.Point.subtract(point, p1);
// Calculate the scalar t for the direction vector d
const t = geometry_1.Point.dotProduct(pq, d) / geometry_1.Point.dotProduct(d, d);
// Check if the intersection point lies on the edge segment
if (t >= 0 && t <= 1) {
// Calculate and return the intersection point x
return geometry_1.Point.linear(p1, p2, t);
}
else if (t < 0) {
return p1;
}
else {
return p2;
}
};
// Calculate the intersection for each segment of the edge and return the closest one
const routedPoints = this.route(edge);
let intersectionPoint = routedPoints[0];
let index = 0;
for (let i = 0; i < routedPoints.length - 1; ++i) {
const intersection = calcOrthogonalIntersectionForSegment(routedPoints[i], routedPoints[i + 1]);
if (geometry_1.Point.euclideanDistance(point, intersection) < geometry_1.Point.euclideanDistance(point, intersectionPoint)) {
intersectionPoint = intersection;
index = i;
}
}
const derivative = geometry_1.Point.subtract(routedPoints[index + 1], routedPoints[index]);
return { point: intersectionPoint, derivative };
}
pointAt(edge, t) {
const segments = this.calculateSegment(edge, t);
if (!segments)
return undefined;
const { segmentStart, segmentEnd, lambda } = segments;
return geometry_1.Point.linear(segmentStart, segmentEnd, lambda);
}
derivativeAt(edge, t) {
const segments = this.calculateSegment(edge, t);
if (!segments)
return undefined;
const { segmentStart, segmentEnd } = segments;
return {
x: segmentEnd.x - segmentStart.x,
y: segmentEnd.y - segmentStart.y
};
}
calculateSegment(edge, t) {
if (t < 0 || t > 1)
return undefined;
const routedPoints = this.route(edge);
if (routedPoints.length < 2)
return undefined;
const segmentLengths = [];
let totalLength = 0;
for (let i = 0; i < routedPoints.length - 1; ++i) {
segmentLengths[i] = geometry_1.Point.euclideanDistance(routedPoints[i], routedPoints[i + 1]);
totalLength += segmentLengths[i];
}
let currentLenght = 0;
const tAsLenght = t * totalLength;
for (let i = 0; i < routedPoints.length - 1; ++i) {
const newLength = currentLenght + segmentLengths[i];
// avoid division by (almost) zero
if (segmentLengths[i] > 1E-8) {
if (newLength >= tAsLenght) {
const lambda = Math.max(0, (tAsLenght - currentLenght)) / segmentLengths[i];
return {
segmentStart: routedPoints[i],
segmentEnd: routedPoints[i + 1],
lambda
};
}
}
currentLenght = newLength;
}
return {
segmentEnd: routedPoints.pop(),
segmentStart: routedPoints.pop(),
lambda: 1
};
}
addHandle(edge, kind, type, routingPointIndex) {
const handle = new model_1.SRoutingHandleImpl();
handle.kind = kind;
handle.pointIndex = routingPointIndex;
handle.type = type;
if (kind === 'target' && edge.id === model_1.edgeInProgressID)
handle.id = model_1.edgeInProgressTargetHandleID;
edge.add(handle);
return handle;
}
getHandlePosition(edge, route, handle) {
switch (handle.kind) {
case 'source':
if (edge.source instanceof model_1.SDanglingAnchorImpl)
return edge.source.position;
else
return route[0];
case 'target':
if (edge.target instanceof model_1.SDanglingAnchorImpl)
return edge.target.position;
else {
return route[route.length - 1];
}
default:
const position = this.getInnerHandlePosition(edge, route, handle);
if (position !== undefined)
return position;
if (handle.pointIndex >= 0 && handle.pointIndex < edge.routingPoints.length)
return edge.routingPoints[handle.pointIndex];
}
return undefined;
}
findRouteSegment(edge, route, handleIndex) {
const getIndex = (rp) => {
if (rp.pointIndex !== undefined)
return rp.pointIndex;
else if (rp.kind === 'target')
return edge.routingPoints.length;
else
return -2;
};
let start, end;
for (const rp of route) {
const i = getIndex(rp);
if (i <= handleIndex && (start === undefined || i > getIndex(start)))
start = rp;
if (i > handleIndex && (end === undefined || i < getIndex(end)))
end = rp;
}
return { start, end };
}
getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection = 0) {
const translatedRefPoint = (0, smodel_utils_1.translatePoint)(refPoint, refContainer, connectable.parent);
const anchorComputer = this.getAnchorComputer(connectable);
const strokeCorrection = 0.5 * connectable.strokeWidth;
const anchor = anchorComputer.getAnchor(connectable, translatedRefPoint, anchorCorrection + strokeCorrection);
return (0, smodel_utils_1.translatePoint)(anchor, connectable.parent, edge.parent);
}
getAnchorComputer(connectable) {
return this.anchorRegistry.get(this.kind, connectable.anchorKind);
}
applyHandleMoves(edge, moves) {
const remainingMoves = moves.slice();
moves.forEach(move => {
const handle = move.handle;
if (handle.kind === 'source' && !(edge.source instanceof model_1.SDanglingAnchorImpl)) {
// detach source
const anchor = new model_1.SDanglingAnchorImpl();
anchor.id = edge.id + '_dangling-source';
anchor.original = edge.source;
anchor.position = move.toPosition;
handle.root.add(anchor);
handle.danglingAnchor = anchor;
edge.sourceId = anchor.id;
}
else if (handle.kind === 'target' && !(edge.target instanceof model_1.SDanglingAnchorImpl)) {
// detach target
const anchor = new model_1.SDanglingAnchorImpl();
anchor.id = edge.id + '_dangling-target';
anchor.original = edge.target;
anchor.position = move.toPosition;
handle.root.add(anchor);
handle.danglingAnchor = anchor;
edge.targetId = anchor.id;
}
if (handle.danglingAnchor) {
handle.danglingAnchor.position = move.toPosition;
remainingMoves.splice(remainingMoves.indexOf(move), 1);
}
});
if (remainingMoves.length > 0)
this.applyInnerHandleMoves(edge, remainingMoves);
this.cleanupRoutingPoints(edge, edge.routingPoints, true, true);
}
cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints) {
const sourceAnchors = new DefaultAnchors(edge.source, edge.parent, "source");
const targetAnchors = new DefaultAnchors(edge.target, edge.parent, "target");
this.resetRoutingPointsOnReconnect(edge, routingPoints, updateHandles, sourceAnchors, targetAnchors);
}
resetRoutingPointsOnReconnect(edge, routingPoints, updateHandles, sourceAnchors, targetAnchors) {
if (routingPoints.length === 0 || edge.source instanceof model_1.SDanglingAnchorImpl || edge.target instanceof model_1.SDanglingAnchorImpl) {
const options = this.getOptions(edge);
const corners = this.calculateDefaultCorners(edge, sourceAnchors, targetAnchors, options);
routingPoints.splice(0, routingPoints.length, ...corners);
if (updateHandles) {
let maxPointIndex = -2;
edge.children.forEach(child => {
if (child instanceof model_1.SRoutingHandleImpl) {
if (child.kind === 'target')
child.pointIndex = routingPoints.length;
else if (child.kind === 'line' && child.pointIndex >= routingPoints.length)
edge.remove(child);
else
maxPointIndex = Math.max(child.pointIndex, maxPointIndex);
}
});
for (let i = maxPointIndex; i < routingPoints.length - 1; ++i)
this.addHandle(edge, 'manhattan-50%', 'volatile-routing-point', i);
}
return true;
}
return false;
}
applyReconnect(edge, newSourceId, newTargetId) {
let hasChanged = false;
if (newSourceId) {
const newSource = edge.root.index.getById(newSourceId);
if (newSource instanceof model_2.SConnectableElementImpl) {
edge.sourceId = newSource.id;
hasChanged = true;
}
}
if (newTargetId) {
const newTarget = edge.root.index.getById(newTargetId);
if (newTarget instanceof model_2.SConnectableElementImpl) {
edge.targetId = newTarget.id;
hasChanged = true;
}
}
if (hasChanged) {
// reset attached elements in index
edge.index.remove(edge);
if (edge.id === model_1.edgeInProgressID) {
// create a proper edge id after connecting the edge in progress
const idGen = (counter) => `${edge.sourceId}_to_${edge.targetId}_${counter}`;
let idx = 0;
let newId = idGen(idx);
while (edge.index.getById(newId) !== undefined) {
newId = idGen(++idx);
}
edge.id = newId;
const progressTargetHandle = edge.children.find(child => child.id === model_1.edgeInProgressTargetHandleID);
if (progressTargetHandle instanceof model_1.SRoutingHandleImpl) {
// remove in progress target handle
edge.remove(progressTargetHandle);
if (progressTargetHandle.danglingAnchor) {
// remove dangling anchor
progressTargetHandle.danglingAnchor.parent.remove(progressTargetHandle.danglingAnchor);
}
}
}
edge.index.add(edge);
if (this.getSelfEdgeIndex(edge) > -1) {
edge.routingPoints = [];
this.cleanupRoutingPoints(edge, edge.routingPoints, true, true);
}
}
}
takeSnapshot(edge) {
return {
routingPoints: edge.routingPoints.slice(),
routingHandles: edge.children
.filter(child => child instanceof model_1.SRoutingHandleImpl)
.map(child => child),
routedPoints: this.route(edge),
router: this,
source: edge.source,
target: edge.target
};
}
applySnapshot(edge, snapshot) {
edge.routingPoints = snapshot.routingPoints;
edge.removeAll(child => child instanceof model_1.SRoutingHandleImpl);
edge.routerKind = snapshot.router.kind;
snapshot.routingHandles.forEach(handle => edge.add(handle));
if (snapshot.source)
edge.sourceId = snapshot.source.id;
if (snapshot.target)
edge.targetId = snapshot.target.id;
// update index
edge.root.index.remove(edge);
edge.root.index.add(edge);
}
calculateDefaultCorners(edge, sourceAnchors, targetAnchors, options) {
const selfEdgeIndex = this.getSelfEdgeIndex(edge);
if (selfEdgeIndex >= 0) {
const standardDist = options.standardDistance;
const delta = options.selfEdgeOffset * Math.min(sourceAnchors.bounds.width, sourceAnchors.bounds.height);
switch (selfEdgeIndex % 4) {
case 0:
return [
{ x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.RIGHT).y + delta },
{ x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.BOTTOM).y + standardDist },
{ x: sourceAnchors.get(Side.BOTTOM).x + delta, y: sourceAnchors.get(Side.BOTTOM).y + standardDist },
];
case 1:
return [
{ x: sourceAnchors.get(Side.BOTTOM).x - delta, y: sourceAnchors.get(Side.BOTTOM).y + standardDist },
{ x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.BOTTOM).y + standardDist },
{ x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.LEFT).y + delta },
];
case 2:
return [
{ x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.LEFT).y - delta },
{ x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.TOP).y - standardDist },
{ x: sourceAnchors.get(Side.TOP).x - delta, y: sourceAnchors.get(Side.TOP).y - standardDist },
];
case 3:
return [
{ x: sourceAnchors.get(Side.TOP).x + delta, y: sourceAnchors.get(Side.TOP).y - standardDist },
{ x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.TOP).y - standardDist },
{ x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.RIGHT).y - delta },
];
}
}
return [];
}
getSelfEdgeIndex(edge) {
if (!edge.source || edge.source !== edge.target)
return -1;
return edge.source.outgoingEdges
.filter(otherEdge => otherEdge.target === edge.source)
.indexOf(edge);
}
commitRoute(edge, routedPoints) {
const newRoutingPoints = [];
for (let i = 1; i < routedPoints.length - 1; ++i)
newRoutingPoints.push({ x: routedPoints[i].x, y: routedPoints[i].y });
edge.routingPoints = newRoutingPoints;
}
};
exports.AbstractEdgeRouter = AbstractEdgeRouter;
__decorate([
(0, inversify_1.inject)(anchor_1.AnchorComputerRegistry),
__metadata("design:type", anchor_1.AnchorComputerRegistry)
], AbstractEdgeRouter.prototype, "anchorRegistry", void 0);
exports.AbstractEdgeRouter = AbstractEdgeRouter = __decorate([
(0, inversify_1.injectable)()
], AbstractEdgeRouter);
//# sourceMappingURL=abstract-edge-router.js.map