Skip to content

Commit

Permalink
add history node
Browse files Browse the repository at this point in the history
  • Loading branch information
nimec01 committed Nov 26, 2024
1 parent cc29437 commit b677cec
Show file tree
Hide file tree
Showing 11 changed files with 298 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 @@ -6,6 +6,7 @@ braintree
catmull
compositTitleSize
curv
deephistory
doublecircle
elems
gantt
Expand Down
70 changes: 70 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,76 @@ 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 it should be possible to use a (deep) history node shorthand', () => {
imgSnapshotTest(
`
stateDiagram-v2
state "A" as A {
state "B" as B
state "C" as C
state A_History [[H]]
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 [[H*]]
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 [[H]]

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 [[H*]]

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
2 changes: 2 additions & 0 deletions docs/syntax/flowchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ Below is a comprehensive list of the newly introduced shapes and their correspon
| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` |
| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` |
| Deep History | Circle with an H\* | `deephistory` | Deep history entrance | `H*` |
| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
Expand All @@ -337,6 +338,7 @@ Below is a comprehensive list of the newly introduced shapes and their correspon
| Event | Rounded Rectangle | `rounded` | Represents an event | `event` |
| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` |
| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
| History | Circle with an H | `history` | History entrance | `H` |
| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` |
| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` |
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 (v\<MERMAID_RELEASE_VERSION>+)

It is possible to add (deep) history nodes in the diagram using <\<history>> <\<deephistory>> or their respective shorthand <\<H>> <\<H\*>>.

```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
2 changes: 2 additions & 0 deletions packages/mermaid/scripts/docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ This Markdown should be kept.
| Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` |
| Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` |
| Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` |
| Deep History | Circle with an H\\* | \`deephistory\` | Deep history entrance | \`H*\` |
| Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` |
| Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` |
| Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` |
Expand All @@ -190,6 +191,7 @@ This Markdown should be kept.
| Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` |
| Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` |
| Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` |
| History | Circle with an H | \`history\` | History entrance | \`H\` |
| Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` |
| Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` |
| Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` |
Expand Down
14 changes: 14 additions & 0 deletions packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,17 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili

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

<STATE>.*"<<H>>" {this.popState();yytext=yytext.slice(0,-5).trim(); /*console.warn('History: ',yytext);*/return 'HISTORY';}
<STATE>.*"<<H*>>" {this.popState();yytext=yytext.slice(0,-6).trim(); /*console.warn('History: ',yytext);*/return 'DEEPHISTORY';}
<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>.*"[[H]]" {this.popState();yytext=yytext.slice(0,-5).trim(); /*console.warn('History: ',yytext);*/return 'HISTORY';}
<STATE>.*"[[H*]]" {this.popState();yytext=yytext.slice(0,-6).trim(); /*console.warn('History: ',yytext);*/return 'DEEPHISTORY';}
<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 @@ -237,6 +245,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
5 changes: 5 additions & 0 deletions packages/mermaid/src/diagrams/state/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ g.stateGroup line {
stroke: ${options.specialStateColor};
}
.node .history {
fill: ${options.background};
stroke: ${options.specialStateColor};
}
.node circle.state-end {
fill: ${options.innerEndBackground};
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 (v<MERMAID_RELEASE_VERSION>+)

It is possible to add (deep) history nodes in the diagram using &lt;&lt;history&gt;&gt; &lt;&lt;deephistory&gt;&gt; or their respective shorthand &lt;&lt;H&gt;&gt; &lt;&lt;H\*&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
17 changes: 17 additions & 0 deletions packages/mermaid/src/rendering-util/rendering-elements/shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { waveRectangle } from './shapes/waveRectangle.js';
import { windowPane } from './shapes/windowPane.js';
import { classBox } from './shapes/classBox.js';
import { kanbanItem } from './shapes/kanbanItem.js';
import { history, deephistory } from './shapes/history.js';

type ShapeHandler = <T extends SVGGraphicsElement>(
parent: D3Selection<T>,
Expand Down Expand Up @@ -243,6 +244,22 @@ export const shapesDefs = [
internalAliases: ['forkJoin'],
handler: forkJoin,
},
{
semanticName: 'History',
name: 'Circle with an H',
shortName: 'history',
description: 'History entrance',
aliases: ['H'],
handler: history,
},
{
semanticName: 'Deep History',
name: 'Circle with an H*',
shortName: 'deephistory',
description: 'Deep history entrance',
aliases: ['H*'],
handler: deephistory,
},
{
semanticName: 'Collate',
name: 'Hourglass',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';

export async function historyBase<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg } = await labelHelper(parent, node, getNodeClasses(node));

const radius = 16;
let circleElem;
const { cssStyles } = node;

if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const roughNode = rc.circle(0, 0, radius * 2, options);

circleElem = shapeSvg.insert(() => roughNode, ':first-child');
circleElem
.attr('class', 'basic label-container history')
.attr('style', handleUndefinedAttr(cssStyles));
} else {
circleElem = shapeSvg
.insert('circle', ':first-child')
.attr('class', 'basic label-container history')
.attr('style', nodeStyles)
.attr('r', radius)
.attr('cx', 0)
.attr('cy', 0);
}

updateNodeBounds(node, circleElem);

node.intersect = function (point) {
return intersect.circle(node, radius, point);
};

return shapeSvg;
}

export async function history<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
node.label = 'H';
return await historyBase(parent, node);
}

export async function deephistory<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
node.label = 'H*';
return await historyBase(parent, node);
}

0 comments on commit b677cec

Please sign in to comment.