@kieler/klighd-core
Version:
Core KLighD diagram visualization with Sprotty
745 lines • 32.4 kB
JavaScript
"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