Compare commits

...

30 Commits

Author SHA1 Message Date
omkarht
2cdaf03ada Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-09-05 12:12:01 +05:30
omkarht
f6fa0260e7 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-09-04 12:56:49 +05:30
omkarht
29aad6d23c Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-09-04 12:12:03 +05:30
omkarht
82ef7b5fdb docs: add version placeholders for new features 2025-09-02 19:02:02 +05:30
omkarht
11cd3f1262 feat: add central connection rendering and parsing tests 2025-09-02 18:52:18 +05:30
omkarht
ac4aa94e78 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-09-02 15:13:07 +05:30
omkarht
c40faac80d Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-08-29 13:26:48 +05:30
omkarht
c530baed3f Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-08-28 13:07:57 +05:30
omkarht
045699de10 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 6638-sequence-diagram-additional-messages 2025-08-25 15:14:50 +05:30
omkarht
1988d24227 fix: fixed reverse arrows placing for autonumber
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-22 16:46:49 +05:30
omkarht
39f90debe7 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-08-22 13:03:01 +05:30
omkarht
73e9849f99 chore: added changeset
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-21 16:12:25 +05:30
omkarht
5a05540a5f Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 6638-sequence-diagram-additional-messages 2025-08-21 16:09:18 +05:30
omkarht
2b58df9665 fix: refactored documentation
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-19 14:53:08 +05:30
omkarht
0b42bdba07 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-08-11 15:28:48 +05:30
omkarht
74c96db3e2 docs: document new syntax for half-arrow message and central connection
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-07 12:47:31 +05:30
omkarht
bd47c57eaf chore: refactored code
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-06 18:56:58 +05:30
omkarht
3e5d2db514 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-08-06 18:56:18 +05:30
omkarht
40990bb096 Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-07-29 18:17:34 +05:30
omkarht
7ca0665764 fix: fixed failing test case
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 19:13:51 +05:30
omkarht
81a6a361ab Merge branch 'develop' into 6638-sequence-diagram-additional-messages 2025-07-28 16:51:38 +05:30
omkarht
62faacdeeb fix: fixed failing test cases
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 16:01:43 +05:30
omkarht
0e40d8e8a8 fix: fixed failing test cases
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 15:55:07 +05:30
omkarht
e8d6daf4f6 add support for central connection circle after arrow in lifeline direction
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 15:37:33 +05:30
omkarht
cb4ed605b2 chore: added rendering test cases for arrow head type
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-23 16:59:04 +05:30
omkarht
ba9db26bfa Lexing : Added Support for new arrow types through lexing
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-22 19:25:27 +05:30
omkarht
252b1837f7 added arrowhead types for dotted line type
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-22 19:20:06 +05:30
omkarht
6b9c15d7f0 added reverse arrow head types
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-22 16:30:21 +05:30
omkarht
fda640c90c fix: adjusted arrowhead design
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-21 19:03:29 +05:30
omkarht
584a789183 6638: add support for additional message types for sequence diagram
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-21 13:33:52 +05:30
12 changed files with 1112 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Add half-arrowheads (solid & stick) and central connection support

View File

@@ -655,5 +655,126 @@ describe('Sequence Diagram Special Cases', () => {
expect(svg).to.not.have.attr('style');
});
});
describe('Central Connection Rendering Tests', () => {
it('should render central connection circles on actor vertical lines', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
participant Charlie
Alice ()->>() Bob: Central connection
Bob ()-->> Charlie: Reverse central connection
Charlie ()<<-->>() Alice: Dual central connection`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with different arrow types', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
Alice ()->>() Bob: Solid open arrow
Alice ()-->>() Bob: Dotted open arrow
Alice ()-x() Bob: Solid cross
Alice ()--x() Bob: Dotted cross
Alice ()->() Bob: Solid arrow`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with bidirectional arrows', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
Alice ()<<->>() Bob: Bidirectional solid
Alice ()<<-->>() Bob: Bidirectional dotted`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with activations', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
participant Charlie
Alice ()->>() Bob: Activate Bob
activate Bob
Bob ()-->> Charlie: Message to Charlie
Bob ()->>() Alice: Response to Alice
deactivate Bob`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections mixed with normal messages', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
participant Charlie
Alice ->> Bob: Normal message
Bob ()->>() Charlie: Central connection
Charlie -->> Alice: Normal dotted message
Alice ()<<-->>() Bob: Dual central connection
Bob -x Charlie: Normal cross message`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with notes', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
participant Charlie
Alice ()->>() Bob: Central connection
Note over Alice,Bob: Central connection note
Bob ()-->> Charlie: Reverse central connection
Note right of Charlie: Response note
Charlie ()<<-->>() Alice: Dual central connection`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with loops and alternatives', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
participant Bob
participant Charlie
loop Every minute
Alice ()->>() Bob: Central heartbeat
Bob ()-->> Charlie: Forward heartbeat
end
alt Success
Charlie ()<<-->>() Alice: Success response
else Failure
Charlie ()-x() Alice: Failure response
end`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
it('should render central connections with different participant types', () => {
imgSnapshotTest(
`sequenceDiagram
participant Alice
actor Bob
participant Charlie@{"type":"boundary"}
participant David@{"type":"control"}
participant Eve@{"type":"entity"}
Alice ()->>() Bob: To actor
Bob ()-->> Charlie: To boundary
Charlie ()->>() David: To control
David ()<<-->>() Eve: To entity
Eve ()-x() Alice: Back to participant`,
{ look: 'classic', sequence: { diagramMarginX: 50, diagramMarginY: 10 } }
);
});
});
});
});

View File

@@ -1053,4 +1053,167 @@ describe('Sequence diagram', () => {
]);
});
});
describe('render new arrow type', () => {
it('should render Solid half arrow top', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice -|\\ John: Hello John, how are you?
Alice-|\\ John: Hi Alice, I can hear you!
Alice -|\\ John: Test
`
);
});
it('should render Solid half arrow bottom', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice-|/John: Hello John, how are you?
Alice-|/John: Hi Alice, I can hear you!
Alice-|/John: Test
`
);
});
it('should render Stick half arrow top ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice-\\\\John: Hello John, how are you?
Alice-\\\\John: Hi Alice, I can hear you!
Alice-\\\\John: Test
`
);
});
it('should render Stick half arrow bottom ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice-//John: Hello John, how are you?
Alice-//John: Hi Alice, I can hear you!
Alice-//John: Test
`
);
});
it('should render Solid half arrow top reverse ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice/|-John: Hello Alice, how are you?
Alice/|-John: Hi Alice, I can hear you!
Alice/|-John: Test
`
);
});
it('should render Solid half arrow bottom reverse ', () => {
imgSnapshotTest(
`sequenceDiagram
Alice \\|- John: Hello Alice, how are you?
Alice \\|- John: Hi Alice, I can hear you!
Alice \\|- John: Test`
);
});
it('should render Stick half arrow top reverse ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice //-John: Hello Alice, how are you?
Alice //-John: Hi Alice, I can hear you!
Alice //-John: Test`
);
});
it('should render Stick half arrow bottom reverse ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice \\\\-John: Hello Alice, how are you?
Alice \\\\-John: Hi Alice, I can hear you!
Alice \\\\-John: Test`
);
});
it('should render Solid half arrow top dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice --|\\John: Hello John, how are you?
Alice --|\\John: Hi Alice, I can hear you!
Alice --|\\John: Test`
);
});
it('should render Solid half arrow bottom dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice --|/John: Hello John, how are you?
Alice --|/John: Hi Alice, I can hear you!
Alice --|/John: Test`
);
});
it('should render Stick half arrow top dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice--\\\\John: Hello John, how are you?
Alice--\\\\John: Hi Alice, I can hear you!
Alice--\\\\John: Test`
);
});
it('should render Stick half arrow bottom dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice--//John: Hello John, how are you?
Alice--//John: Hi Alice, I can hear you!
Alice--//John: Test`
);
});
it('should render Solid half arrow top reverse dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice/|--John: Hello Alice, how are you?
Alice/|--John: Hi Alice, I can hear you!
Alice/|--John: Test`
);
});
it('should render Solid half arrow bottom reverse dotted', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice\\|--John: Hello Alice, how are you?
Alice\\|--John: Hi Alice, I can hear you!
Alice\\|--John: Test`
);
});
it('should render Stick half arrow top reverse dotted ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice//--John: Hello Alice, how are you?
Alice//--John: Hi Alice, I can hear you!
Alice//--John: Test`
);
});
it('should render Stick half arrow bottom reverse dotted ', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice\\\\--John: Hello Alice, how are you?
Alice\\\\--John: Hi Alice, I can hear you!
Alice\\\\--John: Test`
);
});
});
});

View File

@@ -329,7 +329,11 @@ Messages can be of two displayed either solid or with a dotted line.
[Actor][Arrow][Actor]:Message text
```
There are ten types of arrows currently supported:
Lines can be solid or dotted, and can end with various types of arrowheads, crosses, or open arrows.
#### Supported Arrow Types
**Standard Arrow Types**
| Type | Description |
| -------- | ---------------------------------------------------- |
@@ -344,6 +348,58 @@ There are ten types of arrows currently supported:
| `-)` | Solid line with an open arrow at the end (async) |
| `--)` | Dotted line with a open arrow at the end (async) |
**Half-Arrows (v\<MERMAID_RELEASE_VERSION>+)**
The following half-arrow types are supported for more expressive sequence diagrams. Both solid and dotted variants are available by increasing the number of dashes (`-` → `--`).
---
| Type | Description |
| ------- | ---------------------------------------------------- |
| `-\|\` | Solid line with top half arrowhead |
| `--\|\` | Dotted line with top half arrowhead |
| `-\|/` | Solid line with bottom half arrowhead |
| `--\|/` | Dotted line with bottom half arrowhead |
| `/\|-` | Solid line with reverse top half arrowhead |
| `/\|--` | Dotted line with reverse top half arrowhead |
| `\\-` | Solid line with reverse bottom half arrowhead |
| `\\--` | Dotted line with reverse bottom half arrowhead |
| `-\\` | Solid line with top stick half arrowhead |
| `--\\` | Dotted line with top stick half arrowhead |
| `-//` | Solid line with bottom stick half arrowhead |
| `--//` | Dotted line with bottom stick half arrowhead |
| `//-` | Solid line with reverse top stick half arrowhead |
| `//--` | Dotted line with reverse top stick half arrowhead |
| `\\-` | Solid line with reverse bottom stick half arrowhead |
| `\\--` | Dotted line with reverse bottom stick half arrowhead |
## Central Connections (v\<MERMAID_RELEASE_VERSION>+)
Mermaid sequence diagrams support **central lifeline connections** using a `()`.
This is useful to represent messages or signals that connect to a central point, rather than from one actor directly to another.
To indicate a central connection, append `()` to the arrow syntax.
#### Basic Syntax
```mermaid-example
sequenceDiagram
participant Alice
participant John
Alice->>()John: Hello John
Alice()->>John: How are you?
John()->>()Alice: Great!
```
```mermaid
sequenceDiagram
participant Alice
participant John
Alice->>()John: Hello John
Alice()->>John: How are you?
John()->>()Alice: Great!
```
## Activations
It is possible to activate and deactivate an actor. (de)activation can be dedicated declarations:

View File

@@ -78,7 +78,7 @@ 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'; } //final_4.11
"->>" return 'SOLID_ARROW';
"<<->>" return 'BIDIRECTIONAL_SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
@@ -89,10 +89,36 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\-\-[x] return 'DOTTED_CROSS';
\-[\)] return 'SOLID_POINT';
\-\-[\)] return 'DOTTED_POINT';
//normal-dotted
\-\-\|\\ return 'SOLID_ARROW_TOP_DOTTED';
\-\-\|\/ return 'SOLID_ARROW_BOTTOM_DOTTED';
\-\-\\\\ return 'STICK_ARROW_TOP_DOTTED';
\-\-\/\/ return 'STICK_ARROW_BOTTOM_DOTTED';
//reverse-dotted
\/\|\-\- return 'SOLID_ARROW_TOP_REVERSE_DOTTED';
\\\|\-\- return 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED';
\/\/\-\- return 'STICK_ARROW_TOP_REVERSE_DOTTED';
\\\\\-\- return 'STICK_ARROW_BOTTOM_REVERSE_DOTTED';
//normal
\-\|\\ return 'SOLID_ARROW_TOP';
\-\|\/ return 'SOLID_ARROW_BOTTOM';
\-\\\\ return 'STICK_ARROW_TOP';
\-\/\/ return 'STICK_ARROW_BOTTOM';
//reverse
\/\|\- return 'SOLID_ARROW_TOP_REVERSE';
\\\|\- return 'SOLID_ARROW_BOTTOM_REVERSE';
\/\/\- return 'STICK_ARROW_TOP_REVERSE';
\\\\\- return 'STICK_ARROW_BOTTOM_REVERSE';
":"(?:(?:no)?wrap:)?[^#\n;]* return 'TXT';
":" return 'TXT';
"+" return '+';
"-" return '-';
"()" return '()';
<<EOF>> return 'NEWLINE';
. return 'INVALID';
@@ -304,6 +330,20 @@ signal
{ $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5},
{type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1.actor}
]}
| actor signaltype '()' actor text2
{ $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5, activate: true, centralConnection: yy.LINETYPE.CENTRAL_CONNECTION},
{type: 'centralConnection', signalType: yy.LINETYPE.CENTRAL_CONNECTION, actor: $4.actor, }
]}
| actor '()' signaltype actor text2
{ $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$3, msg:$5, activate: false, centralConnection: yy.LINETYPE.CENTRAL_CONNECTION_REVERSE},
{type: 'centralConnectionReverse', signalType: yy.LINETYPE.CENTRAL_CONNECTION_REVERSE, actor: $1.actor}
]}
| actor '()' signaltype '()' actor text2
{ $$ = [$1,$5,{type: 'addMessage', from:$1.actor, to:$5.actor, signalType:$3, msg:$6, activate: true, centralConnection: yy.LINETYPE.CENTRAL_CONNECTION_DUAL},
{type: 'centralConnection', signalType: yy.LINETYPE.CENTRAL_CONNECTION, actor: $5.actor, },
{type: 'centralConnectionReverse', signalType: yy.LINETYPE.CENTRAL_CONNECTION_REVERSE, actor: $1.actor}
]}
| actor signaltype actor text2
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
@@ -337,7 +377,28 @@ 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; }
| SOLID_ARROW_TOP { $$ = yy.LINETYPE.SOLID_TOP; }
| SOLID_ARROW_BOTTOM { $$ = yy.LINETYPE.SOLID_BOTTOM; }
| STICK_ARROW_TOP { $$ = yy.LINETYPE.STICK_TOP; }
| STICK_ARROW_BOTTOM { $$ = yy.LINETYPE.STICK_BOTTOM; }
| SOLID_ARROW_TOP_DOTTED { $$ = yy.LINETYPE.SOLID_TOP_DOTTED; }
| SOLID_ARROW_BOTTOM_DOTTED { $$ = yy.LINETYPE.SOLID_BOTTOM_DOTTED; }
| STICK_ARROW_TOP_DOTTED { $$ = yy.LINETYPE.STICK_TOP_DOTTED; }
| STICK_ARROW_BOTTOM_DOTTED { $$ = yy.LINETYPE.STICK_BOTTOM_DOTTED; }
| SOLID_ARROW_TOP_REVERSE { $$ = yy.LINETYPE.SOLID_ARROW_TOP_REVERSE; }
| SOLID_ARROW_BOTTOM_REVERSE { $$ = yy.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE; }
| STICK_ARROW_TOP_REVERSE { $$ = yy.LINETYPE.STICK_ARROW_TOP_REVERSE; }
| STICK_ARROW_BOTTOM_REVERSE { $$ = yy.LINETYPE.STICK_ARROW_BOTTOM_REVERSE; }
| SOLID_ARROW_TOP_REVERSE_DOTTED { $$ = yy.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED; }
| SOLID_ARROW_BOTTOM_REVERSE_DOTTED { $$ = yy.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED; }
| STICK_ARROW_TOP_REVERSE_DOTTED { $$ = yy.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED; }
| STICK_ARROW_BOTTOM_REVERSE_DOTTED { $$ = yy.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED; }
| 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; }

View File

@@ -64,6 +64,30 @@ const LINETYPE = {
PAR_OVER_START: 32,
BIDIRECTIONAL_SOLID: 33,
BIDIRECTIONAL_DOTTED: 34,
SOLID_TOP: 41,
SOLID_BOTTOM: 42,
STICK_TOP: 43,
STICK_BOTTOM: 44,
SOLID_ARROW_TOP_REVERSE: 45,
SOLID_ARROW_BOTTOM_REVERSE: 46,
STICK_ARROW_TOP_REVERSE: 47,
STICK_ARROW_BOTTOM_REVERSE: 48,
SOLID_TOP_DOTTED: 51,
SOLID_BOTTOM_DOTTED: 52,
STICK_TOP_DOTTED: 53,
STICK_BOTTOM_DOTTED: 54,
SOLID_ARROW_TOP_REVERSE_DOTTED: 55,
SOLID_ARROW_BOTTOM_REVERSE_DOTTED: 56,
STICK_ARROW_TOP_REVERSE_DOTTED: 57,
STICK_ARROW_BOTTOM_REVERSE_DOTTED: 58,
CENTRAL_CONNECTION: 59,
CENTRAL_CONNECTION_REVERSE: 60,
CENTRAL_CONNECTION_DUAL: 61,
} as const;
const ARROWTYPE = {
@@ -244,7 +268,8 @@ export class SequenceDB implements DiagramDB {
idTo?: Message['to'],
message?: { text: string; wrap: boolean },
messageType?: number,
activate = false
activate = false,
centralConnection?: number
) {
if (messageType === this.LINETYPE.ACTIVE_END) {
const cnt = this.activationCount(idFrom ?? '');
@@ -271,6 +296,7 @@ export class SequenceDB implements DiagramDB {
wrap: message?.wrap ?? this.autoWrap(),
type: messageType,
activate,
centralConnection: centralConnection ?? 0,
});
return true;
}
@@ -563,6 +589,12 @@ export class SequenceDB implements DiagramDB {
case 'activeStart':
this.addSignal(param.actor, undefined, undefined, param.signalType);
break;
case 'centralConnection':
this.addSignal(param.actor, undefined, undefined, param.signalType);
break;
case 'centralConnectionReverse':
this.addSignal(param.actor, undefined, undefined, param.signalType);
break;
case 'activeEnd':
this.addSignal(param.actor, undefined, undefined, param.signalType);
break;
@@ -606,7 +638,14 @@ export class SequenceDB implements DiagramDB {
this.state.records.lastDestroyed = undefined;
}
}
this.addSignal(param.from, param.to, param.msg, param.signalType, param.activate);
this.addSignal(
param.from,
param.to,
param.msg,
param.signalType,
param.activate,
param.centralConnection
);
break;
case 'boxStart':
this.addBox(param.boxData);

View File

@@ -104,6 +104,7 @@ describe('more than one sequence diagram', () => {
[
{
"activate": false,
"centralConnection": 0,
"from": "Alice",
"id": "0",
"message": "Hello Bob, how are you?",
@@ -113,6 +114,7 @@ describe('more than one sequence diagram', () => {
},
{
"activate": false,
"centralConnection": 0,
"from": "Bob",
"id": "1",
"message": "I am good thanks!",
@@ -131,6 +133,7 @@ describe('more than one sequence diagram', () => {
[
{
"activate": false,
"centralConnection": 0,
"from": "Alice",
"id": "0",
"message": "Hello Bob, how are you?",
@@ -140,6 +143,7 @@ describe('more than one sequence diagram', () => {
},
{
"activate": false,
"centralConnection": 0,
"from": "Bob",
"id": "1",
"message": "I am good thanks!",
@@ -160,6 +164,7 @@ describe('more than one sequence diagram', () => {
[
{
"activate": false,
"centralConnection": 0,
"from": "Alice",
"id": "0",
"message": "Hello John, how are you?",
@@ -169,6 +174,7 @@ describe('more than one sequence diagram', () => {
},
{
"activate": false,
"centralConnection": 0,
"from": "John",
"id": "1",
"message": "I am good thanks!",
@@ -181,6 +187,254 @@ describe('more than one sequence diagram', () => {
});
});
describe('Central Connection Parsing', () => {
describe('when parsing central connection syntax', () => {
it('should parse actor ()->>() actor syntax as CENTRAL_CONNECTION_DUAL', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()->>() Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(3); // addMessage + centralConnection + centralConnectionReverse
// Find the actual message (type: 'addMessage')
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL
activate: true,
type: 0, // SOLID (based on test output)
});
});
it('should parse actor ()-->>() actor syntax as CENTRAL_CONNECTION_DUAL', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()-->>() Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(3); // addMessage + centralConnection + centralConnectionReverse
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL
activate: true,
type: 1, // DOTTED (based on test output)
});
});
it('should parse actor ->>() actor syntax as CENTRAL_CONNECTION', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ->>() Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(2); // addMessage + centralConnection (no activation for this pattern)
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 59, // CENTRAL_CONNECTION
activate: true,
type: 0, // SOLID (based on actual parsing)
});
});
it('should parse actor ()-->> actor syntax as CENTRAL_CONNECTION_REVERSE', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()-->> Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(2); // addMessage + centralConnectionReverse
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 60, // CENTRAL_CONNECTION_REVERSE
activate: false,
type: 1, // DOTTED (based on test output)
});
});
it('should parse actor ()->> actor syntax as CENTRAL_CONNECTION_REVERSE', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()->> Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(2); // addMessage + centralConnectionReverse
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 60, // CENTRAL_CONNECTION_REVERSE
activate: false,
type: 0, // SOLID (based on test output)
});
});
it('should parse actor ()<<-->>() actor syntax as CENTRAL_CONNECTION_DUAL', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()<<-->>() Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(3); // addMessage + centralConnection + centralConnectionReverse
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL
activate: true,
type: 34, // BIDIRECTIONAL_DOTTED
});
});
it('should parse actor ()<<->>() actor syntax as CENTRAL_CONNECTION_DUAL', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()<<->>() Bob: Hello Bob, how are you?
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(3); // addMessage + centralConnection + centralConnectionReverse
const actualMessage = messages.find((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessage).toMatchObject({
from: 'Alice',
to: 'Bob',
message: 'Hello Bob, how are you?',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL
activate: true,
type: 33, // BIDIRECTIONAL_SOLID
});
});
it('should handle multiple central connection types in one diagram', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
participant Charlie
Alice ()->>() Bob: Message 1
Bob ()-->> Charlie: Message 2
Charlie ()<<-->>() Alice: Message 3
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(8); // 3 addMessages + 5 central connection markers
// Filter to get only the actual messages
const actualMessages = messages.filter((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessages).toHaveLength(3);
expect(actualMessages[0]).toMatchObject({
from: 'Alice',
to: 'Bob',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL (()->>())
});
expect(actualMessages[1]).toMatchObject({
from: 'Bob',
to: 'Charlie',
centralConnection: 60, // CENTRAL_CONNECTION_REVERSE (()-->>)
});
expect(actualMessages[2]).toMatchObject({
from: 'Charlie',
to: 'Alice',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL (()<<-->>())
});
});
it('should handle central connections with different arrow types', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ()-x() Bob: Cross message
Alice ()--x() Bob: Dotted cross message
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(6); // 2 addMessages + 4 central connection markers
const actualMessages = messages.filter((msg) => msg.type !== undefined && msg.from && msg.to);
expect(actualMessages).toHaveLength(2);
expect(actualMessages[0]).toMatchObject({
from: 'Alice',
to: 'Bob',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL (()-x())
type: 3, // SOLID_CROSS
});
expect(actualMessages[1]).toMatchObject({
from: 'Alice',
to: 'Bob',
centralConnection: 61, // CENTRAL_CONNECTION_DUAL (()--x())
type: 4, // DOTTED_CROSS
});
});
it('should not break existing parsing without central connections', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice
participant Bob
Alice ->> Bob: Normal message
Bob -->> Alice: Normal dotted message
Alice -x Bob: Normal cross message
`);
const messages = diagram.db.getMessages();
expect(messages).toHaveLength(3);
messages.forEach((msg) => {
expect(msg.centralConnection).toBe(0); // No central connection
});
expect(messages[0].type).toBe(0); // SOLID (based on actual parsing)
expect(messages[1].type).toBe(1); // DOTTED (based on actual parsing)
expect(messages[2].type).toBe(3); // SOLID_CROSS
});
});
});
describe('when parsing a sequenceDiagram', function () {
let diagram;
beforeEach(async function () {
@@ -2058,6 +2312,36 @@ Bob->>Alice:Got it!
expect(messages[0].from).toBe('Alice');
expect(messages[0].to).toBe('Bob');
});
it('1 should parse ', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
actor Bob
actor Alice
Bob -|\\ Alice: Hello Alice, how are you?
Bob -|/ Alice: Hello Alice, how are you?
Bob -// Alice: Hello Alice, how are you?
Bob -\\\\ Alice: Hello Alice, how are you?
Bob \\|- Alice: Hello Alice, how are you?
Bob /|- Alice: Hello Alice, how are you?
Bob //- Alice: Hello Alice, how are you?
Bob \\\\- Alice: Hello Alice, how are you?
`);
const messages = diagram.db.getMessages();
});
it('2 should parse ', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
actor Bob
actor Alice
Alice ()<<->>() Bob: hey?
`);
const messages = diagram.db.getMessages();
});
describe('when parsing extended participant syntax', () => {
it('should parse participants with different quote styles and whitespace', async () => {
const diagram = await Diagram.fromText(`

View File

@@ -282,6 +282,49 @@ const drawNote = async function (elem: any, noteModel: NoteModel) {
bounds.models.addNote(noteModel);
};
const drawCentralConnection = function (
elem: any,
msg: any,
msgModel: any,
diagObj: Diagram,
startx: number,
stopx: number,
lineStartY: number
) {
const actors = diagObj.db.getActors();
const fromActor = actors.get(msg.from);
const toActor = actors.get(msg.to);
const fromCenter = fromActor.x + fromActor.width / 2;
const toCenter = toActor.x + toActor.width / 2;
const g = elem.append('g');
const drawCircle = (cx: number) => {
g.append('circle')
.attr('cx', cx)
.attr('cy', lineStartY)
.attr('r', 5)
.attr('width', 10)
.attr('height', 10);
};
const { CENTRAL_CONNECTION, CENTRAL_CONNECTION_REVERSE, CENTRAL_CONNECTION_DUAL } =
diagObj.db.LINETYPE;
switch (msg.centralConnection) {
case CENTRAL_CONNECTION:
drawCircle(toCenter);
break;
case CENTRAL_CONNECTION_REVERSE:
drawCircle(fromCenter);
break;
case CENTRAL_CONNECTION_DUAL:
drawCircle(fromCenter);
drawCircle(toCenter);
break;
}
};
const messageFont = (cnf) => {
return {
fontFamily: cnf.messageFontFamily,
@@ -367,7 +410,7 @@ async function boundMessage(_diagram, msgModel): Promise<number> {
* @param lineStartY - The Y coordinate at which the message line starts
* @param diagObj - The diagram object.
*/
const drawMessage = async function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
const drawMessage = async function (diagram, msgModel, lineStartY: number, diagObj: Diagram, msg) {
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
const textObj = svgDrawCommon.getTextObj();
@@ -433,6 +476,9 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
line.attr('y1', lineStartY);
line.attr('x2', stopx);
line.attr('y2', lineStartY);
if (hasCentralConnection(msg, diagObj)) {
drawCentralConnection(diagram, msg, msgModel, diagObj, startx, stopx, lineStartY);
}
}
// Make an SVG Container
// Draw the line
@@ -441,7 +487,15 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
type === diagObj.db.LINETYPE.DOTTED_CROSS ||
type === diagObj.db.LINETYPE.DOTTED_POINT ||
type === diagObj.db.LINETYPE.DOTTED_OPEN ||
type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED
type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED ||
type === diagObj.db.LINETYPE.SOLID_TOP_DOTTED ||
type === diagObj.db.LINETYPE.SOLID_BOTTOM_DOTTED ||
type === diagObj.db.LINETYPE.STICK_TOP_DOTTED ||
type === diagObj.db.LINETYPE.STICK_BOTTOM_DOTTED ||
type === diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED
) {
line.style('stroke-dasharray', '3, 3');
line.attr('class', 'messageLine1');
@@ -457,6 +511,51 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
line.attr('stroke-width', 2);
line.attr('stroke', 'none'); // handled by theme/css anyway
line.style('fill', 'none'); // remove any fill colour
if (type === diagObj.db.LINETYPE.SOLID_TOP || type === diagObj.db.LINETYPE.SOLID_TOP_DOTTED) {
line.attr('marker-end', 'url(' + url + '#solidTopArrowHead)');
}
if (
type === diagObj.db.LINETYPE.SOLID_BOTTOM ||
type === diagObj.db.LINETYPE.SOLID_BOTTOM_DOTTED
) {
line.attr('marker-end', 'url(' + url + '#solidBottomArrowHead)');
}
if (type === diagObj.db.LINETYPE.STICK_TOP || type === diagObj.db.LINETYPE.STICK_TOP_DOTTED) {
line.attr('marker-end', 'url(' + url + '#stickTopArrowHead)');
}
if (
type === diagObj.db.LINETYPE.STICK_BOTTOM ||
type === diagObj.db.LINETYPE.STICK_BOTTOM_DOTTED
) {
line.attr('marker-end', 'url(' + url + '#stickBottomArrowHead)');
}
if (
type === diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE ||
type === diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED
) {
line.attr('marker-start', 'url(' + url + '#solidBottomArrowHead)');
}
if (
type === diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE ||
type === diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED
) {
line.attr('marker-start', 'url(' + url + '#solidTopArrowHead)');
}
if (
type === diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE ||
type === diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED
) {
line.attr('marker-start', 'url(' + url + '#stickBottomArrowHead)');
}
if (
type === diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE ||
type === diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED
) {
line.attr('marker-start', 'url(' + url + '#stickTopArrowHead)');
}
if (type === diagObj.db.LINETYPE.SOLID || type === diagObj.db.LINETYPE.DOTTED) {
line.attr('marker-end', 'url(' + url + '#arrowhead)');
}
@@ -481,7 +580,18 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
type === diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID ||
type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED;
if (isBidirectional) {
const isReverseArrowType =
type === diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE ||
type === diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE ||
type === diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE ||
type === diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED ||
type === diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE ||
type === diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED;
let x = 0;
if (isBidirectional || isReverseArrowType) {
const SEQUENCE_NUMBER_RADIUS = 6;
if (startx < stopx) {
@@ -489,6 +599,7 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
} else {
line.attr('x1', startx + SEQUENCE_NUMBER_RADIUS);
}
x = 3.5;
}
diagram
@@ -498,7 +609,8 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
.attr('x2', startx)
.attr('y2', lineStartY)
.attr('stroke-width', 0)
.attr('marker-start', 'url(' + url + '#sequencenumber)');
.attr('marker-start', 'url(' + url + '#sequencenumber)')
.attr('transform', `translate(-${x}, 0)`);
diagram
.append('text')
@@ -508,7 +620,8 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
.attr('font-size', '12px')
.attr('text-anchor', 'middle')
.attr('class', 'sequenceNumber')
.text(sequenceIndex);
.text(sequenceIndex)
.attr('transform', `translate(-${x}, 0)`);
}
};
@@ -857,6 +970,10 @@ export const draw = async function (_text: string, id: string, _version: string,
svgDraw.insertArrowCrossHead(diagram);
svgDraw.insertArrowFilledHead(diagram);
svgDraw.insertSequenceNumber(diagram);
svgDraw.insertSolidTopArrowHead(diagram);
svgDraw.insertSolidBottomArrowHead(diagram);
svgDraw.insertStickTopArrowHead(diagram);
svgDraw.insertStickBottomArrowHead(diagram);
/**
* @param msg - The message to draw.
@@ -897,6 +1014,12 @@ export const draw = async function (_text: string, id: string, _version: string,
case diagObj.db.LINETYPE.ACTIVE_START:
bounds.newActivation(msg, diagram, actors);
break;
case diagObj.db.LINETYPE.CENTRAL_CONNECTION:
bounds.newActivation(msg, diagram, actors);
break;
case diagObj.db.LINETYPE.CENTRAL_CONNECTION_REVERSE:
bounds.newActivation(msg, diagram, actors);
break;
case diagObj.db.LINETYPE.ACTIVE_END:
activeEnd(msg, bounds.getVerticalPos());
break;
@@ -1055,7 +1178,7 @@ export const draw = async function (_text: string, id: string, _version: string,
createdActors,
destroyedActors
);
messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY });
messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY, msg });
bounds.models.addMessage(msgModel);
} catch (e) {
log.error('error while drawing message', e);
@@ -1068,6 +1191,27 @@ export const draw = async function (_text: string, id: string, _version: string,
diagObj.db.LINETYPE.SOLID_OPEN,
diagObj.db.LINETYPE.DOTTED_OPEN,
diagObj.db.LINETYPE.SOLID,
diagObj.db.LINETYPE.SOLID_TOP,
diagObj.db.LINETYPE.SOLID_BOTTOM,
diagObj.db.LINETYPE.STICK_TOP,
diagObj.db.LINETYPE.STICK_BOTTOM,
diagObj.db.LINETYPE.SOLID_TOP_DOTTED,
diagObj.db.LINETYPE.SOLID_BOTTOM_DOTTED,
diagObj.db.LINETYPE.STICK_TOP_DOTTED,
diagObj.db.LINETYPE.STICK_BOTTOM_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.DOTTED,
diagObj.db.LINETYPE.SOLID_CROSS,
diagObj.db.LINETYPE.DOTTED_CROSS,
@@ -1087,7 +1231,7 @@ export const draw = async function (_text: string, id: string, _version: string,
await drawActors(diagram, actors, actorKeys, false);
for (const e of messagesToDraw) {
await drawMessage(diagram, e.messageModel, e.lineStartY, diagObj);
await drawMessage(diagram, e.messageModel, e.lineStartY, diagObj, e.msg);
}
if (conf.mirrorActors) {
await drawActors(diagram, actors, actorKeys, true);
@@ -1461,12 +1605,78 @@ const buildNoteModel = async function (msg, actors, diagObj) {
return noteModel;
};
// Central connection positioning constants
const CENTRAL_CONNECTION_BASE_OFFSET = 4;
const CENTRAL_CONNECTION_BIDIRECTIONAL_OFFSET = 6;
/**
* Check if a message has central connection
* @param msg - The message object
* @param diagObj - The diagram object containing LINETYPE constants
* @returns True if the message has any type of central connection
*/
const hasCentralConnection = function (msg, diagObj) {
const { CENTRAL_CONNECTION, CENTRAL_CONNECTION_REVERSE, CENTRAL_CONNECTION_DUAL } =
diagObj.db.LINETYPE;
return [CENTRAL_CONNECTION, CENTRAL_CONNECTION_REVERSE, CENTRAL_CONNECTION_DUAL].includes(
msg.centralConnection
);
};
/**
* Calculate the positioning offset for central connection arrows
* @param msg - The message object
* @param diagObj - The diagram object containing LINETYPE constants
* @param isArrowToRight - Whether the arrow is pointing to the right
* @returns The offset to apply to startx position
*/
const calculateCentralConnectionOffset = function (msg, diagObj, isArrowToRight) {
const { CENTRAL_CONNECTION_REVERSE, CENTRAL_CONNECTION_DUAL, BIDIRECTIONAL_SOLID } =
diagObj.db.LINETYPE;
let offset = 0;
if (
msg.centralConnection === CENTRAL_CONNECTION_REVERSE ||
msg.centralConnection === CENTRAL_CONNECTION_DUAL
) {
offset += CENTRAL_CONNECTION_BASE_OFFSET;
}
if (msg.centralConnection === CENTRAL_CONNECTION_DUAL && msg.type === BIDIRECTIONAL_SOLID) {
offset += isArrowToRight ? 0 : -CENTRAL_CONNECTION_BIDIRECTIONAL_OFFSET;
}
return offset;
};
const buildMessageModel = function (msg, actors, diagObj) {
if (
![
diagObj.db.LINETYPE.SOLID_OPEN,
diagObj.db.LINETYPE.DOTTED_OPEN,
diagObj.db.LINETYPE.SOLID,
diagObj.db.LINETYPE.SOLID_TOP,
diagObj.db.LINETYPE.SOLID_BOTTOM,
diagObj.db.LINETYPE.STICK_TOP,
diagObj.db.LINETYPE.STICK_BOTTOM,
diagObj.db.LINETYPE.SOLID_TOP_DOTTED,
diagObj.db.LINETYPE.SOLID_BOTTOM_DOTTED,
diagObj.db.LINETYPE.STICK_TOP_DOTTED,
diagObj.db.LINETYPE.STICK_BOTTOM_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.DOTTED,
diagObj.db.LINETYPE.SOLID_CROSS,
diagObj.db.LINETYPE.DOTTED_CROSS,
@@ -1484,6 +1694,8 @@ const buildMessageModel = function (msg, actors, diagObj) {
let startx = isArrowToRight ? fromRight : fromLeft;
let stopx = isArrowToRight ? toLeft : toRight;
// Apply central connection positioning adjustments
startx += calculateCentralConnectionOffset(msg, diagObj, isArrowToRight);
// As the line width is considered, the left and right values will be off by 2.
const isArrowToActivation = Math.abs(toLeft - toRight) > 2;
@@ -1517,7 +1729,30 @@ const buildMessageModel = function (msg, actors, diagObj) {
* Shorten the length of arrow at the end and move the marker forward (using refX) to have a clean arrowhead
* This is not required for open arrows that don't have arrowheads
*/
if (![diagObj.db.LINETYPE.SOLID_OPEN, diagObj.db.LINETYPE.DOTTED_OPEN].includes(msg.type)) {
if (
![
diagObj.db.LINETYPE.SOLID_OPEN,
diagObj.db.LINETYPE.DOTTED_OPEN,
diagObj.db.LINETYPE.STICK_TOP,
diagObj.db.LINETYPE.STICK_BOTTOM,
diagObj.db.LINETYPE.STICK_TOP_DOTTED,
diagObj.db.LINETYPE.STICK_BOTTOM_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE,
diagObj.db.LINETYPE.STICK_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.STICK_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE,
].includes(msg.type)
) {
stopx += adjustValue(3);
}
@@ -1525,9 +1760,14 @@ const buildMessageModel = function (msg, actors, diagObj) {
* 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
)
[
diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID,
diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE_DOTTED,
diagObj.db.LINETYPE.SOLID_ARROW_TOP_REVERSE,
diagObj.db.LINETYPE.SOLID_ARROW_BOTTOM_REVERSE,
].includes(msg.type)
) {
startx -= adjustValue(3);
}

View File

@@ -1709,6 +1709,77 @@ const _drawMenuItemTextCandidateFunc = (function () {
};
})();
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*
* @param elem
*/
export const insertSolidTopArrowHead = function (elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'solidTopArrowHead')
.attr('refX', 7.9)
.attr('refY', 7.25)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', 'M 0 0 L 10 8 L 0 8 z'); // this is actual shape for arrowhead
};
export const insertSolidBottomArrowHead = function (elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'solidBottomArrowHead')
.attr('refX', 7.9)
.attr('refY', 0.75)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', 'M 0 0 L 10 0 L 0 8 z');
};
export const insertStickTopArrowHead = function (elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'stickTopArrowHead')
.attr('refX', 7.5)
.attr('refY', 7)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', 'M 0 0 L 7 7')
.attr('stroke', 'black')
.attr('stroke-width', 1.5)
.attr('fill', 'none');
};
export const insertStickBottomArrowHead = function (elem) {
elem
.append('defs')
.append('marker')
.attr('id', 'stickBottomArrowHead')
.attr('refX', 7.5)
.attr('refY', 0)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto-start-reverse')
.append('path')
.attr('d', 'M 0 7 L 7 0')
.attr('stroke', 'black')
.attr('stroke-width', 1.5)
.attr('fill', 'none');
};
export default {
drawRect,
drawText,
@@ -1731,4 +1802,8 @@ export default {
getNoteRect,
fixLifeLineHeights,
sanitizeUrl,
insertSolidTopArrowHead,
insertSolidBottomArrowHead,
insertStickTopArrowHead,
insertStickBottomArrowHead,
};

View File

@@ -35,6 +35,7 @@ export interface Message {
type?: number;
activate?: boolean;
placement?: string;
centralConnection?: number;
}
export interface AddMessageParams {
@@ -50,6 +51,8 @@ export interface AddMessageParams {
| 'destroyParticipant'
| 'activeStart'
| 'activeEnd'
| 'centralConnection'
| 'centralConnectionReverse'
| 'addNote'
| 'addLinks'
| 'addALink'

View File

@@ -216,7 +216,11 @@ Messages can be of two displayed either solid or with a dotted line.
[Actor][Arrow][Actor]:Message text
```
There are ten types of arrows currently supported:
Lines can be solid or dotted, and can end with various types of arrowheads, crosses, or open arrows.
#### Supported Arrow Types
**Standard Arrow Types**
| Type | Description |
| -------- | ---------------------------------------------------- |
@@ -231,6 +235,49 @@ There are ten types of arrows currently supported:
| `-)` | Solid line with an open arrow at the end (async) |
| `--)` | Dotted line with a open arrow at the end (async) |
**Half-Arrows (v<MERMAID_RELEASE_VERSION>+)**
The following half-arrow types are supported for more expressive sequence diagrams. Both solid and dotted variants are available by increasing the number of dashes (`-` → `--`).
---
| Type | Description |
| ------- | ---------------------------------------------------- |
| `-\|\` | Solid line with top half arrowhead |
| `--\|\` | Dotted line with top half arrowhead |
| `-\|/` | Solid line with bottom half arrowhead |
| `--\|/` | Dotted line with bottom half arrowhead |
| `/\|-` | Solid line with reverse top half arrowhead |
| `/\|--` | Dotted line with reverse top half arrowhead |
| `\\-` | Solid line with reverse bottom half arrowhead |
| `\\--` | Dotted line with reverse bottom half arrowhead |
| `-\\` | Solid line with top stick half arrowhead |
| `--\\` | Dotted line with top stick half arrowhead |
| `-//` | Solid line with bottom stick half arrowhead |
| `--//` | Dotted line with bottom stick half arrowhead |
| `//-` | Solid line with reverse top stick half arrowhead |
| `//--` | Dotted line with reverse top stick half arrowhead |
| `\\-` | Solid line with reverse bottom stick half arrowhead |
| `\\--` | Dotted line with reverse bottom stick half arrowhead |
## Central Connections (v<MERMAID_RELEASE_VERSION>+)
Mermaid sequence diagrams support **central lifeline connections** using a `()`.
This is useful to represent messages or signals that connect to a central point, rather than from one actor directly to another.
To indicate a central connection, append `()` to the arrow syntax.
#### Basic Syntax
```mermaid-example
sequenceDiagram
participant Alice
participant John
Alice->>()John: Hello John
Alice()->>John: How are you?
John()->>()Alice: Great!
```
## Activations
It is possible to activate and deactivate an actor. (de)activation can be dedicated declarations:

View File

@@ -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', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT']
Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT']
`);
});