UNPKG

@eclipse-glsp/layout-elk

Version:

Integration of ELK graph layout algorithms in GLSP Node Server

290 lines 12.2 kB
"use strict"; 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.GlspElkLayoutEngine = exports.ElkFactory = void 0; /******************************************************************************** * Copyright (c) 2018-2025 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 ********************************************************************************/ const server_1 = require("@eclipse-glsp/server"); const inversify_1 = require("inversify"); const element_filter_1 = require("./element-filter"); const layout_configurator_1 = require("./layout-configurator"); exports.ElkFactory = Symbol('ElkFactory'); /** * An implementation of GLSP's {@link LayoutEngine} interface that retrieves the graphical model from the {@link ModelState}, * transforms this model into an ELK graph and then invokes the underlying ELK instance for layout computation. */ let GlspElkLayoutEngine = class GlspElkLayoutEngine { constructor(elkFactory, filter, configurator, modelState) { this.filter = filter; this.configurator = configurator; this.modelState = modelState; this.elk = elkFactory(); } layout(operation) { const root = this.modelState.root; if (!(root instanceof server_1.GGraph)) { return root; } this.elkEdges = []; this.idToElkElement = new Map(); const elkGraph = this.transformToElk(root); return this.elk.layout(elkGraph).then(result => { this.applyLayout(result); return root; }); } transformToElk(model) { if (model instanceof server_1.GGraph) { const graph = this.transformGraph(model); this.elkEdges.forEach(elkEdge => { const parent = this.findCommonAncestor(elkEdge); if (parent) { parent.edges.push(elkEdge); } }); return graph; } else if (model instanceof server_1.GNode) { return this.transformNode(model); } else if (model instanceof server_1.GEdge) { return this.transformEdge(model); } else if (model instanceof server_1.GLabel) { return this.transformLabel(model); } else if (model instanceof server_1.GPort) { return this.transformPort(model); } throw new Error('Type not supported: ' + model.type); } /** * Searches for all children of the given element that are an instance of the given {@link GModelElementConstructor} * and are included by the {@link ElementFilter}. Also considers children that are nested inside of {@link GCompartment}s. * @param element The element whose children should be queried. * @param constructor The class instance that should be matched * @returns A list of all matching children. */ findChildren(element, constructor) { const result = []; element.children.forEach(child => { if (child instanceof constructor && this.filter.apply(element)) { result.push(child); } else if (child instanceof server_1.GCompartment) { result.push(...this.findChildren(child, constructor)); } }); return result; } findCommonAncestor(elkEdge) { if (elkEdge.sources.length === 0 || elkEdge.targets.length === 0) { this.logger.warn('Edges with multiple sources or targets are not supported by the GLSPElkLayoutEngine', elkEdge); return undefined; } const source = this.modelState.index.get(elkEdge.sources[0]); const target = this.modelState.index.get(elkEdge.targets[0]); if (!source || !target) { return undefined; } const sourceParent = (0, server_1.findParent)(source.parent, parent => parent instanceof server_1.GNode || parent instanceof server_1.GGraph); const targetParent = (0, server_1.findParent)(target.parent, parent => parent instanceof server_1.GNode || parent instanceof server_1.GGraph); if (!sourceParent || !targetParent) { return undefined; } let ancestor; if (sourceParent === targetParent) { ancestor = this.idToElkElement.get(sourceParent.id); } else if (source === targetParent) { ancestor = this.idToElkElement.get(source.id); } else if (target === sourceParent) { ancestor = this.idToElkElement.get(target.id); } return ancestor; } transformGraph(graph) { const elkGraph = { id: graph.id, layoutOptions: this.configurator.apply(graph) }; if (graph.children) { elkGraph.children = this.findChildren(graph, server_1.GNode).map(child => this.transformToElk(child)); elkGraph.edges = []; this.elkEdges.push(...this.findChildren(graph, server_1.GEdge).map(child => this.transformToElk(child))); } this.idToElkElement.set(graph.id, elkGraph); return elkGraph; } transformNode(node) { const elkNode = { id: node.id, layoutOptions: this.configurator.apply(node) }; if (node.children) { elkNode.children = this.findChildren(node, server_1.GNode).map(child => this.transformToElk(child)); elkNode.edges = []; this.elkEdges.push(...this.findChildren(node, server_1.GEdge).map(child => this.transformToElk(child))); elkNode.labels = this.findChildren(node, server_1.GLabel).map(child => this.transformToElk(child)); elkNode.ports = this.findChildren(node, server_1.GPort).map(child => this.transformToElk(child)); } this.transformShape(elkNode, node); this.idToElkElement.set(node.id, elkNode); return elkNode; } transformShape(elkShape, shape) { if (shape.position) { elkShape.x = shape.position.x; elkShape.y = shape.position.y; } if (shape.size) { elkShape.width = shape.size.width; elkShape.height = shape.size.height; } } transformEdge(edge) { const elkEdge = { id: edge.id, sources: [edge.sourceId], targets: [edge.targetId], layoutOptions: this.configurator.apply(edge) }; if (edge.children) { elkEdge.labels = this.findChildren(edge, server_1.GLabel).map(child => this.transformToElk(child)); } const points = edge.routingPoints; if (points && points.length >= 2) { elkEdge.sections = [ { id: edge.id + ':section', startPoint: points[0], bendPoints: points.slice(1, points.length - 1), endPoint: points[points.length - 1] } ]; } this.idToElkElement.set(edge.id, elkEdge); return elkEdge; } transformLabel(label) { const elkLabel = { id: label.id, text: label.text, layoutOptions: this.configurator.apply(label) }; this.transformShape(elkLabel, label); this.idToElkElement.set(label.id, elkLabel); return elkLabel; } transformPort(port) { const elkPort = { id: port.id, layoutOptions: this.configurator.apply(port) }; if (port.children) { elkPort.labels = this.findChildren(port, server_1.GLabel).map(child => this.transformToElk(child)); this.elkEdges.push(...this.findChildren(port, server_1.GEdge).map(child => this.transformToElk(child))); } this.transformShape(elkPort, port); this.idToElkElement.set(port.id, elkPort); return elkPort; } applyLayout(elkNode) { const element = this.modelState.index.get(elkNode.id); if (element instanceof server_1.GNode) { this.applyShape(element, elkNode); } if (elkNode.children) { for (const child of elkNode.children) { this.applyLayout(child); } } if (elkNode.edges) { for (const elkEdge of elkNode.edges) { const edge = this.modelState.index.get(elkEdge.id); if (edge instanceof server_1.GEdge) { this.applyEdge(edge, elkEdge); } } } if (elkNode.ports) { for (const elkPort of elkNode.ports) { const port = this.modelState.index.findByClass(elkPort.id, server_1.GPort); if (port) { this.applyShape(port, elkPort); } } } } applyShape(shape, elkShape) { if (elkShape.x !== undefined && elkShape.y !== undefined) { shape.position = { x: elkShape.x, y: elkShape.y }; } if (elkShape.width !== undefined && elkShape.height !== undefined) { shape.size = { width: elkShape.width, height: elkShape.height }; } if (elkShape.labels) { for (const elkLabel of elkShape.labels) { const label = elkLabel.id ? this.modelState.index.findByClass(elkLabel.id, server_1.GLabel) : undefined; if (label) { this.applyShape(label, elkLabel); } } } } applyEdge(edge, elkEdge) { const points = []; if (elkEdge.sections && elkEdge.sections.length > 0) { const section = elkEdge.sections[0]; if (section.startPoint) { points.push(section.startPoint); } if (section.bendPoints) { points.push(...section.bendPoints); } if (section.endPoint) { points.push(section.endPoint); } } edge.routingPoints = points; if (elkEdge.labels) { elkEdge.labels.forEach(elkLabel => { const label = elkLabel.id ? this.modelState.index.findByClass(elkLabel.id, server_1.GLabel) : undefined; if (label) { this.applyShape(label, elkLabel); } }); } } }; exports.GlspElkLayoutEngine = GlspElkLayoutEngine; __decorate([ (0, inversify_1.inject)(server_1.Logger), __metadata("design:type", server_1.Logger) ], GlspElkLayoutEngine.prototype, "logger", void 0); exports.GlspElkLayoutEngine = GlspElkLayoutEngine = __decorate([ (0, inversify_1.injectable)(), __metadata("design:paramtypes", [Function, Object, Object, Object]) ], GlspElkLayoutEngine); //# sourceMappingURL=glsp-elk-layout-engine.js.map