Add styling support for requirement diagram and box shape

This commit is contained in:
yari-dewalt
2025-01-22 11:07:15 -08:00
parent f7648e85d9
commit 081681f05b
4 changed files with 106 additions and 16 deletions

View File

@@ -9,6 +9,7 @@
%x string
%x token
%x unqString
%x style
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -72,6 +73,17 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"type" return 'TYPE';
"docref" return 'DOCREF';
"style" { this.begin("style"); return 'STYLE'; }
<style>\w+ return 'ALPHA';
<style>":" return 'COLON';
<style>";" return 'SEMICOLON';
<style>"," return 'COMMA';
<style>"%" return 'PERCENT';
<style>"-" return 'MINUS';
<style>"#" return 'BRKT';
<style>" " /* skip spaces */
<style>\n { this.popState(); }
"<-" return 'END_ARROW_L';
"->" {return 'END_ARROW_R';}
"-" {return 'LINE';}
@@ -82,6 +94,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
[\w][^\r\n\{\<\>\-\=]* { yytext = yytext.trim(); return 'unqString';}
<*>\w+ return 'ALPHA';
<*>[0-9]+ return 'NUM';
/lex
%start start
@@ -106,7 +121,9 @@ diagram
| relationshipDef diagram
| directive diagram
| direction diagram
| NEWLINE diagram;
| styleStatement diagram
| NEWLINE diagram
;
direction
: direction_tb
@@ -198,6 +215,22 @@ relationship
| TRACES
{ $$=yy.Relationships.TRACES;};
styleStatement
: STYLE ALPHA stylesOpt {$$ = $STYLE;yy.setCssStyle($2,$stylesOpt);}
;
stylesOpt
: style {$$ = [$style]}
| stylesOpt COMMA style {$stylesOpt.push($style);$$ = $stylesOpt;}
;
style
: styleComponent
| style styleComponent {$$ = $style + $styleComponent;}
;
styleComponent: ALPHA | NUM | COLON | UNIT | SPACE | BRKT | PCT | MINUS | LABEL | SEMICOLON;
requirementName: unqString | qString;
id : unqString | qString;

View File

@@ -66,12 +66,14 @@ const getInitialRequirement = (): Requirement => ({
verifyMethod: VerifyType.VERIFY_ANALYSIS as VerifyType,
name: '',
type: RequirementType.REQUIREMENT as RequirementType,
cssStyles: [],
});
const getInitialElement = (): Element => ({
name: '',
type: '',
docRef: '',
cssStyles: [],
});
// Update initial declarations
@@ -99,6 +101,7 @@ const addRequirement = (name: string, type: RequirementType) => {
text: latestRequirement.text,
risk: latestRequirement.risk,
verifyMethod: latestRequirement.verifyMethod,
cssStyles: [],
});
}
resetLatestRequirement();
@@ -138,6 +141,7 @@ const addElement = (name: string) => {
name,
type: latestElement.type,
docRef: latestElement.docRef,
cssStyles: [],
});
log.info('Added new element: ', name);
}
@@ -179,6 +183,20 @@ const clear = () => {
commonClear();
};
export const setCssStyle = function (id: string, styles: string[]) {
const node = requirements.get(id) ?? elements.get(id);
if (!styles || !node) {
return;
}
for (const s of styles) {
if (s.includes(',')) {
node.cssStyles.push(...s.split(','));
} else {
node.cssStyles.push(s);
}
}
};
const getData = () => {
const config = getConfig();
const nodes: Node[] = [];
@@ -251,5 +269,6 @@ export default {
addRelationship,
getRelationships,
clear,
setCssStyle,
getData,
};

View File

@@ -17,6 +17,7 @@ export interface Requirement {
text: string;
risk: RiskLevel;
verifyMethod: VerifyType;
cssStyles: string[];
}
export type RelationshipType =
@@ -38,4 +39,5 @@ export interface Element {
name: string;
type: string;
docRef: string;
cssStyles: string[];
}

View File

@@ -1,7 +1,7 @@
import { updateNodeBounds } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { userNodeOverrides } from './handDrawnShapeStyles.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { calculateTextWidth, decodeEntities } from '../../../utils.js';
@@ -14,12 +14,14 @@ export async function requirementBox<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const requirementNode = node as unknown as Requirement;
const elementNode = node as unknown as Element;
const config = getConfig().requirement;
const PADDING = 20;
const GAP = 20;
const isRequirementNode = 'id' in node;
const isRequirementNode = 'verifyMethod' in node;
// Add outer g element
const shapeSvg = parent
@@ -29,36 +31,68 @@ export async function requirementBox<T extends SVGGraphicsElement>(
let typeHeight;
if (isRequirementNode) {
typeHeight = await addText(shapeSvg, `&lt;&lt;${requirementNode.type}&gt;&gt;`, 0);
typeHeight = await addText(
shapeSvg,
`&lt;&lt;${requirementNode.type}&gt;&gt;`,
0,
node.labelStyle
);
} else {
typeHeight = await addText(shapeSvg, '&lt;&lt;Element&gt;&gt;', 0);
typeHeight = await addText(shapeSvg, '&lt;&lt;Element&gt;&gt;', 0, node.labelStyle);
}
let accumulativeHeight = typeHeight;
const nameHeight = await addText(shapeSvg, requirementNode.name, accumulativeHeight);
const nameHeight = await addText(
shapeSvg,
requirementNode.name,
accumulativeHeight,
node.labelStyle
);
accumulativeHeight += nameHeight + GAP;
// Requirement
if (isRequirementNode) {
const idHeight = await addText(shapeSvg, `Id: ${requirementNode.id}`, accumulativeHeight);
const idHeight = await addText(
shapeSvg,
`Id: ${requirementNode.id}`,
accumulativeHeight,
node.labelStyle
);
accumulativeHeight += idHeight;
const textHeight = await addText(shapeSvg, `Text: ${requirementNode.text}`, accumulativeHeight);
const textHeight = await addText(
shapeSvg,
`Text: ${requirementNode.text}`,
accumulativeHeight,
node.labelStyle
);
accumulativeHeight += textHeight;
const riskHeight = await addText(shapeSvg, `Risk: ${requirementNode.risk}`, accumulativeHeight);
const riskHeight = await addText(
shapeSvg,
`Risk: ${requirementNode.risk}`,
accumulativeHeight,
node.labelStyle
);
accumulativeHeight += riskHeight;
await addText(shapeSvg, `Verification: ${requirementNode.verifyMethod}`, accumulativeHeight);
await addText(
shapeSvg,
`Verification: ${requirementNode.verifyMethod}`,
accumulativeHeight,
node.labelStyle
);
} else {
// Element
const typeHeight = await addText(
shapeSvg,
`Type: ${elementNode.type ? elementNode.type : 'Not specified'}`,
accumulativeHeight
accumulativeHeight,
node.labelStyle
);
accumulativeHeight += typeHeight;
await addText(
shapeSvg,
`Doc Ref: ${elementNode.docRef ? elementNode.docRef : 'None'}`,
accumulativeHeight
accumulativeHeight,
node.labelStyle
);
}
@@ -84,7 +118,7 @@ export async function requirementBox<T extends SVGGraphicsElement>(
const roughRect = rc.rectangle(x, y, totalWidth, totalHeight, options);
const rect = shapeSvg.insert(() => roughRect, ':first-child');
rect.attr('class', 'basic label-container');
rect.attr('class', 'basic label-container').attr('style', nodeStyles);
// Re-translate labels now that rect is centered
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -123,7 +157,8 @@ export async function requirementBox<T extends SVGGraphicsElement>(
y + typeHeight + nameHeight + GAP,
options
);
shapeSvg.insert(() => roughLine);
const dividerLine = shapeSvg.insert(() => roughLine);
dividerLine.attr('style', nodeStyles);
updateNodeBounds(node, rect);
@@ -138,9 +173,9 @@ async function addText<T extends SVGGraphicsElement>(
parentGroup: D3Selection<T>,
inputText: string,
yOffset: number,
styles: string[] = []
style = ''
) {
const textEl = parentGroup.insert('g').attr('class', 'label').attr('style', styles.join('; '));
const textEl = parentGroup.insert('g').attr('class', 'label').attr('style', style);
const config = getConfig();
const useHtmlLabels = config.htmlLabels ?? true;
@@ -151,6 +186,7 @@ async function addText<T extends SVGGraphicsElement>(
width: calculateTextWidth(inputText, config) + 50, // Add room for error when splitting text into multiple lines
classes: 'markdown-node-label',
useHtmlLabels,
style,
},
config
);