/*! * Copyright (c) 2023, 2024, Oracle and/or its affiliates. */ import { intersect } from './array.mjs'; /** * @file * * Contains joint.dia.Graph-related utility functions. */ const { dia } = joint; export function getElementBy(graph, propName, value) { return graph.getElements().find(el => el.get(propName) === value); } export function getElementById(graph, id) { return getElementBy(graph, 'id', id); } export function getLinkBy(graph, propName, value) { return graph.getLinks().find(link => link.get(propName) === value); } export function getLinkById(graph, id) { return getLinkBy(graph, 'id', id); } export function getCellBy(graph, propName, value) { return graph.getCells().find(cell => cell.get(propName) === value); } export function getCellById(graph, id) { return getCellBy(graph, 'id', id); } export function getElementWithRegistrationPoint(graph, p) { return graph.getElements().find(el => { const { x, y } = el.position(); return x === p.x && y === p.y; }); } export function getFreePosition(graph, p, gridSize) { let { x, y } = p; while (getElementWithRegistrationPoint(graph, { x, y })) { x += gridSize; y += gridSize; } return { x, y }; } export function getElement(graph, el) { if (el == null) { return null; } if (typeof el === 'string') { return getElementById(graph, el) || null; } else { if (el instanceof dia.Element) { return el; } else if (el.id != null) { return getElementById(graph, el.id) || null; } } return null; } export function getLink(graph, link) { if (link == null) { return null; } if (typeof link === 'string') { return getLinkById(graph, link) || null; } else { if (link instanceof dia.Link) { return link; } else if (link.id != null) { return getLinkById(graph, link.id) || null; } } return null; } export function getCell(graph, cell) { if (cell == null) { return null; } if (typeof cell === 'string') { return getCellById(graph, cell) || null; } else { if (cell instanceof dia.Link || cell instanceof dia.Element) { return cell; } else if (cell.id != null) { return getCellById(graph, cell.id) || null; } } return null; } export function getCells(graph, cells) { return cells.map(cell => getCell(graph, cell)).filter(cell => cell != null); } // Is there no method for this in the library? // if no anchor - anchor is center-center export function getAnchorCoords(anchor = {}, bbox) { // we do not need to care about rotation as we don't rotate els const { name = 'topLeft', args = {} } = anchor; const { dx = '50%', dy = '50%' } = args; const fromPoint = (() => { let { x, y } = bbox; // this is 'topLeft' switch (name) { case 'topRight': x += bbox.width; break; case 'modelCenter': x += bbox.width / 2; y += bbox.height / 2; break; case 'top': x += bbox.width / 2; break; case 'bottom': x += bbox.width / 2; y += bbox.height; break; case 'left': y += bbox.height / 2; break; case 'right': x += bbox.width; y += bbox.height / 2; break; case 'bottomLeft': y += bbox.height; break; case 'bottomRight': x += bbox.width; y += bbox.height; break; // What to do with these?... // 'perpendicular' - anchor that ensures an orthogonal route to the other endpoint // 'midSide' - anchor in the middle of the side of view bbox closest to the other endpoint } return { x, y }; })(); if (typeof dx == 'number') { return { x: fromPoint.x + dx, y: fromPoint.y + dy }; } // else return { x: fromPoint.x + Math.round(parseFloat(dx) / 100 * bbox.width), y: fromPoint.y + Math.round(parseFloat(dy) / 100 * bbox.height) }; } export function getRoute(graph, cells = [], opt = {}) { const { linkless = false } = opt; cells = [...cells]; // If linkless is true, the cells (param) are not dia.Cell instances but objects // containing the .element and the .previousElement. We will find the connection // based on this relationship. NOTE: This can lead to an edge-case which we currently // cannot solve - if an element is connected to the previous element with more than // one link - we have to randomly select one as we do not know which is the one that // was really used. if (linkless) { const first = cells.filter(obj => obj.previousElement == null); // if there is more than 1 first, it is invalid if (first.length !== 1) { return []; } let current = first[0]; let index = cells.findIndex(obj => obj === current); const result = [current.element]; cells.splice(index, 1); while (cells.length) { index = cells.findIndex(obj => obj.previousElement === current.element); // if there are still cells (as we got into the `while`) but no continuity... if (index === -1) { return []; } const outboundLinks = graph.getConnectedLinks(current.element, { outbound: true }); // el -> neighbors current = cells[index]; cells.splice(index, 1); const inboundLinks = graph.getConnectedLinks(current.element, { inbound: true }); // neighbors -> el const sharedLinks = intersect(inboundLinks, outboundLinks); // no shared links: invalid if (!sharedLinks.length) { return []; } result.push(sharedLinks[0], current.element); } return result; } // else - route with links specified - currently not supported by the backend, so TODO. return []; }