UNPKG

@finos/legend-extension-dsl-diagram

Version:
187 lines 8.96 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { RelationshipView } from '../metamodel/pure/packageableElements/diagram/DSL_Diagram_RelationshipView.js'; import { Point } from '../metamodel/pure/packageableElements/diagram/geometry/DSL_Diagram_Point.js'; import { Class, getAllOwnClassProperties, } from '@finos/legend-graph'; import { deleteEntry, guaranteeNonNullable } from '@finos/legend-shared'; import { PositionedRectangle } from '../metamodel/pure/packageableElements/diagram/geometry/DSL_Diagram_PositionedRectangle.js'; import { Rectangle } from '../metamodel/pure/packageableElements/diagram/geometry/DSL_Diagram_Rectangle.js'; export class Vector { x; y; constructor(x, y) { this.x = x; this.y = y; } static fromPoints(a, b) { return new Vector(b.x - a.x, b.y - a.y); } unit() { const norm = Math.sqrt(this.x * this.x + this.y * this.y); return new Vector(this.x / norm, this.y / norm); } normal(other) { return new Vector(other.y - this.y, -(other.x - this.x)); } dotProduct(other) { return this.x * other.x + this.y * other.y; } } /** * Get absolute position of element on the screen by recursively walking up element tree */ export const getElementPosition = (element) => { let xPosition = 0; let yPosition = 0; while (element.offsetParent) { xPosition += element.offsetLeft - element.scrollLeft + element.clientLeft; yPosition += element.offsetTop - element.scrollTop + element.clientTop; element = element.offsetParent; } return new Point(xPosition, yPosition); }; export const getClassView = (diagram, sourceViewId) => diagram.classViews.find((classView) => classView.id === sourceViewId); export const cleanUpDeadReferencesInDiagram = (diagram, graph) => { // Delete orphan property views const propertyViewsToRemove = diagram.propertyViews.filter((propertyView) => !(propertyView.property.ownerReference.value instanceof Class ? getAllOwnClassProperties(propertyView.property.ownerReference.value) : propertyView.property.ownerReference.value.properties) .map((property) => property.name) .includes(propertyView.property.value.name)); propertyViewsToRemove.forEach((propertyView) => deleteEntry(diagram.propertyViews, propertyView)); // Fix orphan class views const classViewsToRemove = diagram.classViews.filter((cv) => !graph.getNullableClass(cv.class.value.path)); classViewsToRemove.forEach((cw) => deleteEntry(diagram.classViews, cw)); // Fix orphan gneralization views const generalizationViewsToRemove = diagram.generalizationViews.filter((g) => { const srcClass = g.from.classView.value.class.value; const targetClass = g.to.classView.value.class.value; return (!graph.getNullableClass(srcClass.path) || !graph.getNullableClass(targetClass.path) || srcClass.generalizations.filter((c) => c.value.rawType === targetClass) .length === 0); }); generalizationViewsToRemove.forEach((g) => deleteEntry(diagram.generalizationViews, g)); }; export const _relationshipView_setPath = (relationshipView, val) => { relationshipView.path = val; }; /** * Simplify the path. * * Flatten the path if the angle is wide enough between 3 consecutive points * Also remove unnecessary inside points */ export const _relationshipView_simplifyPath = (relationshipView) => { const fullPath = relationshipView.buildFullPath(); // NOTE: this method here will `swallow` up points inside of the boxes const newPath = RelationshipView.pruneUnnecessaryInsidePoints(fullPath, relationshipView.from.classView.value, relationshipView.to.classView.value); // recompute the offset point from center inside of `from` and `to` classviews. // for each, we first check if `manageInsidePointsDynamically` removes any points from the full path // if it does we will update the offset if (newPath[0] !== fullPath[0]) { const center = relationshipView.from.classView.value.center(); relationshipView.from._offsetX = guaranteeNonNullable(newPath[0], 'Diagram path expected to have at least 2 points').x - center.x; relationshipView.from._offsetY = guaranteeNonNullable(newPath[0], 'Diagram path expected to have at least 2 points').y - center.y; } if (newPath[newPath.length - 1] !== fullPath[fullPath.length - 1]) { const center = relationshipView.to.classView.value.center(); relationshipView.to._offsetX = newPath[newPath.length - 1].x - center.x; relationshipView.to._offsetY = newPath[newPath.length - 1].y - center.y; } // find the point which can be flattened due to its wide angle const result = []; for (let i = 0; i < newPath.length - 2; i++) { const v1 = Vector.fromPoints(newPath[i + 1], newPath[i]).unit(); const v2 = Vector.fromPoints(newPath[i + 1], newPath[i + 2]).unit(); const dot = v1.dotProduct(v2); const angle = (Math.acos(dot) * 180) / Math.PI; if (Math.abs(angle - 180) > 5) { result.push(newPath[i + 1]); } } // NOTE: this new path does not contain the 2 end points _relationshipView_setPath(relationshipView, result); }; /** * Based on the location, find the point on the path that matches or create new point * (within a threshold of proximity) from the coordinate and put this in the path array * so it doesn't look too weird */ export const _findOrBuildPoint = (relationshipView, x, y, zoom, allowChange) => { for (const pt of relationshipView.path) { if (Math.sqrt((x - pt.x) * (x - pt.x) + (y - pt.y) * (y - pt.y)) < 10 / zoom) { return pt; } } const fullPath = relationshipView.buildFullPath(allowChange); const newPath = []; let point; for (let i = 0; i < fullPath.length - 1; i++) { const a = fullPath[i]; const b = fullPath[i + 1]; const u = new Vector(a.x, a.y).normal(new Vector(b.x, b.y)).unit(); const v = Vector.fromPoints(a, new Point(x, y)); // if the selection point is not too far from the segment // of the path, create a new point and make it part of the path if (Math.abs(u.dotProduct(v)) < 5 / zoom) { const lx = (a.x < b.x ? a.x : b.x) - 5 / zoom; const hx = (a.x < b.x ? b.x : a.x) + 5 / zoom; const ly = (a.y < b.y ? a.y : b.y) - 5 / zoom; const hy = (a.y < b.y ? b.y : a.y) + 5 / zoom; if (lx <= x && x <= hx && ly <= y && y <= hy) { point = new Point(x, y); newPath.push(point); } } if (i < fullPath.length - 2) { newPath.push(fullPath[i + 1]); } } if (point && allowChange) { // NOTE: this new path does not contain the 2 end points _relationshipView_setPath(relationshipView, newPath); } return point; }; export const rotatePointX = (point, angle) => point.x * Math.cos(angle) - point.y * Math.sin(angle); export const rotatePointY = (point, angle) => point.x * Math.sin(angle) + point.y * Math.cos(angle); export const getBottomRightCornerPoint = (pR) => new Point(pR.position.x + pR.rectangle.width, pR.position.y + pR.rectangle.height); /** * Build a small box at the bottom right corner of the rectangle so we * can use that for selection to resize the box */ export const buildBottomRightCornerBox = (pR) => { const bottomRightCornerPoint = getBottomRightCornerPoint(pR); const boxSize = 10; return new PositionedRectangle(new Point(bottomRightCornerPoint.x - boxSize / 2, bottomRightCornerPoint.y - boxSize / 2), new Rectangle(boxSize, boxSize)); }; /** * Check if a box contains another box */ export const boxContains = (box, otherBox) => { otherBox = box.normalizeBox(otherBox); return (box.contains(otherBox.position.x, otherBox.position.y) || box.contains(otherBox.position.x + otherBox.rectangle.width, otherBox.position.y) || box.contains(otherBox.position.x, otherBox.position.y + otherBox.rectangle.height) || box.contains(otherBox.position.x + otherBox.rectangle.width, otherBox.position.y + otherBox.rectangle.height)); }; //# sourceMappingURL=DSL_Diagram_Helper.js.map