Skip to content

Commit

Permalink
feat: add history nodes to state diagram
Browse files Browse the repository at this point in the history
  • Loading branch information
nimec01 committed Aug 7, 2024
1 parent 47601ac commit f63708c
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 0 deletions.
1 change: 1 addition & 0 deletions .cspell/mermaid-terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ bmatrix
braintree
catmull
compositTitleSize
deephistory
doublecircle
elems
gantt
Expand Down
35 changes: 35 additions & 0 deletions cypress/integration/rendering/stateDiagram-v2.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,41 @@ describe('State diagram', () => {
}
);
});
it('v2 it should be possible to use a (deep) history node', () => {
imgSnapshotTest(
`
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History [[history]]
B --> C
C --> B
}
state "D" as D {
state "E" as E {
state "F" as F
state "G" as G
F --> G
G --> F
}
state "I" as I
state D_History [[deephistory]]
E --> I
I --> E
}
G --> A_History
A --> D_History
`,
{
logLevel: 0,
}
);
});
it('v2 A compound state should be able to link to itself', () => {
imgSnapshotTest(
`
Expand Down
31 changes: 31 additions & 0 deletions demos/state.html
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,37 @@ <h2>You can add Notes</h2>
</pre>
<hr />

<h2>You can add History nodes</h2>
<pre class="mermaid">
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History [[history]]

B --> C
C --> B
}
state "D" as D {
state "E" as E {
state "F" as F
state "G" as G

F --> G
G --> F
}
state "I" as I
state D_History [[deephistory]]

E --> I
I --> E
}

G --> A_History
A --> D_History
</pre>
<hr />

<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
Expand Down
62 changes: 62 additions & 0 deletions docs/syntax/stateDiagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,68 @@ It is possible to specify a fork in the diagram using <\<fork>> <\<join>>.
State4 --> [*]
```

## History (v11.0.0+)

It is possible to add (deep) history nodes in the diagram using <\<history>> <\<deephistory>>.

```mermaid-example
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History <<history>>
B --> C
C --> B
}
state "D" as D {
state "E" as E {
state "F" as F
state "G" as G
F --> G
G --> F
}
state "I" as I
state D_History <<deephistory>>
E --> I
I --> E
}
G --> A_History
A --> D_History
```

```mermaid
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History <<history>>
B --> C
C --> B
}
state "D" as D {
state "E" as E {
state "F" as F
state "G" as G
F --> G
G --> F
}
state "I" as I
state D_History <<deephistory>>
E --> I
I --> E
}
G --> A_History
A --> D_History
```

## Notes

Sometimes nothing says it better than a Post-it note. That is also the case in state diagrams.
Expand Down
43 changes: 43 additions & 0 deletions packages/mermaid/src/dagre-wrapper/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,47 @@ const choice = (parent, node) => {
return shapeSvg;
};

const historyBase = async (parent, node) => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
getClassesFromNode(node, undefined),
true
);
const circle = shapeSvg.insert('circle', ':first-child');

// center the circle around its coordinate
circle
.attr('class', 'history')
.attr('style', node.style)
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('r', 16)
.attr('width', 34)
.attr('height', 34);

log.info('History main');

updateNodeBounds(node, circle);

node.intersect = function (point) {
log.info('History intersect', node, bbox.width / 2 + halfPadding, point);
return intersect.circle(node, bbox.width / 2 + halfPadding, point);
};

return shapeSvg;
};

const history = async (parent, node) => {
node.labelText = 'H';
return await historyBase(parent, node);
};

const deephistory = async (parent, node) => {
node.labelText = 'H*';
return await historyBase(parent, node);
};

const hexagon = async (parent, node) => {
const { shapeSvg, bbox } = await labelHelper(
parent,
Expand Down Expand Up @@ -1125,6 +1166,8 @@ const shapes = {
subroutine,
fork: forkJoin,
join: forkJoin,
history,
deephistory,
class_box,
};

Expand Down
10 changes: 10 additions & 0 deletions packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,13 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili

<INITIAL,struct>"state"\s+ { /* console.log('Starting STATE '); */ this.pushState('STATE'); }

<STATE>.*"<<history>>" {this.popState();yytext=yytext.slice(0,-11).trim(); /*console.warn('History: ',yytext);*/return 'HISTORY';}
<STATE>.*"<<deephistory>>" {this.popState();yytext=yytext.slice(0,-15).trim(); /*console.warn('Deep History: ',yytext);*/return 'DEEPHISTORY';}
<STATE>.*"<<fork>>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"<<join>>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"<<choice>>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
<STATE>.*"[[history]]" {this.popState();yytext=yytext.slice(0,-11).trim(); /*console.warn('History: ',yytext);*/return 'HISTORY';}
<STATE>.*"[[deephistory]]" {this.popState();yytext=yytext.slice(0,-15).trim(); /*console.warn('Deep History: ',yytext);*/return 'DEEPHISTORY';}
<STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
Expand Down Expand Up @@ -225,6 +229,12 @@ statement
| CONCURRENT {
$$={ stmt: 'state', id: yy.getDividerId(), type: 'divider' }
}
| HISTORY {
$$={ stmt: 'state', id: $1, type: 'history' }
}
| DEEPHISTORY {
$$={ stmt: 'state', id: $1, type: 'deephistory' }
}
| note notePosition ID NOTE_TEXT
{
/* console.warn('got NOTE, position: ', $2.trim(), 'id = ', $3.trim(), 'note: ', $4);*/
Expand Down
6 changes: 6 additions & 0 deletions packages/mermaid/src/diagrams/state/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ g.stateGroup line {
stroke: ${options.background};
stroke-width: 1.5
}
.node .history {
fill: #fff;
stroke: #000;
}
.end-state-inner {
fill: ${options.compositeBackground || options.background};
// stroke: ${options.background};
Expand Down
33 changes: 33 additions & 0 deletions packages/mermaid/src/docs/syntax/stateDiagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,39 @@ It is possible to specify a fork in the diagram using &lt;&lt;fork&gt;&gt; &lt;&
State4 --> [*]
```

## History (v11.0.0+)

It is possible to add (deep) history nodes in the diagram using &lt;&lt;history&gt;&gt; &lt;&lt;deephistory&gt;&gt;.

```mermaid-example
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History <<history>>
B --> C
C --> B
}
state "D" as D {
state "E" as E {
state "F" as F
state "G" as G
F --> G
G --> F
}
state "I" as I
state D_History <<deephistory>>
E --> I
I --> E
}
G --> A_History
A --> D_History
```

## Notes

Sometimes nothing says it better than a Post-it note. That is also the case in state diagrams.
Expand Down

0 comments on commit f63708c

Please sign in to comment.