Merge branch 'develop' of github.com-mermaid:mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class

This commit is contained in:
saurabhg772244
2025-02-15 22:56:53 +05:30
23 changed files with 1664 additions and 1206 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
`mermaidAPI.getDiagramFromText()` now returns a new db instance on each call for state diagrams

5
.github/lychee.toml vendored
View File

@@ -47,7 +47,10 @@ exclude = [
"https://(www.)?drupal.org", "https://(www.)?drupal.org",
# Swimm returns 404, eventhough the link is valid # Swimm returns 404, eventhough the link is valid
"https://docs.swimm.io" "https://docs.swimm.io",
# Timeout
"https://huehive.co"
] ]
# Exclude all private IPs from checking. # Exclude all private IPs from checking.

View File

@@ -7,9 +7,6 @@ on:
- master - master
- release/** - release/**
pull_request: pull_request:
issue_comment:
types:
- created
merge_group: merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }} concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -31,12 +28,10 @@ env:
) || ) ||
github.event.before github.event.before
}} }}
# Check if this is a new comment with '/visual-test'
RUN_VISUAL_TEST: >- RUN_VISUAL_TEST: >-
${{ github.event_name == 'issue_comment' && github.event.action == 'created' && contains(github.event.comment.body, '/visual-test') && github.event.issue.pull_request != null }} ${{ github.repository == 'mermaid-js/mermaid' && (github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/')) }}
jobs: jobs:
cache: cache:
if: ${{ github.event_name != 'issue_comment' || contains(github.event.comment.body, '/visual-test') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1 image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
@@ -134,8 +129,6 @@ jobs:
# e.g. if this action was run from a fork # e.g. if this action was run from a fork
record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }} record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }}
env: env:
# Only set Argos environment variables if the visual test comment trigger is present
ARGOS_TOKEN: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.ARGOS_TOKEN || '' }}
ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }} ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }}
ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }} ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }}
ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }} ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }}

View File

@@ -257,6 +257,34 @@ pie
### Git graph [experimental - <a href="https://mermaid.live/edit#pako:eNqNkMFugzAMhl8F-VyVAR1tOW_aA-zKxSSGRCMJCk6lCvHuNZPKZdM0n-zf3_8r8QIqaIIGMqnB8kfEybQ--y4VnLP8-9RF9Mpkmm40hmlnDKmvkPiH_kfS7nFo_VN0FAf6XwocQGgxa_nGsm1bYEOOWmik1dRjGrmF1q-Cpkkj07u2HCI0PY4zHQATh8-7V9BwTPSE3iwOEd1OjQE1iWkBvk_bzQY7s0Sq4Hs7bHqKo8iGeZqbPN_WR7mpSd1RHpvPVhuMbG7XOq_L-oJlRfW5wteq0qorrpe-PBW9Pr8UJcK6rg-BLYPQ">live editor</a>] ### Git graph [experimental - <a href="https://mermaid.live/edit#pako:eNqNkMFugzAMhl8F-VyVAR1tOW_aA-zKxSSGRCMJCk6lCvHuNZPKZdM0n-zf3_8r8QIqaIIGMqnB8kfEybQ--y4VnLP8-9RF9Mpkmm40hmlnDKmvkPiH_kfS7nFo_VN0FAf6XwocQGgxa_nGsm1bYEOOWmik1dRjGrmF1q-Cpkkj07u2HCI0PY4zHQATh8-7V9BwTPSE3iwOEd1OjQE1iWkBvk_bzQY7s0Sq4Hs7bHqKo8iGeZqbPN_WR7mpSd1RHpvPVhuMbG7XOq_L-oJlRfW5wteq0qorrpe-PBW9Pr8UJcK6rg-BLYPQ">live editor</a>]
```
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit
```
```mermaid
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit
```
### Bar chart (using gantt chart) [<a href="https://mermaid.js.org/syntax/gantt.html">docs</a> - <a href="https://mermaid.live/edit#pako:eNptkU1vhCAQhv8KIenNugiI4rkf6bmXpvEyFVxJFDYyNt1u9r8X63Z7WQ9m5pknLzieaBeMpQ3dg0dsPUkPOhwteXZIXmJcbCT3xMAxkuh8Z8kIEclyMIB209fqKcwTICFvG4IvFy_oLrZ-g9F26ILfQgvNFN94VaRXQ1iWqpumZBcu1J8p1E1TXDx59eQNr5LyEqjJn6hv5QnGNlxevZJmdLLpy5xJSzut45biYCfb0iaVxvawjNjS1p-TCguG16PvaIPzYjO67e3BwX6GiTY9jPFKH43DMF_hGMDY1J4oHg-_f8hFTJFd8L3br3yZx4QHxENsdrt1nO8dDstH3oVpF50ZYMbhU6ud4qoGLqyqBJRCmO6j0HXPZdGbihUc6Pmc0QP49xD-b5X69ZQv2gjO81IwzWqhC1lKrjJ6pA3nVS7SMiVjrKirWlYp5fs3osgrWeo00lorLWvOzz8JVbXm">live editor</a>] ### Bar chart (using gantt chart) [<a href="https://mermaid.js.org/syntax/gantt.html">docs</a> - <a href="https://mermaid.live/edit#pako:eNptkU1vhCAQhv8KIenNugiI4rkf6bmXpvEyFVxJFDYyNt1u9r8X63Z7WQ9m5pknLzieaBeMpQ3dg0dsPUkPOhwteXZIXmJcbCT3xMAxkuh8Z8kIEclyMIB209fqKcwTICFvG4IvFy_oLrZ-g9F26ILfQgvNFN94VaRXQ1iWqpumZBcu1J8p1E1TXDx59eQNr5LyEqjJn6hv5QnGNlxevZJmdLLpy5xJSzut45biYCfb0iaVxvawjNjS1p-TCguG16PvaIPzYjO67e3BwX6GiTY9jPFKH43DMF_hGMDY1J4oHg-_f8hFTJFd8L3br3yZx4QHxENsdrt1nO8dDstH3oVpF50ZYMbhU6ud4qoGLqyqBJRCmO6j0HXPZdGbihUc6Pmc0QP49xD-b5X69ZQv2gjO81IwzWqhC1lKrjJ6pA3nVS7SMiVjrKirWlYp5fs3osgrWeo00lorLWvOzz8JVbXm">live editor</a>]
``` ```

View File

@@ -23,12 +23,10 @@ export default eyesPlugin(
}); });
// copy any needed variables from process.env to config.env // copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false; config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.CI && !!process.env.ARGOS_TOKEN; config.env.useArgos = process.env.RUN_VISUAL_TEST === 'true';
if (config.env.useArgos) { if (config.env.useArgos) {
registerArgosTask(on, config, { registerArgosTask(on, config);
token: 'fc3a35cf5200db928d65b2047861582d9444032b',
});
} else { } else {
addMatchImageSnapshotPlugin(on, config); addMatchImageSnapshotPlugin(on, config);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -52,28 +52,33 @@ Examples are provided in [Getting Started](../intro/getting-started.md)
[K8s.dev blog: Improve your documentation with Mermaid.js diagrams](https://www.kubernetes.dev/blog/2021/12/01/improve-your-documentation-with-mermaid.js-diagrams/) [K8s.dev blog: Improve your documentation with Mermaid.js diagrams](https://www.kubernetes.dev/blog/2021/12/01/improve-your-documentation-with-mermaid.js-diagrams/)
## Jupyter Integration with mermaid-js ## Jupyter / Python Integration with mermaid-js
Here's an example of Python integration with mermaid-js which uses the mermaid.ink service, that displays the graph in a Jupyter notebook. Here's an example of Python integration with mermaid-js which uses the mermaid.ink service, that displays the graph in a Jupyter notebook and save it as _.png_ image with the stated resolution (in this example, `dpi=1200`).
```python ```python
import base64 import base64
import io, requests
from IPython.display import Image, display from IPython.display import Image, display
from PIL import Image as im
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
def mm(graph): def mm(graph):
graphbytes = graph.encode("utf8") graphbytes = graph.encode("utf8")
base64_bytes = base64.urlsafe_b64encode(graphbytes) base64_bytes = base64.urlsafe_b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii") base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string)) img = im.open(io.BytesIO(requests.get('https://mermaid.ink/img/' + base64_string).content))
plt.imshow(img)
plt.axis('off') # allow to hide axis
plt.savefig('image.png', dpi=1200)
mm(""" mm("""
graph LR; graph LR;
A--> B & C & D; A--> B & C & D
B--> A & E; B--> A & E
C--> A & E; C--> A & E
D--> A & E; D--> A & E
E--> B & C & D; E--> B & C & D
""") """)
``` ```
@@ -81,4 +86,4 @@ graph LR;
![Example graph of the Python integration](img/python-mermaid-integration.png) ![Example graph of the Python integration](img/python-mermaid-integration.png)
<!--- cspell:ignore Elle Jaoude Neurodiverse graphbytes ---> <!--- cspell:ignore Elle Jaoude Neurodiverse graphbytes imshow savefig --->

View File

@@ -92,7 +92,7 @@
"cypress": "^13.14.1", "cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.0", "cypress-split": "^1.24.0",
"esbuild": "^0.21.5", "esbuild": "^0.25.0",
"eslint": "^9.4.0", "eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-cypress": "^3.3.0", "eslint-plugin-cypress": "^3.3.0",

View File

@@ -1,4 +1,4 @@
import stateDb from '../stateDb.js'; import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison'; import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
@@ -7,7 +7,9 @@ setConfig({
}); });
describe('state parser can parse...', () => { describe('state parser can parse...', () => {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB();
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
}); });
@@ -18,7 +20,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
state "Small State 1" as namedState1`; state "Small State 1" as namedState1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -31,7 +32,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
namedState1 : Small State 1`; namedState1 : Small State 1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -42,7 +42,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2 const diagramText = `stateDiagram-v2
namedState1:Small State 1`; namedState1:Small State 1`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -60,7 +59,6 @@ describe('state parser can parse...', () => {
state assemblies state assemblies
`; `;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('assemble')).not.toBeUndefined(); expect(states.get('assemble')).not.toBeUndefined();
expect(states.get('assemblies')).not.toBeUndefined(); expect(states.get('assemblies')).not.toBeUndefined();
@@ -71,7 +69,6 @@ describe('state parser can parse...', () => {
state "as" as as state "as" as as
`; `;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('as')).not.toBeUndefined(); expect(states.get('as')).not.toBeUndefined();
expect(states.get('as').descriptions.join(' ')).toEqual('as'); expect(states.get('as').descriptions.join(' ')).toEqual('as');
@@ -96,7 +93,6 @@ describe('state parser can parse...', () => {
namedState2 --> bigState2: should point to \\nbigState2 container`; namedState2 --> bigState2: should point to \\nbigState2 container`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined(); expect(states.get('namedState1')).not.toBeUndefined();
@@ -120,7 +116,6 @@ describe('state parser can parse...', () => {
inner1 --> inner2 inner1 --> inner2
}`; }`;
stateDiagram.parser.parse(diagramText); stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get('bigState1')).not.toBeUndefined(); expect(states.get('bigState1')).not.toBeUndefined();
@@ -137,7 +132,6 @@ describe('state parser can parse...', () => {
stateDiagram-v2 stateDiagram-v2
[*] --> ${prop} [*] --> ${prop}
${prop} --> [*]`); ${prop} --> [*]`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
expect(states.get(prop)).not.toBeUndefined(); expect(states.get(prop)).not.toBeUndefined();
}); });

View File

@@ -1,13 +1,15 @@
import stateDb from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js'; import { setConfig } from '../../../config.js';
import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
setConfig({ setConfig({
securityLevel: 'strict', securityLevel: 'strict',
}); });
describe('ClassDefs and classes when parsing a State diagram', () => { describe('ClassDefs and classes when parsing a State diagram', () => {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB();
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
}); });
@@ -16,7 +18,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
describe('defining (classDef)', () => { describe('defining (classDef)', () => {
it('has "classDef" as a keyword, an id, and can set a css style attribute', function () { it('has "classDef" as a keyword, an id, and can set a css style attribute', function () {
stateDiagram.parser.parse('stateDiagram-v2\n classDef exampleClass background:#bbb;'); stateDiagram.parser.parse('stateDiagram-v2\n classDef exampleClass background:#bbb;');
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses(); const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(1); expect(styleClasses.get('exampleClass').styles.length).toEqual(1);
@@ -27,7 +28,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;' 'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses(); const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(3); expect(styleClasses.get('exampleClass').styles.length).toEqual(3);
@@ -41,7 +41,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background:#bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef exampleStyleClass background:#bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -53,7 +52,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background: #bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef exampleStyleClass background: #bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -65,7 +63,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse( stateDiagram.parser.parse(
'stateDiagram-v2\n classDef __proto__ background:#bbb,border:1.5px solid red;\n classDef constructor background:#bbb,border:1.5px solid red;' 'stateDiagram-v2\n classDef __proto__ background:#bbb,border:1.5px solid red;\n classDef constructor background:#bbb,border:1.5px solid red;'
); );
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('__proto__').styles.length).toBe(2); expect(classes.get('__proto__').styles.length).toBe(2);
expect(classes.get('constructor').styles.length).toBe(2); expect(classes.get('constructor').styles.length).toBe(2);
@@ -81,7 +78,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a exampleStyleClass'; diagram += 'class a exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDb.getClasses(); const classes = stateDb.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toEqual(2); expect(classes.get('exampleStyleClass').styles.length).toEqual(2);
@@ -102,7 +98,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a_a exampleStyleClass'; diagram += 'class a_a exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2); expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -122,7 +117,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'a --> b:::exampleStyleClass' + '\n'; diagram += 'a --> b:::exampleStyleClass' + '\n';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
@@ -161,7 +155,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b exampleStyleClass'; diagram += 'class a,b exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
let classes = stateDiagram.parser.yy.getClasses(); let classes = stateDiagram.parser.yy.getClasses();
let states = stateDiagram.parser.yy.getStates(); let states = stateDiagram.parser.yy.getStates();
@@ -180,7 +173,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b,c, d, e exampleStyleClass'; diagram += 'class a,b,c, d, e exampleStyleClass';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses(); const classes = stateDiagram.parser.yy.getClasses();
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
@@ -208,7 +200,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += '}\n'; diagram += '}\n';
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates(); const states = stateDiagram.parser.yy.getStates();
@@ -224,7 +215,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(`stateDiagram-v2 stateDiagram.parser.parse(`stateDiagram-v2
id1 id1
style id1 background:#bbb`); style id1 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']); expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -234,7 +224,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id1 id1
id2 id2
style id1,id2 background:#bbb`); style id1,id2 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']); expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -247,7 +236,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id2 id2
style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`); style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData(); const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual([ expect(data4Layout.nodes[0].cssStyles).toEqual([

View File

@@ -1,6 +1,6 @@
import { line, curveBasis } from 'd3'; import { line, curveBasis } from 'd3';
import idCache from './id-cache.js'; import idCache from './id-cache.js';
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
import common from '../common/common.js'; import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
@@ -414,13 +414,13 @@ let edgeCount = 0;
export const drawEdge = function (elem, path, relation) { export const drawEdge = function (elem, path, relation) {
const getRelationType = function (type) { const getRelationType = function (type) {
switch (type) { switch (type) {
case stateDb.relationType.AGGREGATION: case StateDB.relationType.AGGREGATION:
return 'aggregation'; return 'aggregation';
case stateDb.relationType.EXTENSION: case StateDB.relationType.EXTENSION:
return 'extension'; return 'extension';
case stateDb.relationType.COMPOSITION: case StateDB.relationType.COMPOSITION:
return 'composition'; return 'composition';
case stateDb.relationType.DEPENDENCY: case StateDB.relationType.DEPENDENCY:
return 'dependency'; return 'dependency';
} }
}; };
@@ -459,7 +459,7 @@ export const drawEdge = function (elem, path, relation) {
svgPath.attr( svgPath.attr(
'marker-end', 'marker-end',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')' 'url(' + url + '#' + getRelationType(StateDB.relationType.DEPENDENCY) + 'End' + ')'
); );
if (relation.title !== undefined) { if (relation.title !== undefined) {

View File

@@ -1,28 +1,28 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
import common from '../common/common.js'; import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
clear as commonClear, clear as commonClear,
setDiagramTitle, getAccDescription,
getAccTitle,
getDiagramTitle, getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js'; } from '../common/commonDb.js';
import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js'; import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js';
import { getDir } from './stateRenderer-v3-unified.js'; import { getDir } from './stateRenderer-v3-unified.js';
import { import {
DEFAULT_DIAGRAM_DIRECTION, DEFAULT_DIAGRAM_DIRECTION,
STMT_STATE,
STMT_RELATION,
STMT_CLASSDEF,
STMT_STYLEDEF,
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE, DEFAULT_STATE_TYPE,
DIVIDER_TYPE, DIVIDER_TYPE,
STMT_APPLYCLASS,
STMT_CLASSDEF,
STMT_RELATION,
STMT_STATE,
STMT_STYLEDEF,
} from './stateCommon.js'; } from './stateCommon.js';
const START_NODE = '[*]'; const START_NODE = '[*]';
@@ -46,15 +46,6 @@ function newClassesList() {
return new Map(); return new Map();
} }
let nodes = [];
let edges = [];
let direction = DEFAULT_DIAGRAM_DIRECTION;
let rootDoc = [];
let classes = newClassesList(); // style classes defined by a classDef
// --------------------------------------
const newDoc = () => { const newDoc = () => {
return { return {
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */ /** @type {{ id1: string, id2: string, relationTitle: string }[]} */
@@ -63,40 +54,99 @@ const newDoc = () => {
documents: {}, documents: {},
}; };
}; };
let documents = {
const clone = (o) => JSON.parse(JSON.stringify(o));
export class StateDB {
constructor() {
this.clear();
// Needed for JISON since it only supports direct properties
this.setRootDoc = this.setRootDoc.bind(this);
this.getDividerId = this.getDividerId.bind(this);
this.setDirection = this.setDirection.bind(this);
this.trimColon = this.trimColon.bind(this);
}
/**
* @private
* @type {Array}
*/
nodes = [];
/**
* @private
* @type {Array}
*/
edges = [];
/**
* @private
* @type {string}
*/
direction = DEFAULT_DIAGRAM_DIRECTION;
/**
* @private
* @type {Array}
*/
rootDoc = [];
/**
* @private
* @type {Map<string, any>}
*/
classes = newClassesList(); // style classes defined by a classDef
/**
* @private
* @type {Object}
*/
documents = {
root: newDoc(), root: newDoc(),
}; };
let currentDocument = documents.root; /**
let startEndCount = 0; * @private
let dividerCnt = 0; * @type {Object}
*/
currentDocument = this.documents.root;
/**
* @private
* @type {number}
*/
startEndCount = 0;
/**
* @private
* @type {number}
*/
dividerCnt = 0;
export const lineType = { static relationType = {
LINE: 0,
DOTTED_LINE: 1,
};
export const relationType = {
AGGREGATION: 0, AGGREGATION: 0,
EXTENSION: 1, EXTENSION: 1,
COMPOSITION: 2, COMPOSITION: 2,
DEPENDENCY: 3, DEPENDENCY: 3,
}; };
const clone = (o) => JSON.parse(JSON.stringify(o)); setRootDoc(o) {
const setRootDoc = (o) => {
log.info('Setting root doc', o); log.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o }; // rootDoc = { id: 'root', doc: o };
rootDoc = o; this.rootDoc = o;
}; this.extract(o);
}
const getRootDoc = () => rootDoc; getRootDoc() {
return this.rootDoc;
}
const docTranslator = (parent, node, first) => { /**
* @private
* @param {Object} parent
* @param {Object} node
* @param {boolean} first
*/
docTranslator(parent, node, first) {
if (node.stmt === STMT_RELATION) { if (node.stmt === STMT_RELATION) {
docTranslator(parent, node.state1, true); this.docTranslator(parent, node.state1, true);
docTranslator(parent, node.state2, false); this.docTranslator(parent, node.state2, false);
} else { } else {
if (node.stmt === STMT_STATE) { if (node.stmt === STMT_STATE) {
if (node.id === '[*]') { if (node.id === '[*]') {
@@ -115,7 +165,6 @@ const docTranslator = (parent, node, first) => {
let i; let i;
for (i = 0; i < node.doc.length; i++) { for (i = 0; i < node.doc.length; i++) {
if (node.doc[i].type === DIVIDER_TYPE) { if (node.doc[i].type === DIVIDER_TYPE) {
// debugger;
const newNode = clone(node.doc[i]); const newNode = clone(node.doc[i]);
newNode.doc = clone(currentDoc); newNode.doc = clone(currentDoc);
doc.push(newNode); doc.push(newNode);
@@ -137,17 +186,17 @@ const docTranslator = (parent, node, first) => {
node.doc = doc; node.doc = doc;
} }
node.doc.forEach((docNode) => docTranslator(node, docNode, true)); node.doc.forEach((docNode) => this.docTranslator(node, docNode, true));
} }
} }
}; }
const getRootDocV2 = () => { getRootDocV2() {
docTranslator({ id: 'root' }, { id: 'root', doc: rootDoc }, true); this.docTranslator({ id: 'root' }, { id: 'root', doc: this.rootDoc }, true);
return { id: 'root', doc: rootDoc }; return { id: 'root', doc: this.rootDoc };
// Here // Here
}; }
/** /**
* Convert all of the statements (stmts) that were parsed into states and relationships. * Convert all of the statements (stmts) that were parsed into states and relationships.
* This is done because a state diagram may have nested sections, * This is done because a state diagram may have nested sections,
* where each section is a 'document' and has its own set of statements. * where each section is a 'document' and has its own set of statements.
@@ -155,10 +204,10 @@ const getRootDocV2 = () => {
* refer to the fork as a whole (document). * refer to the fork as a whole (document).
* See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement. * See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement.
* This will push the statement into the list of statements for the current document. * This will push the statement into the list of statements for the current document.
* * @private
* @param _doc * @param _doc
*/ */
const extract = (_doc) => { extract(_doc) {
// const res = { states: [], relations: [] }; // const res = { states: [], relations: [] };
let doc; let doc;
if (_doc.doc) { if (_doc.doc) {
@@ -171,7 +220,7 @@ const extract = (_doc) => {
// doc = root; // doc = root;
// } // }
log.info(doc); log.info(doc);
clear(true); this.clear(true);
log.info('Extract initial document:', doc); log.info('Extract initial document:', doc);
@@ -179,7 +228,7 @@ const extract = (_doc) => {
log.warn('Statement', item.stmt); log.warn('Statement', item.stmt);
switch (item.stmt) { switch (item.stmt) {
case STMT_STATE: case STMT_STATE:
addState( this.addState(
item.id.trim(), item.id.trim(),
item.type, item.type,
item.doc, item.doc,
@@ -191,38 +240,48 @@ const extract = (_doc) => {
); );
break; break;
case STMT_RELATION: case STMT_RELATION:
addRelation(item.state1, item.state2, item.description); this.addRelation(item.state1, item.state2, item.description);
break; break;
case STMT_CLASSDEF: case STMT_CLASSDEF:
addStyleClass(item.id.trim(), item.classes); this.addStyleClass(item.id.trim(), item.classes);
break; break;
case STMT_STYLEDEF: case STMT_STYLEDEF:
{ {
const ids = item.id.trim().split(','); const ids = item.id.trim().split(',');
const styles = item.styleClass.split(','); const styles = item.styleClass.split(',');
ids.forEach((id) => { ids.forEach((id) => {
let foundState = getState(id); let foundState = this.getState(id);
if (foundState === undefined) { if (foundState === undefined) {
const trimmedId = id.trim(); const trimmedId = id.trim();
addState(trimmedId); this.addState(trimmedId);
foundState = getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim()); foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
}); });
} }
break; break;
case STMT_APPLYCLASS: case STMT_APPLYCLASS:
setCssClass(item.id.trim(), item.styleClass); this.setCssClass(item.id.trim(), item.styleClass);
break; break;
} }
}); });
const diagramStates = getStates(); const diagramStates = this.getStates();
const config = getConfig(); const config = getConfig();
const look = config.look; const look = config.look;
resetDataFetching(); resetDataFetching();
dataFetcher(undefined, getRootDocV2(), diagramStates, nodes, edges, true, look, classes); dataFetcher(
nodes.forEach((node) => { undefined,
this.getRootDocV2(),
diagramStates,
this.nodes,
this.edges,
true,
look,
this.classes
);
this.nodes.forEach((node) => {
if (Array.isArray(node.label)) { if (Array.isArray(node.label)) {
// add the rest as description // add the rest as description
node.description = node.label.slice(1); node.description = node.label.slice(1);
@@ -237,9 +296,9 @@ const extract = (_doc) => {
node.label = node.label[0]; node.label = node.label[0];
} }
}); });
}; }
/** /**
* Function called by parser when a node definition has been found. * Function called by parser when a node definition has been found.
* *
* @param {null | string} id * @param {null | string} id
@@ -251,7 +310,7 @@ const extract = (_doc) => {
* @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style. * @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style.
* @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style. * @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style.
*/ */
export const addState = function ( addState(
id, id,
type = DEFAULT_STATE_TYPE, type = DEFAULT_STATE_TYPE,
doc = null, doc = null,
@@ -260,12 +319,12 @@ export const addState = function (
classes = null, classes = null,
styles = null, styles = null,
textStyles = null textStyles = null
) { ) {
const trimmedId = id?.trim(); const trimmedId = id?.trim();
// add the state if needed // add the state if needed
if (!currentDocument.states.has(trimmedId)) { if (!this.currentDocument.states.has(trimmedId)) {
log.info('Adding state ', trimmedId, descr); log.info('Adding state ', trimmedId, descr);
currentDocument.states.set(trimmedId, { this.currentDocument.states.set(trimmedId, {
id: trimmedId, id: trimmedId,
descriptions: [], descriptions: [],
type, type,
@@ -276,27 +335,27 @@ export const addState = function (
textStyles: [], textStyles: [],
}); });
} else { } else {
if (!currentDocument.states.get(trimmedId).doc) { if (!this.currentDocument.states.get(trimmedId).doc) {
currentDocument.states.get(trimmedId).doc = doc; this.currentDocument.states.get(trimmedId).doc = doc;
} }
if (!currentDocument.states.get(trimmedId).type) { if (!this.currentDocument.states.get(trimmedId).type) {
currentDocument.states.get(trimmedId).type = type; this.currentDocument.states.get(trimmedId).type = type;
} }
} }
if (descr) { if (descr) {
log.info('Setting state description', trimmedId, descr); log.info('Setting state description', trimmedId, descr);
if (typeof descr === 'string') { if (typeof descr === 'string') {
addDescription(trimmedId, descr.trim()); this.addDescription(trimmedId, descr.trim());
} }
if (typeof descr === 'object') { if (typeof descr === 'object') {
descr.forEach((des) => addDescription(trimmedId, des.trim())); descr.forEach((des) => this.addDescription(trimmedId, des.trim()));
} }
} }
if (note) { if (note) {
const doc2 = currentDocument.states.get(trimmedId); const doc2 = this.currentDocument.states.get(trimmedId);
doc2.note = note; doc2.note = note;
doc2.note.text = common.sanitizeText(doc2.note.text, getConfig()); doc2.note.text = common.sanitizeText(doc2.note.text, getConfig());
} }
@@ -304,123 +363,126 @@ export const addState = function (
if (classes) { if (classes) {
log.info('Setting state classes', trimmedId, classes); log.info('Setting state classes', trimmedId, classes);
const classesList = typeof classes === 'string' ? [classes] : classes; const classesList = typeof classes === 'string' ? [classes] : classes;
classesList.forEach((cssClass) => setCssClass(trimmedId, cssClass.trim())); classesList.forEach((cssClass) => this.setCssClass(trimmedId, cssClass.trim()));
} }
if (styles) { if (styles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const stylesList = typeof styles === 'string' ? [styles] : styles; const stylesList = typeof styles === 'string' ? [styles] : styles;
stylesList.forEach((style) => setStyle(trimmedId, style.trim())); stylesList.forEach((style) => this.setStyle(trimmedId, style.trim()));
} }
if (textStyles) { if (textStyles) {
log.info('Setting state styles', trimmedId, styles); log.info('Setting state styles', trimmedId, styles);
const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles; const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles;
textStylesList.forEach((textStyle) => setTextStyle(trimmedId, textStyle.trim())); textStylesList.forEach((textStyle) => this.setTextStyle(trimmedId, textStyle.trim()));
}
} }
};
export const clear = function (saveCommon) { clear(saveCommon) {
nodes = []; this.nodes = [];
edges = []; this.edges = [];
documents = { this.documents = {
root: newDoc(), root: newDoc(),
}; };
currentDocument = documents.root; this.currentDocument = this.documents.root;
// number of start and end nodes; used to construct ids // number of start and end nodes; used to construct ids
startEndCount = 0; this.startEndCount = 0;
classes = newClassesList(); this.classes = newClassesList();
if (!saveCommon) { if (!saveCommon) {
commonClear(); commonClear();
} }
}; }
export const getState = function (id) { getState(id) {
return currentDocument.states.get(id); return this.currentDocument.states.get(id);
}; }
getStates() {
return this.currentDocument.states;
}
logDocuments() {
log.info('Documents = ', this.documents);
}
getRelations() {
return this.currentDocument.relations;
}
export const getStates = function () { /**
return currentDocument.states;
};
export const logDocuments = function () {
log.info('Documents = ', documents);
};
export const getRelations = function () {
return currentDocument.relations;
};
/**
* If the id is a start node ( [*] ), then return a new id constructed from * If the id is a start node ( [*] ), then return a new id constructed from
* the start node name and the current start node count. * the start node name and the current start node count.
* else return the given id * else return the given id
* *
* @param {string} id * @param {string} id
* @returns {string} - the id (original or constructed) * @returns {string} - the id (original or constructed)
* @private
*/ */
function startIdIfNeeded(id = '') { startIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
if (id === START_NODE) { if (id === START_NODE) {
startEndCount++; this.startEndCount++;
fixedId = `${START_TYPE}${startEndCount}`; fixedId = `${START_TYPE}${this.startEndCount}`;
} }
return fixedId; return fixedId;
} }
/** /**
* If the id is a start node ( [*] ), then return the start type ('start') * If the id is a start node ( [*] ), then return the start type ('start')
* else return the given type * else return the given type
* *
* @param {string} id * @param {string} id
* @param {string} type * @param {string} type
* @returns {string} - the type that should be used * @returns {string} - the type that should be used
* @private
*/ */
function startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === START_NODE ? START_TYPE : type; return id === START_NODE ? START_TYPE : type;
} }
/** /**
* If the id is an end node ( [*] ), then return a new id constructed from * If the id is an end node ( [*] ), then return a new id constructed from
* the end node name and the current start_end node count. * the end node name and the current start_end node count.
* else return the given id * else return the given id
* *
* @param {string} id * @param {string} id
* @returns {string} - the id (original or constructed) * @returns {string} - the id (original or constructed)
* @private
*/ */
function endIdIfNeeded(id = '') { endIdIfNeeded(id = '') {
let fixedId = id; let fixedId = id;
if (id === END_NODE) { if (id === END_NODE) {
startEndCount++; this.startEndCount++;
fixedId = `${END_TYPE}${startEndCount}`; fixedId = `${END_TYPE}${this.startEndCount}`;
} }
return fixedId; return fixedId;
} }
/** /**
* If the id is an end node ( [*] ), then return the end type * If the id is an end node ( [*] ), then return the end type
* else return the given type * else return the given type
* *
* @param {string} id * @param {string} id
* @param {string} type * @param {string} type
* @returns {string} - the type that should be used * @returns {string} - the type that should be used
* @private
*/ */
function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === END_NODE ? END_TYPE : type; return id === END_NODE ? END_TYPE : type;
} }
/** /**
* *
* @param item1 * @param item1
* @param item2 * @param item2
* @param relationTitle * @param relationTitle
*/ */
export function addRelationObjs(item1, item2, relationTitle) { addRelationObjs(item1, item2, relationTitle) {
let id1 = startIdIfNeeded(item1.id.trim()); let id1 = this.startIdIfNeeded(item1.id.trim());
let type1 = startTypeIfNeeded(item1.id.trim(), item1.type); let type1 = this.startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = startIdIfNeeded(item2.id.trim()); let id2 = this.startIdIfNeeded(item2.id.trim());
let type2 = startTypeIfNeeded(item2.id.trim(), item2.type); let type2 = this.startTypeIfNeeded(item2.id.trim(), item2.type);
addState( this.addState(
id1, id1,
type1, type1,
item1.doc, item1.doc,
@@ -430,7 +492,7 @@ export function addRelationObjs(item1, item2, relationTitle) {
item1.styles, item1.styles,
item1.textStyles item1.textStyles
); );
addState( this.addState(
id2, id2,
type2, type2,
item2.doc, item2.doc,
@@ -441,71 +503,71 @@ export function addRelationObjs(item1, item2, relationTitle) {
item2.textStyles item2.textStyles
); );
currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
id2, id2,
relationTitle: common.sanitizeText(relationTitle, getConfig()), relationTitle: common.sanitizeText(relationTitle, getConfig()),
}); });
} }
/** /**
* Add a relation between two items. The items may be full objects or just the string id of a state. * Add a relation between two items. The items may be full objects or just the string id of a state.
* *
* @param {string | object} item1 * @param {string | object} item1
* @param {string | object} item2 * @param {string | object} item2
* @param {string} title * @param {string} title
*/ */
export const addRelation = function (item1, item2, title) { addRelation(item1, item2, title) {
if (typeof item1 === 'object') { if (typeof item1 === 'object') {
addRelationObjs(item1, item2, title); this.addRelationObjs(item1, item2, title);
} else { } else {
const id1 = startIdIfNeeded(item1.trim()); const id1 = this.startIdIfNeeded(item1.trim());
const type1 = startTypeIfNeeded(item1); const type1 = this.startTypeIfNeeded(item1);
const id2 = endIdIfNeeded(item2.trim()); const id2 = this.endIdIfNeeded(item2.trim());
const type2 = endTypeIfNeeded(item2); const type2 = this.endTypeIfNeeded(item2);
addState(id1, type1); this.addState(id1, type1);
addState(id2, type2); this.addState(id2, type2);
currentDocument.relations.push({ this.currentDocument.relations.push({
id1, id1,
id2, id2,
title: common.sanitizeText(title, getConfig()), title: common.sanitizeText(title, getConfig()),
}); });
} }
}; }
export const addDescription = function (id, descr) { addDescription(id, descr) {
const theState = currentDocument.states.get(id); const theState = this.currentDocument.states.get(id);
const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr; const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr;
theState.descriptions.push(common.sanitizeText(_descr, getConfig())); theState.descriptions.push(common.sanitizeText(_descr, getConfig()));
}; }
export const cleanupLabel = function (label) { cleanupLabel(label) {
if (label.substring(0, 1) === ':') { if (label.substring(0, 1) === ':') {
return label.substr(2).trim(); return label.substr(2).trim();
} else { } else {
return label.trim(); return label.trim();
} }
}; }
const getDividerId = () => { getDividerId() {
dividerCnt++; this.dividerCnt++;
return 'divider-id-' + dividerCnt; return 'divider-id-' + this.dividerCnt;
}; }
/** /**
* Called when the parser comes across a (style) class definition * Called when the parser comes across a (style) class definition
* @example classDef my-style fill:#f96; * @example classDef my-style fill:#f96;
* *
* @param {string} id - the id of this (style) class * @param {string} id - the id of this (style) class
* @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma) * @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/ */
export const addStyleClass = function (id, styleAttributes = '') { addStyleClass(id, styleAttributes = '') {
// create a new style class object with this id // create a new style class object with this id
if (!classes.has(id)) { if (!this.classes.has(id)) {
classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef this.classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef
} }
const foundClass = classes.get(id); const foundClass = this.classes.get(id);
if (styleAttributes !== undefined && styleAttributes !== null) { if (styleAttributes !== undefined && styleAttributes !== null) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => { styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
// remove any trailing ; // remove any trailing ;
@@ -520,17 +582,17 @@ export const addStyleClass = function (id, styleAttributes = '') {
foundClass.styles.push(fixedAttrib); foundClass.styles.push(fixedAttrib);
}); });
} }
}; }
/** /**
* Return all of the style classes * Return all of the style classes
* @returns {{} | any | classes} * @returns {{} | any | classes}
*/ */
export const getClasses = function () { getClasses() {
return classes; return this.classes;
}; }
/** /**
* Add a (style) class or css class to a state with the given id. * Add a (style) class or css class to a state with the given id.
* If the state isn't already in the list of known states, add it. * If the state isn't already in the list of known states, add it.
* Might be called by parser when a style class or CSS class should be applied to a state * Might be called by parser when a style class or CSS class should be applied to a state
@@ -538,19 +600,19 @@ export const getClasses = function () {
* @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to * @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to
* @param {string} cssClassName CSS class name * @param {string} cssClassName CSS class name
*/ */
export const setCssClass = function (itemIds, cssClassName) { setCssClass(itemIds, cssClassName) {
itemIds.split(',').forEach(function (id) { itemIds.split(',').forEach((id) => {
let foundState = getState(id); let foundState = this.getState(id);
if (foundState === undefined) { if (foundState === undefined) {
const trimmedId = id.trim(); const trimmedId = id.trim();
addState(trimmedId); this.addState(trimmedId);
foundState = getState(trimmedId); foundState = this.getState(trimmedId);
} }
foundState.classes.push(cssClassName); foundState.classes.push(cssClassName);
}); });
}; }
/** /**
* Add a style to a state with the given id. * Add a style to a state with the given id.
* @example style stateId fill:#f9f,stroke:#333,stroke-width:4px * @example style stateId fill:#f9f,stroke:#333,stroke-width:4px
* where 'style' is the keyword * where 'style' is the keyword
@@ -560,67 +622,55 @@ export const setCssClass = function (itemIds, cssClassName) {
* @param itemId The id of item to apply the style to * @param itemId The id of item to apply the style to
* @param styleText - the text of the attributes for the style * @param styleText - the text of the attributes for the style
*/ */
export const setStyle = function (itemId, styleText) { setStyle(itemId, styleText) {
const item = getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.styles.push(styleText); item.styles.push(styleText);
} }
}; }
/** /**
* Add a text style to a state with the given id * Add a text style to a state with the given id
* *
* @param itemId The id of item to apply the css class to * @param itemId The id of item to apply the css class to
* @param cssClassName CSS class name * @param cssClassName CSS class name
*/ */
export const setTextStyle = function (itemId, cssClassName) { setTextStyle(itemId, cssClassName) {
const item = getState(itemId); const item = this.getState(itemId);
if (item !== undefined) { if (item !== undefined) {
item.textStyles.push(cssClassName); item.textStyles.push(cssClassName);
} }
}; }
const getDirection = () => direction; getDirection() {
const setDirection = (dir) => { return this.direction;
direction = dir; }
}; setDirection(dir) {
this.direction = dir;
}
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim()); trimColon(str) {
return str && str[0] === ':' ? str.substr(1).trim() : str.trim();
}
export const getData = () => { getData() {
const config = getConfig(); const config = getConfig();
return { nodes, edges, other: {}, config, direction: getDir(getRootDocV2()) }; return {
}; nodes: this.nodes,
edges: this.edges,
other: {},
config,
direction: getDir(this.getRootDocV2()),
};
}
export default { getConfig() {
getConfig: () => getConfig().state, return getConfig().state;
getData, }
addState, getAccTitle = getAccTitle;
clear, setAccTitle = setAccTitle;
getState, getAccDescription = getAccDescription;
getStates, setAccDescription = setAccDescription;
getRelations, setDiagramTitle = setDiagramTitle;
getClasses, getDiagramTitle = getDiagramTitle;
getDirection, }
addRelation,
getDividerId,
setDirection,
cleanupLabel,
lineType,
relationType,
logDocuments,
getRootDoc,
setRootDoc,
getRootDocV2,
extract,
trimColon,
getAccTitle,
setAccTitle,
getAccDescription,
setAccDescription,
addStyleClass,
setCssClass,
addDescription,
setDiagramTitle,
getDiagramTitle,
};

View File

@@ -1,8 +1,9 @@
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
describe('State Diagram stateDb', () => { describe('State Diagram stateDb', () => {
let stateDb;
beforeEach(() => { beforeEach(() => {
stateDb.clear(); stateDb = new StateDB();
}); });
describe('addStyleClass', () => { describe('addStyleClass', () => {
@@ -20,8 +21,9 @@ describe('State Diagram stateDb', () => {
}); });
describe('addDescription to a state', () => { describe('addDescription to a state', () => {
let stateDb;
beforeEach(() => { beforeEach(() => {
stateDb.clear(); stateDb = new StateDB();
stateDb.addState('state1'); stateDb.addState('state1');
}); });
@@ -73,3 +75,25 @@ describe('State Diagram stateDb', () => {
}); });
}); });
}); });
describe('state db class', () => {
let stateDb;
beforeEach(() => {
stateDb = new StateDB();
});
// This is to ensure that functions used in state JISON are exposed as function from StateDb
it('should have functions used in flow JISON as own property', () => {
const functionsUsedInParser = [
'setRootDoc',
'trimColon',
'getDividerId',
'setAccTitle',
'setAccDescription',
'setDirection',
];
for (const fun of functionsUsedInParser) {
expect(Object.hasOwn(stateDb, fun)).toBe(true);
}
});
});

View File

@@ -1,11 +1,12 @@
import { parser } from './parser/stateDiagram.jison'; import stateDiagram, { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
import stateDiagram from './parser/stateDiagram.jison';
describe('state diagram V2, ', function () { describe('state diagram V2, ', function () {
// TODO - these examples should be put into ./parser/stateDiagram.spec.js // TODO - these examples should be put into ./parser/stateDiagram.spec.js
describe('when parsing an info graph it', function () { describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB();
parser.yy = stateDb; parser.yy = stateDb;
stateDiagram.parser.yy = stateDb; stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear(); stateDiagram.parser.yy.clear();
@@ -127,7 +128,6 @@ describe('state diagram V2, ', function () {
`; `;
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const rels = stateDb.getRelations(); const rels = stateDb.getRelations();
const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2'); const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2');
@@ -402,7 +402,6 @@ describe('state diagram V2, ', function () {
`; `;
stateDiagram.parser.parse(diagram); stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDb.getStates(); const states = stateDb.getStates();
expect(states.get('Active').doc[0].id).toEqual('Idle'); expect(states.get('Active').doc[0].id).toEqual('Idle');

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison'; import parser from './parser/stateDiagram.jison';
import db from './stateDb.js'; import { StateDB } from './stateDb.js';
import styles from './styles.js'; import styles from './styles.js';
import renderer from './stateRenderer-v3-unified.js'; import renderer from './stateRenderer-v3-unified.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new StateDB();
},
renderer, renderer,
styles, styles,
init: (cnf) => { init: (cnf) => {

View File

@@ -1,9 +1,11 @@
import { parser } from './parser/stateDiagram.jison'; import { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js'; import { StateDB } from './stateDb.js';
describe('state diagram, ', function () { describe('state diagram, ', function () {
describe('when parsing an info graph it', function () { describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () { beforeEach(function () {
stateDb = new StateDB();
parser.yy = stateDb; parser.yy = stateDb;
}); });

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js'; import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types // @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison'; import parser from './parser/stateDiagram.jison';
import db from './stateDb.js'; import { StateDB } from './stateDb.js';
import styles from './styles.js'; import styles from './styles.js';
import renderer from './stateRenderer.js'; import renderer from './stateRenderer.js';
export const diagram: DiagramDefinition = { export const diagram: DiagramDefinition = {
parser, parser,
db, get db() {
return new StateDB();
},
renderer, renderer,
styles, styles,
init: (cnf) => { init: (cnf) => {

View File

@@ -36,7 +36,6 @@ export const getClasses = function (
text: string, text: string,
diagramObj: any diagramObj: any
): Map<string, DiagramStyleClassDef> { ): Map<string, DiagramStyleClassDef> {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses(); return diagramObj.db.getClasses();
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -46,28 +46,33 @@ https://codepen.io/Ryuno-Ki/pen/LNxwgR
[K8s.dev blog: Improve your documentation with Mermaid.js diagrams](https://www.kubernetes.dev/blog/2021/12/01/improve-your-documentation-with-mermaid.js-diagrams/) [K8s.dev blog: Improve your documentation with Mermaid.js diagrams](https://www.kubernetes.dev/blog/2021/12/01/improve-your-documentation-with-mermaid.js-diagrams/)
## Jupyter Integration with mermaid-js ## Jupyter / Python Integration with mermaid-js
Here's an example of Python integration with mermaid-js which uses the mermaid.ink service, that displays the graph in a Jupyter notebook. Here's an example of Python integration with mermaid-js which uses the mermaid.ink service, that displays the graph in a Jupyter notebook and save it as _.png_ image with the stated resolution (in this example, `dpi=1200`).
```python ```python
import base64 import base64
import io, requests
from IPython.display import Image, display from IPython.display import Image, display
from PIL import Image as im
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
def mm(graph): def mm(graph):
graphbytes = graph.encode("utf8") graphbytes = graph.encode("utf8")
base64_bytes = base64.urlsafe_b64encode(graphbytes) base64_bytes = base64.urlsafe_b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii") base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string)) img = im.open(io.BytesIO(requests.get('https://mermaid.ink/img/' + base64_string).content))
plt.imshow(img)
plt.axis('off') # allow to hide axis
plt.savefig('image.png', dpi=1200)
mm(""" mm("""
graph LR; graph LR;
A--> B & C & D; A--> B & C & D
B--> A & E; B--> A & E
C--> A & E; C--> A & E
D--> A & E; D--> A & E
E--> B & C & D; E--> B & C & D
""") """)
``` ```
@@ -75,4 +80,4 @@ graph LR;
![Example graph of the Python integration](img/python-mermaid-integration.png) ![Example graph of the Python integration](img/python-mermaid-integration.png)
<!--- cspell:ignore Elle Jaoude Neurodiverse graphbytes ---> <!--- cspell:ignore Elle Jaoude Neurodiverse graphbytes imshow savefig --->

View File

@@ -72,6 +72,7 @@ import { FlowDB } from './diagrams/flowchart/flowDb.js';
import { SequenceDB } from './diagrams/sequence/sequenceDb.js'; import { SequenceDB } from './diagrams/sequence/sequenceDb.js';
import { decodeEntities, encodeEntities } from './utils.js'; import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js'; import { toBase64 } from './utils/base64.js';
import { StateDB } from './diagrams/state/stateDb.js';
/** /**
* @see https://vitest.dev/guide/mocking.html Mock part of a module * @see https://vitest.dev/guide/mocking.html Mock part of a module
@@ -837,6 +838,31 @@ graph TD;A--x|text including URL space|B;`)
}); });
it('should not modify db when rendering different diagrams', async () => { it('should not modify db when rendering different diagrams', async () => {
const stateDiagram1 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction LR
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
const stateDiagram2 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction TB
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
expect(stateDiagram1.db).not.toBe(stateDiagram2.db);
assert(stateDiagram1.db instanceof StateDB);
assert(stateDiagram2.db instanceof StateDB);
expect(stateDiagram1.db.getDirection()).not.toEqual(stateDiagram2.db.getDirection());
const flowDiagram1 = await mermaidAPI.getDiagramFromText( const flowDiagram1 = await mermaidAPI.getDiagramFromText(
`flowchart LR `flowchart LR
A -- text --> B -- text2 --> C` A -- text --> B -- text2 --> C`

1509
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -103,5 +103,5 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */
}, },
"exclude": ["node_modules", "dist", "coverage"] "exclude": ["**/node_modules/*", "**/dist/*", ".git", "coverage"]
} }