Skip to content

Commit

Permalink
fixed port order to group edges better
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakae committed Feb 13, 2024
1 parent a81b01c commit 5da476e
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 48 deletions.
39 changes: 29 additions & 10 deletions extension/src-language-server/stpa/diagram/diagram-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { getDescription } from "../../utils";
import { StpaServices } from "../stpa-module";
import { collectElementsWithSubComps, leafElement } from "../utils";
import { filterModel } from "./filtering";
import { CSEdge, CSNode, ParentNode, STPAEdge, STPANode, STPAPort } from "./stpa-interfaces";
import { CSEdge, CSNode, ParentNode, STPAEdge, STPANode, PastaPort } from "./stpa-interfaces";
import {
CS_EDGE_TYPE,
CS_INTERMEDIATE_EDGE_TYPE,
Expand All @@ -53,7 +53,7 @@ import {
STPA_EDGE_TYPE,
STPA_INTERMEDIATE_EDGE_TYPE,
STPA_NODE_TYPE,
STPA_PORT_TYPE,
PORT_TYPE,
} from "./stpa-model";
import { StpaSynthesisOptions, showLabelsValue } from "./stpa-synthesis-options";
import {
Expand All @@ -64,6 +64,7 @@ import {
getTargets,
setLevelOfCSNodes,
setLevelsForSTPANodes,
sortPorts,
} from "./utils";

export class StpaDiagramGenerator extends LangiumDiagramGenerator {
Expand Down Expand Up @@ -271,6 +272,7 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator {
...this.generateVerticalCSEdges(filteredModel.controlStructure.nodes, args),
//...this.generateHorizontalCSEdges(filteredModel.controlStructure.edges, args)
];
sortPorts(CSChildren.filter(node => node.type.startsWith("node")) as CSNode[]);
// add control structure to roots children
rootChildren.push({
type: PARENT_TYPE,
Expand Down Expand Up @@ -864,15 +866,30 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator {
}

protected generateIntermediateCSEdges(
source: AstNode | undefined,
target: AstNode | undefined,
source: Node | undefined,
target: Node | undefined,
edgeId: string,
edgeType: EdgeType,
args: GeneratorContext<Model>,
ancestor?: Node | Graph
): { sourcePort: string; targetPort: string } {
const sources = this.generatePortsForCSHierarchy(source, edgeId, edgeType === EdgeType.CONTROL_ACTION ? PortSide.SOUTH : PortSide.NORTH, args.idCache, ancestor);
const targets = this.generatePortsForCSHierarchy(target, edgeId, edgeType === EdgeType.CONTROL_ACTION ? PortSide.NORTH : PortSide.SOUTH, args.idCache, ancestor);
const assocEdge = { node1: source?.name ?? "", node2: target?.name ?? "" };
const sources = this.generatePortsForCSHierarchy(
source,
assocEdge,
edgeId,
edgeType === EdgeType.CONTROL_ACTION ? PortSide.SOUTH : PortSide.NORTH,
args.idCache,
ancestor
);
const targets = this.generatePortsForCSHierarchy(
target,
assocEdge,
edgeId,
edgeType === EdgeType.CONTROL_ACTION ? PortSide.NORTH : PortSide.SOUTH,
args.idCache,
ancestor
);
for (let i = 0; i < sources.nodes.length - 1; i++) {
const sEdgeType = CS_INTERMEDIATE_EDGE_TYPE;
sources.nodes[i + 1]?.children?.push(
Expand Down Expand Up @@ -912,6 +929,7 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator {
// adds ports for current node and its (grand)parents up to the ancestor. The ancestor get no port.
protected generatePortsForCSHierarchy(
current: AstNode | undefined,
assocEdge: { node1: string; node2: string },
edgeId: string,
side: PortSide,
idCache: IdCache<AstNode>,
Expand All @@ -928,12 +946,12 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator {
if (invisibleChild && ids.length !== 0) {
// add port for the invisible node first
const invisiblePortId = idCache.uniqueId(edgeId + "_newTransition");
invisibleChild.children?.push(this.createPort(invisiblePortId, side));
invisibleChild.children?.push(this.createPort(invisiblePortId, side, assocEdge));
ids.push(invisiblePortId);
nodes.push(invisibleChild);
}
const nodePortId = idCache.uniqueId(edgeId + "_newTransition");
currentNode?.children?.push(this.createPort(nodePortId, side));
currentNode?.children?.push(this.createPort(nodePortId, side, assocEdge));
ids.push(nodePortId);
nodes.push(currentNode);
current = current?.$container;
Expand Down Expand Up @@ -982,11 +1000,12 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator {
* @param side The side of the port.
* @returns an STPAPort.
*/
protected createPort(id: string, side: PortSide): STPAPort {
protected createPort(id: string, side: PortSide, assocEdge?: { node1: string; node2: string }): PastaPort {
return {
type: STPA_PORT_TYPE,
type: PORT_TYPE,
id: id,
side: side,
assocEdge: assocEdge,
};
}

Expand Down
14 changes: 7 additions & 7 deletions extension/src-language-server/stpa/diagram/layout-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
import { LayoutOptions } from "elkjs";
import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout";
import { SGraph, SModelIndex, SNode, SPort } from "sprotty-protocol";
import { CSNode, ParentNode, STPANode, STPAPort } from "./stpa-interfaces";
import { CSNode, ParentNode, STPANode, PastaPort } from "./stpa-interfaces";
import {
CS_NODE_TYPE,
INVISIBLE_NODE_TYPE,
PARENT_TYPE,
PROCESS_MODEL_NODE_TYPE,
PortSide,
STPA_NODE_TYPE,
STPA_PORT_TYPE,
PORT_TYPE,
} from "./stpa-model";

export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
Expand Down Expand Up @@ -95,10 +95,10 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
}
processModelNodeOptions(snode: SNode): LayoutOptions | undefined {
return {
"org.eclipse.elk.separateConnectedComponents": "false",
"org.eclipse.elk.direction": "DOWN",
"org.eclipse.elk.layered.considerModelOrder.strategy": "NODES_AND_EDGES",
"org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder": "true"
"org.eclipse.elk.layered.crossingMinimization.forceNodeModelOrder": "true",
// TODO: wait for node size fix in elkjs
// "org.eclipse.elk.algorithm": "rectpacking",
};
}

Expand Down Expand Up @@ -172,9 +172,9 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator {
}

protected portOptions(sport: SPort, index: SModelIndex): LayoutOptions | undefined {
if (sport.type === STPA_PORT_TYPE) {
if (sport.type === PORT_TYPE) {
let side = "";
switch ((sport as STPAPort).side) {
switch ((sport as PastaPort).side) {
case PortSide.WEST:
side = "WEST";
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ export interface STPANode extends SNode {
}

/** Port representing a port in the STPA graph. */
export interface STPAPort extends SPort {
export interface PastaPort extends SPort {
side?: PortSide
assocEdge?: {node1: string, node2: string}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion extension/src-language-server/stpa/diagram/stpa-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const CS_EDGE_TYPE = 'edge:controlStructure';
export const STPA_EDGE_TYPE = 'edge:stpa';
export const STPA_INTERMEDIATE_EDGE_TYPE = 'edge:stpa-intermediate';
export const CS_INTERMEDIATE_EDGE_TYPE = 'edge:cs-intermediate';
export const STPA_PORT_TYPE = 'port:stpa';
export const PORT_TYPE = 'port:pasta';
export const HEADER_LABEL_TYPE = 'label:header';

/**
Expand Down
43 changes: 37 additions & 6 deletions extension/src-language-server/stpa/diagram/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import {
isSystemResponsibilities,
isUCA,
} from "../../generated/ast";
import { STPANode } from "./stpa-interfaces";
import { CSNode, PastaPort, STPANode } from "./stpa-interfaces";
import { STPAAspect } from "./stpa-model";
import { groupValue } from "./stpa-synthesis-options";
import { SModelElement } from "sprotty-protocol"

Check failure on line 40 in extension/src-language-server/stpa/diagram/utils.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

Missing semicolon

/**
* Getter for the references contained in {@code node}.
Expand Down Expand Up @@ -364,8 +365,7 @@ export function getAspectsThatShouldHaveDesriptions(model: Model): STPAAspect[]
return aspectsToShowDescriptions;
}


export function getCommonAncestor(node: Node, target: Node): Node|Graph | undefined {
export function getCommonAncestor(node: Node, target: Node): Node | Graph | undefined {
const nodeAncestors = getAncestors(node);
const targetAncestors = getAncestors(target);
for (const ancestor of nodeAncestors) {
Expand All @@ -376,12 +376,43 @@ export function getCommonAncestor(node: Node, target: Node): Node|Graph | undefi
return undefined;
}

export function getAncestors(node: Node): (Node|Graph)[] {
const ancestors: (Node|Graph)[] = [];
export function getAncestors(node: Node): (Node | Graph)[] {
const ancestors: (Node | Graph)[] = [];
let current: Node | Graph | undefined = node;
while (current?.$type !== "Graph") {
ancestors.push(current.$container);
current = current.$container;
}
return ancestors;
}
}

export function sortPorts(nodes: CSNode[]): void {
for (const node of nodes) {
const children = node.children?.filter(child => child.type.startsWith("node")) as CSNode[];
sortPorts(children);
const ports: PastaPort[] = []

Check failure on line 393 in extension/src-language-server/stpa/diagram/utils.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

Missing semicolon
const otherChildren: SModelElement[] = []

Check failure on line 394 in extension/src-language-server/stpa/diagram/utils.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

Missing semicolon
node.children?.forEach(child => {
if (child.type.startsWith("port")) {
ports.push(child as any as PastaPort);
} else {
otherChildren.push(child);
}
});

const newPorts: PastaPort[] = [];
for (const port of ports) {
newPorts.push(port);
if (port.assocEdge) {
for (const otherPort of ports) {
if (port.assocEdge.node1 == otherPort.assocEdge?.node2 && port.assocEdge.node2 == otherPort.assocEdge.node1) {

Check failure on line 408 in extension/src-language-server/stpa/diagram/utils.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

Expected '===' and instead saw '=='

Check failure on line 408 in extension/src-language-server/stpa/diagram/utils.ts

View workflow job for this annotation

GitHub Actions / Run eslint and test and build

Expected '===' and instead saw '=='
newPorts.push(otherPort);
ports.splice(ports.indexOf(otherPort), 1);
}
}
}
}

node.children = [...otherChildren, ...newPorts];
}
}
2 changes: 1 addition & 1 deletion extension/src-language-server/stpa/stpa-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const STPAModule: Module<StpaServices, PartialLangiumServices & SprottyDi
StpaValidator: () => new StpaValidator(),
},
layout: {
ElkFactory: () => () => new ElkConstructor({ algorithms: ["layered"] }),
ElkFactory: () => () => new ElkConstructor({ algorithms: ["layered", "rectpacking"] }),
ElementFilter: () => new DefaultElementFilter(),
LayoutConfigurator: () => new StpaLayoutConfigurator(),
},
Expand Down
6 changes: 3 additions & 3 deletions extension/src-webview/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ import {
PROCESS_MODEL_NODE_TYPE,
STPAEdge,
STPANode,
STPAPort,
PastaPort,
STPA_EDGE_TYPE,
STPA_INTERMEDIATE_EDGE_TYPE,
STPA_NODE_TYPE,
STPA_PORT_TYPE,
PORT_TYPE,
} from "./stpa/stpa-model";
import { StpaMouseListener } from "./stpa/stpa-mouselistener";
import {
Expand Down Expand Up @@ -118,7 +118,7 @@ const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =
configureModelElement(context, STPA_INTERMEDIATE_EDGE_TYPE, STPAEdge, IntermediateEdgeView);
configureModelElement(context, CS_INTERMEDIATE_EDGE_TYPE, CSEdge, IntermediateEdgeView);
configureModelElement(context, CS_EDGE_TYPE, CSEdge, PolylineArrowEdgeView);
configureModelElement(context, STPA_PORT_TYPE, STPAPort, PortView);
configureModelElement(context, PORT_TYPE, PastaPort, PortView);

// FTA
configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA);
Expand Down
34 changes: 17 additions & 17 deletions extension/src-webview/stpa/helper-methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { SEdge, SModelElement, SNode } from "sprotty";
import { PortSide, STPAAspect, STPAEdge, STPANode, STPAPort, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE, STPA_PORT_TYPE } from "./stpa-model";
import { PortSide, STPAAspect, STPAEdge, STPANode, PastaPort, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE, PORT_TYPE } from "./stpa-model";

/**
* Collects all children of the nodes in {@code nodes}.
Expand Down Expand Up @@ -45,23 +45,23 @@ export function flagConnectedElements(node: SNode): (STPANode | STPAEdge)[] {
// flagging four sub components
flaggingOutgoingForSubcomponents(node as STPANode, elements);
// to find the connected edges and nodes of the selected node, the ports are inspected
for (const port of node.children.filter(child => child.type === STPA_PORT_TYPE)) {
for (const port of node.children.filter(child => child.type === PORT_TYPE)) {
// the edges for a port are defined in the parent node
// hence we have to search in the children of the parent node
for (const child of node.parent.children) {
if ((node as STPANode).aspect === STPAAspect.SYSTEMCONSTRAINT && (node.parent as STPANode).aspect !== STPAAspect.SYSTEMCONSTRAINT) {
// for the top system constraint node the intermediate outoging edges should not be highlighted
if (child.type === STPA_EDGE_TYPE) {
flagIncomingEdges(child as STPAEdge, port as STPAPort, elements);
flagOutgoingEdges(child as STPAEdge, port as STPAPort, elements);
flagIncomingEdges(child as STPAEdge, port as PastaPort, elements);
flagOutgoingEdges(child as STPAEdge, port as PastaPort, elements);
}
} else if (child.type === STPA_INTERMEDIATE_EDGE_TYPE) {
// the intermediate edges should in general only be highlighted when they are outgoing edges
flagOutgoingEdges(child as STPAEdge, port as STPAPort, elements);
flagOutgoingEdges(child as STPAEdge, port as PastaPort, elements);
} else if (child.type === STPA_EDGE_TYPE) {
// flag incoming and outgoing edges
flagIncomingEdges(child as STPAEdge, port as STPAPort, elements);
flagOutgoingEdges(child as STPAEdge, port as STPAPort, elements);
flagIncomingEdges(child as STPAEdge, port as PastaPort, elements);
flagOutgoingEdges(child as STPAEdge, port as PastaPort, elements);
}
}
}
Expand All @@ -85,20 +85,20 @@ function flagPredNodes(edge: SEdge, elements: SModelElement[]): void {
for (const subH of subHazards) {
subH.highlight = true;
elements.push(subH);
for (const port of subH.children.filter(child => child.type === STPA_PORT_TYPE && (child as STPAPort).side === PortSide.SOUTH)) {
for (const port of subH.children.filter(child => child.type === PORT_TYPE && (child as PastaPort).side === PortSide.SOUTH)) {
for (const child of subH.parent.children) {
if (child.type.startsWith('edge:stpa')) {
flagIncomingEdges(child as STPAEdge, port as STPAPort, elements);
flagIncomingEdges(child as STPAEdge, port as PastaPort, elements);
}
}
}
}
}
// flag incoming edges from node by going over the ports
for (const port of node.children.filter(child => child.type === STPA_PORT_TYPE && (child as STPAPort).side === PortSide.SOUTH)) {
for (const port of node.children.filter(child => child.type === PORT_TYPE && (child as PastaPort).side === PortSide.SOUTH)) {
for (const child of node.parent.children) {
if (child.type.startsWith('edge:stpa')) {
flagIncomingEdges(child as STPAEdge, port as STPAPort, elements);
flagIncomingEdges(child as STPAEdge, port as PastaPort, elements);
}
}
}
Expand All @@ -114,10 +114,10 @@ function flagSuccNodes(edge: SEdge, elements: SModelElement[]): void {
elements.push(node);
flaggingOutgoingForSubcomponents(node, elements);
// flag outgoing edges from node by going over the ports
for (const port of node.children.filter(child => child.type === STPA_PORT_TYPE && (child as STPAPort).side === PortSide.NORTH)) {
for (const port of node.children.filter(child => child.type === PORT_TYPE && (child as PastaPort).side === PortSide.NORTH)) {
for (const child of node.parent.children) {
if (child.type.startsWith('edge:stpa')) {
flagOutgoingEdges(child as STPAEdge, port as STPAPort, elements);
flagOutgoingEdges(child as STPAEdge, port as PastaPort, elements);
}
}
}
Expand All @@ -137,10 +137,10 @@ function flaggingOutgoingForSubcomponents(node: STPANode, elements: SModelElemen
if (isSubHazard(node)) {
(node.parent as STPANode).highlight = true;
elements.push(node.parent as STPANode);
for (const port of (node.parent as STPANode).children.filter(child => child.type === STPA_PORT_TYPE && (child as STPAPort).side === PortSide.NORTH)) {
for (const port of (node.parent as STPANode).children.filter(child => child.type === PORT_TYPE && (child as PastaPort).side === PortSide.NORTH)) {
for (const child of (node.parent as STPANode).parent.children) {
if (child.type.startsWith('edge:stpa')) {
flagOutgoingEdges(child as STPAEdge, port as STPAPort, elements);
flagOutgoingEdges(child as STPAEdge, port as PastaPort, elements);
}
}
}
Expand All @@ -153,7 +153,7 @@ function flaggingOutgoingForSubcomponents(node: STPANode, elements: SModelElemen
* @param port The port which is checked to be the source of the {@code edge}.
* @param elements The elements which should be highlighted.
*/
function flagIncomingEdges(edge: STPAEdge, port: STPAPort, elements: SModelElement[]): void {
function flagIncomingEdges(edge: STPAEdge, port: PastaPort, elements: SModelElement[]): void {
if (edge.targetId === port.id) {
// if the edge leads to another edge, highlight all connected edges
let furtherEdge: STPAEdge | undefined = edge;
Expand All @@ -174,7 +174,7 @@ function flagIncomingEdges(edge: STPAEdge, port: STPAPort, elements: SModelEleme
* @param port The port which is checked to be the source of the {@code edge}.
* @param elements The elements which should be highlighted.
*/
function flagOutgoingEdges(edge: STPAEdge, port: STPAPort, elements: SModelElement[]): void {
function flagOutgoingEdges(edge: STPAEdge, port: PastaPort, elements: SModelElement[]): void {
if (edge.sourceId === port.id) {
// if the edge leads to another edge, highlight all connected edges
let furtherEdge: STPAEdge | undefined = edge;
Expand Down
Loading

0 comments on commit 5da476e

Please sign in to comment.