Skip to content

Commit

Permalink
fix(SingleLineDiagram): not connected route in SLD Editor (#391)
Browse files Browse the repository at this point in the history
* Intermediate commit

* Added default dimensions for ConductingEquipment

* Slow revert

* Make ESLint happy

* Small refactoring

* Removed drawRoute alias

Co-authored-by: Rob Tjalma <rob@tjalma.com>
  • Loading branch information
2 people authored and ca-d committed Nov 30, 2021
1 parent 4088ebd commit f101fa8
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 55 deletions.
125 changes: 82 additions & 43 deletions src/editors/SingleLineDiagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import panzoom from 'panzoom';
import { Side } from '../../public/js/ortho-connector.js';
import {
getAbsolutePosition,
SVG_GRID_SIZE,
drawRoute,
drawRouteBetweenElements,
DEFAULT_ELEMENT_SIZE,
createTerminalElement,
createBusBarElement,
createVoltageLevelElement,
createBayElement,
createConductingEquipmentElement,
createConnectivityNodeElement,
getElementDimensions,
getAbsolutePositionConnectivityNode,
getBusBarLength,
createPowerTransformerElement,
Expand All @@ -31,6 +31,7 @@ import {
isBusBar,
getConnectedTerminals,
getPathNameAttribute,
getNameAttribute,
} from './singlelinediagram/foundation.js';

/**
Expand Down Expand Up @@ -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));
});
});
Expand All @@ -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));
});
});
Expand Down
64 changes: 52 additions & 12 deletions src/editors/singlelinediagram/sld-drawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
*/
Expand Down

0 comments on commit f101fa8

Please sign in to comment.