diff --git a/src/editors/SingleLineDiagram.ts b/src/editors/SingleLineDiagram.ts index 981e347e7..515c99afa 100644 --- a/src/editors/SingleLineDiagram.ts +++ b/src/editors/SingleLineDiagram.ts @@ -14,8 +14,7 @@ import panzoom from 'panzoom'; import { Side } from '../../public/js/ortho-connector.js'; import { getAbsolutePosition, - SVG_GRID_SIZE, - drawRoute, + drawRouteBetweenElements, DEFAULT_ELEMENT_SIZE, createTerminalElement, createBusBarElement, @@ -23,6 +22,7 @@ import { createBayElement, createConductingEquipmentElement, createConnectivityNodeElement, + getElementDimensions, getAbsolutePositionConnectivityNode, getBusBarLength, createPowerTransformerElement, @@ -31,6 +31,7 @@ import { isBusBar, getConnectedTerminals, getPathNameAttribute, + getNameAttribute, } from './singlelinediagram/foundation.js'; /** @@ -174,47 +175,69 @@ export default class SingleLineDiagramPlugin extends LitElement { Array.from(bay.querySelectorAll('ConnectivityNode')) .filter(cNode => cNode.getAttribute('name') !== 'grounded') .forEach(cNode => { - const cnPosition = getAbsolutePositionConnectivityNode(cNode); - - Array.from( - this.doc.querySelectorAll('ConductingEquipment, PowerTransformer') - ) - .filter(element => - element.querySelector( + // For each Connectivity Node, the routes must be drawn. + const cNodeAbsolutePosition = getAbsolutePositionConnectivityNode(cNode); + const cNodeDimensions = getElementDimensions(getNameAttribute(bay)!, getNameAttribute(cNode)!, this.svg); + + // Get all the connected Conducting Equipments to this specific Connectivity Node.. + Array.from(this.doc.querySelectorAll('ConductingEquipment,PowerTransformer')) + .filter(cEquipment => + cEquipment.querySelector( `Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]` ) ) - .forEach(element => { - const elementPosition = getAbsolutePosition(element); - const terminalElement = element.querySelector( - `Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]` - ); - + .forEach(cEquipment => { + const cEquipmentAbsolutePosition = getAbsolutePosition(cEquipment); let sideToDrawTerminalOn: Side; - if (elementPosition.y! > cnPosition.y!) { - const sidesOfRoutes = drawRoute( - cnPosition, - elementPosition, + /** + * TODO: ConductingEquipment dimensions are just the defaults, + * retrieving dimensions the same way as for ConnectivityNode doesn't work. + * + * Instead, just insert DEFAULT_ELEMENT_SIZE for height and width for ConductingEquipment. + */ + if (cEquipmentAbsolutePosition.y! > cNodeAbsolutePosition.y!) { + const sidesOfRoutes = drawRouteBetweenElements( + cNodeAbsolutePosition, + cEquipmentAbsolutePosition, + cNodeDimensions, + { + height: DEFAULT_ELEMENT_SIZE, + width: DEFAULT_ELEMENT_SIZE + }, this.svg ); sideToDrawTerminalOn = sidesOfRoutes.pointBSide; } else { - const sidesOfRoutes = drawRoute( - elementPosition, - cnPosition, + const sidesOfRoutes = drawRouteBetweenElements( + cEquipmentAbsolutePosition, + cNodeAbsolutePosition, + { + height: DEFAULT_ELEMENT_SIZE, + width: DEFAULT_ELEMENT_SIZE + }, + cNodeDimensions, this.svg ); sideToDrawTerminalOn = sidesOfRoutes.pointASide; } + /** + * Add the terminal belonging to the connected Conducting Equipment. + */ + const terminalElement = cEquipment.querySelector( + `Terminal[connectivityNode="${cNode.getAttribute('pathName')}"]` + ); + + // Create the Terminal SVG element and add it to the Conducting Equipment group. const terminal = createTerminalElement( - elementPosition, + cEquipmentAbsolutePosition, sideToDrawTerminalOn, terminalElement! ); + this.svg - .querySelectorAll(`g[id="${identity(element)}"]`) + .querySelectorAll(`g[id="${identity(cEquipment)}"]`) .forEach(eq => eq.appendChild(terminal)); }); }); @@ -227,46 +250,62 @@ export default class SingleLineDiagramPlugin extends LitElement { const busBarPosition = getAbsolutePosition(busBar); Array.from(this.doc.querySelectorAll('ConductingEquipment')) - .filter(element => - element.querySelector(`Terminal[connectivityNode="${pathName}"]`) + .filter(cEquipment => + cEquipment.querySelector(`Terminal[connectivityNode="${pathName}"]`) ) - .forEach(element => { - const eqPosition = getAbsolutePosition(element); - const terminalElement = element.querySelector( - `Terminal[connectivityNode="${pathName}"]` - ); + .forEach(cEquipment => { + const cEquipmentAbsolutePosition = getAbsolutePosition(cEquipment); let sideToDrawTerminalOn: Side; // Height of busbar shape should be 1, because it's smaller. const customShape = { width: DEFAULT_ELEMENT_SIZE, height: 1 }; - // The X coordinate of - if (busBarPosition.y! > eqPosition.y!) { - const sidesOfRoutes = drawRoute( - eqPosition, - { x: eqPosition.x, y: busBarPosition.y }, + if (busBarPosition.y! > cEquipmentAbsolutePosition.y!) { + const sidesOfRoutes = drawRouteBetweenElements( + cEquipmentAbsolutePosition, + // The x of the busbar position should equal the x of the Conducting Equipment, + // so it will be a straight line. + { + x: cEquipmentAbsolutePosition.x!, + // The drawRoute function draws the routes to the middle of the elements, + // for the Bus Bar the height is 1, so we extract the value that is added in the function. + y: busBarPosition.y! - ((DEFAULT_ELEMENT_SIZE - customShape.height) / 2) + }, + customShape, + customShape, this.svg, - customShape ); sideToDrawTerminalOn = sidesOfRoutes.pointASide; } else { - const sidesOfRoutes = drawRoute( - { x: eqPosition.x, y: busBarPosition.y }, - eqPosition, + const sidesOfRoutes = drawRouteBetweenElements( + { + x: cEquipmentAbsolutePosition.x!, + y: busBarPosition.y! - ((DEFAULT_ELEMENT_SIZE - customShape.height) / 2) + }, + cEquipmentAbsolutePosition, + customShape, + customShape, this.svg, - customShape ); sideToDrawTerminalOn = sidesOfRoutes.pointBSide; } + /** + * Add the terminal belonging to the connected Conducting Equipment. + */ + const terminalElement = cEquipment.querySelector( + `Terminal[connectivityNode="${pathName}"]` + ); + const terminal = createTerminalElement( - eqPosition, + cEquipmentAbsolutePosition, sideToDrawTerminalOn, terminalElement! ); + this.svg - .querySelectorAll(` g[id="${identity(element)}"]`) + .querySelectorAll(` g[id="${identity(cEquipment)}"]`) .forEach(eq => eq.appendChild(terminal)); }); }); diff --git a/src/editors/singlelinediagram/sld-drawing.ts b/src/editors/singlelinediagram/sld-drawing.ts index c7abfe600..bd8bafef3 100644 --- a/src/editors/singlelinediagram/sld-drawing.ts +++ b/src/editors/singlelinediagram/sld-drawing.ts @@ -350,24 +350,32 @@ export function createConnectivityNodeElement( * @param shape - A custom shape defining custom height and width of the shapes. * @returns The sides where the routes are being drawn next to both points. */ -export function drawRoute( +export function drawRouteBetweenElements( pointA: Point, pointB: Point, + pointAShape: Shape, + pointBShape: Shape, svgToDrawOn: HTMLElement, - shape?: Shape ): PointSides { + /** + * The point on each side of the route should be in the middle of the element, + * so we have to do a little conversion of the 'left' and 'top' coordinate. + */ + const positionMiddleOfA = convertRoutePointToMiddleOfElement(pointA, pointAShape); + const positionMiddleOfB = convertRoutePointToMiddleOfElement(pointB, pointBShape); + const shapeA = { - left: pointA.x!, - top: pointA.y!, - width: shape?.width ?? DEFAULT_ELEMENT_SIZE, - height: shape?.height ?? DEFAULT_ELEMENT_SIZE, + left: positionMiddleOfA.x!, + top: positionMiddleOfA.y!, + width: pointAShape?.width, + height: pointAShape?.height, }; const shapeB = { - left: pointB.x!, - top: pointB.y!, - width: shape?.width ?? DEFAULT_ELEMENT_SIZE, - height: shape?.height ?? DEFAULT_ELEMENT_SIZE, + left: positionMiddleOfB.x!, + top: positionMiddleOfB.y!, + width: pointBShape?.width, + height: pointBShape?.height, }; // Get the preferred sides. @@ -399,13 +407,33 @@ export function drawRoute( line.setAttribute('d', d); line.setAttribute('fill', 'transparent'); line.setAttribute('stroke', 'currentColor'); - line.setAttribute('stroke-width', '1'); + line.setAttribute('stroke-width', '1.5'); svgToDrawOn.appendChild(line); return sides; } +/** + * Get the dimensions of a specific element within a specific bay. + * @param bayName - The name of the bay. + * @param elementName - The name of the element. + * @param svg - The SVG to search on. + * @returns The shape (width and height) of the specific element. + */ + export function getElementDimensions(bayName: string, elementName: string, svg: HTMLElement): Shape { + let {height, width} = {height: 0, width: 0}; + + svg.querySelectorAll( + `g[id="${bayName}"] > g[id="${elementName}"]` + ).forEach(b => { + height = b.getBoundingClientRect().height; + width = b.getBoundingClientRect().width; + }); + + return {height, width}; +} + /** * Small simple algorithm deciding on which side the route should be drawn * of two ends of a route. @@ -486,7 +514,19 @@ function getAbsolutePositionTerminal( } /** - * Calculate length of the busbar that is depending on the most far right equipment + * Convert a top left coordinate to the middle of an element. + * @param point - The top left point of the element. + * @param shape - The shape of the element. + * @returns The point of the element in the middle. + */ +function convertRoutePointToMiddleOfElement(point: Point, shape: Shape): Point { + return { + x: point.x! + ((DEFAULT_ELEMENT_SIZE - shape.width) / 2), + y: point.y! + ((DEFAULT_ELEMENT_SIZE - shape.height) / 2) + } +} + +/* Calculate length of the busbar that is depending on the most far right equipment * @param root - Either the whole SCL file or the voltage level where the bus bar resides * @returns - the length of the bus bar */