Skip to content

Commit 1058d04

Browse files
feat: highlight linked equipment in IED select
1 parent 44f9d45 commit 1058d04

File tree

9 files changed

+282
-61
lines changed

9 files changed

+282
-61
lines changed

communication-mapping-editor.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ export class CommunicationMappingEditor extends LitElement {
8585

8686
@state() mouseY2 = 0;
8787

88+
@state() linkedEquipments: Element[] = [];
89+
8890
@state()
8991
get idle(): boolean {
9092
return !(this.placing || this.placingLabel);
@@ -259,6 +261,24 @@ export class CommunicationMappingEditor extends LitElement {
259261
return !(service || ied || source || target || cbName || receive || send);
260262
}
261263

264+
resetIedSelection(): void {
265+
this.selectedIed = undefined;
266+
this.linkedEquipments = [];
267+
}
268+
269+
selectIED(ied: IED): void {
270+
if (this.selectedIed !== ied.element) {
271+
this.selectedIed = ied.element;
272+
this.linkedEquipments = Array.from(
273+
this.selectedIed.ownerDocument.querySelectorAll(
274+
`ConductingEquipment LNode[iedName="${this.selectedIed.getAttribute(
275+
'name'
276+
)}"]`
277+
)
278+
).map(lNode => lNode.closest('ConductingEquipment')!);
279+
} else this.resetIedSelection();
280+
}
281+
262282
constructor() {
263283
super();
264284

@@ -353,8 +373,7 @@ export class CommunicationMappingEditor extends LitElement {
353373
handleClick = () => this.startPlacing(ied.element);
354374
else if (!this.editMode)
355375
handleClick = () => {
356-
if (this.selectedIed !== ied.element) this.selectedIed = ied.element;
357-
else this.selectedIed = undefined;
376+
this.selectIED(ied);
358377
};
359378

360379
return svg`<svg
@@ -508,7 +527,7 @@ export class CommunicationMappingEditor extends LitElement {
508527
offIcon="edit_off"
509528
@click="${(evt: Event) => {
510529
this.editMode = (evt.target as IconButtonToggle).on;
511-
this.selectedIed = undefined;
530+
this.resetIedSelection();
512531
}}"
513532
></mwc-icon-button-toggle>
514533
<mwc-icon-button
@@ -576,7 +595,9 @@ export class CommunicationMappingEditor extends LitElement {
576595
return html` ${this.renderInfoBox()}
577596
<div id="container">
578597
<style>
579-
${this.showLabel ? nothing : `.label:not(.ied) {display: none} `}
598+
${this.showLabel
599+
? nothing
600+
: `.label:not(.ied):not(.linked) {display: none} `}
580601
</style>
581602
<svg
582603
xmlns="${svgNs}"
@@ -595,7 +616,10 @@ export class CommunicationMappingEditor extends LitElement {
595616
this.mouseY2 = Math.round(y * 2) / 2;
596617
}}
597618
>
598-
${sldSvg(this.substation, this.gridSize)}
619+
${sldSvg(this.substation, {
620+
gridSize: this.gridSize,
621+
linkedEquipments: this.linkedEquipments,
622+
})}
599623
${this.ieds.map(ied => this.renderIED(ied))}
600624
${this.ieds.map(ied => this.renderLabel(ied.element))}
601625
${placingLabelTarget} ${iedPlacingTarget}
@@ -613,7 +637,7 @@ export class CommunicationMappingEditor extends LitElement {
613637
background-color: white;
614638
}
615639
616-
g.equipment {
640+
g.equipment:not(.linked) {
617641
opacity: 0.2;
618642
}
619643
@@ -625,7 +649,7 @@ export class CommunicationMappingEditor extends LitElement {
625649
opacity: 0.2;
626650
}
627651
628-
g.label:not(.ied) {
652+
g.label:not(.ied):not(.linked) {
629653
opacity: 0.2;
630654
}
631655
@@ -681,5 +705,10 @@ export class CommunicationMappingEditor extends LitElement {
681705
bottom: 15px;
682706
right: 15px;
683707
}
708+
709+
.linked > rect {
710+
fill: black;
711+
opacity: 0.1;
712+
}
684713
`;
685714
}

foundation/sldSvg.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
ringedEqTypes,
1919
robotoDataURL,
2020
sldNs,
21+
SldSvgOptions,
2122
svgNs,
2223
xlinkNs,
2324
xmlBoolean,
@@ -43,7 +44,7 @@ function renderedLabelPosition(element: Element): Point {
4344
return [x, y];
4445
}
4546

46-
function renderLabel(element: Element) {
47+
function renderLabel(element: Element, linkedEquipments: Element[]) {
4748
const deg = 0;
4849
const text: string | null | TemplateResult<2>[] =
4950
element.getAttribute('name');
@@ -64,6 +65,7 @@ function renderLabel(element: Element) {
6465
element.tagName === 'VoltageLevel',
6566
ied: element.tagName === 'IED',
6667
equipment: element.tagName === 'ConductingEquipment',
68+
linked: linkedEquipments.includes(element),
6769
});
6870
return svg`<g class="${classes}" id="label:${id}"
6971
transform="rotate(${deg} ${x + 0.5} ${y - 0.5})">
@@ -432,7 +434,7 @@ function renderConnectivityNode(cNode: Element) {
432434
</g>`;
433435
}
434436

435-
function renderEquipment(equipment: Element) {
437+
function renderEquipment(equipment: Element, linkedEquipments: Element[]) {
436438
const [x, y] = renderedPosition(equipment);
437439
const { flip, rot } = attributes(equipment);
438440
const deg = 90 * rot;
@@ -469,7 +471,10 @@ function renderEquipment(equipment: Element) {
469471
stroke-width="0.06" marker-start="url(#grounded)" />`
470472
: nothing;
471473

472-
return svg`<g class="${classMap({ equipment: true })}"
474+
return svg`<g class="${classMap({
475+
equipment: true,
476+
linked: linkedEquipments.includes(equipment),
477+
})}"
473478
id="${identity(equipment)}"
474479
transform="translate(${x} ${y}) rotate(${deg} 0.5 0.5)${
475480
flip ? ' scale(-1,1) translate(-1 0)' : ''
@@ -488,7 +493,10 @@ function renderEquipment(equipment: Element) {
488493
</g>`;
489494
}
490495

491-
function renderContainer(bayOrVL: Element): TemplateResult<2> {
496+
function renderContainer(
497+
bayOrVL: Element,
498+
selectedElements: Element[]
499+
): TemplateResult<2> {
492500
const isVL = bayOrVL.tagName === 'VoltageLevel';
493501

494502
const [x, y] = renderedPosition(bayOrVL);
@@ -506,18 +514,21 @@ function renderContainer(bayOrVL: Element): TemplateResult<2> {
506514
stroke="${isVL ? '#2aa198' : '#6c71c4'}" />
507515
${Array.from(bayOrVL.children)
508516
.filter(isBay)
509-
.map(bay => renderContainer(bay))}
517+
.map(bay => renderContainer(bay, selectedElements))}
510518
${Array.from(bayOrVL.children)
511519
.filter(child => child.tagName === 'ConductingEquipment')
512-
.map(equipment => renderEquipment(equipment))}
520+
.map(equipment => renderEquipment(equipment, selectedElements))}
513521
${Array.from(bayOrVL.children)
514522
.filter(child => child.tagName === 'PowerTransformer')
515523
.map(equipment => renderPowerTransformer(equipment))}
516524
</g>`;
517525
}
518526

519-
export function sldSvg(substation: Element, gridSize?: number): TemplateResult {
520-
const nested = !gridSize;
527+
export function sldSvg(
528+
substation: Element,
529+
options: SldSvgOptions
530+
): TemplateResult {
531+
const nested = !options.gridSize;
521532

522533
const {
523534
dim: [w, h],
@@ -527,8 +538,8 @@ export function sldSvg(substation: Element, gridSize?: number): TemplateResult {
527538
xmlns="${svgNs}"
528539
xmlns:xlink="${xlinkNs}"
529540
${nested ? nothing : `viewBox = '0 0 ${w} ${h}'`}
530-
${nested ? nothing : `width="${w * gridSize}"`}
531-
${nested ? nothing : `height="${h * gridSize}"`}
541+
${nested ? nothing : `width="${w * options.gridSize!}"`}
542+
${nested ? nothing : `height="${h * options.gridSize!}"`}
532543
id="sld"
533544
stroke-width="0.06"
534545
fill="none"
@@ -545,7 +556,7 @@ export function sldSvg(substation: Element, gridSize?: number): TemplateResult {
545556
<rect width="100%" height="100%" fill="white" />
546557
${Array.from(substation.children)
547558
.filter(child => child.tagName === 'VoltageLevel')
548-
.map(vl => svg`${renderContainer(vl)}`)}
559+
.map(vl => svg`${renderContainer(vl, options.linkedEquipments)}`)}
549560
${Array.from(substation.querySelectorAll('ConnectivityNode'))
550561
.filter(
551562
node =>
@@ -567,6 +578,6 @@ export function sldSvg(substation: Element, gridSize?: number): TemplateResult {
567578
substation.querySelectorAll(
568579
'VoltageLevel, Bay, ConductingEquipment, PowerTransformer, Line'
569580
)
570-
).map(element => renderLabel(element))}
581+
).map(element => renderLabel(element, options.linkedEquipments))}
571582
</svg>`;
572583
}

foundation/sldUtil.ts

Lines changed: 5 additions & 0 deletions
Large diffs are not rendered by default.

scl-communication-editor.test.ts

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { sendKeys, sendMouse, setViewport } from '@web/test-runner-commands';
44

55
import { visualDiff } from '@web/test-runner-visual-regression';
66

7-
import { commScd, scd, ssd } from './testfiles.js';
7+
import { commScd, lNodeConn, scd, ssd } from './testfiles.js';
88

99
import SlcCommunicationEditor from './scl-communication-editor.js';
1010

@@ -21,6 +21,7 @@ customElements.define('scl-communication-editor', SlcCommunicationEditor);
2121
const pureSSD = new DOMParser().parseFromString(ssd, 'application/xml');
2222
const docWithIED = new DOMParser().parseFromString(scd, 'application/xml');
2323
const docComm = new DOMParser().parseFromString(commScd, 'application/xml');
24+
const docLNode = new DOMParser().parseFromString(lNodeConn, 'application/xml');
2425

2526
function wheel(editor: SlcCommunicationEditor, type: 'in' | 'out'): void {
2627
const wheelEvent = new WheelEvent('wheel', {
@@ -397,68 +398,106 @@ describe('scl-communication-editor', () => {
397398

398399
describe('with selected IED', () => {
399400
let editor: SlcCommunicationEditor;
400-
beforeEach(async () => {
401-
editor = await fixture(
402-
html`<scl-communication-editor
403-
.doc=${docComm}
404-
></scl-communication-editor>`
405-
);
406-
div.prepend(editor);
407401

408-
await setViewport({ width: 1200, height: 800 });
402+
describe('in a zero line view', () => {
403+
beforeEach(async () => {
404+
editor = await fixture(
405+
html`<scl-communication-editor
406+
.doc=${docComm}
407+
></scl-communication-editor>`
408+
);
409+
div.prepend(editor);
409410

410-
await sendMouse({ type: 'click', position: [172, 220] });
411-
});
411+
await setViewport({ width: 1200, height: 800 });
412412

413-
afterEach(async () => {
414-
editor.remove();
415-
});
413+
await sendMouse({ type: 'click', position: [172, 220] });
414+
});
416415

417-
it('per default looks like the latest snapshot', async () => {
418-
await editor.updateComplete;
419-
await timeout(200);
420-
await visualDiff(editor, `#19 filter on IED select`);
421-
});
416+
afterEach(async () => {
417+
editor.remove();
418+
});
422419

423-
it('with filtered receiving messages looks like the latest snapshot', async () => {
424-
await editor.updateComplete;
420+
it('per default looks like the latest snapshot', async () => {
421+
await editor.updateComplete;
422+
await timeout(200);
423+
await visualDiff(editor, `#19 filter on IED select`);
424+
});
425425

426-
await sendMouse({ type: 'click', position: [577, 24] });
426+
it('with filtered receiving messages looks like the latest snapshot', async () => {
427+
await editor.updateComplete;
427428

428-
await timeout(200);
429+
await sendMouse({ type: 'click', position: [577, 24] });
429430

430-
await visualDiff(editor, `#20 filter receiving messages`);
431-
});
431+
await timeout(200);
432432

433-
it('with filtered sending messages looks like the latest snapshot', async () => {
434-
await editor.updateComplete;
433+
await visualDiff(editor, `#20 filter receiving messages`);
434+
});
435+
436+
it('with filtered sending messages looks like the latest snapshot', async () => {
437+
await editor.updateComplete;
435438

436-
await sendMouse({ type: 'click', position: [638, 24] });
439+
await sendMouse({ type: 'click', position: [638, 24] });
437440

438-
await timeout(200);
441+
await timeout(200);
439442

440-
await visualDiff(editor, `#20 filter sending messages`);
441-
});
443+
await visualDiff(editor, `#20 filter sending messages`);
444+
});
442445

443-
it('with filtered sending and receiving messages looks like the latest snapshot', async () => {
444-
await editor.updateComplete;
446+
it('with filtered sending and receiving messages looks like the latest snapshot', async () => {
447+
await editor.updateComplete;
445448

446-
await sendMouse({ type: 'click', position: [577, 24] });
447-
await sendMouse({ type: 'click', position: [638, 24] });
449+
await sendMouse({ type: 'click', position: [577, 24] });
450+
await sendMouse({ type: 'click', position: [638, 24] });
448451

449-
await timeout(200);
452+
await timeout(200);
453+
454+
await visualDiff(editor, `#21 filter sending and receiving messages`);
455+
});
456+
457+
it('with IEd unselcted looks like the latest snapshot', async () => {
458+
await editor.updateComplete;
459+
460+
await sendMouse({ type: 'click', position: [172, 220] });
461+
462+
await timeout(200);
450463

451-
await visualDiff(editor, `#21 filter sending and receiving messages`);
464+
await visualDiff(editor, `#22 un done IED selection`);
465+
});
452466
});
453467

454-
it('with IEd unselcted looks like the latest snapshot', async () => {
455-
await editor.updateComplete;
468+
describe('in a SLD view', () => {
469+
beforeEach(async () => {
470+
editor = await fixture(
471+
html`<scl-communication-editor
472+
.doc=${docLNode}
473+
></scl-communication-editor>`
474+
);
475+
div.prepend(editor);
476+
477+
await setViewport({ width: 600, height: 800 });
478+
});
456479

457-
await sendMouse({ type: 'click', position: [172, 220] });
480+
afterEach(async () => {
481+
editor.remove();
482+
});
458483

459-
await timeout(200);
484+
it('and BCU selected selected looks like the latest snapshot', async () => {
485+
await editor.updateComplete;
486+
487+
await sendMouse({ type: 'click', position: [270, 175] });
488+
489+
await timeout(200);
490+
await visualDiff(editor, `#31 BCU selected`);
491+
});
460492

461-
await visualDiff(editor, `#22 un done IED selection`);
493+
it('and MU selected selected looks like the latest snapshot', async () => {
494+
await editor.updateComplete;
495+
496+
await sendMouse({ type: 'click', position: [270, 368] });
497+
498+
await timeout(200);
499+
await visualDiff(editor, `#32 MU selected`);
500+
});
462501
});
463502
});
464503

36.8 KB
Loading
36.6 KB
Loading
38.1 KB
Loading
38 KB
Loading

0 commit comments

Comments
 (0)