Skip to content

Commit

Permalink
missing feedback edges are highlighted along with source node and labels
Browse files Browse the repository at this point in the history
  • Loading branch information
Drakae committed Oct 11, 2024
1 parent 761b054 commit 2506178
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -264,21 +264,29 @@ export function translateCommandsToEdges(
// add missing feedback edges
if (addMissing && missingFeedback) {
// add feedback edge to each node to which a feedback is missing
let hasMissingFeedback = false;
for (const target of missingFeedback.get(source?.name ?? "") ?? []) {
if (source && target) {
hasMissingFeedback = true;
createEdgeForCommand(
source,
target,
idCache.uniqueId(`${source?.name}_missingFeedback_${target}`),
EdgeType.FEEDBACK,
// TODO: mark edge as missing to render it red
["MISSING FEEDBACK"],
EdgeType.MISSING_FEEDBACK,
["MISSING"],
idToSNode,
idCache,
edges
);
}
}
// set flag for missing feedback to the source node
if (hasMissingFeedback) {
const sourceNode = idToSNode.get(idCache.getId(source) ?? "");
if (sourceNode && sourceNode.type === CS_NODE_TYPE) {
(sourceNode as CSNode).hasMissingFeedback = true;
}
}
}

return edges;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface PastaPort extends SPort {
*/
export interface CSNode extends SNode {
level?: number;
hasMissingFeedback?: boolean;
}

/**
Expand Down
1 change: 1 addition & 0 deletions extension/src-language-server/stpa/diagram/stpa-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum STPAAspect {
export enum EdgeType {
CONTROL_ACTION,
FEEDBACK,
MISSING_FEEDBACK,
INPUT,
OUTPUT,
UNDEFINED
Expand Down
22 changes: 21 additions & 1 deletion extension/src-webview/css/stpa-diagram.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,27 @@

.stpa-node {
stroke: black;
}
}

/* red highlighting for visualization of missing feedback edges */
.missing-feedback-label {
fill: red !important;
}

.missing-feedback-node {
stroke: red !important;
stroke-width: 3 !important;
}

.missing-edge {
stroke: red !important;
stroke-width: 3 !important;
}

.missing-edge-arrow {
stroke: red !important;
fill: red !important;
}

/* Feedback edges */
.feedback-edge {
Expand Down
5 changes: 3 additions & 2 deletions extension/src-webview/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import {
PolylineArrowEdgeView,
PortView,
STPAGraphView,
PastaLabelView,
STPANodeView,
} from "./stpa/stpa-views";
import { snippetModule } from './snippets/snippet-module';
Expand All @@ -119,8 +120,8 @@ const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) =

// configure the diagram elements
const context = { bind, unbind, isBound, rebind };
configureModelElement(context, "label", SLabel, SLabelView);
configureModelElement(context, "label:xref", SLabel, SLabelView);
configureModelElement(context, "label", SLabel, PastaLabelView);
configureModelElement(context, "label:xref", SLabel, PastaLabelView);
configureModelElement(context, HEADER_LABEL_TYPE, SLabel, HeaderLabelView);
configureModelElement(context, "html", HtmlRoot, HtmlRootView);
configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView);
Expand Down
2 changes: 2 additions & 0 deletions extension/src-webview/stpa/stpa-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class PastaPort extends SPort {
*/
export class CSNode extends SNode {
level?: number;
hasMissingFeedback?: boolean;
static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, layoutContainerFeature, fadeFeature];
}

Expand Down Expand Up @@ -104,6 +105,7 @@ export enum STPAAspect {
export enum EdgeType {
CONTROL_ACTION,
FEEDBACK,
MISSING_FEEDBACK,
INPUT,
OUTPUT,
UNDEFINED,
Expand Down
35 changes: 30 additions & 5 deletions extension/src-webview/stpa/stpa-views.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from '.
import { SendModelRendererAction } from '../snippets/actions';
import { renderDiamond, renderHexagon, renderMirroredTriangle, renderOval, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from '../views-rendering';
import { collectAllChildren } from './helper-methods';
import { CSEdge, CS_EDGE_TYPE, CS_INTERMEDIATE_EDGE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model';
import { CSEdge, CSNode, CS_EDGE_TYPE, CS_INTERMEDIATE_EDGE_TYPE, CS_NODE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model';

/** Determines if path/aspect highlighting is currently on. */
let highlighting: boolean;
Expand All @@ -46,6 +46,8 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
const hidden = (edge.type === STPA_EDGE_TYPE || edge.type === STPA_INTERMEDIATE_EDGE_TYPE) && highlighting && !(edge as STPAEdge).highlight;
// feedback edges in the control structure should be dashed
const feedbackEdge = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.FEEDBACK;
// edges that represent missing edges should be highlighted
const missing = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;

const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption);
const printEdge = colorStyle === "black & white";
Expand All @@ -57,7 +59,7 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1;
}
return <path class-print-edge={printEdge} class-stpa-edge={coloredEdge || lessColoredEdge}
class-feedback-edge={feedbackEdge} class-greyed-out={hidden} aspect={aspect} d={path} />;
class-feedback-edge={feedbackEdge} class-missing-edge={missing} class-greyed-out={hidden} aspect={aspect} d={path} />;
}

protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] {
Expand All @@ -76,8 +78,11 @@ export class PolylineArrowEdgeView extends PolylineEdgeView {
if (edge.type === STPA_EDGE_TYPE || edge.type === STPA_INTERMEDIATE_EDGE_TYPE) {
aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1;
}
// edges that represent missing edges should be highlighted
const missing = (edge.type === CS_EDGE_TYPE || edge.type === CS_INTERMEDIATE_EDGE_TYPE) && (edge as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;

return [
<path class-print-edge-arrow={printEdge} class-stpa-edge-arrow={coloredEdge || lessColoredEdge} class-greyed-out={hidden} aspect={aspect}
<path class-missing-edge-arrow={missing} class-print-edge-arrow={printEdge} class-stpa-edge-arrow={coloredEdge || lessColoredEdge} class-greyed-out={hidden} aspect={aspect}
class-sprotty-edge-arrow={sprottyEdge} d="M 6,-3 L 0,0 L 6,3 Z"
transform={`rotate(${this.angle(p2, p1)} ${p2.x} ${p2.y}) translate(${p2.x} ${p2.y})`} />
];
Expand Down Expand Up @@ -208,8 +213,10 @@ export class CSNodeView extends RectangularNodeView {
const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption);
const sprottyNode = colorStyle === "standard";
const printNode = !sprottyNode;
const missingFeedback = node.type === CS_NODE_TYPE && (node as CSNode).hasMissingFeedback;
return <g>
<rect class-print-node={printNode}
<rect
class-missing-feedback-node={missingFeedback} class-print-node={printNode}
class-sprotty-node={sprottyNode} class-sprotty-port={node instanceof SPort}
class-mouseover={node.hoverFeedback} class-selected={node.selected}
x="0" y="0" width={Math.max(node.size.width, 0)} height={Math.max(node.size.height, 0)}
Expand Down Expand Up @@ -262,6 +269,24 @@ export class HeaderLabelView extends SLabelView {
render(label: Readonly<SLabel>, context: RenderingContext): VNode | undefined {
return <g class-header={true}>
{super.render(label, context)}
</g>
</g>;
}
}

@injectable()
export class PastaLabelView extends SLabelView {
render(label: Readonly<SLabel>, context: RenderingContext): VNode | undefined {
// label belongs to a node which may have missing feedback
const nodeMissingFeedback = label.parent.type === CS_NODE_TYPE && (label.parent as CSNode).hasMissingFeedback;
// label belongs to an edge which may be a missing feedback edge
const edgeMissingFeedback = (label.parent.type === CS_EDGE_TYPE || label.parent.type === CS_INTERMEDIATE_EDGE_TYPE) && (label.parent as CSEdge).edgeType === EdgeType.MISSING_FEEDBACK;
const missingFeedbackLabel = nodeMissingFeedback || edgeMissingFeedback;

const vnode = super.render(label, context);
if (vnode?.data?.class) {
vnode.data.class['missing-feedback-label'] = missingFeedbackLabel ?? false;
}

return vnode;
}
}

0 comments on commit 2506178

Please sign in to comment.