From 56f3fa64956671dc1b56cb8e7be452baff10a709 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 10 Oct 2022 18:02:20 -0700 Subject: [PATCH 01/12] API: getClasses() with the diagram renderer (not flowchart renderer); add const --- packages/mermaid/src/mermaidAPI.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 7c967e5fd9..a85fecb242 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -33,6 +33,8 @@ import DOMPurify from 'dompurify'; import { MermaidConfig } from './config.type'; import { evaluate } from './diagrams/common/common'; +const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram']; + /** * @param text * @param parseError @@ -255,8 +257,8 @@ const render = async function ( } // classDef - if (graphType === 'flowchart' || graphType === 'flowchart-v2' || graphType === 'graph') { - const classes: any = flowRenderer.getClasses(text, diag); + if (CLASSDEF_DIAGRAMS.includes(graphType)) { + const classes: any = diag.renderer.getClasses(text, diag); const htmlLabels = cnf.htmlLabels || cnf.flowchart?.htmlLabels; for (const className in classes) { if (htmlLabels) { From 278a19f87aec156d81e5178579971cd5cc29d6d0 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Mon, 10 Oct 2022 18:25:33 -0700 Subject: [PATCH 02/12] state demo add classDefs to example with explanations; add some headers; update composite from docs --- demos/state.html | 51 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/demos/state.html b/demos/state.html index 7f85fceb1e..57f89779c6 100644 --- a/demos/state.html +++ b/demos/state.html @@ -15,6 +15,7 @@

State diagram demos

+

Very simple showing change from State1 to State2

 		stateDiagram
 		  accTitle: This is the accessible title
@@ -24,16 +25,43 @@ 

State diagram demos


+

This has classDef statements to apply style classes to specific states

+

Here are the classDef statements:

+

+ + classDef notMoving fill:white
+ classDef movement font-style:italic;
+ classDef badBadEvent fill:#f00,color:white,font-weight:bold
+
+

+

And these are how they are applied:

+

+ + class Still notMoving
+ class Moving, Crash movement
+ class Crash badBadEvent
+
+

 		stateDiagram-v2
 		  accTitle: This is the accessible title
       accDescr: This is an accessible description
+
+      classDef notMoving fill:white
+      classDef movement font-style:italic;
+      classDef badBadEvent fill:#f00,color:white,font-weight:bold
+
       [*] --> Still
       Still --> [*]
       Still --> Moving
       Moving --> Still
       Moving --> Crash
       Crash --> [*]
+
+      class Still notMoving
+      class Moving, Crash movement
+      class Crash badBadEvent
+
     

@@ -46,14 +74,29 @@

State diagram demos


- +

This shows Composite states

-    stateDiagram
+
+
+    stateDiagram-v2
     [*] --> First
+    First --> Second
+    First --> Third
+
     state First {
-    [*] --> second
-    second --> [*]
+        [*] --> 1st
+        1st --> [*]
+    }
+    state Second {
+        [*] --> 2nd
+        2nd --> [*]
     }
+    state Third {
+        [*] --> 3rd
+        3rd --> [*]
+    }
+
+
     
     stateDiagram

From 965eae5f453349aed94791eb0d93fabaabf1e2fb Mon Sep 17 00:00:00 2001
From: "Ashley Engelund (weedySeaDragon @ github)" 
Date: Mon, 10 Oct 2022 18:26:31 -0700
Subject: [PATCH 03/12] add spec for stateDB addStyleClass

---
 .../mermaid/src/diagrams/state/stateDb.spec.js  | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 packages/mermaid/src/diagrams/state/stateDb.spec.js

diff --git a/packages/mermaid/src/diagrams/state/stateDb.spec.js b/packages/mermaid/src/diagrams/state/stateDb.spec.js
new file mode 100644
index 0000000000..786c122aaf
--- /dev/null
+++ b/packages/mermaid/src/diagrams/state/stateDb.spec.js
@@ -0,0 +1,17 @@
+import stateDb from './stateDb';
+
+describe('stateDb', () => {
+  describe('addStyleClass', () => {
+    it('is added to the list of style classes', () => {
+      const newStyleClassId = 'newStyleClass';
+      const newStyleClassAttribs = 'font-weight:bold, border:blue;';
+
+      stateDb.addStyleClass(newStyleClassId, newStyleClassAttribs);
+      const styleClasses = stateDb.getClasses();
+      expect(styleClasses[newStyleClassId].id).toEqual(newStyleClassId);
+      expect(styleClasses[newStyleClassId].styles.length).toEqual(2);
+      expect(styleClasses[newStyleClassId].styles[0]).toEqual('font-weight:bold');
+      expect(styleClasses[newStyleClassId].styles[1]).toEqual('border:blue');
+    });
+  });
+});

From 7b7db4f60ee006b0a49be614e5b95aac50cce103 Mon Sep 17 00:00:00 2001
From: "Ashley Engelund (weedySeaDragon @ github)" 
Date: Mon, 10 Oct 2022 18:50:04 -0700
Subject: [PATCH 04/12] (minor) API: removed unused flowRenderer import; add
 comment

---
 packages/mermaid/src/mermaidAPI.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts
index a85fecb242..d4333e3709 100644
--- a/packages/mermaid/src/mermaidAPI.ts
+++ b/packages/mermaid/src/mermaidAPI.ts
@@ -20,7 +20,6 @@ import * as configApi from './config';
 import { addDiagrams } from './diagram-api/diagram-orchestration';
 import classDb from './diagrams/class/classDb';
 import flowDb from './diagrams/flowchart/flowDb';
-import flowRenderer from './diagrams/flowchart/flowRenderer';
 import ganttDb from './diagrams/gantt/ganttDb';
 import Diagram, { getDiagramFromText } from './Diagram';
 import errorRenderer from './diagrams/error/errorRenderer';
@@ -33,6 +32,7 @@ import DOMPurify from 'dompurify';
 import { MermaidConfig } from './config.type';
 import { evaluate } from './diagrams/common/common';
 
+// diagram names that support classDef statements
 const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram'];
 
 /**

From d1f3b889d601f26782e621e7f046991abcc8bbf3 Mon Sep 17 00:00:00 2001
From: "Ashley Engelund (weedySeaDragon @ github)" 
Date: Mon, 10 Oct 2022 22:03:22 -0700
Subject: [PATCH 05/12] add cypress tests for classDefs and applying classes to
 states

---
 .../rendering/stateDiagram-v2.spec.js         | 38 +++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js
index aa2bc06ef6..5b43c890cb 100644
--- a/cypress/integration/rendering/stateDiagram-v2.spec.js
+++ b/cypress/integration/rendering/stateDiagram-v2.spec.js
@@ -521,4 +521,42 @@ stateDiagram-v2
       { logLevel: 0, fontFamily: 'courier' }
     );
   });
+
+  describe('classDefs and applying classes', () => {
+    it('v2 states can have a class applied', () => {
+      imgSnapshotTest(
+        `
+          stateDiagram-v2
+          [*] --> A
+          A --> B: test({ foo#colon; 'far' })
+          B --> [*]
+            classDef badBadEvent fill:#f00,color:white,font-weight:bold 
+            class B badBadEvent
+           `,
+        { logLevel: 0, fontFamily: 'courier' }
+      );
+    });
+    it('v2 can have multiple classes applied to multiple states', () => {
+      imgSnapshotTest(
+        `
+          stateDiagram-v2
+          classDef notMoving fill:white
+          classDef movement font-style:italic;
+          classDef badBadEvent fill:#f00,color:white,font-weight:bold
+    
+          [*] --> Still
+          Still --> [*]
+          Still --> Moving
+          Moving --> Still
+          Moving --> Crash
+          Crash --> [*]
+    
+          class Still notMoving
+          class Moving, Crash movement
+          class Crash badBadEvent
+        `,
+        { logLevel: 0, fontFamily: 'courier' }
+      );
+    });
+  });
 });

From 15dd60ab8560a6f4650fe6e4d579ce0f7acd904e Mon Sep 17 00:00:00 2001
From: "Ashley Engelund (weedySeaDragon @ github)" 
Date: Tue, 11 Oct 2022 08:34:30 -0700
Subject: [PATCH 06/12] state demo: add more diagrams, add explanatory text

---
 demos/state.html | 63 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 44 insertions(+), 19 deletions(-)

diff --git a/demos/state.html b/demos/state.html
index 57f89779c6..dbe2286a30 100644
--- a/demos/state.html
+++ b/demos/state.html
@@ -61,23 +61,31 @@ 

And these are how they are applied:

class Still notMoving class Moving, Crash movement class Crash badBadEvent -

-    stateDiagram
+    stateDiagram-v2
       accTitle: very very simple state
     accDescr: This is a state diagram showing one state
     State1
     
+
+

You can label the relationships

+
+    stateDiagram-v2
+    [*] --> State1
+    State1 --> State2 : Transition 1
+    State1 --> State3 : Transition 2
+    State1 --> State4 : Transition 3
+    State1 --> [*]
+    

+

This shows Composite states

-
-
     stateDiagram-v2
     [*] --> First
     First --> Second
@@ -95,31 +103,48 @@ 

This shows Composite states

[*] --> 3rd 3rd --> [*] } +
+
- +

Compsite states can link to themselves

+
+      stateDiagram-v2
+            state Active {
+              Idle
+            }
+            Inactive --> Idle: ACT
+            Active --> Active: LOG
     
+
+ +

transition labels can span multiple lines using "br" tags or \n

-    stateDiagram
-    State1: The state with a note
-    note right of State1
-    Important information! You can write
-    notes.
-    end note
-    State1 --> State2
-    note left of State2 : This is the note to the left.
+      stateDiagram-v2
+      [*] --> S1
+      S1 --> S2: This long line uses a br tag
to create multiple
lines. + S1 --> S3: This transition descripton uses \na newline character\nto create multiple\nlines. +
+
+ +

You can add Notes

-    stateDiagram
-    State1
-    note right of State1
-    Line1
Line2
Line3
Line4
Line5 - end note + stateDiagram-v2 + direction LR + State1: A state with a note + note right of State1 + Important information!
You can write notes.
And\nthey\ncan\nbe\nmulti-\nline. + end note + State1 --> State2 + note left of State2 : Notes can be to the left of a state\n(like this one). + note right of State2 : Notes can be to the right of a state\n(like this one).
+
' + ); + let states = stateDb.getStates(); + expect(states[testStateId].descriptions[0]).toEqual('desc outside the script '); + }); + + it('adds the description to the state with the given id', () => { + stateDb.addDescription(testStateId, 'the description'); + let states = stateDb.getStates(); + expect(states[testStateId].descriptions[0]).toEqual('the description'); + }); + }); }); From 3c0727c7442b503878bcaadf0c4d704d1b89b5e3 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 11 Oct 2022 10:16:57 -0700 Subject: [PATCH 09/12] diagram-v2 spec: added tests for labels, composite; fix typos, --- .../diagrams/state/stateDiagram-v2.spec.js | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js index ad224f14da..2f3b765da2 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js @@ -1,10 +1,13 @@ import { parser } from './parser/stateDiagram'; import stateDb from './stateDb'; +import stateDiagram from './parser/stateDiagram.jison'; describe('state diagram, ', function () { describe('when parsing an info graph it', function () { beforeEach(function () { parser.yy = stateDb; + stateDiagram.parser.yy = stateDb; + stateDiagram.parser.yy.clear(); }); it('super simple', function () { @@ -121,6 +124,30 @@ describe('state diagram, ', function () { parser.parse(str); }); + describe('relationship labels', () => { + it('simple states with : labels', () => { + const diagram = ` + stateDiagram-v2 + [*] --> State1 + State1 --> State2 : Transition 1 + State1 --> State3 : Transition 2 + State1 --> State4 : Transition 3 + State1 --> [*] + `; + + stateDiagram.parser.parse(diagram); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const rels = stateDb.getRelations(); + const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2'); + expect(rel_1_2.relationTitle).toEqual('Transition 1'); + const rel_1_3 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State3'); + expect(rel_1_3.relationTitle).toEqual('Transition 2'); + const rel_1_4 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State4'); + expect(rel_1_4.relationTitle).toEqual('Transition 3'); + }); + }); + it('scale', function () { const str = `stateDiagram-v2\n scale 350 width @@ -355,7 +382,7 @@ describe('state diagram, ', function () { parser.parse(str); }); - it('should handle notes for composit states', function () { + it('should handle notes for composite (nested) states', function () { const str = `stateDiagram-v2\n [*] --> NotShooting @@ -372,5 +399,28 @@ describe('state diagram, ', function () { parser.parse(str); }); + + it('A composite state should be able to link to itself', () => { + const diagram = ` + stateDiagram-v2 + state Active { + Idle + } + Inactive --> Idle: ACT + Active --> Active: LOG + `; + + stateDiagram.parser.parse(diagram); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDb.getStates(); + expect(states['Active'].doc[0].id).toEqual('Idle'); + + const rels = stateDb.getRelations(); + const rel_Inactive_Idle = rels.find((rel) => rel.id1 === 'Inactive' && rel.id2 === 'Idle'); + expect(rel_Inactive_Idle.relationTitle).toEqual('ACT'); + const rel_Active_Active = rels.find((rel) => rel.id1 === 'Active' && rel.id2 === 'Active'); + expect(rel_Active_Active.relationTitle).toEqual('LOG'); + }); }); }); From ba71afcce5238561b4d43b4cc623580c8ac96454 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 11 Oct 2022 10:19:28 -0700 Subject: [PATCH 10/12] diagram-v2: store results of stateDb.extract(), apply class to state; code cleanup --- .../src/diagrams/state/stateRenderer-v2.js | 324 +++++++++++------- 1 file changed, 203 insertions(+), 121 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 13c474b5e1..84b62b2ca8 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -7,7 +7,23 @@ import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import addSVGAccessibilityFields from '../../accessibility'; +const DEFAULT_DIR = 'TD'; + +// When information is parsed and processed (extracted) by stateDb.extract() +// These are globals so the information can be accessed as needed (e.g. in setUpNode, etc.) +let diagramStates = []; +let diagramClasses = []; + +// List of nodes created from the parsed diagram statement items +let nodeDb = {}; + +let graphItemCount = 0; // used to construct ids, etc. + +// Configuration const conf = {}; + +// ----------------------------------------------------------------------- + export const setConf = function (cnf) { const keys = Object.keys(cnf); for (let i = 0; i < keys.length; i++) { @@ -15,150 +31,187 @@ export const setConf = function (cnf) { } }; -let nodeDb = {}; - /** * Returns the all the styles from classDef statements in the graph definition. * - * @param {any} text - * @param diag + * @param {string} text - the diagram text to be parsed + * @param {Diagram} diagramObj * @returns {object} ClassDef styles */ -export const getClasses = function (text, diag) { +export const getClasses = function (text, diagramObj) { log.trace('Extracting classes'); - diag.sb.clear(); - - // Parse the graph definition - diag.parser.parse(text); - return diag.sb.getClasses(); + if (diagramClasses.length > 0) return diagramClasses; // we have already extracted the classes + + diagramObj.db.clear(); + try { + // Parse the graph definition + diagramObj.parser.parse(text); + // must run extract() to turn the parsed statements into states, relationships, classes, etc. + diagramObj.db.extract(diagramObj.db.getRootDocV2()); + + return diagramObj.db.getClasses(); + } catch (e) { + return e; + } }; -const setupNode = (g, parent, node, altFlag) => { - // Add the node - if (node.id !== 'root') { +/** + * Get classes from the db info item. + * If there aren't any or if dbInfoItem isn't defined, return an empty string. + * Else create 1 string from the list of classes found + * + * @param {undefined | null | object} dbInfoItem + * @returns {string} + */ +function getClassesFromDbInfo(dbInfoItem) { + if (typeof dbInfoItem === 'undefined' || dbInfoItem === null) return ''; + else { + if (dbInfoItem.classes) { + return dbInfoItem.classes.join(' '); + } else return ''; + } +} + +/** + * Create a graph node based on the statement information + * + * @param g - graph + * @param {object} parent + * @param {object} parsedItem - parsed statement item + * @param {object} diagramDb + * @param {boolean} altFlag - for clusters, add the "statediagram-cluster-alt" CSS class + * @todo This duplicates some of what is done in stateDb.js extract method + */ +const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { + const itemId = parsedItem.id; + const classStr = getClassesFromDbInfo(diagramStates[itemId]); + + if (itemId !== 'root') { let shape = 'rect'; - if (node.start === true) { + if (parsedItem.start === true) { shape = 'start'; } - if (node.start === false) { + if (parsedItem.start === false) { shape = 'end'; } - if (node.type !== 'default') { - shape = node.type; + if (parsedItem.type !== 'default') { + shape = parsedItem.type; } - if (!nodeDb[node.id]) { - nodeDb[node.id] = { - id: node.id, + // Add the node to our list (nodeDb) + if (!nodeDb[itemId]) { + nodeDb[itemId] = { + id: itemId, shape, - description: common.sanitizeText(node.id, getConfig()), - classes: 'statediagram-state', + description: common.sanitizeText(itemId, getConfig()), + classes: classStr + ' statediagram-state', }; } - // Build of the array of description strings accordinging - if (node.description) { - if (Array.isArray(nodeDb[node.id].description)) { + const newNode = nodeDb[itemId]; + + // Build of the array of description strings + if (parsedItem.description) { + if (Array.isArray(newNode.description)) { // There already is an array of strings,add to it - nodeDb[node.id].shape = 'rectWithTitle'; - nodeDb[node.id].description.push(node.description); + newNode.shape = 'rectWithTitle'; + newNode.description.push(parsedItem.description); } else { - if (nodeDb[node.id].description.length > 0) { - // if there is a description already transformit to an array - nodeDb[node.id].shape = 'rectWithTitle'; - if (nodeDb[node.id].description === node.id) { + if (newNode.description.length > 0) { + // if there is a description already transform it to an array + newNode.shape = 'rectWithTitle'; + if (newNode.description === itemId) { // If the previous description was the is, remove it - nodeDb[node.id].description = [node.description]; + newNode.description = [parsedItem.description]; } else { - nodeDb[node.id].description = [nodeDb[node.id].description, node.description]; + newNode.description = [newNode.description, parsedItem.description]; } } else { - nodeDb[node.id].shape = 'rect'; - nodeDb[node.id].description = node.description; + newNode.shape = 'rect'; + newNode.description = parsedItem.description; } } - nodeDb[node.id].description = common.sanitizeTextOrArray( - nodeDb[node.id].description, - getConfig() - ); + newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig()); } - // - if (nodeDb[node.id].description.length === 1 && nodeDb[node.id].shape === 'rectWithTitle') { - nodeDb[node.id].shape = 'rect'; + // update the node shape + if (newNode.description.length === 1 && newNode.shape === 'rectWithTitle') { + newNode.shape = 'rect'; } // Save data for description and group so that for instance a statement without description overwrites // one with description // group - if (!nodeDb[node.id].type && node.doc) { - log.info('Setting cluster for ', node.id, getDir(node)); - nodeDb[node.id].type = 'group'; - nodeDb[node.id].dir = getDir(node); - nodeDb[node.id].shape = node.type === 'divider' ? 'divider' : 'roundedWithTitle'; - nodeDb[node.id].classes = - nodeDb[node.id].classes + + if (!newNode.type && parsedItem.doc) { + log.info('Setting cluster for ', itemId, getDir(parsedItem)); + newNode.type = 'group'; + newNode.dir = getDir(parsedItem); + newNode.shape = parsedItem.type === 'divider' ? 'divider' : 'roundedWithTitle'; + + newNode.classes = + newNode.classes + ' ' + (altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster'); } + // This is what will be added to the graph const nodeData = { labelStyle: '', - shape: nodeDb[node.id].shape, - labelText: nodeDb[node.id].description, - // typeof nodeDb[node.id].description === 'object' - // ? nodeDb[node.id].description[0] - // : nodeDb[node.id].description, - classes: nodeDb[node.id].classes, //classStr, + shape: newNode.shape, + labelText: newNode.description, + // typeof newNode.description === 'object' + // ? newNode.description[0] + // : newNode.description, + classes: newNode.classes, style: '', //styles.style, - id: node.id, - dir: nodeDb[node.id].dir, - domId: 'state-' + node.id + '-' + cnt, - type: nodeDb[node.id].type, + id: itemId, + dir: newNode.dir, + domId: 'state-' + itemId + '-' + graphItemCount, + type: newNode.type, padding: 15, //getConfig().flowchart.padding }; - if (node.note) { + if (parsedItem.note) { // Todo: set random id const noteData = { labelStyle: '', shape: 'note', - labelText: node.note.text, + labelText: parsedItem.note.text, classes: 'statediagram-note', //classStr, - style: '', //styles.style, - id: node.id + '----note-' + cnt, - domId: 'state-' + node.id + '----note-' + cnt, - type: nodeDb[node.id].type, + style: '', // styles.style, + id: itemId + '----note-' + graphItemCount, + domId: 'state-' + itemId + '----note-' + graphItemCount, + type: newNode.type, padding: 15, //getConfig().flowchart.padding }; const groupData = { labelStyle: '', shape: 'noteGroup', - labelText: node.note.text, - classes: nodeDb[node.id].classes, //classStr, - style: '', //styles.style, - id: node.id + '----parent', - domId: 'state-' + node.id + '----parent-' + cnt, + labelText: parsedItem.note.text, + classes: newNode.classes, //classStr, + style: '', // styles.style, + id: itemId + '----parent', + domId: 'state-' + itemId + '----parent-' + graphItemCount, type: 'group', padding: 0, //getConfig().flowchart.padding }; - cnt++; + graphItemCount++; - g.setNode(node.id + '----parent', groupData); + g.setNode(itemId + '----parent', groupData); g.setNode(noteData.id, noteData); - g.setNode(node.id, nodeData); + g.setNode(itemId, nodeData); - g.setParent(node.id, node.id + '----parent'); - g.setParent(noteData.id, node.id + '----parent'); + g.setParent(itemId, itemId + '----parent'); + g.setParent(noteData.id, itemId + '----parent'); - let from = node.id; + let from = itemId; let to = noteData.id; - if (node.note.position === 'left of') { + if (parsedItem.note.position === 'left of') { from = noteData.id; - to = node.id; + to = itemId; } g.setEdge(from, to, { arrowhead: 'none', @@ -172,66 +225,92 @@ const setupNode = (g, parent, node, altFlag) => { thickness: 'normal', }); } else { - g.setNode(node.id, nodeData); + g.setNode(itemId, nodeData); } } if (parent) { if (parent.id !== 'root') { - log.trace('Setting node ', node.id, ' to be child of its parent ', parent.id); - g.setParent(node.id, parent.id); + log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id); + g.setParent(itemId, parent.id); } } - if (node.doc) { + if (parsedItem.doc) { log.trace('Adding nodes children '); - setupDoc(g, node, node.doc, !altFlag); + setupDoc(g, parsedItem, parsedItem.doc, diagramDb, !altFlag); } }; -let cnt = 0; -const setupDoc = (g, parent, doc, altFlag) => { - // cnt = 0; + +/** + * Turn parsed statements (item.stmt) into nodes, relationships, etc. for a document. + * (A document may be nested within others.) + * + * @param g + * @param parentParsedItem - parsed Item that is the parent of this document (doc) + * @param doc - the document to set up + * @param diagramDb + * @param altFlag + * @todo This duplicates some of what is done in stateDb.js extract method + */ +const setupDoc = (g, parentParsedItem, doc, diagramDb, altFlag) => { + // graphItemCount = 0; log.trace('items', doc); doc.forEach((item) => { - if (item.stmt === 'state' || item.stmt === 'default') { - setupNode(g, parent, item, altFlag); - } else if (item.stmt === 'relation') { - setupNode(g, parent, item.state1, altFlag); - setupNode(g, parent, item.state2, altFlag); - const edgeData = { - id: 'edge' + cnt, - arrowhead: 'normal', - arrowTypeEnd: 'arrow_barb', - style: 'fill:none', - labelStyle: '', - label: common.sanitizeText(item.description, getConfig()), - arrowheadStyle: 'fill: #333', - labelpos: 'c', - labelType: 'text', - thickness: 'normal', - classes: 'transition', - }; - let startId = item.state1.id; - let endId = item.state2.id; - - g.setEdge(startId, endId, edgeData, cnt); - cnt++; + switch (item.stmt) { + case 'state': + setupNode(g, parentParsedItem, item, diagramDb, altFlag); + break; + case 'default': + setupNode(g, parentParsedItem, item, diagramDb, altFlag); + break; + case 'relation': + { + setupNode(g, parentParsedItem, item.state1, diagramDb, altFlag); + setupNode(g, parentParsedItem, item.state2, diagramDb, altFlag); + const edgeData = { + id: 'edge' + graphItemCount, + arrowhead: 'normal', + arrowTypeEnd: 'arrow_barb', + style: 'fill:none', + labelStyle: '', + label: common.sanitizeText(item.description, getConfig()), + arrowheadStyle: 'fill: #333', + labelpos: 'c', + labelType: 'text', + thickness: 'normal', + classes: 'transition', + }; + g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount); + graphItemCount++; + } + break; } }); }; -const getDir = (nodes, defaultDir) => { - let dir = defaultDir || 'TB'; - if (nodes.doc) { - for (let i = 0; i < nodes.doc.length; i++) { - const node = nodes.doc[i]; - if (node.stmt === 'dir') { - dir = node.value; + +/** + * Get the direction from the statement items. Default is TB (top to bottom). + * Look through all of the documents (docs) in the parsedItems + * + * @param {object[]} parsedItem - the parsed statement item to look through + * @param [defaultDir='TB'] - the direction to use if none is found + * @returns {string} + */ +const getDir = (parsedItem, defaultDir = DEFAULT_DIR) => { + let dir = defaultDir; + if (parsedItem.doc) { + for (let i = 0; i < parsedItem.doc.length; i++) { + const parsedItemDoc = parsedItem.doc[i]; + if (parsedItemDoc.stmt === 'dir') { + dir = parsedItemDoc.value; } } } return dir; }; + /** - * Draws a flowchart in the tag with id: id based on the graph definition in text. + * Draws a state diagram in the tag with id: id based on the graph definition in text. * * @param {any} text * @param {any} id @@ -244,18 +323,21 @@ export const draw = function (text, id, _version, diag) { nodeDb = {}; // Fetch the default direction, use TD if none was found let dir = diag.db.getDirection(); - if (typeof dir === 'undefined') { - dir = 'LR'; - } + if (typeof dir === 'undefined') dir = DEFAULT_DIR; const { securityLevel, state: conf } = getConfig(); const nodeSpacing = conf.nodeSpacing || 50; const rankSpacing = conf.rankSpacing || 50; log.info(diag.db.getRootDocV2()); + + // This parses the diagram text and sets the classes, relations, styles, classDefs, etc. diag.db.extract(diag.db.getRootDocV2()); log.info(diag.db.getRootDocV2()); + diagramStates = diag.db.getStates(); + diagramClasses = diag.db.getClasses(); + // Create the input mermaid.graph const g = new graphlib.Graph({ multigraph: true, @@ -272,7 +354,7 @@ export const draw = function (text, id, _version, diag) { return {}; }); - setupNode(g, undefined, diag.db.getRootDocV2(), true); + setupNode(g, undefined, diag.db.getRootDocV2(), diag.db, true); // Set up an SVG group so that we can translate the final graph. let sandboxElement; From 85ba4549fb847656aa383d17cc5d83a146e69ffb Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 11 Oct 2022 13:02:49 -0700 Subject: [PATCH 11/12] (minor) add "V2" to top level of v2 diagram spec --- packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js index 2f3b765da2..39ec0f0d2c 100644 --- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js +++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js @@ -2,7 +2,7 @@ import { parser } from './parser/stateDiagram'; import stateDb from './stateDb'; import stateDiagram from './parser/stateDiagram.jison'; -describe('state diagram, ', function () { +describe('state diagram V2, ', function () { describe('when parsing an info graph it', function () { beforeEach(function () { parser.yy = stateDb; From 589dd7035655d2d93c9eca056781e22178e3548b Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 11 Oct 2022 13:03:55 -0700 Subject: [PATCH 12/12] common consts; add consts in stateRenderer-v2 (will esp. make theme usage easier) --- .../mermaid/src/diagrams/state/stateCommon.ts | 32 +++ .../mermaid/src/diagrams/state/stateDb.js | 36 ++-- .../src/diagrams/state/stateRenderer-v2.js | 185 ++++++++++++------ 3 files changed, 176 insertions(+), 77 deletions(-) create mode 100644 packages/mermaid/src/diagrams/state/stateCommon.ts diff --git a/packages/mermaid/src/diagrams/state/stateCommon.ts b/packages/mermaid/src/diagrams/state/stateCommon.ts new file mode 100644 index 0000000000..2df19eee8d --- /dev/null +++ b/packages/mermaid/src/diagrams/state/stateCommon.ts @@ -0,0 +1,32 @@ +/** + * Constants common to all State Diagram code + */ + +// default diagram direction +export const DEFAULT_DIAGRAM_DIRECTION = 'LR'; + +// default direction for any nested documents (composites) +export const DEFAULT_NESTED_DOC_DIR = 'TB'; + +// parsed statement type for a state +export const STMT_STATE = 'state'; +// parsed statement type for a relation +export const STMT_RELATION = 'relation'; +// parsed statement type for a classDef +export const STMT_CLASSDEF = 'classDef'; +// parsed statement type for applyClass +export const STMT_APPLYCLASS = 'applyClass'; + +export const DEFAULT_STATE_TYPE = 'default'; +export const DIVIDER_TYPE = 'divider'; + +export default { + DEFAULT_DIAGRAM_DIRECTION, + DEFAULT_NESTED_DOC_DIR, + STMT_STATE, + STMT_RELATION, + STMT_CLASSDEF, + STMT_APPLYCLASS, + DEFAULT_STATE_TYPE, + DIVIDER_TYPE, +}; diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index 1fa0ac98a9..5cc5ebc080 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -11,19 +11,27 @@ import { clear as commonClear, } from '../../commonDb'; -const DEFAULT_DIRECTION = 'TB'; +import { + DEFAULT_DIAGRAM_DIRECTION, + STMT_STATE, + STMT_RELATION, + STMT_CLASSDEF, + STMT_APPLYCLASS, + DEFAULT_STATE_TYPE, + DIVIDER_TYPE, +} from './stateCommon'; + const START_NODE = '[*]'; const START_TYPE = 'start'; const END_NODE = START_NODE; const END_TYPE = 'end'; -const DEFAULT_TYPE = 'default'; const COLOR_KEYWORD = 'color'; const FILL_KEYWORD = 'fill'; const BG_FILL = 'bgFill'; const STYLECLASS_SEP = ','; -let direction = DEFAULT_DIRECTION; +let direction = DEFAULT_DIAGRAM_DIRECTION; let rootDoc = []; let classes = []; // style classes defined by a classDef @@ -69,11 +77,11 @@ const setRootDoc = (o) => { const getRootDoc = () => rootDoc; const docTranslator = (parent, node, first) => { - if (node.stmt === 'relation') { + if (node.stmt === STMT_RELATION) { docTranslator(parent, node.state1, true); docTranslator(parent, node.state2, false); } else { - if (node.stmt === 'state') { + if (node.stmt === STMT_STATE) { if (node.id === '[*]') { node.id = first ? parent.id + '_start' : parent.id + '_end'; node.start = first; @@ -86,7 +94,7 @@ const docTranslator = (parent, node, first) => { let currentDoc = []; let i; for (i = 0; i < node.doc.length; i++) { - if (node.doc[i].type === 'divider') { + if (node.doc[i].type === DIVIDER_TYPE) { // debugger; const newNode = clone(node.doc[i]); newNode.doc = clone(currentDoc); @@ -100,7 +108,7 @@ const docTranslator = (parent, node, first) => { // If any divider was encountered if (doc.length > 0 && currentDoc.length > 0) { const newNode = { - stmt: 'state', + stmt: STMT_STATE, id: generateId(), type: 'divider', doc: clone(currentDoc), @@ -149,7 +157,7 @@ const extract = (_doc) => { doc.forEach((item) => { switch (item.stmt) { - case 'state': + case STMT_STATE: addState( item.id, item.type, @@ -161,13 +169,13 @@ const extract = (_doc) => { item.textStyles ); break; - case 'relation': + case STMT_RELATION: addRelation(item.state1, item.state2, item.description); break; - case 'classDef': + case STMT_CLASSDEF: addStyleClass(item.id, item.classes); break; - case 'applyClass': + case STMT_APPLYCLASS: setCssClass(item.id, item.styleClass); break; } @@ -188,7 +196,7 @@ const extract = (_doc) => { */ export const addState = function ( id, - type = DEFAULT_TYPE, + type = DEFAULT_STATE_TYPE, doc = null, descr = null, note = null, @@ -309,7 +317,7 @@ function startIdIfNeeded(id = '') { * @param {string} type * @returns {string} - the type that should be used */ -function startTypeIfNeeded(id = '', type = DEFAULT_TYPE) { +function startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { return id === START_NODE ? START_TYPE : type; } @@ -338,7 +346,7 @@ function endIdIfNeeded(id = '') { * @param {string} type * @returns {string} - the type that should be used */ -function endTypeIfNeeded(id = '', type = DEFAULT_TYPE) { +function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { return id === END_NODE ? END_TYPE : type; } diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 84b62b2ca8..c4c1cb7f04 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -6,9 +6,58 @@ import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import addSVGAccessibilityFields from '../../accessibility'; - -const DEFAULT_DIR = 'TD'; - +import { + DEFAULT_DIAGRAM_DIRECTION, + DEFAULT_NESTED_DOC_DIR, + STMT_STATE, + STMT_RELATION, + DEFAULT_STATE_TYPE, + DIVIDER_TYPE, +} from './stateCommon'; + +// -------------------------------------- +// Shapes +const SHAPE_STATE = 'rect'; +const SHAPE_STATE_WITH_DESC = 'rectWithTitle'; +const SHAPE_START = 'start'; +const SHAPE_END = 'end'; +const SHAPE_DIVIDER = 'divider'; +const SHAPE_GROUP = 'roundedWithTitle'; +const SHAPE_NOTE = 'note'; +const SHAPE_NOTEGROUP = 'noteGroup'; + +// -------------------------------------- +// CSS classes +const CSS_DIAGRAM = 'statediagram'; +const CSS_STATE = 'state'; +const CSS_DIAGRAM_STATE = `${CSS_DIAGRAM}-${CSS_STATE}`; +const CSS_EDGE = 'transition'; +const CSS_NOTE = 'note'; +const CSS_NOTE_EDGE = 'note-edge'; +const CSS_EDGE_NOTE_EDGE = `${CSS_EDGE} ${CSS_NOTE_EDGE}`; +const CSS_DIAGRAM_NOTE = `${CSS_DIAGRAM}-${CSS_NOTE}`; +const CSS_CLUSTER = 'cluster'; +const CSS_DIAGRAM_CLUSTER = `${CSS_DIAGRAM}-${CSS_CLUSTER}`; +const CSS_CLUSTER_ALT = 'cluster-alt'; +const CSS_DIAGRAM_CLUSTER_ALT = `${CSS_DIAGRAM}-${CSS_CLUSTER_ALT}`; + +// -------------------------------------- +// DOM and element IDs +const PARENT = 'parent'; +const NOTE = 'note'; +const DOMID_STATE = 'state'; +const DOMID_TYPE_SPACER = '----'; +const NOTE_ID = `${DOMID_TYPE_SPACER}${NOTE}`; +const PARENT_ID = `${DOMID_TYPE_SPACER}${PARENT}`; +// -------------------------------------- +// Graph edge settings +const G_EDGE_STYLE = 'fill:none'; +const G_EDGE_ARROWHEADSTYLE = 'fill: #333'; +const G_EDGE_LABELPOS = 'c'; +const G_EDGE_LABELTYPE = 'text'; +const G_EDGE_THICKNESS = 'normal'; + +// -------------------------------------- // When information is parsed and processed (extracted) by stateDb.extract() // These are globals so the information can be accessed as needed (e.g. in setUpNode, etc.) let diagramStates = []; @@ -72,6 +121,21 @@ function getClassesFromDbInfo(dbInfoItem) { } } +/** + * Create a standard string for the dom ID of an item. + * If a type is given, insert that before the counter, preceded by the type spacer + * + * @param itemId + * @param counter + * @param type + * @param typeSpacer + * @returns {string} + */ +export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) { + const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : ''; + return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`; +} + /** * Create a graph node based on the statement information * @@ -80,23 +144,16 @@ function getClassesFromDbInfo(dbInfoItem) { * @param {object} parsedItem - parsed statement item * @param {object} diagramDb * @param {boolean} altFlag - for clusters, add the "statediagram-cluster-alt" CSS class - * @todo This duplicates some of what is done in stateDb.js extract method */ const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { const itemId = parsedItem.id; const classStr = getClassesFromDbInfo(diagramStates[itemId]); if (itemId !== 'root') { - let shape = 'rect'; - if (parsedItem.start === true) { - shape = 'start'; - } - if (parsedItem.start === false) { - shape = 'end'; - } - if (parsedItem.type !== 'default') { - shape = parsedItem.type; - } + let shape = SHAPE_STATE; + if (parsedItem.start === true) shape = SHAPE_START; + if (parsedItem.start === false) shape = SHAPE_END; + if (parsedItem.type !== DEFAULT_STATE_TYPE) shape = parsedItem.type; // Add the node to our list (nodeDb) if (!nodeDb[itemId]) { @@ -104,55 +161,56 @@ const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { id: itemId, shape, description: common.sanitizeText(itemId, getConfig()), - classes: classStr + ' statediagram-state', + classes: `${classStr} ${CSS_DIAGRAM_STATE}`, }; } const newNode = nodeDb[itemId]; + // Save data for description and group so that for instance a statement without description overwrites + // one with description @todo TODO What does this mean? If important, add a test for it + // Build of the array of description strings if (parsedItem.description) { if (Array.isArray(newNode.description)) { // There already is an array of strings,add to it - newNode.shape = 'rectWithTitle'; + newNode.shape = SHAPE_STATE_WITH_DESC; newNode.description.push(parsedItem.description); } else { if (newNode.description.length > 0) { // if there is a description already transform it to an array - newNode.shape = 'rectWithTitle'; + newNode.shape = SHAPE_STATE_WITH_DESC; if (newNode.description === itemId) { - // If the previous description was the is, remove it + // If the previous description was this, remove it newNode.description = [parsedItem.description]; } else { newNode.description = [newNode.description, parsedItem.description]; } } else { - newNode.shape = 'rect'; + newNode.shape = SHAPE_STATE; newNode.description = parsedItem.description; } } newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig()); } - // update the node shape - if (newNode.description.length === 1 && newNode.shape === 'rectWithTitle') { - newNode.shape = 'rect'; + // If there's only 1 description entry, just use a regular state shape + if (newNode.description.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) { + newNode.shape = SHAPE_STATE; } - // Save data for description and group so that for instance a statement without description overwrites - // one with description - // group if (!newNode.type && parsedItem.doc) { log.info('Setting cluster for ', itemId, getDir(parsedItem)); newNode.type = 'group'; newNode.dir = getDir(parsedItem); - newNode.shape = parsedItem.type === 'divider' ? 'divider' : 'roundedWithTitle'; - + newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP; newNode.classes = newNode.classes + ' ' + - (altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster'); + CSS_DIAGRAM_CLUSTER + + ' ' + + (altFlag ? CSS_DIAGRAM_CLUSTER_ALT : ''); } // This is what will be added to the graph @@ -167,7 +225,7 @@ const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { style: '', //styles.style, id: itemId, dir: newNode.dir, - domId: 'state-' + itemId + '-' + graphItemCount, + domId: stateDomId(itemId, graphItemCount), type: newNode.type, padding: 15, //getConfig().flowchart.padding }; @@ -176,35 +234,36 @@ const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { // Todo: set random id const noteData = { labelStyle: '', - shape: 'note', + shape: SHAPE_NOTE, labelText: parsedItem.note.text, - classes: 'statediagram-note', //classStr, + classes: CSS_DIAGRAM_NOTE, style: '', // styles.style, - id: itemId + '----note-' + graphItemCount, - domId: 'state-' + itemId + '----note-' + graphItemCount, + id: itemId + NOTE_ID + '-' + graphItemCount, + domId: stateDomId(itemId, graphItemCount, NOTE), type: newNode.type, padding: 15, //getConfig().flowchart.padding }; const groupData = { labelStyle: '', - shape: 'noteGroup', + shape: SHAPE_NOTEGROUP, labelText: parsedItem.note.text, - classes: newNode.classes, //classStr, + classes: newNode.classes, style: '', // styles.style, - id: itemId + '----parent', - domId: 'state-' + itemId + '----parent-' + graphItemCount, + id: itemId + PARENT_ID, + domId: stateDomId(itemId, graphItemCount, PARENT), type: 'group', padding: 0, //getConfig().flowchart.padding }; graphItemCount++; - g.setNode(itemId + '----parent', groupData); + const parentNodeId = itemId + PARENT_ID; + g.setNode(parentNodeId, groupData); g.setNode(noteData.id, noteData); g.setNode(itemId, nodeData); - g.setParent(itemId, itemId + '----parent'); - g.setParent(noteData.id, itemId + '----parent'); + g.setParent(itemId, parentNodeId); + g.setParent(noteData.id, parentNodeId); let from = itemId; let to = noteData.id; @@ -216,13 +275,13 @@ const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => { g.setEdge(from, to, { arrowhead: 'none', arrowType: '', - style: 'fill:none', + style: G_EDGE_STYLE, labelStyle: '', - classes: 'transition note-edge', - arrowheadStyle: 'fill: #333', - labelpos: 'c', - labelType: 'text', - thickness: 'normal', + classes: CSS_EDGE_NOTE_EDGE, + arrowheadStyle: G_EDGE_ARROWHEADSTYLE, + labelpos: G_EDGE_LABELPOS, + labelType: G_EDGE_LABELTYPE, + thickness: G_EDGE_THICKNESS, }); } else { g.setNode(itemId, nodeData); @@ -257,13 +316,13 @@ const setupDoc = (g, parentParsedItem, doc, diagramDb, altFlag) => { log.trace('items', doc); doc.forEach((item) => { switch (item.stmt) { - case 'state': + case STMT_STATE: setupNode(g, parentParsedItem, item, diagramDb, altFlag); break; - case 'default': + case DEFAULT_STATE_TYPE: setupNode(g, parentParsedItem, item, diagramDb, altFlag); break; - case 'relation': + case STMT_RELATION: { setupNode(g, parentParsedItem, item.state1, diagramDb, altFlag); setupNode(g, parentParsedItem, item.state2, diagramDb, altFlag); @@ -271,14 +330,14 @@ const setupDoc = (g, parentParsedItem, doc, diagramDb, altFlag) => { id: 'edge' + graphItemCount, arrowhead: 'normal', arrowTypeEnd: 'arrow_barb', - style: 'fill:none', + style: G_EDGE_STYLE, labelStyle: '', label: common.sanitizeText(item.description, getConfig()), - arrowheadStyle: 'fill: #333', - labelpos: 'c', - labelType: 'text', - thickness: 'normal', - classes: 'transition', + arrowheadStyle: G_EDGE_ARROWHEADSTYLE, + labelpos: G_EDGE_LABELPOS, + labelType: G_EDGE_LABELTYPE, + thickness: G_EDGE_THICKNESS, + classes: CSS_EDGE, }; g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount); graphItemCount++; @@ -289,14 +348,14 @@ const setupDoc = (g, parentParsedItem, doc, diagramDb, altFlag) => { }; /** - * Get the direction from the statement items. Default is TB (top to bottom). + * Get the direction from the statement items. * Look through all of the documents (docs) in the parsedItems - * + * Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction. * @param {object[]} parsedItem - the parsed statement item to look through - * @param [defaultDir='TB'] - the direction to use if none is found + * @param [defaultDir=DEFAULT_NESTED_DOC_DIR] - the direction to use if none is found * @returns {string} */ -const getDir = (parsedItem, defaultDir = DEFAULT_DIR) => { +const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => { let dir = defaultDir; if (parsedItem.doc) { for (let i = 0; i < parsedItem.doc.length; i++) { @@ -323,7 +382,7 @@ export const draw = function (text, id, _version, diag) { nodeDb = {}; // Fetch the default direction, use TD if none was found let dir = diag.db.getDirection(); - if (typeof dir === 'undefined') dir = DEFAULT_DIR; + if (typeof dir === 'undefined') dir = DEFAULT_DIAGRAM_DIRECTION; const { securityLevel, state: conf } = getConfig(); const nodeSpacing = conf.nodeSpacing || 50; @@ -370,7 +429,7 @@ export const draw = function (text, id, _version, diag) { // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); - render(element, g, ['barb'], 'statediagram', id); + render(element, g, ['barb'], CSS_DIAGRAM, id); const padding = 8; @@ -380,7 +439,7 @@ export const draw = function (text, id, _version, diag) { const height = bounds.height + padding * 2; // Zoom in a bit - svg.attr('class', 'statediagram'); + svg.attr('class', CSS_DIAGRAM); const svgBounds = svg.node().getBBox(); @@ -400,7 +459,7 @@ export const draw = function (text, id, _version, diag) { // Get dimensions of label const dim = label.getBBox(); - const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + const rect = document.createElementNS('http://www.w3.org/2000/svg', SHAPE_STATE); rect.setAttribute('rx', 0); rect.setAttribute('ry', 0); rect.setAttribute('width', dim.width);