From c1df62638d1b1a158f74dc6285f03d59dcb2d6f8 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Thu, 9 Mar 2023 21:14:30 +0900 Subject: [PATCH 01/21] class body grammer apply to class stetement only --- .../diagrams/class/parser/classDiagram.jison | 123 ++++++++++-------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 0c9ad2f2a..4e68372f3 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -19,6 +19,8 @@ %x acc_title %x acc_descr %x acc_descr_multiline +%x class +%x class-body %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; @@ -41,35 +43,30 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili \s*(\r?\n)+ return 'NEWLINE'; \s+ /* skip whitespace */ + "classDiagram-v2" return 'CLASS_DIAGRAM'; "classDiagram" return 'CLASS_DIAGRAM'; -[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';} -"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';} -<> return "EOF_IN_STRUCT"; -[{] return "OPEN_IN_STRUCT"; -[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}} -[\n] /* nothing */ -[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";} +"[*]" return 'EDGE_STATE'; -"class" return 'CLASS'; -"cssClass" return 'CSSCLASS'; -"callback" return 'CALLBACK'; -"link" return 'LINK'; -"click" return 'CLICK'; -"note for" return 'NOTE_FOR'; -"note" return 'NOTE'; -"<<" return 'ANNOTATION_START'; -">>" return 'ANNOTATION_END'; -[~] this.begin("generic"); -[~] this.popState(); -[^~]* return "GENERICTYPE"; -["] this.begin("string"); -["] this.popState(); -[^"]* return "STR"; +"class" { this.begin('class'); return 'CLASS';} +\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } +\s+ /* skip whitespace */ +[{] { this.begin("class-body"); return 'STRUCT_START';} +[}] { this.popState(); return 'STRUCT_STOP'; } +<> return "EOF_IN_STRUCT"; +"[*]" { return 'EDGE_STATE';} +[{] return "OPEN_IN_STRUCT"; +[\n] /* nothing */ +[^{}\n]* { return "MEMBER";} -[`] this.begin("bqstring"); -[`] this.popState(); -[^`]+ return "BQUOTE_STR"; +<*>"cssClass" return 'CSSCLASS'; +<*>"callback" return 'CALLBACK'; +<*>"link" return 'LINK'; +<*>"click" return 'CLICK'; +<*>"note for" return 'NOTE_FOR'; +<*>"note" return 'NOTE'; +<*>"<<" return 'ANNOTATION_START'; +<*>">>" return 'ANNOTATION_END'; /* ---interactivity command--- @@ -77,7 +74,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili line was introduced with 'click'. 'href ""' attaches the specified link to the node that was specified by 'click'. */ -"href"[\s]+["] this.begin("href"); +<*>"href"[\s]+["] this.begin("href"); ["] this.popState(); [^"]* return 'HREF'; @@ -89,41 +86,53 @@ the line was introduced with 'click'. arguments to the node that was specified by 'click'. Function arguments are optional: 'call ()' simply executes 'callback_name' without any arguments. */ -"call"[\s]+ this.begin("callback_name"); +<*>"call"[\s]+ this.begin("callback_name"); \([\s]*\) this.popState(); \( this.popState(); this.begin("callback_args"); [^(]* return 'CALLBACK_NAME'; \) this.popState(); [^)]* return 'CALLBACK_ARGS'; -"_self" return 'LINK_TARGET'; -"_blank" return 'LINK_TARGET'; -"_parent" return 'LINK_TARGET'; -"_top" return 'LINK_TARGET'; +[~] this.popState(); +[^~]* return "GENERICTYPE"; +<*>[~] this.begin("generic"); -\s*\<\| return 'EXTENSION'; -\s*\|\> return 'EXTENSION'; -\s*\> return 'DEPENDENCY'; -\s*\< return 'DEPENDENCY'; -\s*\* return 'COMPOSITION'; -\s*o return 'AGGREGATION'; -\s*\(\) return 'LOLLIPOP'; -\-\- return 'LINE'; -\.\. return 'DOTTED_LINE'; -":"{1}[^:\n;]+ return 'LABEL'; -":"{3} return 'STYLE_SEPARATOR'; -\- return 'MINUS'; -"." return 'DOT'; -\+ return 'PLUS'; -\% return 'PCT'; -"=" return 'EQUALS'; -\= return 'EQUALS'; -\w+ return 'ALPHA'; -"[" return 'SQS'; -"]" return 'SQE'; -[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION'; -[0-9]+ return 'NUM'; -[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| +["] this.popState(); +[^"]* return "STR"; +<*>["] this.begin("string"); + +[`] this.popState(); +[^`]+ return "BQUOTE_STR"; +<*>[`] this.begin("bqstring"); + +<*>"_self" return 'LINK_TARGET'; +<*>"_blank" return 'LINK_TARGET'; +<*>"_parent" return 'LINK_TARGET'; +<*>"_top" return 'LINK_TARGET'; + +<*>\s*\<\| return 'EXTENSION'; +<*>\s*\|\> return 'EXTENSION'; +<*>\s*\> return 'DEPENDENCY'; +<*>\s*\< return 'DEPENDENCY'; +<*>\s*\* return 'COMPOSITION'; +<*>\s*o return 'AGGREGATION'; +<*>\s*\(\) return 'LOLLIPOP'; +<*>\-\- return 'LINE'; +<*>\.\. return 'DOTTED_LINE'; +<*>":"{1}[^:\n;]+ return 'LABEL'; +<*>":"{3} return 'STYLE_SEPARATOR'; +<*>\- return 'MINUS'; +<*>"." return 'DOT'; +<*>\+ return 'PLUS'; +<*>\% return 'PCT'; +<*>"=" return 'EQUALS'; +<*>\= return 'EQUALS'; +<*>\w+ return 'ALPHA'; +<*>"[" return 'SQS'; +<*>"]" return 'SQE'; +<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION'; +<*>[0-9]+ return 'NUM'; +<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]| [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]| [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]| @@ -184,9 +193,9 @@ Function arguments are optional: 'call ()' simply executes 'callb [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]| [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]| [\uFFD2-\uFFD7\uFFDA-\uFFDC] - return 'UNICODE_TEXT'; -\s return 'SPACE'; -<> return 'EOF'; + return 'UNICODE_TEXT'; +<*>\s return 'SPACE'; +<*><> return 'EOF'; /lex From 4017bb3c49d26c50ec87e3afcb9bf2b0b8cd1593 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Thu, 9 Mar 2023 17:42:38 +0900 Subject: [PATCH 02/21] Implement namespace parser --- .../mermaid/src/diagrams/class/classDb.ts | 25 +++++++++++++ .../src/diagrams/class/classDiagram.spec.ts | 31 ++++++++++++++++ .../diagrams/class/parser/classDiagram.jison | 36 ++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 48ef7ccbe..851353477 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -392,6 +392,29 @@ const setDirection = (dir: string) => { direction = dir; }; +/** + * Function called by parser when a namespace keyword has been found. + * + * @param id - Id of the namespace to add + * @public + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const addNamespace = function (id: string) { + // TODO: Implement here +}; + +/** + * Function called by parser when a namespace definition has been found. + * + * @param id - Id of the namespace to add + * @param classNames - Ids of the class to add + * @public + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const addClassesToNamespace = function (id: string, classNames: string[]) { + // TODO: Implement here +}; + export default { parseDirective, setAccTitle, @@ -425,4 +448,6 @@ export default { setDiagramTitle, getDiagramTitle, setClassLabel, + addNamespace, + addClassesToNamespace, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 24fbc41ea..11b556179 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -571,6 +571,37 @@ foo() const str = 'classDiagram\n' + 'note "test"\n'; parser.parse(str); }); + + it('should handle "namespace"', function () { + const str = `classDiagram +namespace Namespace1 { class Class1 } +namespace Namespace2 { class Class1 +} +namespace Namespace3 { +class Class1 { +int : test +string : foo +test() +foo() +} +} +namespace Namespace4 { +class Class1 { +int : test +string : foo +test() +foo() +} +class Class2 { +int : test +string : foo +test() +foo() +} +} +`; + parser.parse(str); + }); }); describe('when fetching data from a classDiagram it', function () { diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 4e68372f3..1c0ccdbbe 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -21,6 +21,8 @@ %x acc_descr_multiline %x class %x class-body +%x namespace +%x namespace-body %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; @@ -48,9 +50,20 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili "classDiagram" return 'CLASS_DIAGRAM'; "[*]" return 'EDGE_STATE'; -"class" { this.begin('class'); return 'CLASS';} +"namespace" { this.begin('namespace'); return 'NAMESPACE'; } +\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } +\s+ /* skip whitespace */ +[{] { this.begin("namespace-body"); return 'STRUCT_START';} +[}] { this.popState(); return 'STRUCT_STOP'; } +<> return "EOF_IN_STRUCT"; +\s*(\r?\n)+ return 'NEWLINE'; +\s+ /* skip whitespace */ +"[*]" return 'EDGE_STATE'; + +"class" { this.begin('class'); return 'CLASS';} \s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } \s+ /* skip whitespace */ +[}] { this.popState(); this.popState(); return 'STRUCT_STOP';} [{] { this.begin("class-body"); return 'STRUCT_START';} [}] { this.popState(); return 'STRUCT_STOP'; } <> return "EOF_IN_STRUCT"; @@ -264,6 +277,11 @@ classLabel : SQS STR SQE { $$=$2; } ; +namespaceName + : alphaNumToken { $$=$1; } + | alphaNumToken namespaceName { $$=$1+$2; } + ; + className : alphaNumToken { $$=$1; } | classLiteralName { $$=$1; } @@ -275,6 +293,7 @@ className statement : relationStatement { yy.addRelation($1); } | relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); } + | namespaceStatement | classStatement | methodStatement | annotationStatement @@ -288,6 +307,21 @@ statement | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } ; +namespaceStatement + : namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);} + | namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);} + ; + +namespaceIdentifier + : NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);} + ; + +classStatements + : classStatement {$$=[$1]} + | classStatement NEWLINE {$$=[$1]} + | classStatement NEWLINE classStatements {$3.unshift($1); $$=$3} + ; + classStatement : classIdentifier | classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);} From 75502d076e843d3eb932c86027849347df737e08 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 10 Mar 2023 19:37:54 +0900 Subject: [PATCH 03/21] Implement database for namespace data --- .../mermaid/src/diagrams/class/classDb.ts | 46 ++++++++++++++++--- .../src/diagrams/class/classDiagram.spec.ts | 19 ++++++++ .../mermaid/src/diagrams/class/classTypes.ts | 9 ++++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 851353477..18e218244 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -14,7 +14,14 @@ import { setDiagramTitle, getDiagramTitle, } from '../../commonDb'; -import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes'; +import { + ClassRelation, + ClassNode, + ClassNote, + ClassMap, + NamespaceMap, + NamespaceNode, +} from './classTypes'; const MERMAID_DOM_ID_PREFIX = 'classId-'; @@ -22,6 +29,8 @@ let relations: ClassRelation[] = []; let classes: ClassMap = {}; let notes: ClassNote[] = []; let classCounter = 0; +let namespaces: NamespaceMap = {}; +let namespaceCounter = 0; let functions: any[] = []; @@ -100,6 +109,8 @@ export const clear = function () { notes = []; functions = []; functions.push(setupToolTips); + namespaces = {}; + namespaceCounter = 0; commonClear(); }; @@ -393,14 +404,28 @@ const setDirection = (dir: string) => { }; /** - * Function called by parser when a namespace keyword has been found. + * Function called by parser when a namespace definition has been found. * * @param id - Id of the namespace to add * @public */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const addNamespace = function (id: string) { - // TODO: Implement here +export const addNamespace = function (name: string) { + namespaces[name] = { + id: name, + domId: MERMAID_DOM_ID_PREFIX + name + '-' + namespaceCounter, + classes: {}, + children: {}, + }; + + namespaceCounter++; +}; + +const getNamespace = function (name: string): NamespaceNode { + return namespaces[name]; +}; + +const getNamespaces = function (): NamespaceMap { + return namespaces; }; /** @@ -410,9 +435,14 @@ export const addNamespace = function (id: string) { * @param classNames - Ids of the class to add * @public */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const addClassesToNamespace = function (id: string, classNames: string[]) { - // TODO: Implement here + if (namespaces[id] !== undefined) { + classNames.map((className) => { + namespaces[id].classes[className] = classes[className]; + delete classes[className]; + classCounter--; + }); + } }; export default { @@ -450,4 +480,6 @@ export default { setClassLabel, addNamespace, addClassesToNamespace, + getNamespace, + getNamespaces, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 11b556179..48d7ca64d 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1008,6 +1008,25 @@ foo() expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); + + it('should add classes namespaces', function () { + const str = `classDiagram +namespace Namespace1 { +class Class1 { +int : test +string : foo +test() +foo() +} +class Class2 +}`; + parser.parse(str); + + const testNamespace = parser.yy.getNamespace('Namespace1'); + expect(Object.keys(testNamespace.classes).length).toBe(2); + expect(Object.keys(testNamespace.children).length).toBe(0); + expect(testNamespace.classes['Class1'].id).toBe('Class1'); + }); }); describe('when parsing classDiagram with text labels', () => { diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 4cacad3db..cf6f20f0b 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -52,4 +52,13 @@ export type ClassRelation = { lineType: number; }; }; + +export interface NamespaceNode { + id: string; + domId: string; + classes: ClassMap; + children: NamespaceMap; +} + export type ClassMap = Record; +export type NamespaceMap = Record; From 434961b44a5705a9f198047a76712bd97b982f91 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 10 Mar 2023 18:04:05 +0900 Subject: [PATCH 04/21] Implement rendering logic --- .../rendering/classDiagram-v2.spec.js | 14 ++++ .../mermaid/src/diagrams/class/classDb.ts | 20 ++++-- .../src/diagrams/class/classRenderer-v2.ts | 67 ++++++++++++++++++- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 71810cfa4..90ad677ea 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -548,4 +548,18 @@ class C13["With Città foreign language"] ` ); }); + it('should add classes namespaces', function () { + imgSnapshotTest( + ` + classDiagram + namespace Namespace1 { + class C1 + class C2 + } + C1 --> C2 + class C3 + class C4 + ` + ); + }); }); diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 18e218244..6468b5e8c 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -245,7 +245,11 @@ const setTooltip = function (ids: string, tooltip?: string) { } }); }; -export const getTooltip = function (id: string) { +export const getTooltip = function (id: string, namespace?: string) { + if (namespace) { + return namespaces[namespace].classes[id].tooltip; + } + return classes[id].tooltip; }; /** @@ -409,13 +413,17 @@ const setDirection = (dir: string) => { * @param id - Id of the namespace to add * @public */ -export const addNamespace = function (name: string) { - namespaces[name] = { - id: name, - domId: MERMAID_DOM_ID_PREFIX + name + '-' + namespaceCounter, +export const addNamespace = function (id: string) { + if (namespaces[id] !== undefined) { + return; + } + + namespaces[id] = { + id: id, classes: {}, children: {}, - }; + domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter, + } as NamespaceNode; namespaceCounter++; }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index e308990c6..e9b221141 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -8,7 +8,7 @@ import utils from '../../utils'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes'; +import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes'; const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); @@ -19,6 +19,58 @@ let conf = { curve: undefined, }; +interface RectParameters { + id: string; + shape: 'rect'; + labelStyle: string; + domId: string; + labelText: string; + padding: number | undefined; + style?: string; +} + +/** + * Function that adds the vertices found during parsing to the graph to be rendered. + * + * @param namespaces - Object containing the vertices. + * @param g - The graph that is to be drawn. + * @param _id - id of the graph + * @param diagObj - The diagram object + */ +export const addNamespaces = function ( + namespaces: NamespaceMap, + g: graphlib.Graph, + _id: string, + diagObj: any +) { + const keys = Object.keys(namespaces); + log.info('keys:', keys); + log.info(namespaces); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function (id) { + const vertex = namespaces[id]; + + // parent node must be one of [rect, roundedWithTitle, noteGroup, divider] + const shape = 'rect'; + + const node: RectParameters = { + shape: shape, + id: vertex.id, + domId: vertex.domId, + labelText: sanitizeText(vertex.id), + labelStyle: '', + // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release + padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, + }; + + g.setNode(vertex.id, node); + addClasses(vertex.classes, g, _id, diagObj, vertex.id); + + log.info('setNode', node); + }); +}; + /** * Function that adds the vertices found during parsing to the graph to be rendered. * @@ -31,7 +83,8 @@ export const addClasses = function ( classes: ClassMap, g: graphlib.Graph, _id: string, - diagObj: any + diagObj: any, + parent?: string ) { const keys = Object.keys(classes); log.info('keys:', keys); @@ -55,6 +108,7 @@ export const addClasses = function ( const vertexText = vertex.label ?? vertex.id; const radius = 0; const shape = 'class_box'; + // Add the node const node = { labelStyle: styles.labelStyle, @@ -67,7 +121,7 @@ export const addClasses = function ( style: styles.style, id: vertex.id, domId: vertex.domId, - tooltip: diagObj.db.getTooltip(vertex.id) || '', + tooltip: diagObj.db.getTooltip(vertex.id, parent) || '', haveCallback: vertex.haveCallback, link: vertex.link, width: vertex.type === 'group' ? 500 : undefined, @@ -76,6 +130,11 @@ export const addClasses = function ( padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, }; g.setNode(vertex.id, node); + + if (parent) { + g.setParent(vertex.id, parent); + } + log.info('setNode', node); }); }; @@ -275,10 +334,12 @@ export const draw = function (text: string, id: string, _version: string, diagOb }); // Fetch the vertices/nodes and edges/links from the parsed graph definition + const namespaces: NamespaceMap = diagObj.db.getNamespaces(); const classes: ClassMap = diagObj.db.getClasses(); const relations: ClassRelation[] = diagObj.db.getRelations(); const notes: ClassNote[] = diagObj.db.getNotes(); log.info(relations); + addNamespaces(namespaces, g, id, diagObj); addClasses(classes, g, id, diagObj); addRelations(relations, g); addNotes(notes, g, relations.length + 1, classes); From b51b2c7b04fab4135044b984dcb94a4a4edd6c41 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 17 Mar 2023 14:21:31 +0900 Subject: [PATCH 05/21] Add namespace sample to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index d42e2f7e1..d8e06b62f 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,13 @@ class Class10 { int id size() } +namespace Namespace01 { + class Class11 + class Class12 { + int id + size() + } +} ``` ```mermaid @@ -184,6 +191,13 @@ class Class10 { int id size() } +namespace Namespace01 { + class Class11 + class Class12 { + int id + size() + } +} ``` ### State diagram [docs - live editor] From b725b69fda5641904a5ec89ef75533093439a874 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 17 Mar 2023 14:47:32 +0900 Subject: [PATCH 06/21] Add package of class diagrams to doc --- docs/syntax/classDiagram.md | 28 +++++++++++++++++++ .../mermaid/src/docs/syntax/classDiagram.md | 17 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 69144ef39..bc44a4c48 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -421,6 +421,34 @@ And `Link` can be one of: | -- | Solid | | .. | Dashed | +## Define Namespace + +A namespace groups classes. + +Code: + +```mermaid-example +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + +```mermaid +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + ## Cardinality / Multiplicity on relations Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies. diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 9d3766590..588aff874 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -277,6 +277,23 @@ And `Link` can be one of: | -- | Solid | | .. | Dashed | +## Define Namespace + +A namespace groups classes. + +Code: + +```mermaid-example +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + ## Cardinality / Multiplicity on relations Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies. From e357bbee4287eb303db00ed31738d6ef2ecc809f Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Thu, 30 Mar 2023 11:04:19 -0700 Subject: [PATCH 07/21] Refactor to consolidate shared svgDraw components --- packages/mermaid/src/diagrams/c4/svgDraw.js | 46 ++----- .../mermaid/src/diagrams/class/svgDraw.js | 2 +- .../src/diagrams/common/svgDrawCommon.js | 122 ++++++++++++++++++ .../mermaid/src/diagrams/mindmap/svgDraw.js | 4 + .../src/diagrams/sequence/sequenceRenderer.ts | 7 +- .../mermaid/src/diagrams/sequence/svgDraw.js | 113 +++------------- .../src/diagrams/sequence/svgDraw.spec.js | 12 -- .../src/diagrams/user-journey/svgDraw.js | 79 +----------- 8 files changed, 165 insertions(+), 220 deletions(-) create mode 100644 packages/mermaid/src/diagrams/common/svgDrawCommon.js diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index 690dd26ad..ce465cad3 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -1,28 +1,9 @@ import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.attrs !== 'undefined' && rectData.attrs !== null) { - for (let attrKey in rectData.attrs) { - rectElem.attr(attrKey, rectData.attrs[attrKey]); - } - } - - if (rectData.class !== 'undefined') { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; export const drawImage = function (elem, width, height, x, y, link) { @@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) { // // draw rect of c4Shape - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); + switch (c4Shape.typeC4Shape.text) { case 'person': case 'external_person': @@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) { .append('path') .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; + export const insertArrowEnd = function (elem) { elem .append('defs') @@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) { .append('path') .attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; + /** * Setup node number. The result is appended to the svg. * @@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) { .attr('r', 6); // .style("fill", '#f00'); }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getNoteRect = function () { - return { - x: 0, - y: 0, - fill: '#EDF2AE', - stroke: '#666', - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; -}; - const getC4ShapeFont = (cnf, typeC4Shape) => { return { fontFamily: cnf[typeC4Shape + 'FontFamily'], @@ -714,6 +686,4 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getNoteRect, - sanitizeUrl, // TODO why is this exported? }; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index cc6909280..bc6d0ab11 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -440,7 +440,6 @@ const buildLegacyDisplay = function (text) { } const parameters = text.substring(methodStart + 1, methodEnd); - const classifier = text.substring(methodEnd + 1, 1); cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2)); displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; @@ -462,6 +461,7 @@ const buildLegacyDisplay = function (text) { cssStyle, }; }; + /** * Adds a for a member in a diagram * diff --git a/packages/mermaid/src/diagrams/common/svgDrawCommon.js b/packages/mermaid/src/diagrams/common/svgDrawCommon.js new file mode 100644 index 000000000..e054f53b6 --- /dev/null +++ b/packages/mermaid/src/diagrams/common/svgDrawCommon.js @@ -0,0 +1,122 @@ +import { sanitizeUrl } from '@braintree/sanitize-url'; + +export const drawRect = function (elem, rectData) { + const rectElem = elem.append('rect'); + rectElem.attr('x', rectData.x); + rectElem.attr('y', rectData.y); + rectElem.attr('fill', rectData.fill); + rectElem.attr('stroke', rectData.stroke); + rectElem.attr('width', rectData.width); + rectElem.attr('height', rectData.height); + rectElem.attr('rx', rectData.rx); + rectElem.attr('ry', rectData.ry); + + if (rectData.attrs !== 'undefined' && rectData.attrs !== null) { + for (let attrKey in rectData.attrs) { + rectElem.attr(attrKey, rectData.attrs[attrKey]); + } + } + + if (rectData.class !== 'undefined') { + rectElem.attr('class', rectData.class); + } + + return rectElem; +}; + +/** + * Draws a background rectangle + * + * @param {any} elem Diagram (reference for bounds) + * @param {any} bounds Shape of the rectangle + */ +export const drawBackgroundRect = function (elem, bounds) { + const rectElem = drawRect(elem, { + x: bounds.startx, + y: bounds.starty, + width: bounds.stopx - bounds.startx, + height: bounds.stopy - bounds.starty, + fill: bounds.fill, + stroke: bounds.stroke, + class: 'rect', + }); + rectElem.lower(); +}; + +export const drawText = function (elem, textData) { + // Remove and ignore br:s + const nText = textData.text.replace(//gi, ' '); + + const textElem = elem.append('text'); + textElem.attr('x', textData.x); + textElem.attr('y', textData.y); + textElem.attr('class', 'legend'); + + textElem.style('text-anchor', textData.anchor); + + if (textData.class !== undefined) { + textElem.attr('class', textData.class); + } + + const span = textElem.append('tspan'); + span.attr('x', textData.x + textData.textMargin * 2); + span.text(nText); + + return textElem; +}; + +export const drawImage = function (elem, x, y, link) { + const imageElem = elem.append('image'); + imageElem.attr('x', x); + imageElem.attr('y', y); + var sanitizedLink = sanitizeUrl(link); + imageElem.attr('xlink:href', sanitizedLink); +}; + +export const drawEmbeddedImage = function (elem, x, y, link) { + const imageElem = elem.append('use'); + imageElem.attr('x', x); + imageElem.attr('y', y); + var sanitizedLink = sanitizeUrl(link); + imageElem.attr('xlink:href', '#' + sanitizedLink); +}; + +export const getNoteRect = function () { + return { + x: 0, + y: 0, + width: 100, + height: 100, + fill: '#EDF2AE', + stroke: '#666', + anchor: 'start', + rx: 0, + ry: 0, + }; +}; + +export const getTextObj = function () { + return { + x: 0, + y: 0, + width: 100, + height: 100, + fill: undefined, + anchor: undefined, + 'text-anchor': 'start', + style: '#666', + textMargin: 0, + rx: 0, + ry: 0, + tspan: true, + valign: undefined, + }; +}; + +export default { + drawRect, + drawImage, + drawText, + getNoteRect, + getTextObj, +}; diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js index 2b1aa021e..44172f222 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js @@ -69,6 +69,7 @@ const defaultBkg = function (elem, node, section) { .attr('x2', node.width) .attr('y2', node.height); }; + const rectBkg = function (elem, node) { elem .append('rect') @@ -77,6 +78,7 @@ const rectBkg = function (elem, node) { .attr('height', node.height) .attr('width', node.width); }; + const cloudBkg = function (elem, node) { const w = node.width; const h = node.height; @@ -107,6 +109,7 @@ const cloudBkg = function (elem, node) { H0 V0 Z` ); }; + const bangBkg = function (elem, node) { const w = node.width; const h = node.height; @@ -138,6 +141,7 @@ const bangBkg = function (elem, node) { H0 V0 Z` ); }; + const circleBkg = function (elem, node) { elem .append('circle') diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index acee7bbe5..f45de2e49 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -3,6 +3,7 @@ import { select, selectAll } from 'd3'; import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw'; import { log } from '../../logger'; import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; @@ -225,7 +226,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) { bounds.bumpVerticalPos(conf.boxMargin); noteModel.height = conf.boxMargin; noteModel.starty = bounds.getVerticalPos(); - const rect = svgDraw.getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = noteModel.startx; rect.y = noteModel.starty; rect.width = noteModel.width || conf.width; @@ -233,7 +234,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) { const g = elem.append('g'); const rectElem = svgDraw.drawRect(g, rect); - const textObj = svgDraw.getTextObj(); + const textObj = svgDrawCommon.getTextObj(); textObj.x = noteModel.startx; textObj.y = noteModel.starty; textObj.width = rect.width; @@ -347,7 +348,7 @@ function boundMessage(_diagram, msgModel): number { const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) { const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel; const textDims = utils.calculateTextDimensions(message, messageFont(conf)); - const textObj = svgDraw.getTextObj(); + const textObj = svgDrawCommon.getTextObj(); textObj.x = startx; textObj.y = starty + 10; textObj.width = stopx - startx; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index be34daf4b..668cecb36 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,33 +1,13 @@ import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import { addFunction } from '../../interactionDb'; import { parseFontSize } from '../../utils'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.class !== undefined) { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; -// const sanitizeUrl = function (s) { -// return s -// .replace(/&/g, '&') -// .replace(/ { addFunction(() => { const arr = document.querySelectorAll(id); @@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => { }); }); }; + export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) { if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) { return { height: 0, width: 0 }; @@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe return { height: rectData.height + linkY, width: menuWidth }; }; -export const drawImage = function (elem, x, y, link) { - const imageElem = elem.append('image'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', sanitizedLink); -}; - -export const drawEmbeddedImage = function (elem, x, y, link) { - const imageElem = elem.append('use'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', '#' + sanitizedLink); -}; - export const popupMenu = function (popid) { return ( "var pu = document.getElementById('" + @@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) { pu.style.display = 'none'; } }; + export const drawText = function (elem, textData) { - let prevTextHeight = 0, - textHeight = 0; + let prevTextHeight = 0; + let textHeight = 0; const lines = textData.text.split(common.lineBreakRegex); const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize); @@ -188,6 +154,7 @@ export const drawText = function (elem, textData) { break; } } + if ( textData.anchor !== undefined && textData.textMargin !== undefined && @@ -217,6 +184,7 @@ export const drawText = function (elem, textData) { break; } } + for (let [i, line] of lines.entries()) { if ( textData.textMargin !== undefined && @@ -371,7 +339,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { } } - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); var cssclass = 'actor'; if (actor.properties != null && actor.properties['class']) { cssclass = actor.properties['class']; @@ -391,9 +359,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { if (actor.properties != null && actor.properties['icon']) { const iconSrc = actor.properties['icon'].trim(); if (iconSrc.charAt(0) === '@') { - drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); + svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); } else { - drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); + svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); } } @@ -438,7 +406,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { const actElem = elem.append('g'); actElem.attr('class', 'actor-man'); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = actor.x; rect.y = actor.y; rect.fill = '#eaeaea'; @@ -447,7 +415,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { rect.class = 'actor'; rect.rx = 3; rect.ry = 3; - // drawRect(actElem, rect); actElem .append('line') @@ -532,6 +499,7 @@ export const drawBox = function (elem, box, conf) { export const anchorElement = function (elem) { return elem.append('g'); }; + /** * Draws an activation in the diagram * @@ -542,7 +510,7 @@ export const anchorElement = function (elem) { * @param {any} actorActivations - Number of activations on the actor. */ export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) { - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); const g = bounds.anchored; rect.x = bounds.startx; rect.y = bounds.starty; @@ -594,7 +562,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { }); } - let txt = getTextObj(); + let txt = svgDrawCommon.getTextObj(); txt.text = labelText; txt.x = loopModel.startx; txt.y = loopModel.starty; @@ -610,7 +578,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { txt.class = 'labelText'; drawLabel(g, txt); - txt = getTextObj(); + txt = svgDrawCommon.getTextObj(); txt.text = loopModel.title; txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2; txt.y = loopModel.starty + boxMargin + boxTextMargin; @@ -661,16 +629,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { * @param {any} bounds Shape of the rectangle */ export const drawBackgroundRect = function (elem, bounds) { - const rectElem = drawRect(elem, { - x: bounds.startx, - y: bounds.starty, - width: bounds.stopx - bounds.startx, - height: bounds.stopy - bounds.starty, - fill: bounds.fill, - stroke: bounds.stroke, - class: 'rect', - }); - rectElem.lower(); + svgDrawCommon.drawBackgroundRect(elem, bounds); }; export const insertDatabaseIcon = function (elem) { @@ -737,6 +696,7 @@ export const insertArrowHead = function (elem) { .append('path') .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -755,6 +715,7 @@ export const insertArrowFilledHead = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; + /** * Setup node number. The result is appended to the svg. * @@ -776,6 +737,7 @@ export const insertSequenceNumber = function (elem) { .attr('r', 6); // .style("fill", '#f00'); }; + /** * Setup cross head and define the marker. The result is appended to the svg. * @@ -802,37 +764,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - anchor: undefined, - style: '#666', - width: undefined, - height: undefined, - textMargin: 0, - rx: 0, - ry: 0, - tspan: true, - valign: undefined, - }; -}; - -export const getNoteRect = function () { - return { - x: 0, - y: 0, - fill: '#EDF2AE', - stroke: '#666', - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; -}; - const _drawTextCandidateFunc = (function () { /** * @param {any} content @@ -1062,8 +993,6 @@ export default { drawActor, drawBox, drawPopup, - drawImage, - drawEmbeddedImage, anchorElement, drawActivation, drawLoop, @@ -1075,8 +1004,6 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getTextObj, - getNoteRect, popupMenu, popdownMenu, fixLifeLineHeights, diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index ed60285ed..648ee58f8 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -174,16 +174,4 @@ describe('svgDraw', function () { expect(rect.lower).toHaveBeenCalled(); }); }); - describe('sanitizeUrl', function () { - it('should sanitize malicious urls', function () { - const maliciousStr = 'javascript:script:alert(1)'; - const result = svgDraw.sanitizeUrl(maliciousStr); - expect(result).not.toContain('javascript:alert(1)'); - }); - it('should not sanitize non dangerous urls', function () { - const maliciousStr = 'javajavascript:script:alert(1)'; - const result = svgDraw.sanitizeUrl(maliciousStr); - expect(result).not.toContain('javascript:alert(1)'); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/user-journey/svgDraw.js b/packages/mermaid/src/diagrams/user-journey/svgDraw.js index f6dbe71e1..108f4b2f9 100644 --- a/packages/mermaid/src/diagrams/user-journey/svgDraw.js +++ b/packages/mermaid/src/diagrams/user-journey/svgDraw.js @@ -1,21 +1,8 @@ import { arc as d3arc } from 'd3'; +import * as svgDrawCommon from '../common/svgDrawCommon'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.class !== undefined) { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; export const drawFace = function (element, faceData) { @@ -128,25 +115,7 @@ export const drawCircle = function (element, circleData) { }; export const drawText = function (elem, textData) { - // Remove and ignore br:s - const nText = textData.text.replace(//gi, ' '); - - const textElem = elem.append('text'); - textElem.attr('x', textData.x); - textElem.attr('y', textData.y); - textElem.attr('class', 'legend'); - - textElem.style('text-anchor', textData.anchor); - - if (textData.class !== undefined) { - textElem.attr('class', textData.class); - } - - const span = textElem.append('tspan'); - span.attr('x', textData.x + textData.textMargin * 2); - span.text(nText); - - return textElem; + return svgDrawCommon.drawText(elem, textData); }; export const drawLabel = function (elem, txtObject) { @@ -192,7 +161,7 @@ export const drawLabel = function (elem, txtObject) { export const drawSection = function (elem, section, conf) { const g = elem.append('g'); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = section.x; rect.y = section.y; rect.fill = section.fill; @@ -249,7 +218,7 @@ export const drawTask = function (elem, task, conf) { score: task.score, }); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = task.x; rect.y = task.y; rect.fill = task.fill; @@ -298,41 +267,7 @@ export const drawTask = function (elem, task, conf) { * @param {any} bounds The bounds of the drawing */ export const drawBackgroundRect = function (elem, bounds) { - const rectElem = drawRect(elem, { - x: bounds.startx, - y: bounds.starty, - width: bounds.stopx - bounds.startx, - height: bounds.stopy - bounds.starty, - fill: bounds.fill, - class: 'rect', - }); - rectElem.lower(); -}; - -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - 'text-anchor': 'start', - width: 100, - height: 100, - textMargin: 0, - rx: 0, - ry: 0, - }; -}; - -export const getNoteRect = function () { - return { - x: 0, - y: 0, - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; + svgDrawCommon.drawBackgroundRect(elem, bounds); }; const _drawTextCandidateFunc = (function () { @@ -475,7 +410,5 @@ export default { drawLabel, drawTask, drawBackgroundRect, - getTextObj, - getNoteRect, initGraphics, }; From 38efaf93c86b173470e2afb9ffaaecb231fe48b9 Mon Sep 17 00:00:00 2001 From: raghvendra Date: Sat, 29 Apr 2023 16:06:17 +0530 Subject: [PATCH 08/21] updating es6 rules in flowchart diagram --- .../mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js | 6 +++--- packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js | 6 +++--- packages/mermaid/src/diagrams/flowchart/flowRenderer.js | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js index 66eece46d..6a90c46f5 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -414,7 +414,7 @@ export const addEdges = function (edges, diagObj, graph, svg) { edges.forEach(function (edge) { // Identify Link - var linkIdBase = 'L-' + edge.start + '-' + edge.end; + const linkIdBase = 'L-' + edge.start + '-' + edge.end; // count the links from+to the same node to give unique id if (linkIdCnt[linkIdBase] === undefined) { linkIdCnt[linkIdBase] = 0; @@ -425,8 +425,8 @@ export const addEdges = function (edges, diagObj, graph, svg) { } let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase]; log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]); - var linkNameStart = 'LS-' + edge.start; - var linkNameEnd = 'LE-' + edge.end; + const linkNameStart = 'LS-' + edge.start; + const linkNameEnd = 'LE-' + edge.end; const edgeData = { style: '', labelStyle: '' }; edgeData.minlen = edge.length || 1; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index 0b8d47543..23f94942c 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -214,7 +214,7 @@ export const addEdges = function (edges, g, diagObj) { cnt++; // Identify Link - var linkIdBase = 'L-' + edge.start + '-' + edge.end; + const linkIdBase = 'L-' + edge.start + '-' + edge.end; // count the links from+to the same node to give unique id if (linkIdCnt[linkIdBase] === undefined) { linkIdCnt[linkIdBase] = 0; @@ -225,8 +225,8 @@ export const addEdges = function (edges, g, diagObj) { } let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase]; log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]); - var linkNameStart = 'LS-' + edge.start; - var linkNameEnd = 'LE-' + edge.end; + const linkNameStart = 'LS-' + edge.start; + const linkNameEnd = 'LE-' + edge.end; const edgeData = { style: '', labelStyle: '' }; edgeData.minlen = edge.length || 1; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 575706935..f69fe93eb 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -176,9 +176,9 @@ export const addEdges = function (edges, g, diagObj) { cnt++; // Identify Link - var linkId = 'L-' + edge.start + '-' + edge.end; - var linkNameStart = 'LS-' + edge.start; - var linkNameEnd = 'LE-' + edge.end; + const linkId = 'L-' + edge.start + '-' + edge.end; + const linkNameStart = 'LS-' + edge.start; + const linkNameEnd = 'LE-' + edge.end; const edgeData = {}; From 3549ffc4ac867cbebf146057b4c49641db503846 Mon Sep 17 00:00:00 2001 From: eopaant Date: Sun, 30 Apr 2023 13:19:35 -0500 Subject: [PATCH 09/21] add master detail relationship support --- packages/mermaid/src/diagrams/er/erDb.js | 1 + packages/mermaid/src/diagrams/er/erMarkers.js | 26 +++++++++++++++++++ .../mermaid/src/diagrams/er/erRenderer.js | 6 +++++ .../src/diagrams/er/parser/erDiagram.jison | 2 ++ .../src/diagrams/er/parser/erDiagram.spec.js | 9 +++++++ 5 files changed, 44 insertions(+) diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js index 5f2af0da1..2f5116cbf 100644 --- a/packages/mermaid/src/diagrams/er/erDb.js +++ b/packages/mermaid/src/diagrams/er/erDb.js @@ -20,6 +20,7 @@ const Cardinality = { ZERO_OR_MORE: 'ZERO_OR_MORE', ONE_OR_MORE: 'ONE_OR_MORE', ONLY_ONE: 'ONLY_ONE', + MD_PARENT: 'MD_PARENT', }; const Identification = { diff --git a/packages/mermaid/src/diagrams/er/erMarkers.js b/packages/mermaid/src/diagrams/er/erMarkers.js index 948411772..48cafae65 100644 --- a/packages/mermaid/src/diagrams/er/erMarkers.js +++ b/packages/mermaid/src/diagrams/er/erMarkers.js @@ -7,6 +7,8 @@ const ERMarkers = { ONE_OR_MORE_END: 'ONE_OR_MORE_END', ZERO_OR_MORE_START: 'ZERO_OR_MORE_START', ZERO_OR_MORE_END: 'ZERO_OR_MORE_END', + MD_PARENT_END: 'MD_PARENT_END', + MD_PARENT_START: 'MD_PARENT_START', }; /** @@ -18,6 +20,30 @@ const ERMarkers = { const insertMarkers = function (elem, conf) { let marker; + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.MD_PARENT_START) + .attr('refX', 0) + .attr('refY', 7) + .attr('markerWidth', 190) + .attr('markerHeight', 240) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); + + elem + .append('defs') + .append('marker') + .attr('id', ERMarkers.MD_PARENT_END) + .attr('refX', 19) + .attr('refY', 7) + .attr('markerWidth', 20) + .attr('markerHeight', 28) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z'); + elem .append('defs') .append('marker') diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 87d7ac607..93e22732a 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -478,6 +478,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { case diagObj.db.Cardinality.ONLY_ONE: svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')'); break; + case diagObj.db.Cardinality.MD_PARENT: + svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_END + ')'); + break; } switch (rel.relSpec.cardB) { @@ -502,6 +505,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) { case diagObj.db.Cardinality.ONLY_ONE: svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')'); break; + case diagObj.db.Cardinality.MD_PARENT: + svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_START + ')'); + break; } // Now label the relationship diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index 8ffa87c63..0c6820b49 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -57,6 +57,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili o\| return 'ZERO_OR_ONE'; o\{ return 'ZERO_OR_MORE'; \|\{ return 'ONE_OR_MORE'; +\s*u return 'MD_PARENT'; \.\. return 'NON_IDENTIFYING'; \-\- return 'IDENTIFYING'; "to" return 'IDENTIFYING'; @@ -170,6 +171,7 @@ cardinality | 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; } | 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; } | 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; } + | 'MD_PARENT' { $$ = yy.Cardinality.MD_PARENT; } ; relType diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index 904521d50..6fdc9eaa7 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -718,5 +718,14 @@ describe('when parsing ER diagram it...', function () { const rels = erDb.getRelationships(); expect(rels[0].roleA).toBe('places'); }); + + it('should represent parent-child relationship correctly', function () { + erDiagram.parser.parse('erDiagram\nPROJECT u--o{ TEAM_MEMBER : "parent"'); + const rels = erDb.getRelationships(); + expect(Object.keys(erDb.getEntities()).length).toBe(2); + expect(rels.length).toBe(1); + expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.MD_PARENT); + expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE); + }); }); }); From 70a52da3932da47d8ffc8288d74576f54dfd1dc9 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Mon, 1 May 2023 19:06:46 +0900 Subject: [PATCH 10/21] namespace rect is not filled --- packages/mermaid/src/diagrams/class/classRenderer-v2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index f31cf8890..40fad703e 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -60,6 +60,7 @@ export const addNamespaces = function ( domId: vertex.domId, labelText: sanitizeText(vertex.id), labelStyle: '', + style: 'fill: none; stroke: black', // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, }; From 0d373f3a6ab7c8f858b243bfc76677f70b93ade6 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Mon, 1 May 2023 19:12:39 +0900 Subject: [PATCH 11/21] Modify jsdoc of addClasses --- packages/mermaid/src/diagrams/class/classRenderer-v2.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 40fad703e..352002242 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -79,6 +79,7 @@ export const addNamespaces = function ( * @param g - The graph that is to be drawn. * @param _id - id of the graph * @param diagObj - The diagram object + * @param parent - id of the parent namespace, if it exists */ export const addClasses = function ( classes: ClassMap, From 18571052ad7cae38e3dc3c0b5ac114282e50da41 Mon Sep 17 00:00:00 2001 From: eopaant Date: Mon, 1 May 2023 22:34:36 -0500 Subject: [PATCH 12/21] added style to reflect true aggregation --- packages/mermaid/src/diagrams/er/styles.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/mermaid/src/diagrams/er/styles.js b/packages/mermaid/src/diagrams/er/styles.js index 42dbcebde..08ea2e851 100644 --- a/packages/mermaid/src/diagrams/er/styles.js +++ b/packages/mermaid/src/diagrams/er/styles.js @@ -33,6 +33,17 @@ const getStyles = (options) => font-size: 18px; fill: ${options.textColor}; } + #MD_PARENT_START { + fill: #f5f5f5 !important; + stroke: ${options.lineColor} !important; + stroke-width: 1; + } + #MD_PARENT_END { + fill: #f5f5f5 !important; + stroke: ${options.lineColor} !important; + stroke-width: 1; + } + `; export default getStyles; From 725b80831e7f48e88355d0dd8b8965eec1219966 Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Tue, 2 May 2023 09:46:59 -0400 Subject: [PATCH 13/21] Reject ridiculous years in Gantt charts. --- packages/mermaid/src/diagrams/gantt/ganttDb.js | 12 +++++++++++- packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index dc811cb64..81ff6d755 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -287,7 +287,17 @@ const getStartDate = function (prevTime, dateFormat, str) { log.debug('Invalid date:' + str); log.debug('With date format:' + dateFormat.trim()); const d = new Date(str); - if (d === undefined || isNaN(d.getTime())) { + if ( + d === undefined || + isNaN(d.getTime()) || + // WebKit browsers can mis-parse invalid dates to be ridiculously + // huge numbers, e.g. new Date('202304') gets parsed as January 1, 202304. + // This can cause virtually infinite loops while rendering, so for the + // purposes of Gantt charts we'll just treat any date beyond 10,000 AD/BC as + // invalid. + d.getFullYear() < -10000 || + d.getFullYear() > 10000 + ) { throw new Error('Invalid date:' + str); } return d; diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts index c7e00bf69..123b4a52d 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts @@ -432,4 +432,10 @@ describe('when using the ganttDb', function () { ganttDb.setTodayMarker(expected); expect(ganttDb.getTodayMarker()).toEqual(expected); }); + + it('should reject dates with ridiculous years', function () { + ganttDb.setDateFormat('YYYYMMDD'); + ganttDb.addTask('test1', 'id1,202304,1d'); + expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304'); + }); }); From 61e31b3fe644bde2317a030bc61978ec6f995bfa Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 3 May 2023 16:26:48 -0700 Subject: [PATCH 14/21] fix import issue --- cSpell.json | 5 +++++ packages/mermaid/src/diagrams/c4/svgDraw.js | 2 +- packages/mermaid/src/diagrams/common/svgDrawCommon.js | 10 +--------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/cSpell.json b/cSpell.json index 94276b683..82e300b8e 100644 --- a/cSpell.json +++ b/cSpell.json @@ -90,8 +90,12 @@ "sidharth", "sidharthv", "sphinxcontrib", + "startx", + "starty", "statediagram", "steph", + "stopx", + "stopy", "stylis", "substate", "sveidqvist", @@ -104,6 +108,7 @@ "tuleap", "ugge", "unist", + "valign", "verdana", "viewports", "vinod", diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index ce465cad3..5ca2f55f8 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -1,4 +1,4 @@ -import common from '../common/common'; +import common from '../common/common.js'; import * as svgDrawCommon from '../common/svgDrawCommon'; import { sanitizeUrl } from '@braintree/sanitize-url'; diff --git a/packages/mermaid/src/diagrams/common/svgDrawCommon.js b/packages/mermaid/src/diagrams/common/svgDrawCommon.js index e054f53b6..9a4ce8aa2 100644 --- a/packages/mermaid/src/diagrams/common/svgDrawCommon.js +++ b/packages/mermaid/src/diagrams/common/svgDrawCommon.js @@ -77,7 +77,7 @@ export const drawEmbeddedImage = function (elem, x, y, link) { const imageElem = elem.append('use'); imageElem.attr('x', x); imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); + const sanitizedLink = sanitizeUrl(link); imageElem.attr('xlink:href', '#' + sanitizedLink); }; @@ -112,11 +112,3 @@ export const getTextObj = function () { valign: undefined, }; }; - -export default { - drawRect, - drawImage, - drawText, - getNoteRect, - getTextObj, -}; From 221640aa258c9e8f97e8113a15a52097cb2d1cf8 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 3 May 2023 17:01:27 -0700 Subject: [PATCH 15/21] Fix path in test --- .../mermaid/src/diagrams/class/classDiagramGrammar.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagramGrammar.spec.ts b/packages/mermaid/src/diagrams/class/classDiagramGrammar.spec.ts index eb0c37969..eae09603f 100644 --- a/packages/mermaid/src/diagrams/class/classDiagramGrammar.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagramGrammar.spec.ts @@ -4,12 +4,12 @@ import { LALRGenerator } from 'jison'; import path from 'path'; const getAbsolutePath = (relativePath: string) => { - return new URL(path.join(path.dirname(import.meta.url), relativePath)).pathname; + return new URL(path.join(__dirname, relativePath)).pathname; }; describe('class diagram grammar', function () { it('should have no conflicts', async function () { - const grammarSource = await readFile(getAbsolutePath('./parser/classDiagram.jison'), 'utf8'); + const grammarSource = await readFile(getAbsolutePath('parser/classDiagram.jison'), 'utf8'); const grammarParser = new LALRGenerator(grammarSource, {}); expect(grammarParser.conflicts).toBe(0); }); From 2216bbad2506e1031312d151866991441543ad1f Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 3 May 2023 17:02:44 -0700 Subject: [PATCH 16/21] remove spellcheck warnings --- cSpell.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cSpell.json b/cSpell.json index 82e300b8e..bd306f411 100644 --- a/cSpell.json +++ b/cSpell.json @@ -23,6 +23,7 @@ "classdef", "codedoc", "colour", + "commitlint", "cpettitt", "customizability", "cuzon", @@ -61,6 +62,7 @@ "lintstagedrc", "logmsg", "lucida", + "markdownish", "matthieu", "mdast", "mdbook", @@ -105,6 +107,7 @@ "textlength", "treemap", "ts-nocheck", + "tsdoc", "tuleap", "ugge", "unist", From 06fdb95389b7e767bda7918c77b6888050ba94cd Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 3 May 2023 22:15:59 -0700 Subject: [PATCH 17/21] Allow overlapping notes --- cSpell.json | 1 + .../rendering/sequencediagram.spec.js | 23 +++ demos/sequence.html | 20 +++ .../mermaid/src/diagrams/common/common.ts | 28 ++++ .../sequence/parser/sequenceDiagram.jison | 9 ++ .../src/diagrams/sequence/sequenceDb.js | 1 + .../diagrams/sequence/sequenceDiagram.spec.js | 23 +++ .../src/diagrams/sequence/sequenceRenderer.ts | 131 +++++++++++------- 8 files changed, 187 insertions(+), 49 deletions(-) diff --git a/cSpell.json b/cSpell.json index bd306f411..24650024e 100644 --- a/cSpell.json +++ b/cSpell.json @@ -59,6 +59,7 @@ "knsv", "knut", "laganeckas", + "linetype", "lintstagedrc", "logmsg", "lucida", diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index 687fc245b..e5459637b 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -123,6 +123,29 @@ context('Sequence diagram', () => { } ); }); + it('should render a sequence diagram with par_over', () => { + imgSnapshotTest( + ` + sequenceDiagram + participant Alice + participant Bob + participant John + par_over Section title + Alice ->> Bob: Message 1
Second line + Bob ->> John: Message 2 + end + par_over Two line
section title + Note over Alice: Alice note + Note over Bob: Bob note
Second line + Note over John: John note + end + par_over Mixed section + Alice ->> Bob: Message 1 + Note left of Bob: Alice/Bob Note + end + ` + ); + }); context('font settings', () => { it('should render different note fonts when configured', () => { imgSnapshotTest( diff --git a/demos/sequence.html b/demos/sequence.html index aa2332520..b2733a384 100644 --- a/demos/sequence.html +++ b/demos/sequence.html @@ -144,6 +144,26 @@ >
+
+      sequenceDiagram
+      participant Alice
+      participant Bob
+      participant John
+      par_over Section title
+        Alice ->> Bob: Message 1
Second line + Bob ->> John: Message 2 + end + par_over Two line
section title + Note over Alice: Alice note + Note over Bob: Bob note
Second line + Note over John: John note + end + par_over Mixed section + Alice ->> Bob: Message 1 + Note left of Bob: Alice/Bob Note + end +
+