mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 06:19:24 +02:00
Add styling support for requirement diagram and box shape
This commit is contained in:
@@ -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;
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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[];
|
||||
}
|
||||
|
@@ -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, `<<${requirementNode.type}>>`, 0);
|
||||
typeHeight = await addText(
|
||||
shapeSvg,
|
||||
`<<${requirementNode.type}>>`,
|
||||
0,
|
||||
node.labelStyle
|
||||
);
|
||||
} else {
|
||||
typeHeight = await addText(shapeSvg, '<<Element>>', 0);
|
||||
typeHeight = await addText(shapeSvg, '<<Element>>', 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
|
||||
);
|
||||
|
Reference in New Issue
Block a user