diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index 8e15f3fac..7b4e98b4d 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -1,4 +1,4 @@ -/// +// import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; @@ -68,6 +68,19 @@ context('Sequence diagram', () => { { sequence: { actorFontFamily: 'courier' } } ); }); + it('should render bidirectional arrows', () => { + imgSnapshotTest( + ` + sequenceDiagram + Alice<<->>John: Hello John, how are you? + Alice<<-->>John: Hi Alice, I can hear you! + John<<->>Alice: This also works the other way + John<<-->>Alice: Yes + Alice->John: Test + John->>Alice: Still works + ` + ); + }); it('should handle different line breaks', () => { imgSnapshotTest( ` diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index e39e660e8..0f0d63213 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -206,18 +206,20 @@ Messages can be of two displayed either solid or with a dotted line. [Actor][Arrow][Actor]:Message text ``` -There are six types of arrows currently supported: +There are ten types of arrows currently supported: -| Type | Description | -| ------ | ------------------------------------------------ | -| `->` | Solid line without arrow | -| `-->` | Dotted line without arrow | -| `->>` | Solid line with arrowhead | -| `-->>` | Dotted line with arrowhead | -| `-x` | Solid line with a cross at the end | -| `--x` | Dotted line with a cross at the end. | -| `-)` | Solid line with an open arrow at the end (async) | -| `--)` | Dotted line with a open arrow at the end (async) | +| Type | Description | +| -------- | ------------------------------------------------------------------------ | +| `->` | Solid line without arrow | +| `-->` | Dotted line without arrow | +| `->>` | Solid line with arrowhead | +| `-->>` | Dotted line with arrowhead | +| `<<->>` | Solid line with bidirectional arrowheads (v\+) | +| `<<-->>` | Dotted line with bidirectional arrowheads (v\+) | +| `-x` | Solid line with a cross at the end | +| `--x` | Dotted line with a cross at the end. | +| `-)` | Solid line with an open arrow at the end (async) | +| `--)` | Dotted line with a open arrow at the end (async) | ## Activations diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index 1d5720707..11b39d232 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -33,7 +33,7 @@ "actor" { this.begin('ID'); return 'participant_actor'; } "create" return 'create'; "destroy" { this.begin('ID'); return 'destroy'; } -[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { this.popState(); this.popState(); return 'NEWLINE'; } "loop" { this.begin('LINE'); return 'loop'; } @@ -73,9 +73,11 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili "off" return 'off'; "," return ','; ";" return 'NEWLINE'; -[^\+\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } +[^\+\<->\->:\n,;]+((?!(\-x|\-\-x|\-\)|\-\-\)))[\-]*[^\+\<->\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; } "->>" return 'SOLID_ARROW'; +"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW'; "-->>" return 'DOTTED_ARROW'; +"<<-->>" return 'BIDIRECTIONAL_DOTTED_ARROW'; "->" return 'SOLID_OPEN_ARROW'; "-->" return 'DOTTED_OPEN_ARROW'; \-[x] return 'SOLID_CROSS'; @@ -310,7 +312,9 @@ signaltype : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } + | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } + | BIDIRECTIONAL_DOTTED_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_DOTTED; } | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; } | SOLID_POINT { $$ = yy.LINETYPE.SOLID_POINT; } diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts index 7e6c21b3c..19f6d561a 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts @@ -328,6 +328,8 @@ export const LINETYPE = { BREAK_START: 30, BREAK_END: 31, PAR_OVER_START: 32, + BIDIRECTIONAL_SOLID: 33, + BIDIRECTIONAL_DOTTED: 34, }; export const ARROWTYPE = { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 97d528df6..87a686129 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -516,6 +516,36 @@ Alice->>Bob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.DOTTED); }); + it('should handle bidirectional arrow messages', async () => { + const str = ` +sequenceDiagram +Alice<<->>Bob:Hello Bob, how are you?`; + + await mermaidAPI.parse(str); + const actors = diagram.db.getActors(); + expect(actors.get('Alice').description).toBe('Alice'); + expect(actors.get('Bob').description).toBe('Bob'); + + const messages = diagram.db.getMessages(); + + expect(messages.length).toBe(1); + expect(messages[0].type).toBe(diagram.db.LINETYPE.BIDIRECTIONAL_SOLID); + }); + it('should handle bidirectional dotted arrow messages', async () => { + const str = ` + sequenceDiagram + Alice<<-->>Bob:Hello Bob, how are you?`; + + await mermaidAPI.parse(str); + const actors = diagram.db.getActors(); + expect(actors.get('Alice').description).toBe('Alice'); + expect(actors.get('Bob').description).toBe('Bob'); + + const messages = diagram.db.getMessages(); + + expect(messages.length).toBe(1); + expect(messages[0].type).toBe(diagram.db.LINETYPE.BIDIRECTIONAL_DOTTED); + }); it('should handle actor activation', async () => { const str = ` sequenceDiagram diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 43d1db890..42bacd5d6 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -436,7 +436,8 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO type === diagObj.db.LINETYPE.DOTTED || type === diagObj.db.LINETYPE.DOTTED_CROSS || type === diagObj.db.LINETYPE.DOTTED_POINT || - type === diagObj.db.LINETYPE.DOTTED_OPEN + type === diagObj.db.LINETYPE.DOTTED_OPEN || + type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED ) { line.style('stroke-dasharray', '3, 3'); line.attr('class', 'messageLine1'); @@ -462,6 +463,13 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO if (type === diagObj.db.LINETYPE.SOLID || type === diagObj.db.LINETYPE.DOTTED) { line.attr('marker-end', 'url(' + url + '#arrowhead)'); } + if ( + type === diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID || + type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED + ) { + line.attr('marker-start', 'url(' + url + '#arrowhead)'); + line.attr('marker-end', 'url(' + url + '#arrowhead)'); + } if (type === diagObj.db.LINETYPE.SOLID_POINT || type === diagObj.db.LINETYPE.DOTTED_POINT) { line.attr('marker-end', 'url(' + url + '#filled-head)'); } @@ -1036,6 +1044,8 @@ export const draw = async function (_text: string, id: string, _version: string, diagObj.db.LINETYPE.DOTTED_CROSS, diagObj.db.LINETYPE.SOLID_POINT, diagObj.db.LINETYPE.DOTTED_POINT, + diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID, + diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED, ].includes(msg.type) ) { sequenceIndex = sequenceIndex + sequenceIndexStep; @@ -1416,6 +1426,8 @@ const buildMessageModel = function (msg, actors, diagObj) { diagObj.db.LINETYPE.DOTTED_CROSS, diagObj.db.LINETYPE.SOLID_POINT, diagObj.db.LINETYPE.DOTTED_POINT, + diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID, + diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED, ].includes(msg.type) ) { return {}; @@ -1423,7 +1435,7 @@ const buildMessageModel = function (msg, actors, diagObj) { const [fromLeft, fromRight] = activationBounds(msg.from, actors); const [toLeft, toRight] = activationBounds(msg.to, actors); const isArrowToRight = fromLeft <= toLeft; - const startx = isArrowToRight ? fromRight : fromLeft; + let startx = isArrowToRight ? fromRight : fromLeft; let stopx = isArrowToRight ? toLeft : toRight; // As the line width is considered, the left and right values will be off by 2. @@ -1462,6 +1474,17 @@ const buildMessageModel = function (msg, actors, diagObj) { if (![diagObj.db.LINETYPE.SOLID_OPEN, diagObj.db.LINETYPE.DOTTED_OPEN].includes(msg.type)) { stopx += adjustValue(3); } + + /** + * Shorten start position of bidirectional arrow to accommodate for second arrowhead + */ + if ( + [diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID, diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED].includes( + msg.type + ) + ) { + startx -= adjustValue(3); + } } const allBounds = [fromLeft, fromRight, toLeft, toRight]; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 848455f78..bcc06602f 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -735,7 +735,7 @@ export const insertArrowHead = function (elem) { .attr('markerUnits', 'userSpaceOnUse') .attr('markerWidth', 12) .attr('markerHeight', 12) - .attr('orient', 'auto') + .attr('orient', 'auto-start-reverse') .append('path') .attr('d', 'M -1 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index ae2c7fd02..249f7bde0 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -141,18 +141,20 @@ Messages can be of two displayed either solid or with a dotted line. [Actor][Arrow][Actor]:Message text ``` -There are six types of arrows currently supported: +There are ten types of arrows currently supported: -| Type | Description | -| ------ | ------------------------------------------------ | -| `->` | Solid line without arrow | -| `-->` | Dotted line without arrow | -| `->>` | Solid line with arrowhead | -| `-->>` | Dotted line with arrowhead | -| `-x` | Solid line with a cross at the end | -| `--x` | Dotted line with a cross at the end. | -| `-)` | Solid line with an open arrow at the end (async) | -| `--)` | Dotted line with a open arrow at the end (async) | +| Type | Description | +| -------- | ----------------------------------------------------------------------- | +| `->` | Solid line without arrow | +| `-->` | Dotted line without arrow | +| `->>` | Solid line with arrowhead | +| `-->>` | Dotted line with arrowhead | +| `<<->>` | Solid line with bidirectional arrowheads (v+) | +| `<<-->>` | Dotted line with bidirectional arrowheads (v+) | +| `-x` | Solid line with a cross at the end | +| `--x` | Dotted line with a cross at the end. | +| `-)` | Solid line with an open arrow at the end (async) | +| `--)` | Dotted line with a open arrow at the end (async) | ## Activations diff --git a/packages/mermaid/src/mermaid.spec.ts b/packages/mermaid/src/mermaid.spec.ts index 9360f7bab..d03f0ee9d 100644 --- a/packages/mermaid/src/mermaid.spec.ts +++ b/packages/mermaid/src/mermaid.spec.ts @@ -207,7 +207,7 @@ describe('when using mermaid and ', () => { [Error: Parse error on line 2: ...equenceDiagramAlice:->Bob: Hello Bob, h... ----------------------^ - Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'] + Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'] `); });