UNPKG

sprotty

Version:

A next-gen framework for graphical views

232 lines 11.4 kB
"use strict"; /******************************************************************************** * Copyright (c) 2024 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.JunctionFinder = void 0; const inversify_1 = require("inversify"); const sgraph_1 = require("../../graph/sgraph"); /** * Finds junction points in the edge routes. A junction point is a point where two or more edges split. * This excludes the source and target points of the edges. * * Only works with straight line segments. */ let JunctionFinder = class JunctionFinder { constructor() { /** Map of edges as SEdgeImpl for faster lookup by id */ this.edgesMap = new Map(); /** Map of unique edges ids with the same source */ this.sourcesMap = new Map(); /** Map of unique edges ids with the same target */ this.targetsMap = new Map(); } apply(routing, parent) { this.findJunctions(routing, parent); } findJunctions(routing, parent) { // gather all edges from the parent const edges = Array.from(parent.index.all().filter(child => child instanceof sgraph_1.SEdgeImpl)); // populate the maps for faster lookup edges.forEach(edge => { this.edgesMap.set(edge.id, edge); const sameSources = this.sourcesMap.get(edge.sourceId); if (sameSources) { sameSources.add(edge.id); } else { this.sourcesMap.set(edge.sourceId, new Set([edge.id])); } const sameTargets = this.targetsMap.get(edge.targetId); if (sameTargets) { sameTargets.add(edge.id); } else { this.targetsMap.set(edge.targetId, new Set([edge.id])); } }); routing.routes.forEach((route, routeId) => { // for each route we find the corresponding edge from the edges map by matching the route id and the edge id const edge = this.edgesMap.get(routeId); if (!edge) { return; } // find the junction points for edges with the same source this.findJunctionPointsWithSameSource(edge, route, routing); // find the junction points for edges with the same target this.findJunctionPointsWithSameTarget(edge, route, routing); }); } /** * Finds the junction points of routes with the same source */ findJunctionPointsWithSameSource(edge, route, routing) { // get an array of edge/route ids with the same source as the current edge, excluding the current edge const sourcesSet = this.sourcesMap.get(edge.sourceId); if (!sourcesSet) { return; } const otherRoutesIds = Array.from(sourcesSet).filter(id => id !== edge.id); const otherRoutes = otherRoutesIds.map(id => routing.get(id)).filter(r => r !== undefined); for (const otherRoute of otherRoutes) { // finds the index where the two routes diverge const junctionIndex = this.getJunctionIndex(route, otherRoute); // if no junction point has been found (i.e. the routes are identical) // or if the junction point is the first point of the routes (i.e the routes diverge at the source) // we can skip this route if (junctionIndex === -1 || junctionIndex === 0) { continue; } this.setJunctionPoints(route, otherRoute, junctionIndex); } } /** * Finds the junction points of routes with the same target */ findJunctionPointsWithSameTarget(edge, route, routing) { // get an array of edge/route ids with the same target as the current edge, excluding the current edge const targetsSet = this.targetsMap.get(edge.targetId); if (!targetsSet) { return; } const otherRoutesIds = Array.from(targetsSet).filter(id => id !== edge.id); const otherRoutes = otherRoutesIds.map(id => routing.get(id)).filter(r => r !== undefined); // we reverse the route so that the target is considered the source for the algorithm route.reverse(); for (const otherRoute of otherRoutes) { // we reverse the other route so that the target is considered the source for the algorithm otherRoute.reverse(); // finds the index where the two routes diverge const junctionIndex = this.getJunctionIndex(route, otherRoute); // if no junction point has been found (i.e. the routes are identical) // or if the junction point is the first point of the routes (i.e the routes diverge at the source) // we can skip this route if (junctionIndex === -1 || junctionIndex === 0) { continue; } this.setJunctionPoints(route, otherRoute, junctionIndex); // we reverse the other route back to its original order otherRoute.reverse(); } // we reverse the route back to their original order route.reverse(); } /** * Set the junction points of two routes according to the segments direction. * If the segments have different directions, the junction point is the previous common point. * If the segments have the same direction, the junction point is the point with the greatest or lowest value axis value depending on the direction. */ setJunctionPoints(route, otherRoute, junctionIndex) { const firstSegmentDirection = this.getSegmentDirection(route[junctionIndex - 1], route[junctionIndex]); const secondSegmentDirection = this.getSegmentDirection(otherRoute[junctionIndex - 1], otherRoute[junctionIndex]); // if the two segments have different directions, then the previous common point is the junction point if (firstSegmentDirection !== secondSegmentDirection) { this.setPreviousPointAsJunction(route, otherRoute, junctionIndex); } else { // the two segments have the same direction if (firstSegmentDirection === 'left' || firstSegmentDirection === 'right') { // if the segments are going horizontally, but their y values are different, then the previous common point is the junction point if (route[junctionIndex].y !== otherRoute[junctionIndex].y) { this.setPreviousPointAsJunction(route, otherRoute, junctionIndex); return; } // depending on the direction, the junction point is the point with the greatest or lowest x value route[junctionIndex].isJunction = firstSegmentDirection === 'left' ? route[junctionIndex].x > otherRoute[junctionIndex].x : route[junctionIndex].x < otherRoute[junctionIndex].x; otherRoute[junctionIndex].isJunction = firstSegmentDirection === 'left' ? otherRoute[junctionIndex].x > route[junctionIndex].x : otherRoute[junctionIndex].x < route[junctionIndex].x; } else { // if the segments are going vertically, but their x values are different, then the previous common point is the junction point if (route[junctionIndex].x !== otherRoute[junctionIndex].x) { this.setPreviousPointAsJunction(route, otherRoute, junctionIndex); return; } // depending on the direction, the junction point is the point with the greatest or lowest y value route[junctionIndex].isJunction = firstSegmentDirection === 'up' ? route[junctionIndex].y > otherRoute[junctionIndex].y : route[junctionIndex].y < otherRoute[junctionIndex].y; otherRoute[junctionIndex].isJunction = firstSegmentDirection === 'up' ? otherRoute[junctionIndex].y > route[junctionIndex].y : otherRoute[junctionIndex].y < route[junctionIndex].y; } } } /** * Set the previous point as a junction point. * This is used when two segments have the same direction but the other axis is different. * For example if the routes are going in opposite directions, or if the route don't split orthogonally. */ setPreviousPointAsJunction(route, sameSourceRoute, junctionIndex) { route[junctionIndex - 1].isJunction = true; sameSourceRoute[junctionIndex - 1].isJunction = true; } /** * Get the main direction of a segment. * The main direction is the axis with the greatest difference between the two points. */ getSegmentDirection(firstPoint, secondPoint) { const dX = secondPoint.x - firstPoint.x; const dY = secondPoint.y - firstPoint.y; let mainDirection = 'horizontal'; if (Math.abs(dX) < Math.abs(dY)) { mainDirection = 'vertical'; } if (mainDirection === 'horizontal') { if (dX > 0) { return 'right'; } else { return 'left'; } } else { if (dY > 0) { return 'down'; } else { return 'up'; } } } /** * Finds the index where two routes diverge. * Returns -1 if no divergence can be found. */ getJunctionIndex(firstRoute, secondRoute) { let idx = 0; while (idx < firstRoute.length && idx < secondRoute.length) { if (firstRoute[idx].x !== secondRoute[idx].x || firstRoute[idx].y !== secondRoute[idx].y) { return idx; } idx++; } return -1; } }; exports.JunctionFinder = JunctionFinder; exports.JunctionFinder = JunctionFinder = __decorate([ (0, inversify_1.injectable)() ], JunctionFinder); //# sourceMappingURL=junction-finder.js.map