diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js
index 185cc4133..7d36c1ff1 100644
--- a/cypress/integration/rendering/sequencediagram.spec.js
+++ b/cypress/integration/rendering/sequencediagram.spec.js
@@ -156,6 +156,81 @@ context('Sequence diagram', () => {
`
);
});
+ it('should render a sequence diagram with basic actor creation and destruction', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ Alice ->> Bob: Hello Bob, how are you ?
+ Bob ->> Alice: Fine, thank you. And you?
+ create participant Polo
+ Alice ->> Polo: Hi Polo!
+ create actor Ola1 as Ola
+ Polo ->> Ola1: Hiii
+ Ola1 ->> Alice: Hi too
+ destroy Ola1
+ Alice --x Ola1: Bye!
+ Alice ->> Bob: And now?
+ create participant Ola2 as Ola
+ Alice ->> Ola2: Hello again
+ destroy Alice
+ Alice --x Ola2: Bye for me!
+ destroy Bob
+ Ola2 --> Bob: The end
+ `
+ );
+ });
+ it('should render a sequence diagram with actor creation and destruction coupled with backgrounds, loops and notes', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ accTitle: test the accTitle
+ accDescr: Test a description
+
+ participant Alice
+ participant Bob
+ autonumber 10 10
+ rect rgb(200, 220, 100)
+ rect rgb(200, 255, 200)
+
+ Alice ->> Bob: Hello Bob, how are you?
+ create participant John as John
Second Line
+ Bob-->>John: How about you John?
+ end
+
+ Bob--x Alice: I am good thanks!
+ Bob-x John: I am good thanks!
+ Note right of John: John thinks a long
long time, so long
that the text does
not fit on a row.
+
+ Bob-->Alice: Checking with John...
+ Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
+ Bob-x John: Hey John - we're still waiting to know
how you're doing
+ Note over John:nowrap: John's trying hard not to break his train of thought.
+ destroy John
+ Bob-x John: John! Cmon!
+ Note over John: After a few more moments, John
finally snaps out of it.
+ end
+
+ autonumber off
+ alt either this
+ create actor Lola
+ Alice->>+Lola: Yes
+ Lola-->>-Alice: OK
+ else or this
+ autonumber
+ Alice->>Lola: No
+ else or this will happen
+ Alice->Lola: Maybe
+ end
+ autonumber 200
+ par this happens in parallel
+ destroy Bob
+ Alice -->> Bob: Parallel message 1
+ and
+ Alice -->> Lola: Parallel message 2
+ end
+ `
+ );
+ });
context('font settings', () => {
it('should render different note fonts when configured', () => {
imgSnapshotTest(
diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
index 074cd5975..04f0de20e 100644
--- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
+++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
@@ -38,6 +38,8 @@
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"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'; }
"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
@@ -138,6 +140,7 @@ directive
statement
: participant_statement
+ | 'create' participant_statement {$2.type='createParticipant'; $$=$2;}
| 'box' restOfLine box_section end
{
$3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
@@ -234,10 +237,11 @@ else_sections
;
participant_statement
- : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
- | 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
- | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
- | 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
+ : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
+ | 'participant' actor 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant';$$=$2;}
+ | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
+ | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
+ | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
;
note_statement
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js
index 89869b64f..b5d68fb9f 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js
@@ -14,12 +14,16 @@ import {
let prevActor = undefined;
let actors = {};
+let createdActors = {};
+let destroyedActors = {};
let boxes = [];
let messages = [];
const notes = [];
let sequenceNumbersEnabled = false;
let wrapEnabled;
let currentBox = undefined;
+let lastCreated = undefined;
+let lastDestroyed = undefined;
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
@@ -165,6 +169,12 @@ export const getBoxes = function () {
export const getActors = function () {
return actors;
};
+export const getCreatedActors = function () {
+ return createdActors;
+};
+export const getDestroyedActors = function () {
+ return destroyedActors;
+};
export const getActor = function (id) {
return actors[id];
};
@@ -194,6 +204,8 @@ export const autoWrap = () => {
export const clear = function () {
actors = {};
+ createdActors = {};
+ destroyedActors = {};
boxes = [];
messages = [];
sequenceNumbersEnabled = false;
@@ -459,10 +471,21 @@ export const apply = function (param) {
});
break;
case 'addParticipant':
- addActor(param.actor, param.actor, param.description, 'participant');
+ addActor(param.actor, param.actor, param.description, param.draw);
break;
- case 'addActor':
- addActor(param.actor, param.actor, param.description, 'actor');
+ case 'createParticipant':
+ if (actors[param.actor]) {
+ throw new Error(
+ "It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
+ );
+ }
+ lastCreated = param.actor;
+ addActor(param.actor, param.actor, param.description, param.draw);
+ createdActors[param.actor] = messages.length;
+ break;
+ case 'destroyParticipant':
+ lastDestroyed = param.actor;
+ destroyedActors[param.actor] = messages.length;
break;
case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType);
@@ -486,6 +509,27 @@ export const apply = function (param) {
addDetails(param.actor, param.text);
break;
case 'addMessage':
+ if (lastCreated) {
+ if (param.to !== lastCreated) {
+ throw new Error(
+ 'The created participant ' +
+ lastCreated +
+ ' does not have an associated creating message after its declaration. Please check the sequence diagram.'
+ );
+ } else {
+ lastCreated = undefined;
+ }
+ } else if (lastDestroyed) {
+ if (param.to !== lastDestroyed && param.from !== lastDestroyed) {
+ throw new Error(
+ 'The destroyed participant ' +
+ lastDestroyed +
+ ' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
+ );
+ } else {
+ lastDestroyed = undefined;
+ }
+ }
addSignal(param.from, param.to, param.msg, param.signalType);
break;
case 'boxStart':
@@ -566,6 +610,8 @@ export default {
showSequenceNumbers,
getMessages,
getActors,
+ getCreatedActors,
+ getDestroyedActors,
getActor,
getActorKeys,
getActorProperty,
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
index baa5d2fcf..8b7d6f8d0 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
@@ -1404,6 +1404,62 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
expect(boxes[0].fill).toEqual('Aqua');
});
+
+ it('should handle simple actor creation', async () => {
+ const str = `
+ sequenceDiagram
+ participant a as Alice
+ a ->>b: Hello Bob?
+ create participant c
+ b-->>c: Hello c!
+ c ->> b: Hello b?
+ create actor d as Donald
+ a ->> d: Hello Donald?
+ `;
+ await mermaidAPI.parse(str);
+ const actors = diagram.db.getActors();
+ const createdActors = diagram.db.getCreatedActors();
+ expect(actors['c'].name).toEqual('c');
+ expect(actors['c'].description).toEqual('c');
+ expect(actors['c'].type).toEqual('participant');
+ expect(createdActors['c']).toEqual(1);
+ expect(actors['d'].name).toEqual('d');
+ expect(actors['d'].description).toEqual('Donald');
+ expect(actors['d'].type).toEqual('actor');
+ expect(createdActors['d']).toEqual(3);
+ });
+ it('should handle simple actor destruction', async () => {
+ const str = `
+ sequenceDiagram
+ participant a as Alice
+ a ->>b: Hello Bob?
+ destroy a
+ b-->>a: Hello Alice!
+ b ->> c: Where is Alice?
+ destroy c
+ b ->> c: Where are you?
+ `;
+ await mermaidAPI.parse(str);
+ const destroyedActors = diagram.db.getDestroyedActors();
+ expect(destroyedActors['a']).toEqual(1);
+ expect(destroyedActors['c']).toEqual(3);
+ });
+ it('should handle the creation and destruction of the same actor', async () => {
+ const str = `
+ sequenceDiagram
+ a ->>b: Hello Bob?
+ create participant c
+ b ->>c: Hello c!
+ c ->> b: Hello b?
+ destroy c
+ b ->> c : Bye c !
+ `;
+ await mermaidAPI.parse(str);
+ const createdActors = diagram.db.getCreatedActors();
+ const destroyedActors = diagram.db.getDestroyedActors();
+ expect(createdActors['c']).toEqual(1);
+ expect(destroyedActors['c']).toEqual(3);
+ });
});
describe('when checking the bounds in a sequenceDiagram', function () {
beforeAll(() => {
@@ -1973,7 +2029,9 @@ participant Alice`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width);
- expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin);
+ expect(bounds.stopy).toBe(
+ models.lastActor().stopy + models.lastActor().height + conf.boxMargin
+ );
});
});
});
@@ -2025,7 +2083,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(
- models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
+ models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
);
});
it('should handle one actor, when logLevel is 3 (dfg0)', async () => {
@@ -2045,7 +2103,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(
- models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin
+ models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
);
});
it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => {
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
index 16a3d21fc..4f8b1889b 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
+++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
@@ -1,6 +1,6 @@
// @ts-nocheck TODO: fix file
import { select, selectAll } from 'd3';
-import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
+import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
import { log } from '../../logger.js';
import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon';
@@ -478,29 +478,19 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
}
};
-export const drawActors = function (
+const addActorRenderingData = function (
diagram,
actors,
+ createdActors,
actorKeys,
verticalPos,
- configuration,
messages,
isFooter
) {
- if (configuration.hideUnusedParticipants === true) {
- const newActors = new Set();
- messages.forEach((message) => {
- newActors.add(message.from);
- newActors.add(message.to);
- });
- actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey));
- }
-
- // Draw the actors
let prevWidth = 0;
let prevMargin = 0;
- let maxHeight = 0;
let prevBox = undefined;
+ let maxHeight = 0;
for (const actorKey of actorKeys) {
const actor = actors[actorKey];
@@ -528,12 +518,16 @@ export const drawActors = function (
actor.height = common.getMax(actor.height || conf.height, conf.height);
actor.margin = actor.margin || conf.actorMargin;
- actor.x = prevWidth + prevMargin;
- actor.y = bounds.getVerticalPos();
+ maxHeight = common.getMax(maxHeight, actor.height);
+
+ // if the actor is created by a message, widen margin
+ if (createdActors[actor.name]) {
+ prevMargin += actor.width / 2;
+ }
+
+ actor.x = prevWidth + prevMargin;
+ actor.starty = bounds.getVerticalPos();
- // Draw the box with the attached line
- const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
- maxHeight = common.getMax(maxHeight, height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width + prevMargin;
@@ -554,6 +548,28 @@ export const drawActors = function (
bounds.bumpVerticalPos(maxHeight);
};
+export const drawActors = function (diagram, actors, actorKeys, isFooter) {
+ if (!isFooter) {
+ for (const actorKey of actorKeys) {
+ const actor = actors[actorKey];
+ // Draw the box with the attached line
+ svgDraw.drawActor(diagram, actor, conf, false);
+ }
+ } else {
+ let maxHeight = 0;
+ bounds.bumpVerticalPos(conf.boxMargin * 2);
+ for (const actorKey of actorKeys) {
+ const actor = actors[actorKey];
+ if (!actor.stopy) {
+ actor.stopy = bounds.getVerticalPos();
+ }
+ const height = svgDraw.drawActor(diagram, actor, conf, true);
+ maxHeight = common.getMax(maxHeight, height);
+ }
+ bounds.bumpVerticalPos(maxHeight + conf.boxMargin);
+ }
+};
+
export const drawActorsPopup = function (diagram, actors, actorKeys, doc) {
let maxHeight = 0;
let maxWidth = 0;
@@ -633,6 +649,95 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
bounds.bumpVerticalPos(heightAdjust);
}
+/**
+ * Adjust the msgModel and the actor for the rendering in case the latter is created or destroyed by the msg
+ * @param msg - the potentially creating or destroying message
+ * @param msgModel - the model associated with the message
+ * @param lineStartY - the y position of the message line
+ * @param index - the index of the current actor under consideration
+ * @param actors - the array of all actors
+ * @param createdActors - the array of actors created in the diagram
+ * @param destroyedActors - the array of actors destroyed in the diagram
+ */
+function adjustCreatedDestroyedData(
+ msg,
+ msgModel,
+ lineStartY,
+ index,
+ actors,
+ createdActors,
+ destroyedActors
+) {
+ function receiverAdjustment(actor, adjustment) {
+ if (actor.x < actors[msg.from].x) {
+ bounds.insert(
+ msgModel.stopx - adjustment,
+ msgModel.starty,
+ msgModel.startx,
+ msgModel.stopy + actor.height / 2 + conf.noteMargin
+ );
+ msgModel.stopx = msgModel.stopx + adjustment;
+ } else {
+ bounds.insert(
+ msgModel.startx,
+ msgModel.starty,
+ msgModel.stopx + adjustment,
+ msgModel.stopy + actor.height / 2 + conf.noteMargin
+ );
+ msgModel.stopx = msgModel.stopx - adjustment;
+ }
+ }
+
+ function senderAdjustment(actor, adjustment) {
+ if (actor.x < actors[msg.to].x) {
+ bounds.insert(
+ msgModel.startx - adjustment,
+ msgModel.starty,
+ msgModel.stopx,
+ msgModel.stopy + actor.height / 2 + conf.noteMargin
+ );
+ msgModel.startx = msgModel.startx + adjustment;
+ } else {
+ bounds.insert(
+ msgModel.stopx,
+ msgModel.starty,
+ msgModel.startx + adjustment,
+ msgModel.stopy + actor.height / 2 + conf.noteMargin
+ );
+ msgModel.startx = msgModel.startx - adjustment;
+ }
+ }
+
+ // if it is a create message
+ if (createdActors[msg.to] == index) {
+ const actor = actors[msg.to];
+ const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
+ receiverAdjustment(actor, adjustment);
+ actor.starty = lineStartY - actor.height / 2;
+ bounds.bumpVerticalPos(actor.height / 2);
+ }
+ // if it is a destroy sender message
+ else if (destroyedActors[msg.from] == index) {
+ const actor = actors[msg.from];
+ if (conf.mirrorActors) {
+ const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
+ senderAdjustment(actor, adjustment);
+ }
+ actor.stopy = lineStartY - actor.height / 2;
+ bounds.bumpVerticalPos(actor.height / 2);
+ }
+ // if it is a destroy receiver message
+ else if (destroyedActors[msg.to] == index) {
+ const actor = actors[msg.to];
+ if (conf.mirrorActors) {
+ const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
+ receiverAdjustment(actor, adjustment);
+ }
+ actor.stopy = lineStartY - actor.height / 2;
+ bounds.bumpVerticalPos(actor.height / 2);
+ }
+}
+
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
@@ -666,8 +771,10 @@ export const draw = function (_text: string, id: string, _version: string, diagO
// Fetch data from the parsing
const actors = diagObj.db.getActors();
+ const createdActors = diagObj.db.getCreatedActors();
+ const destroyedActors = diagObj.db.getDestroyedActors();
const boxes = diagObj.db.getBoxes();
- const actorKeys = diagObj.db.getActorKeys();
+ let actorKeys = diagObj.db.getActorKeys();
const messages = diagObj.db.getMessages();
const title = diagObj.db.getDiagramTitle();
const hasBoxes = diagObj.db.hasAtLeastOneBox();
@@ -686,7 +793,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO
}
}
- drawActors(diagram, actors, actorKeys, 0, conf, messages, false);
+ if (conf.hideUnusedParticipants === true) {
+ const newActors = new Set();
+ messages.forEach((message) => {
+ newActors.add(message.from);
+ newActors.add(message.to);
+ });
+ actorKeys = actorKeys.filter((actorKey) => newActors.has(actorKey));
+ }
+
+ addActorRenderingData(diagram, actors, createdActors, actorKeys, 0, messages, false);
const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
// The arrow head definition is attached to the svg once
@@ -720,7 +836,8 @@ export const draw = function (_text: string, id: string, _version: string, diagO
let sequenceIndex = 1;
let sequenceIndexStep = 1;
const messagesToDraw = [];
- messages.forEach(function (msg) {
+ const backgrounds = [];
+ messages.forEach(function (msg, index) {
let loopModel, noteModel, msgModel;
switch (msg.type) {
@@ -757,7 +874,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
case diagObj.db.LINETYPE.RECT_END:
loopModel = bounds.endLoop();
- svgDraw.drawBackgroundRect(diagram, loopModel);
+ backgrounds.push(loopModel);
bounds.models.addLoop(loopModel);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
break;
@@ -876,13 +993,20 @@ export const draw = function (_text: string, id: string, _version: string, diagO
break;
default:
try {
- // lastMsg = msg
- bounds.resetVerticalPos();
msgModel = msg.msgModel;
msgModel.starty = bounds.getVerticalPos();
msgModel.sequenceIndex = sequenceIndex;
msgModel.sequenceVisible = diagObj.db.showSequenceNumbers();
const lineStartY = boundMessage(diagram, msgModel);
+ adjustCreatedDestroyedData(
+ msg,
+ msgModel,
+ lineStartY,
+ index,
+ actors,
+ createdActors,
+ destroyedActors
+ );
messagesToDraw.push({ messageModel: msgModel, lineStartY: lineStartY });
bounds.models.addMessage(msgModel);
} catch (e) {
@@ -907,15 +1031,16 @@ export const draw = function (_text: string, id: string, _version: string, diagO
}
});
- messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
+ log.debug('createdActors', createdActors);
+ log.debug('destroyedActors', destroyedActors);
+ drawActors(diagram, actors, actorKeys, false);
+ messagesToDraw.forEach((e) => drawMessage(diagram, e.messageModel, e.lineStartY, diagObj));
if (conf.mirrorActors) {
- // Draw actors below diagram
- bounds.bumpVerticalPos(conf.boxMargin * 2);
- drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true);
- bounds.bumpVerticalPos(conf.boxMargin);
- fixLifeLineHeights(diagram, bounds.getVerticalPos());
+ drawActors(diagram, actors, actorKeys, true);
}
+ backgrounds.forEach((e) => svgDraw.drawBackgroundRect(diagram, e));
+ fixLifeLineHeights(diagram, actors, actorKeys, conf);
bounds.models.boxes.forEach(function (box) {
box.height = bounds.getVerticalPos() - box.y;
@@ -937,11 +1062,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO
const { bounds: box } = bounds.getBounds();
- // Adjust line height of actor lines now that the height of the diagram is known
- log.debug('For line height fix Querying: #' + id + ' .actor-line');
- const actorLines = selectAll('#' + id + ' .actor-line');
- actorLines.attr('y2', box.stopy);
-
// Make sure the height of the diagram supports long menus.
let boxHeight = box.stopy - box.starty;
if (boxHeight < requiredBoxSize.maxHeight) {
diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js
index a3f7514f6..0c7bc6421 100644
--- a/packages/mermaid/src/diagrams/sequence/svgDraw.js
+++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js
@@ -4,6 +4,8 @@ import { addFunction } from '../../interactionDb.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
+export const ACTOR_TYPE_WIDTH = 18 * 2;
+
export const drawRect = function (elem, rectData) {
return svgDrawCommon.drawRect(elem, rectData);
};
@@ -294,14 +296,19 @@ export const drawLabel = function (elem, txtObject) {
let actorCnt = -1;
-export const fixLifeLineHeights = (diagram, bounds) => {
- if (!diagram.selectAll) {
+export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => {
+ if (!diagram.select) {
return;
}
- diagram
- .selectAll('.actor-line')
- .attr('class', '200')
- .attr('y2', bounds - 55);
+ actorKeys.forEach((actorKey) => {
+ const actor = actors[actorKey];
+ const actorDOM = diagram.select('#actor' + actor.actorCnt);
+ if (!conf.mirrorActors && actor.stopy) {
+ actorDOM.attr('y2', actor.stopy + actor.height / 2);
+ } else if (conf.mirrorActors) {
+ actorDOM.attr('y2', actor.stopy);
+ }
+ });
};
/**
@@ -313,10 +320,11 @@ export const fixLifeLineHeights = (diagram, bounds) => {
* @param {boolean} isFooter - If the actor is the footer one
*/
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
- const centerY = actor.y + 5;
+ const centerY = actorY + 5;
- const boxpluslineGroup = elem.append('g');
+ const boxpluslineGroup = elem.append('g').lower();
var g = boxpluslineGroup;
if (!isFooter) {
@@ -328,6 +336,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line')
+ .attr('class', '200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999');
@@ -348,7 +357,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
rect.fill = '#eaeaea';
}
rect.x = actor.x;
- rect.y = actor.y;
+ rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
@@ -388,8 +397,11 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
};
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
- const centerY = actor.y + 80;
+ const centerY = actorY + 80;
+
+ elem.lower();
if (!isFooter) {
actorCnt++;
@@ -401,15 +413,18 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line')
+ .attr('class', '200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999');
+
+ actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
actElem.attr('class', 'actor-man');
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
- rect.y = actor.y;
+ rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
@@ -421,33 +436,33 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
.append('line')
.attr('id', 'actor-man-torso' + actorCnt)
.attr('x1', center)
- .attr('y1', actor.y + 25)
+ .attr('y1', actorY + 25)
.attr('x2', center)
- .attr('y2', actor.y + 45);
+ .attr('y2', actorY + 45);
actElem
.append('line')
.attr('id', 'actor-man-arms' + actorCnt)
- .attr('x1', center - 18)
- .attr('y1', actor.y + 33)
- .attr('x2', center + 18)
- .attr('y2', actor.y + 33);
+ .attr('x1', center - ACTOR_TYPE_WIDTH / 2)
+ .attr('y1', actorY + 33)
+ .attr('x2', center + ACTOR_TYPE_WIDTH / 2)
+ .attr('y2', actorY + 33);
actElem
.append('line')
- .attr('x1', center - 18)
- .attr('y1', actor.y + 60)
+ .attr('x1', center - ACTOR_TYPE_WIDTH / 2)
+ .attr('y1', actorY + 60)
.attr('x2', center)
- .attr('y2', actor.y + 45);
+ .attr('y2', actorY + 45);
actElem
.append('line')
.attr('x1', center)
- .attr('y1', actor.y + 45)
- .attr('x2', center + 16)
- .attr('y2', actor.y + 60);
+ .attr('y1', actorY + 45)
+ .attr('x2', center + ACTOR_TYPE_WIDTH / 2 - 2)
+ .attr('y2', actorY + 60);
const circle = actElem.append('circle');
circle.attr('cx', actor.x + actor.width / 2);
- circle.attr('cy', actor.y + 10);
+ circle.attr('cy', actorY + 10);
circle.attr('r', 15);
circle.attr('width', actor.width);
circle.attr('height', actor.height);