UNPKG

@kieler/klighd-core

Version:

Core KLighD diagram visualization with Sprotty

745 lines 32.4 kB
"use strict"; /* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://rtsys.informatik.uni-kiel.de/kieler * * Copyright 2019-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getKRendering = exports.getPoints = exports.getTransformation = exports.reverseTransformation = exports.reverseTransformations = exports.transformationToSVGString = exports.isTranslation = exports.isScale = exports.isRotation = exports.findTextBoundsAndTransformationData = exports.findBoundsAndTransformationData = exports.camelToKebab = exports.fillSingleColor = exports.isSingleColor = exports.findById = exports.evaluateKPosition = exports.calculateY = exports.calculateX = exports.textDecorationColor = exports.textDecorationStyleText = exports.verticalAlignmentText = exports.lineStyleText = exports.lineJoinText = exports.lineCapText = void 0; const sprotty_protocol_1 = require("sprotty-protocol"); const skgraph_models_1 = require("./skgraph-models"); const views_styles_1 = require("./views-styles"); // ------------- Util Class names ------------- // const K_LEFT_POSITION = 'KLeftPositionImpl'; const K_RIGHT_POSITION = 'KRightPositionImpl'; const K_TOP_POSITION = 'KTopPositionImpl'; const K_BOTTOM_POSITION = 'KBottomPositionImpl'; // ------------- constants for string building --------------- // const RGB_START = 'rgb('; const RGB_END = ')'; /** * Translates a KLineCap into the text needed for the SVG 'stroke-linecap' attribute. * @param lineCap The KLineCap. */ function lineCapText(lineCap) { switch (lineCap.lineCap) { case skgraph_models_1.LineCap.CAP_FLAT: { // the flat LineCap option is actually called 'butt' in svg and most other usages. return 'butt'; } case skgraph_models_1.LineCap.CAP_ROUND: { return 'round'; } case skgraph_models_1.LineCap.CAP_SQUARE: { return 'square'; } default: { console.error('error in views.common.ts, unexpected LineCap in switch'); return 'butt'; } } } exports.lineCapText = lineCapText; /** * Translates a KLineJoin into the text needed for the SVG 'stroke-linejoin' attribute. * @param lineJoin The KLineJoin. */ function lineJoinText(lineJoin) { switch (lineJoin.lineJoin) { case skgraph_models_1.LineJoin.JOIN_BEVEL: { return 'bevel'; } case skgraph_models_1.LineJoin.JOIN_MITER: { return 'miter'; } case skgraph_models_1.LineJoin.JOIN_ROUND: { return 'round'; } default: { console.error('error in views.common.ts, unexpected LineJoin in switch'); return 'miter'; } } } exports.lineJoinText = lineJoinText; /** * Translates a KLineStyle into the text needed for the SVG 'stroke-dasharray' attribute. * If the resulting dasharray would be a solid line, return undefined instead. * @param lineStyle The KLineStyle * @param lineWidth The width of the drawn line */ function lineStyleText(lineStyle, lineWidth) { // TODO: implement dashOffset const one = (1 * lineWidth).toString(); const three = (3 * lineWidth).toString(); switch (lineStyle.lineStyle) { case skgraph_models_1.LineStyle.CUSTOM: { if (lineStyle.dashPattern === undefined) { // Draw a solid line if the custom dashPattern is not defined. return undefined; } return lineStyle.dashPattern.join(' '); } case skgraph_models_1.LineStyle.DASH: { return [three, one].join(' '); } case skgraph_models_1.LineStyle.DASHDOT: { return [three, one, one, one].join(' '); } case skgraph_models_1.LineStyle.DASHDOTDOT: { return [three, one, one, one, one, one].join(' '); } case skgraph_models_1.LineStyle.DOT: { return one; } case skgraph_models_1.LineStyle.SOLID: { return undefined; } default: { console.error('error in views.common.ts, unexpected LineStyle in switch'); return undefined; } } } exports.lineStyleText = lineStyleText; /** * Translates a VerticalAlignment into the text needed for the SVG text 'dominant-baseline' attribute. * @param verticalAlignment The VerticalAlignment. */ function verticalAlignmentText(verticalAlignment) { switch (verticalAlignment) { case skgraph_models_1.VerticalAlignment.CENTER: { return 'middle'; } case skgraph_models_1.VerticalAlignment.BOTTOM: { return 'baseline'; } case skgraph_models_1.VerticalAlignment.TOP: { return 'hanging'; } default: { console.error('error in views.common.ts, unexpected VerticalAlignment in switch'); return 'hanging'; } } } exports.verticalAlignmentText = verticalAlignmentText; /** * Translates a KTextUnderline into the text needed for the SVG 'text-decoration-style' attribute. * @param underline The KTextUnderline. */ function textDecorationStyleText(underline) { switch (underline.underline) { case skgraph_models_1.Underline.NONE: { return undefined; } case skgraph_models_1.Underline.SINGLE: { return 'solid'; } case skgraph_models_1.Underline.DOUBLE: { return 'double'; } case skgraph_models_1.Underline.ERROR: { return 'wavy'; } case skgraph_models_1.Underline.SQUIGGLE: { return 'wavy'; } case skgraph_models_1.Underline.LINK: { return 'solid'; } default: { console.error('error in views.common.ts, unexpected Underline in switch'); return undefined; } } } exports.textDecorationStyleText = textDecorationStyleText; // eslint-disable-next-line function textDecorationColor(underline) { return undefined; // TODO: } exports.textDecorationColor = textDecorationColor; /** * Calculates the x-coordinate of the text's positioning box when considering its available space and its alignment. * @param x The calculated x-coordinate pointing to the left coordinate of the text rendering box. * @param width The available width for the text. * @param horizontalAlignment The KHorizontalAlignment. * @param textWidth The real width the rendered text needs. */ function calculateX(x, width, horizontalAlignment, textWidth) { if (textWidth === undefined) { switch (horizontalAlignment.horizontalAlignment) { case skgraph_models_1.HorizontalAlignment.CENTER: { return x + width / 2; } case skgraph_models_1.HorizontalAlignment.LEFT: { return x; } case skgraph_models_1.HorizontalAlignment.RIGHT: { return x + width; } default: { console.error('error in views.common.ts, unexpected HorizontalAlignment in switch'); return x; } } } else { switch (horizontalAlignment.horizontalAlignment) { case skgraph_models_1.HorizontalAlignment.CENTER: { return x + width / 2 - textWidth / 2; } case skgraph_models_1.HorizontalAlignment.LEFT: { return x; } case skgraph_models_1.HorizontalAlignment.RIGHT: { return x + width - textWidth; } default: { console.error('error in views.common.ts, unexpected HorizontalAlignment in switch'); return x; } } } } exports.calculateX = calculateX; /** * Calculates the y-coordinate of the text's positioning box when considering its alignment. * @param y The calculated y-coordinate pointing to the top coordinate of the text rendering box. * @param height The available height for the text. * @param verticalAlignment The KVerticalAlignment. * @param numberOfLines The number of lines in the given text. */ function calculateY(y, height, verticalAlignment, numberOfLines) { let lineHeight = height / numberOfLines; if (numberOfLines === 0) { lineHeight = height; } switch (verticalAlignment.verticalAlignment) { case skgraph_models_1.VerticalAlignment.CENTER: { return y + lineHeight / 2; } case skgraph_models_1.VerticalAlignment.BOTTOM: { return y + lineHeight; } case skgraph_models_1.VerticalAlignment.TOP: { return y; } default: { console.error('error in views.common.ts, unexpected VerticalAlignment in switch'); return y; } } } exports.calculateY = calculateY; /** * Evaluates a position inside given parent bounds. Inspired by the java method PlacementUtil.evaluateKPosition. * @param position The position. * @param parentBounds The parent bounds. * @param topLeft In case position is undefined assume a topLeft KPosition, and a bottomRight KPosition otherwise. * @returns The evaluated position. */ function evaluateKPosition(position, parentBounds, topLeft) { const { width, height } = parentBounds; const point = { x: 0, y: 0 }; let xPos = position.x; let yPos = position.y; if (xPos === undefined) { xPos = { absolute: 0, relative: 0, type: topLeft ? K_LEFT_POSITION : K_RIGHT_POSITION, }; } if (yPos === undefined) { yPos = { absolute: 0, relative: 0, type: topLeft ? K_TOP_POSITION : K_BOTTOM_POSITION, }; } if (xPos.type === K_LEFT_POSITION) { point.x = xPos.relative * width + xPos.absolute; } else { point.x = width - xPos.relative * width - xPos.absolute; } if (yPos.type === K_TOP_POSITION) { point.y = yPos.relative * height + yPos.absolute; } else { point.y = height - yPos.relative * height - yPos.absolute; } return point; } exports.evaluateKPosition = evaluateKPosition; /** * Tries to find the ID in the given map object. * @param map The object containing something under the given ID. * @param idString The ID too look up. */ function findById(map, idString) { if (map === undefined) { return undefined; } return map[idString]; } exports.findById = findById; /** * Returns if the given coloring is a single color and no gradient. * @param coloring The coloring to check. */ function isSingleColor(coloring) { return coloring.targetColor === undefined || coloring.targetAlpha === undefined; } exports.isSingleColor = isSingleColor; /** * Returns the SVG fill string representing the given coloring, if it is a single color. Check that with isSingleColor(KColoring) beforehand. * @param coloring The coloring. */ function fillSingleColor(coloring) { return { color: `${RGB_START}${coloring.color.red},${coloring.color.green},${coloring.color.blue}${RGB_END}`, opacity: coloring.alpha === undefined || coloring.alpha === 255 ? undefined : (coloring.alpha / 255).toString(), }; } exports.fillSingleColor = fillSingleColor; /** * Transforms any string in 'CamelCaseFormat' to a string in 'kebab-case-format'. * @param string The string to transform. */ function camelToKebab(string) { return string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } exports.camelToKebab = camelToKebab; /** * Calculate the bounds of the given rendering and the SVG transformation string that has to be applied to the SVG element for this rendering. * @param rendering The rendering to calculate the bounds and transformation for. * @param kRotation The KRotation style of the rendering. * @param parent The parent SKGraphElement this rendering is contained in. * @param context The rendering context used to render this element. * @param boundingBox If this method should not return the values to be applied to the SVG but the * box coordinates instead. Required to find the true bounding box of text renderings. * @param isEdge If the rendering is for an edge. */ function findBoundsAndTransformationData(rendering, styles, parent, context, isEdge, boundingBox) { if (rendering.type === skgraph_models_1.K_TEXT && !boundingBox) { return findTextBoundsAndTransformationData(rendering, styles, parent, context); } let bounds; let decoration; if (rendering.properties['klighd.lsp.calculated.bounds'] !== undefined) { // Bounds are in the calculatedBounds of the rendering. bounds = rendering.properties['klighd.lsp.calculated.bounds']; } // If no bounds have been found yet, they should be in the boundsMap. if (bounds === undefined && context.boundsMap !== undefined) { bounds = findById(context.boundsMap, rendering.properties['klighd.lsp.rendering.id']); } // If there is a decoration, calculate the bounds and decoration (containing a possible rotation) from that. if (rendering.properties['klighd.lsp.calculated.decoration'] !== undefined) { decoration = rendering.properties['klighd.lsp.calculated.decoration']; bounds = { x: decoration.bounds.x + decoration.origin.x, y: decoration.bounds.y + decoration.origin.y, width: decoration.bounds.width, height: decoration.bounds.height, }; } // Same as above, if the decoration has not been found yet, it should be in the decorationMap. if (decoration === undefined && context.decorationMap !== undefined) { decoration = findById(context.decorationMap, rendering.properties['klighd.lsp.rendering.id']); if (decoration !== undefined) { bounds = { x: decoration.bounds.x + decoration.origin.x, y: decoration.bounds.y + decoration.origin.y, width: decoration.bounds.width, height: decoration.bounds.height, }; } } // Error check: If there are no bounds or decoration, at least try to fall back to possible position attributes in the parent element. // If the parent element has no bounds either, the object can not be rendered. if (decoration === undefined && bounds === undefined && 'bounds' in parent) { bounds = { x: 0, y: 0, width: parent.bounds.width, height: parent.bounds.height, }; } else if (decoration === undefined && bounds === undefined) { return undefined; } if (parent instanceof skgraph_models_1.SKNode && parent.shadow) { // bounds of the shadow indicating the old position of the node bounds = { x: parent.shadowX - parent.position.x, y: parent.shadowY - parent.position.y, width: parent.size.width, height: parent.size.height, }; } // Calculate the svg transformation function string for this element and all its child elements given the bounds and decoration. const transformation = getTransformation(bounds, decoration, styles.kRotation, isEdge); return { bounds, transformation, }; } exports.findBoundsAndTransformationData = findBoundsAndTransformationData; /** * Calculate the bounds of the given text rendering and the SVG transformation string that has to be applied to the SVG element for this text. * @param rendering The text rendering to calculate the bounds and transformation for. * @param styles The styles for this text rendering * @param parent The parent SKGraphElement this rendering is contained in. * @param context The rendering context used to render this element. */ function findTextBoundsAndTransformationData(rendering, styles, parent, context) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; let bounds = { x: undefined, y: undefined, width: undefined, height: undefined, }; let decoration; // Find the text to write first. let text; // KText elements as renderings of labels have their text in the KLabel, not the KText if ('text' in parent) { // if parent is KLabel text = parent.text; } else { text = rendering.text; } if (parent.properties['de.cau.cs.kieler.klighd.labels.textOverride'] !== undefined) { text = parent.properties['de.cau.cs.kieler.klighd.labels.textOverride']; } // The text split into an array for each individual line const lines = (_b = (_a = text === null || text === void 0 ? void 0 : text.split('\n')) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 1; if (rendering.properties['klighd.calculated.text.bounds'] !== undefined) { const textWidth = rendering.properties['klighd.calculated.text.bounds'].width; const textHeight = rendering.properties['klighd.calculated.text.bounds'].height; if (rendering.properties['klighd.lsp.calculated.bounds'] !== undefined) { const foundBounds = rendering.properties['klighd.lsp.calculated.bounds']; bounds.x = calculateX(foundBounds.x, foundBounds.width, (_c = styles.kHorizontalAlignment) !== null && _c !== void 0 ? _c : views_styles_1.DEFAULT_K_HORIZONTAL_ALIGNMENT, textWidth); bounds.y = calculateY(foundBounds.y, foundBounds.height, (_d = styles.kVerticalAlignment) !== null && _d !== void 0 ? _d : views_styles_1.DEFAULT_K_VERTICAL_ALIGNMENT, lines); bounds.width = textWidth; bounds.height = textHeight; } // if no bounds have been found yet, they should be in the boundsMap if (bounds.x === undefined && context.boundsMap !== undefined) { const foundBounds = findById(context.boundsMap, rendering.properties['klighd.lsp.rendering.id']); if (bounds !== undefined) { bounds.x = calculateX(foundBounds.x, foundBounds.width, (_e = styles.kHorizontalAlignment) !== null && _e !== void 0 ? _e : views_styles_1.DEFAULT_K_HORIZONTAL_ALIGNMENT, textWidth); bounds.y = calculateY(foundBounds.y, foundBounds.height, (_f = styles.kVerticalAlignment) !== null && _f !== void 0 ? _f : views_styles_1.DEFAULT_K_VERTICAL_ALIGNMENT, lines); bounds.width = textWidth; bounds.height = textHeight; } } // If there is a decoration, calculate the bounds and decoration (containing a possible rotation) from that. if (rendering.properties['klighd.lsp.calculated.decoration'] !== undefined) { decoration = rendering.properties['klighd.lsp.calculated.decoration']; bounds.x = calculateX(decoration.bounds.x + decoration.origin.x, textWidth, (_g = styles.kHorizontalAlignment) !== null && _g !== void 0 ? _g : views_styles_1.DEFAULT_K_HORIZONTAL_ALIGNMENT, textWidth); bounds.y = calculateY(decoration.bounds.y + decoration.origin.y, textHeight, (_h = styles.kVerticalAlignment) !== null && _h !== void 0 ? _h : views_styles_1.DEFAULT_K_VERTICAL_ALIGNMENT, lines); bounds.width = decoration.bounds.width; bounds.height = decoration.bounds.height; } // Same as above, if the decoration has not been found yet, it should be in the decorationMap. if (decoration === undefined && context.decorationMap !== undefined) { decoration = findById(context.decorationMap, rendering.properties['klighd.lsp.rendering.id']); if (decoration !== undefined) { bounds.x = calculateX(decoration.bounds.x + decoration.origin.x, textWidth, (_j = styles.kHorizontalAlignment) !== null && _j !== void 0 ? _j : views_styles_1.DEFAULT_K_HORIZONTAL_ALIGNMENT, textWidth); bounds.y = calculateY(decoration.bounds.y + decoration.origin.y, textHeight, (_k = styles.kVerticalAlignment) !== null && _k !== void 0 ? _k : views_styles_1.DEFAULT_K_VERTICAL_ALIGNMENT, lines); bounds.width = decoration.bounds.width; bounds.height = decoration.bounds.height; } } } // Error check: If there are no bounds or decoration, at least try to fall back to possible size attributes in the parent element. // If the parent element has no bounds either, the object can not be rendered. if (decoration === undefined && bounds.x === undefined && 'bounds' in parent) { const parentBounds = { x: 0, y: 0, width: parent.bounds.width, height: parent.bounds.height, }; bounds.x = calculateX(parentBounds.x, parentBounds.width, (_l = styles.kHorizontalAlignment) !== null && _l !== void 0 ? _l : views_styles_1.DEFAULT_K_HORIZONTAL_ALIGNMENT, parentBounds.width); bounds.y = calculateY(parentBounds.y, parentBounds.height, (_m = styles.kVerticalAlignment) !== null && _m !== void 0 ? _m : views_styles_1.DEFAULT_K_VERTICAL_ALIGNMENT, lines); bounds.width = parent.bounds.width; bounds.height = parent.bounds.height; } else if (decoration === undefined && bounds.x === undefined) { return undefined; } // If still no bounds are found, set all by default to 0. if (bounds.x === undefined) { bounds = { x: 0, y: 0, width: 0, height: 0, }; // Do not apply any rotation style in that case either, as the bounds estimation may get confused then. styles.kRotation = undefined; } // Calculate the svg transformation function string for this element given the bounds and decoration. const transformation = getTransformation(bounds, decoration, styles.kRotation, false, true); return { bounds: bounds, transformation, }; } exports.findTextBoundsAndTransformationData = findTextBoundsAndTransformationData; function isRotation(transformation) { return transformation.kind === 'rotate'; } exports.isRotation = isRotation; function isScale(transformation) { return transformation.kind === 'scale'; } exports.isScale = isScale; function isTranslation(transformation) { return transformation.kind === 'translate'; } exports.isTranslation = isTranslation; /** * Converts the transformation into a String, that can be used for the SVG transformation attribute. * @param transformation The transformation to convert. * @returns An SVG transformation string. */ function transformationToSVGString(transformation) { if (isRotation(transformation)) { if (transformation.x === undefined && transformation.y === undefined) { return `${transformation.kind}(${transformation.angle})`; } return `${transformation.kind}(${transformation.angle}, ${transformation.x}, ${transformation.y})`; } if (isTranslation(transformation)) { return `${transformation.kind}(${transformation.x}, ${transformation.y})`; } if (isScale(transformation)) { return `${transformation.kind}(${transformation.factor})`; } console.error(`A transformation has to be a rotation, scale, or translation, but is: ${transformation}. Error in code detected!`); return ''; } exports.transformationToSVGString = transformationToSVGString; /** * Reverses an array of transformations such that applying the transformation and its reverse counterpart will result in the identity transformation. * @param transformations The transformations to reverse. * @returns The reversed transformations. */ function reverseTransformations(transformations) { return transformations.map((transformation) => reverseTransformation(transformation)).reverse(); } exports.reverseTransformations = reverseTransformations; /** * Reverses a transformation such that applying the transformation and its reverse counterpart will result in the identity transformation. * @param transformation The transformation to reverse. * @returns The reversed transformation. */ function reverseTransformation(transformation) { if (isTranslation(transformation)) { return { kind: 'translate', x: -transformation.x, y: -transformation.y, }; } if (isRotation(transformation)) { return { kind: 'rotate', angle: -transformation.angle, x: transformation.x, y: transformation.y, }; } return { kind: 'scale', factor: 1 / transformation.factor, }; } exports.reverseTransformation = reverseTransformation; /** * Calculates the SVG transformation string that has to be applied to the SVG element. * @param bounds The bounds of the rendering. * @param decoration The decoration of the rendering. * @param kRotation The KRotation style of the rendering. * @param isEdge If the rendering is for an edge. * @param isText If the rendering is a text. */ function getTransformation(bounds, decoration, kRotation, isEdge, isText) { if (isEdge === undefined) { isEdge = false; } if (isText === undefined) { isText = false; } const transform = []; // Do the rotation for the element only if the decoration itself exists and is not 0. if (decoration !== undefined && (0, sprotty_protocol_1.toDegrees)(decoration.rotation) !== 0) { // The rotation itself const rotation = { kind: 'rotate', angle: (0, sprotty_protocol_1.toDegrees)(decoration.rotation) }; // If the rotation is around a point other than (0,0), add the additional parameters to the rotation. if (decoration.origin.x !== 0 || decoration.origin.y !== 0) { rotation.x = decoration.origin.x; rotation.y = decoration.origin.y; } transform.push(rotation); } // Translate if there are bounds and if the transformation is not for an edge or a text. This replicates the behavior of KIELER as edges don't really define bounds. if (!isEdge && !isText && bounds !== undefined && (bounds.x !== 0 || bounds.y !== 0)) { transform.push({ kind: 'translate', x: bounds.x, y: bounds.y }); } // Rotate the element also if a KRotation style has to be applied if (kRotation !== undefined && kRotation.rotation !== 0) { // The rotation itself const rotation = { kind: 'rotate', angle: kRotation.rotation }; // Rotate around a defined point other than (0,0) of the object only for non-edges. This replicates the behavior of KIELER as edges don't really define bounds. if (!isEdge) { if (kRotation.rotationAnchor === undefined) { // If the rotation anchor is undefined, rotate around the center by default. const CENTER = { x: { type: K_LEFT_POSITION, absolute: 0, relative: 0.5, }, y: { type: K_TOP_POSITION, absolute: 0, relative: 0.5, }, }; kRotation.rotationAnchor = CENTER; } const rotationAnchor = evaluateKPosition(kRotation.rotationAnchor, bounds, true); // If the rotation is around a point other than (0,0), add the additional parameters to the rotation. if (rotationAnchor.x !== 0 || rotationAnchor.y !== 0) { rotation.x = rotationAnchor.x; rotation.y = rotationAnchor.y; } } transform.push(rotation); } return transform; } exports.getTransformation = getTransformation; /** * calculates an array of all points that the polyline rendering should follow. * @param parent The parent element containing this rendering. * @param rendering The polyline rendering. * @param boundsAndTransformation The bounds and transformation data calculated by findBoundsAndTransformation(...). */ function getPoints(parent, rendering, boundsAndTransformation) { let points = []; // If the rendering has points defined, use them for the rendering. if ('points' in rendering) { const kPositions = rendering.points; kPositions.forEach((kPosition) => { const pos = evaluateKPosition(kPosition, boundsAndTransformation.bounds, true); points.push({ x: pos.x + boundsAndTransformation.bounds.x, y: pos.y + boundsAndTransformation.bounds.y, }); }); } else if ('routingPoints' in parent) { // If no points for the rendering are specified, the parent has to be and edge and have routing points. points = parent.routingPoints; } else { console.error('The rendering does not have any points for its routing.'); } // If the array is empty, do not continue trying to modify the points. if (points.length === 0) { return points; } const firstPoint = points[0]; let minX = firstPoint.x; let maxX = firstPoint.x; let minY = firstPoint.y; let maxY = firstPoint.y; for (let i = 1; i < points.length - 1; i++) { const p = points[i]; if (p.x < minX) { minX = p.x; } if (p.x > maxX) { maxX = p.x; } if (p.y < minY) { minY = p.y; } if (p.y > maxY) { maxY = p.y; } } // hack to avoid paths with no width / height. These paths will not get drawn by chrome due to a bug in their svg renderer TODO: find a fix if there is any better way const EPSILON = 0.001; if (points.length > 1) { const lastPoint = points[points.length - 1]; let lastX = lastPoint.x; let lastY = lastPoint.y; // if this path has no width and the last point does not add anything to that, we need to shift one value by a tiny, invisible value so the width will now be bigger than 0. if (maxX - minX === 0 && lastX === maxX) { lastX += EPSILON; points[points.length - 1] = { x: lastX, y: lastY }; } // same for Y if (maxY - minY === 0 && lastY === maxY) { lastY += EPSILON; points[points.length - 1] = { x: lastX, y: lastY }; } } return points; } exports.getPoints = getPoints; /** * Looks up the first KRendering in the list of data and returns it. KRenderingReferences are handled and dereferenced as well, so only 'real' renderings are returned. * @param datas The list of possible renderings. * @param context The rendering context for this rendering. */ function getKRendering(datas, context) { for (const data of datas) { if (data !== null && data.type === skgraph_models_1.K_RENDERING_REF) { if (context.kRenderingLibrary) { let id = data.properties['klighd.lsp.rendering.id']; // trim the ID to remove the leading parent graph element ID that is prefixed in rendering refs id = id.substring(id.indexOf('$$lib$$$')); for (const rendering of context.kRenderingLibrary.renderings) { if (rendering.properties['klighd.lsp.rendering.id'] === id) { context.boundsMap = data.properties['klighd.lsp.calculated.bounds.map']; context.decorationMap = data.properties['klighd.lsp.calculated.decoration.map']; return rendering; } } } else { console.log('No KRenderingLibrary for KRenderingRef in context'); } } if (data !== null && (0, skgraph_models_1.isRendering)(data)) { return data; } } return undefined; } exports.getKRendering = getKRendering; //# sourceMappingURL=views-common.js.map