Compare commits

..

54 Commits

Author SHA1 Message Date
Shubham P
ea8260aa51 Merge branch 'develop' into 4743-timeline-html-formatting 2025-08-26 12:12:59 +05:30
Sidharth Vinod
6d9fad01a9 Merge pull request #6887 from mermaid-js/docs/flowchart-image-node-doc-fix
docs(flowchart): fix image node documentation
2025-08-25 11:09:44 +00:00
omkarht
075e1b5e1f Merge branch 'develop' into docs/flowchart-image-node-doc-fix 2025-08-25 15:47:23 +05:30
omkarht
3c9bd7be29 Merge branch 'develop' into docs/flowchart-image-node-doc-fix 2025-08-25 12:58:52 +05:30
Shubham P
6995248443 Merge pull request #6704 from mermaid-js/6637-add-new-participant-types-to-sequence-diagrams
6637:Add support for new participant types in sequence diagrams
2025-08-25 07:28:44 +00:00
omkarht
93467a6fce fix: removed changeset
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-25 12:58:17 +05:30
omkarht
95d48e3497 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-25 12:42:03 +05:30
omkarht
e438e035bc chore: added changeset 2025-08-21 19:34:27 +05:30
Alois Klink
2bc5b6d2fa docs(flowchart): fix image node documentation 2025-08-21 19:26:54 +05:30
omkarht
2cfebef122 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-21 13:13:41 +05:30
darshanr0107
dabc220ed2 fix: use new frontmatter in test case
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-20 18:12:44 +05:30
omkarht
7bdcf93412 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-19 13:54:32 +05:30
omkarht
d86e46b705 fix: refactored test cases
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-19 13:44:11 +05:30
omkarht
71e09bcaef fix: refactored code and adjusted shape, shape label
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-18 18:21:41 +05:30
omkarht
cba659d097 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-18 12:16:44 +05:30
omkarht
9f6ee53382 fix: refactored parsing logic for new syntax
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-12 14:20:47 +05:30
omkarht
3248bf3da4 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-12 13:01:18 +05:30
omkarht
e7a7ff8a2a fix: refactored code
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-11 13:20:49 +05:30
omkarht
68fc68c239 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-11 13:15:44 +05:30
autofix-ci[bot]
769b362005 [autofix.ci] apply automated fixes 2025-08-08 14:53:47 +00:00
omkarht
e4d3aa4610 fix: refactored documentation, test cases and sequenceDiagram.jison file
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-08 20:18:21 +05:30
omkarht
716548548a fix: rendering test cases
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-08 19:31:56 +05:30
omkarht
4bece53a3c Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-08 16:08:54 +05:30
omkarht
297be4a868 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-08 13:40:38 +05:30
omkarht
fb6ace73b5 docs: updated documentation for new syntax
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-08 13:30:00 +05:30
autofix-ci[bot]
bf362673fc [autofix.ci] apply automated fixes 2025-08-07 15:26:12 +00:00
omkarht
d042b21b12 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-07 20:47:08 +05:30
omkarht
677ff82d13 chore: implemented new syntx
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-07 20:45:23 +05:30
Knut Sveidqvist
981829a426 Updated order of lexer statements in the grammar 2025-08-07 15:21:13 +02:00
omkarht
327a5aa9fd chore: added logs in sequenceDiagram.jison
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-07 18:07:13 +05:30
omkarht
848f69a75c Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-08-06 17:14:45 +05:30
omkarht
99dbeba407 fix: refactored code
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-06 17:10:55 +05:30
omkarht
d525acc05b chore: Add documentation for the syntax of the newly added participant type
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-08-06 17:08:57 +05:30
omkarht
4915545429 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-07-29 16:26:31 +05:30
omkarht
334fe87bc6 fix: fixed failing test cases
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 18:33:58 +05:30
omkarht
283e7810d2 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-07-28 17:26:44 +05:30
omkarht
237d01d510 fix: shifted matchAsActorOrParticipant function to sequenceDB file from sequenceDiagram.jison file
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-28 16:47:54 +05:30
darshanr0107
d2c5cbd408 Merge branch 'develop' into 4743-timeline-html-formatting 2025-07-11 17:36:16 +05:30
darshanr0107
20467bcbe6 resolve visual regression issues and fix broken diagram in PR 2025-07-11 13:53:17 +05:30
omkarht
afeb761296 Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-07-08 13:53:51 +05:30
omkarht
3abcfbb8d2 added unit tests 2025-07-08 13:48:27 +05:30
omkarht
ee82694645 refactor(lexer): added contextual handling for new participant types as actors 2025-07-08 13:47:41 +05:30
omkarht
012530e98e added changeset 2025-07-01 19:02:57 +05:30
omkarht
a4a27611dd fix: minor refinement 2025-07-01 18:11:41 +05:30
omkarht
5055ade44e Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams 2025-07-01 13:29:18 +05:30
omkarht
b61bec8faf added test cases for new participant types of sequence diagram 2025-07-01 12:58:04 +05:30
darshanr0107
dd213fe86c Merge branch 'develop' into 4743-timeline-html-formatting 2025-07-01 12:34:09 +05:30
omkarht
76d073b027 chore: adjusted database and queue shape 2025-06-27 17:56:03 +05:30
omkarht
cc476d59d1 6637-Add new participant types to Sequence Diagrams 2025-06-27 13:42:19 +05:30
darshanr0107
800f23fc01 Merge branch 'develop' into 4743-timeline-html-formatting 2025-06-25 13:09:25 +05:30
darshanr0107
67aa1a4dc1 Merge branch 'develop' into 4743-timeline-html-formatting 2025-06-20 11:50:35 +05:30
darshanr0107
c1bcdcfbad added changeset 2025-06-20 11:49:37 +05:30
darshanr0107
f528e2daa4 fix multicharacter sanitization 2025-06-19 12:59:51 +05:30
darshanr0107
a867842f32 fix HTML formatting in timeline diagrams 2025-06-19 12:05:22 +05:30
49 changed files with 3588 additions and 2204 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.

View File

@@ -0,0 +1,5 @@
---
'mermaid': major
---
Currently, HTML tags such as <em>, <strong>, <sup>, <a>, <ul>, and <li> are supported in Flowchart and Class diagram labels but not in Timeline diagrams. This change introduces support for basic HTML formatting in Timeline labels, enabling richer text formatting and better usability for multi-line content like descriptions, footnotes, and styled annotations

View File

@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21 uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with: with:
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
@@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21 uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -62,4 +62,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21 uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10

View File

@@ -56,7 +56,7 @@ jobs:
args: -X POST "$APPLITOOLS_SERVER_URL/api/externals/github/push?apiKey=$APPLITOOLS_API_KEY&CommitSha=$GITHUB_SHA&BranchName=${APPLITOOLS_BRANCH}$&ParentBranchName=$APPLITOOLS_PARENT_BRANCH" args: -X POST "$APPLITOOLS_SERVER_URL/api/externals/github/push?apiKey=$APPLITOOLS_API_KEY&CommitSha=$GITHUB_SHA&BranchName=${APPLITOOLS_BRANCH}$&ParentBranchName=$APPLITOOLS_PARENT_BRANCH"
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress id: cypress
with: with:
start: pnpm run dev start: pnpm run dev

View File

@@ -27,12 +27,12 @@ jobs:
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
- name: Install dependencies - name: Install dependencies
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with: with:
runTests: false runTests: false
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress id: cypress
with: with:
install: false install: false

View File

@@ -45,7 +45,7 @@ jobs:
node-version-file: '.node-version' node-version-file: '.node-version'
- name: Cache snapshots - name: Cache snapshots
id: cache-snapshot id: cache-snapshot
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ./cypress/snapshots path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }} key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
@@ -59,7 +59,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with: with:
# just perform install # just perform install
runTests: false runTests: false
@@ -95,13 +95,13 @@ jobs:
# These cached snapshots are downloaded, providing the reference snapshots. # These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots - name: Cache snapshots
id: cache-snapshot id: cache-snapshot
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: ./cypress/snapshots path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }} key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies - name: Install dependencies
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with: with:
runTests: false runTests: false
@@ -117,7 +117,7 @@ jobs:
# Install NPM dependencies, cache them correctly # Install NPM dependencies, cache them correctly
# and run all Cypress tests # and run all Cypress tests
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16 uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress id: cypress
with: with:
install: false install: false

View File

@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Restore lychee cache - name: Restore lychee cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with: with:
path: .lycheecache path: .lycheecache
key: cache-lychee-${{ github.sha }} key: cache-lychee-${{ github.sha }}

View File

@@ -36,7 +36,7 @@ jobs:
- name: Create Release Pull Request or Publish to npm - name: Create Release Pull Request or Publish to npm
id: changesets id: changesets
uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10 uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
with: with:
version: pnpm changeset:version version: pnpm changeset:version
publish: pnpm changeset:publish publish: pnpm changeset:publish

View File

@@ -20,18 +20,18 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Run analysis - name: Run analysis
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
publish_results: true publish_results: true
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
retention-days: 5 retention-days: 5
- name: Upload to code-scanning - name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21 uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -19,7 +19,7 @@ jobs:
message: 'chore: update browsers list' message: 'chore: update browsers list'
push: false push: false
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with: with:
branch: update-browserslist branch: update-browserslist
title: Update Browserslist title: Update Browserslist

View File

@@ -0,0 +1,659 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
const looks = ['classic'];
const participantTypes = [
{ type: 'participant', display: 'participant' },
{ type: 'actor', display: 'actor' },
{ type: 'boundary', display: 'boundary' },
{ type: 'control', display: 'control' },
{ type: 'entity', display: 'entity' },
{ type: 'database', display: 'database' },
{ type: 'collections', display: 'collections' },
{ type: 'queue', display: 'queue' },
];
const restrictedTypes = ['boundary', 'control', 'entity', 'database', 'collections', 'queue'];
const interactionTypes = ['->>', '-->>', '->', '-->', '-x', '--x', '->>+', '-->>+'];
const notePositions = ['left of', 'right of', 'over'];
function getParticipantLine(name, type, alias) {
if (restrictedTypes.includes(type)) {
return ` participant ${name}@{ "type" : "${type}" }\n`;
} else if (alias) {
return ` participant ${name}@{ "type" : "${type}" } \n`;
} else {
return ` participant ${name}@{ "type" : "${type}" }\n`;
}
}
looks.forEach((look) => {
describe(`Sequence Diagram Tests - ${look} look`, () => {
it('should render all participant types', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.forEach((pt, index) => {
const name = `${pt.display}${index}`;
diagramCode += getParticipantLine(name, pt.type);
});
for (let i = 0; i < participantTypes.length - 1; i++) {
diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
}
imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } });
});
it('should render all interaction types', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
interactionTypes.forEach((interaction, index) => {
diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`;
});
imgSnapshotTest(diagramCode, { look });
});
it('should render participant creation and destruction', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.forEach((pt, index) => {
const name = `${pt.display}${index}`;
diagramCode += getParticipantLine('A', pt.type);
diagramCode += getParticipantLine('B', pt.type);
diagramCode += ` create participant ${name}@{ "type" : "${pt.type}" }\n`;
diagramCode += ` A ->> ${name}: Hello ${pt.display}\n`;
if (index % 2 === 0) {
diagramCode += ` destroy ${name}\n`;
}
});
imgSnapshotTest(diagramCode, { look });
});
it('should render notes in all positions', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
notePositions.forEach((position, index) => {
diagramCode += ` Note ${position} A: Note ${position} ${index}\n`;
});
diagramCode += ` A ->> B: Message with notes\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render parallel interactions', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 4).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` par Parallel actions\n`;
for (let i = 0; i < 3; i += 2) {
diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
if (i < participantTypes.length - 2) {
diagramCode += ` and\n`;
}
}
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render alternative flows', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
diagramCode += ` alt Successful case\n`;
diagramCode += ` A ->> B: Request\n`;
diagramCode += ` B -->> A: Success\n`;
diagramCode += ` else Failure case\n`;
diagramCode += ` A ->> B: Request\n`;
diagramCode += ` B --x A: Failure\n`;
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render loops', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` loop For each participant\n`;
for (let i = 0; i < 3; i++) {
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Message ${i}\n`;
}
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render boxes around groups', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += ` box Group 1\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
});
diagramCode += ` end\n`;
diagramCode += ` box rgb(200,220,255) Group 2\n`;
participantTypes.slice(3, 6).forEach((pt, index) => {
diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
});
diagramCode += ` end\n`;
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[3].display}0: Cross-group message\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render with different font settings', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Regular message\n`;
diagramCode += ` Note right of ${participantTypes[1].display}1: Regular note\n`;
imgSnapshotTest(diagramCode, {
look,
sequence: {
actorFontFamily: 'courier',
actorFontSize: 14,
messageFontFamily: 'Arial',
messageFontSize: 12,
noteFontFamily: 'times',
noteFontSize: 16,
noteAlign: 'left',
},
});
});
});
});
// Additional tests for specific combinations
describe('Sequence Diagram Special Cases', () => {
it('should render complex sequence with all features', () => {
const diagramCode = `
sequenceDiagram
box rgb(200,220,255) Authentication
actor User
participant LoginUI@{ "type": "boundary" }
participant AuthService@{ "type": "control" }
participant UserDB@{ "type": "database" }
end
box rgb(200,255,220) Order Processing
participant Order@{ "type": "entity" }
participant OrderQueue@{ "type": "queue" }
participant AuditLogs@{ "type": "collections" }
end
User ->> LoginUI: Enter credentials
LoginUI ->> AuthService: Validate
AuthService ->> UserDB: Query user
UserDB -->> AuthService: User data
alt Valid credentials
AuthService -->> LoginUI: Success
LoginUI -->> User: Welcome
par Place order
User ->> Order: New order
Order ->> OrderQueue: Process
and
Order ->> AuditLogs: Record
end
loop Until confirmed
OrderQueue ->> Order: Update status
Order -->> User: Notification
end
else Invalid credentials
AuthService --x LoginUI: Failure
LoginUI --x User: Retry
end
`;
imgSnapshotTest(diagramCode, {});
});
it('should render with wrapped messages and notes', () => {
const diagramCode = `
sequenceDiagram
participant A
participant B
A ->> B: This is a very long message that should wrap properly in the diagram rendering
Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram
par Wrapped parallel
A ->> B: Parallel message 1<br>with explicit line break
and
B ->> A: Parallel message 2<br>with explicit line break
end
loop Wrapped loop
Note right of B: This is a long note<br>in a loop
A ->> B: Message in loop
end
`;
imgSnapshotTest(diagramCode, { sequence: { wrap: true } });
});
describe('Sequence Diagram Rendering with Different Participant Types', () => {
it('should render a sequence diagram with various participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
participant User@{ "type": "actor" }
participant AuthService@{ "type": "control" }
participant UI@{ "type": "boundary" }
participant OrderController@{ "type": "control" }
participant Product@{ "type": "entity" }
participant MongoDB@{ "type": "database" }
participant Products@{ "type": "collections" }
participant OrderQueue@{ "type": "queue" }
User ->> UI: Login request
UI ->> AuthService: Validate credentials
AuthService -->> UI: Authentication token
UI ->> OrderController: Place order
OrderController ->> Product: Check availability
Product -->> OrderController: Available
OrderController ->> MongoDB: Save order
MongoDB -->> OrderController: Order saved
OrderController ->> OrderQueue: Process payment
OrderQueue -->> User: Order confirmation
`
);
});
it('should render participant creation and destruction with different types', () => {
imgSnapshotTest(`
sequenceDiagram
participant Alice@{ "type" : "boundary" }
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl@{ "type" : "control" }
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bob
Bob->>Alice: I agree
`);
});
it('should handle complex interactions between different participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
box rgb(200,220,255) Authentication
participant User@{ "type": "actor" }
participant LoginUI@{ "type": "boundary" }
participant AuthService@{ "type": "control" }
participant UserDB@{ "type": "database" }
end
box rgb(200,255,220) Order Processing
participant Order@{ "type": "entity" }
participant OrderQueue@{ "type": "queue" }
participant AuditLogs@{ "type": "collections" }
end
User ->> LoginUI: Enter credentials
LoginUI ->> AuthService: Validate
AuthService ->> UserDB: Query user
UserDB -->> AuthService: User data
alt Valid credentials
AuthService -->> LoginUI: Success
LoginUI -->> User: Welcome
par Place order
User ->> Order: New order
Order ->> OrderQueue: Process
and
Order ->> AuditLogs: Record
end
loop Until confirmed
OrderQueue ->> Order: Update status
Order -->> User: Notification
end
else Invalid credentials
AuthService --x LoginUI: Failure
LoginUI --x User: Retry
end
`,
{ sequence: { useMaxWidth: false } }
);
});
it('should render parallel processes with different participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Customer@{ "type": "actor" }
participant Frontend@{ "type": "participant" }
participant PaymentService@{ "type": "boundary" }
participant InventoryManager@{ "type": "control" }
participant Order@{ "type": "entity" }
participant OrdersDB@{ "type": "database" }
participant NotificationQueue@{ "type": "queue" }
Customer ->> Frontend: Place order
Frontend ->> Order: Create order
par Parallel Processing
Order ->> PaymentService: Process payment
and
Order ->> InventoryManager: Reserve items
end
PaymentService -->> Order: Payment confirmed
InventoryManager -->> Order: Items reserved
Order ->> OrdersDB: Save finalized order
OrdersDB -->> Order: Order saved
Order ->> NotificationQueue: Send confirmation
NotificationQueue -->> Customer: Order confirmation
`
);
});
});
it('should render different participant types with notes and loops', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Admin
participant Dashboard
participant AuthService@{ "type" : "boundary" }
participant UserManager@{ "type" : "control" }
participant UserProfile@{ "type" : "entity" }
participant UserDB@{ "type" : "database" }
participant Logs@{ "type" : "database" }
Admin ->> Dashboard: Open user management
loop Authentication check
Dashboard ->> AuthService: Verify admin rights
AuthService ->> Dashboard: Access granted
end
Dashboard ->> UserManager: List users
UserManager ->> UserDB: Query users
UserDB ->> UserManager: Return user data
Note right of UserDB: Encrypted data<br/>requires decryption
UserManager ->> UserProfile: Format profiles
UserProfile ->> UserManager: Formatted data
UserManager ->> Dashboard: Display users
Dashboard ->> Logs: Record access
Logs ->> Admin: Audit trail
`
);
});
it('should render different participant types with alternative flows', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Client
participant MobileApp
participant CloudService@{ "type" : "boundary" }
participant DataProcessor@{ "type" : "control" }
participant Transaction@{ "type" : "entity" }
participant TransactionsDB@{ "type" : "database" }
participant EventBus@{ "type" : "queue" }
Client ->> MobileApp: Initiate transaction
MobileApp ->> CloudService: Authenticate
alt Authentication successful
CloudService -->> MobileApp: Auth token
MobileApp ->> DataProcessor: Process data
DataProcessor ->> Transaction: Create transaction
Transaction ->> TransactionsDB: Save record
TransactionsDB -->> Transaction: Confirmation
Transaction ->> EventBus: Publish event
EventBus -->> Client: Notification
else Authentication failed
CloudService -->> MobileApp: Error
MobileApp -->> Client: Show error
end
`
);
});
it('should render different participant types with wrapping text', () => {
imgSnapshotTest(
`
sequenceDiagram
participant B@{ "type" : "boundary" }
participant C@{ "type" : "control" }
participant E@{ "type" : "entity" }
participant DB@{ "type" : "database" }
participant COL@{ "type" : "collections" }
participant Q@{ "type" : "queue" }
FE ->> B: Another long message<br/>with explicit<br/>line breaks
B -->> FE: Response message that is also quite long and needs to wrap
FE ->> C: Process data
C ->> E: Validate
E -->> C: Validation result
C ->> DB: Save
DB -->> C: Save result
C ->> COL: Log
COL -->> Q: Forward
Q -->> LongNameUser: Final response with confirmation of all actions taken
`,
{ sequence: { wrap: true } }
);
});
describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => {
it('should render long notes left of boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "boundary" }
actor Bob
Alice->>Bob: Hola
Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes left of control', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "control" }
actor Bob
Alice->>Bob: Hola
Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes right of entity', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "entity" }
actor Bob
Alice->>Bob: Hola
Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes right of database', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "database" }
actor Bob
Alice->>Bob: Hola
Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes over collections', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "collections" }
actor Bob
Alice->>Bob: Hola
Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes over queue', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "queue" }
actor Bob
Alice->>Bob: Hola
Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render notes over actor and boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
participant Charlie@{ "type" : "boundary" }
note over Alice: Some note
note over Charlie: Other note
`,
{}
);
});
it('should render long messages from database to collections', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob@{ "type" : "collections" }
Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long messages from control to entity', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob@{ "type" : "entity" }
Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long messages from queue to boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob@{ "type" : "boundary" }
Alice->>Bob: I'm short
Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
`,
{}
);
});
it('should render wrapped long messages from actor to database', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "database" }
Alice->>Bob: I'm short
Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
`,
{}
);
});
});
describe('svg size', () => {
it('should render a sequence diagram when useMaxWidth is true (default)', () => {
renderGraph(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "boundary" }
participant John@{ "type" : "control" }
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: true } }
);
cy.get('svg').should((svg) => {
expect(svg).to.have.attr('width', '100%');
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05);
});
});
it('should render a sequence diagram when useMaxWidth is false', () => {
renderGraph(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "boundary" }
participant John@{ "type" : "control" }
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
expect(width).to.be.within(820 * 0.95, 820 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
});
});

View File

@@ -225,4 +225,24 @@ timeline
{} {}
); );
}); });
it('13: should render markdown htmlLabels', () => {
imgSnapshotTest(
`---
config:
theme: forest
---
timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, <strong>Internal combustion engine </strong>, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data, 3D printing
`,
{}
);
});
}); });

View File

@@ -11,7 +11,7 @@
rel="stylesheet" rel="stylesheet"
/> />
<link <link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet" rel="stylesheet"
/> />
<link <link

View File

@@ -29,8 +29,7 @@ In GitHub, you first [**fork a mermaid repository**](https://github.com/mermaid-
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with. Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
> **💡 Tip** > **💡 Tip** > [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
> [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
```bash ```bash
git clone git@github.com/your-fork/mermaid git clone git@github.com/your-fork/mermaid

View File

@@ -33,8 +33,7 @@ mindmap
## Join the Development ## Join the Development
> **💡 Tip** > **💡 Tip** > **Check out our** [**detailed contribution guide**](./contributing.md).
> **Check out our** [**detailed contribution guide**](./contributing.md).
Where to start: Where to start:
@@ -48,8 +47,7 @@ Where to start:
## A Question Or a Suggestion? ## A Question Or a Suggestion?
> **💡 Tip** > **💡 Tip** > **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
> **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
If you have faced a vulnerability [report it to us](./security.md). If you have faced a vulnerability [report it to us](./security.md).

View File

@@ -22,6 +22,7 @@ While directives allow you to change most of the default configuration settings,
Mermaid basically supports two types of configuration options to be overridden by directives. Mermaid basically supports two types of configuration options to be overridden by directives.
1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are: 1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are:
- theme - theme
- fontFamily - fontFamily
- logLevel - logLevel

View File

@@ -88,103 +88,13 @@ Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib
--- ---
### stackTraceLimit ### prepareStackTrace()?
> `static` **stackTraceLimit**: `number` > `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any`
Defined in: node_modules/.pnpm/@types+node\@22.17.2/node_modules/@types/node/globals.d.ts:161 Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:143
The `Error.stackTraceLimit` property specifies the number of stack frames Optional override for formatting stack traces
collected by a stack trace (whether generated by `new Error().stack` or
`Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes
will affect any stack trace captured _after_ the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will
not capture any frames.
#### Inherited from
`Error.stackTraceLimit`
## Methods
### captureStackTrace()
> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void`
Defined in: node_modules/.pnpm/@types+node\@22.17.2/node_modules/@types/node/globals.d.ts:145
Creates a `.stack` property on `targetObject`, which when accessed returns
a string representing the location in the code at which
`Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with
`${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames
above `constructorOpt`, including `constructorOpt`, will be omitted from the
generated stack trace.
The `constructorOpt` argument is useful for hiding implementation
details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
#### Parameters
##### targetObject
`object`
##### constructorOpt?
`Function`
#### Returns
`void`
#### Inherited from
`Error.captureStackTrace`
---
### prepareStackTrace()
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
Defined in: node_modules/.pnpm/@types+node\@22.17.2/node_modules/@types/node/globals.d.ts:149
#### Parameters #### Parameters
@@ -207,3 +117,43 @@ Defined in: node_modules/.pnpm/@types+node\@22.17.2/node_modules/@types/node/glo
#### Inherited from #### Inherited from
`Error.prepareStackTrace` `Error.prepareStackTrace`
---
### stackTraceLimit
> `static` **stackTraceLimit**: `number`
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:145
#### Inherited from
`Error.stackTraceLimit`
## Methods
### captureStackTrace()
> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void`
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:136
Create .stack property on a target object
#### Parameters
##### targetObject
`object`
##### constructorOpt?
`Function`
#### Returns
`void`
#### Inherited from
`Error.captureStackTrace`

View File

@@ -10,7 +10,7 @@
# Interface: ParseOptions # Interface: ParseOptions
Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L72) Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
## Properties ## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mer
> `optional` **suppressErrors**: `boolean` > `optional` **suppressErrors**: `boolean`
Defined in: [packages/mermaid/src/types.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L77) Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
If `true`, parse will return `false` instead of throwing error when the diagram is invalid. If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
The `parseError` function will not be called. The `parseError` function will not be called.

View File

@@ -10,7 +10,7 @@
# Interface: ParseResult # Interface: ParseResult
Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80) Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92)
## Properties ## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mer
> **config**: [`MermaidConfig`](MermaidConfig.md) > **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88) Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100)
The config passed as YAML frontmatter or directives The config passed as YAML frontmatter or directives
@@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives
> **diagramType**: `string` > **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84) Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96)
The diagram type, e.g. 'flowchart', 'sequence', etc. The diagram type, e.g. 'flowchart', 'sequence', etc.

View File

@@ -10,7 +10,7 @@
# Interface: RenderResult # Interface: RenderResult
Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98) Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110)
## Properties ## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mer
> `optional` **bindFunctions**: (`element`) => `void` > `optional` **bindFunctions**: (`element`) => `void`
Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116) Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128)
Bind function to be called after the svg has been inserted into the DOM. Bind function to be called after the svg has been inserted into the DOM.
This is necessary for adding event listeners to the elements in the svg. This is necessary for adding event listeners to the elements in the svg.
@@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
> **diagramType**: `string` > **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106) Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118)
The diagram type, e.g. 'flowchart', 'sequence', etc. The diagram type, e.g. 'flowchart', 'sequence', etc.
@@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
> **svg**: `string` > **svg**: `string`
Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102) Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114)
The svg code for the rendered graph. The svg code for the rendered graph.

View File

@@ -29,6 +29,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
- **Plugins** - A plugin system for extending the functionality of Mermaid. - **Plugins** - A plugin system for extending the functionality of Mermaid.
Official Mermaid Chart plugins: Official Mermaid Chart plugins:
- [Mermaid Chart GPT](https://chatgpt.com/g/g-684cc36f30208191b21383b88650a45d-mermaid-chart-diagrams-and-charts) - [Mermaid Chart GPT](https://chatgpt.com/g/g-684cc36f30208191b21383b88650a45d-mermaid-chart-diagrams-and-charts)
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview) - [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud) - [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)

View File

@@ -35,11 +35,13 @@ The Mermaid Chart team is excited to introduce a new Visual Editor for Flowchart
Learn more: Learn more:
- Visual Editor For Flowcharts - Visual Editor For Flowcharts
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts) - [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts)
- [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0) - [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0)
- Visual Editor For Sequence diagrams - Visual Editor For Sequence diagrams
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams) - [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams)
- [Demo video](https://youtu.be/imc2u5_N6Dc) - [Demo video](https://youtu.be/imc2u5_N6Dc)

View File

@@ -139,6 +139,7 @@ The following unfinished features are not supported in the short term.
- [ ] Legend - [ ] Legend
- [x] System Context - [x] System Context
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link) - [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
- [x] Person_Ext - [x] Person_Ext
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link) - [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
@@ -152,6 +153,7 @@ The following unfinished features are not supported in the short term.
- [x] System_Boundary - [x] System_Boundary
- [x] Container diagram - [x] Container diagram
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ContainerDb - [x] ContainerDb
- [x] ContainerQueue - [x] ContainerQueue
@@ -161,6 +163,7 @@ The following unfinished features are not supported in the short term.
- [x] Container_Boundary(alias, label, ?tags, $link) - [x] Container_Boundary(alias, label, ?tags, $link)
- [x] Component diagram - [x] Component diagram
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ComponentDb - [x] ComponentDb
- [x] ComponentQueue - [x] ComponentQueue
@@ -169,15 +172,18 @@ The following unfinished features are not supported in the short term.
- [x] ComponentQueue_Ext - [x] ComponentQueue_Ext
- [x] Dynamic diagram - [x] Dynamic diagram
- [x] RelIndex(index, from, to, label, ?tags, $link) - [x] RelIndex(index, from, to, label, ?tags, $link)
- [x] Deployment diagram - [x] Deployment diagram
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link) - [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node() - [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node() - [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node() - [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
- [x] Relationship Types - [x] Relationship Types
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] BiRel (bidirectional relationship) - [x] BiRel (bidirectional relationship)
- [x] Rel_U, Rel_Up - [x] Rel_U, Rel_Up

View File

@@ -983,11 +983,23 @@ flowchart TD
- `b` - `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image. - **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image. - **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are: - **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the width (`w`) accordingly to the height (`h`). If not defined, this will default to `off` Possible values are:
- `on` - `on`
- `off` - `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging. If you want to resize an image, but keep the same aspect ratio, set `h`, and set `constraint: on` to constrain the aspect ratio. E.g.
```mermaid-example
flowchart TD
%% My image with a constrained aspect ratio
A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" }
```
```mermaid
flowchart TD
%% My image with a constrained aspect ratio
A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" }
```
## Links between nodes ## Links between nodes

View File

@@ -360,8 +360,7 @@ gantt
weekday monday weekday monday
``` ```
> **Warning** > **Warning** > `millisecond` and `second` support was added in v10.3.0
> `millisecond` and `second` support was added in v10.3.0
## Output in compact mode ## Output in compact mode

View File

@@ -74,6 +74,126 @@ sequenceDiagram
Bob->>Alice: Hi Alice Bob->>Alice: Hi Alice
``` ```
### Boundary
If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "boundary" }
participant Bob
Alice->>Bob: Request from boundary
Bob->>Alice: Response to boundary
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "boundary" }
participant Bob
Alice->>Bob: Request from boundary
Bob->>Alice: Response to boundary
```
### Control
If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob
Alice->>Bob: Control request
Bob->>Alice: Control response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob
Alice->>Bob: Control request
Bob->>Alice: Control response
```
### Entity
If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "entity" }
participant Bob
Alice->>Bob: Entity request
Bob->>Alice: Entity response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "entity" }
participant Bob
Alice->>Bob: Entity request
Bob->>Alice: Entity response
```
### Database
If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob
Alice->>Bob: DB query
Bob->>Alice: DB result
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob
Alice->>Bob: DB query
Bob->>Alice: DB result
```
### Collections
If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob
Alice->>Bob: Collections request
Bob->>Alice: Collections response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob
Alice->>Bob: Collections request
Bob->>Alice: Collections response
```
### Queue
If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob
Alice->>Bob: Queue message
Bob->>Alice: Queue response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob
Alice->>Bob: Queue message
Bob->>Alice: Queue response
```
### Aliases ### Aliases
The actor can have a convenient identifier and a descriptive label. The actor can have a convenient identifier and a descriptive label.

View File

@@ -64,35 +64,35 @@
}, },
"devDependencies": { "devDependencies": {
"@applitools/eyes-cypress": "^3.44.9", "@applitools/eyes-cypress": "^3.44.9",
"@argos-ci/cypress": "^5.0.7", "@argos-ci/cypress": "^5.0.2",
"@changesets/changelog-github": "^0.5.1", "@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.27.12", "@changesets/cli": "^2.27.12",
"@cspell/eslint-plugin": "^8.19.4", "@cspell/eslint-plugin": "^8.19.4",
"@cypress/code-coverage": "^3.12.49", "@cypress/code-coverage": "^3.12.49",
"@eslint/js": "^9.26.0", "@eslint/js": "^9.26.0",
"@rollup/plugin-typescript": "^12.1.4", "@rollup/plugin-typescript": "^12.1.2",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.17",
"@types/express": "^5.0.3", "@types/express": "^5.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.15",
"@types/mdast": "^4.0.4", "@types/mdast": "^4.0.4",
"@types/node": "^22.13.17", "@types/node": "^22.13.5",
"@types/rollup-plugin-visualizer": "^5.0.3", "@types/rollup-plugin-visualizer": "^5.0.3",
"@vitest/coverage-v8": "^3.0.9", "@vitest/coverage-v8": "^3.0.6",
"@vitest/spy": "^3.0.9", "@vitest/spy": "^3.0.6",
"@vitest/ui": "^3.0.9", "@vitest/ui": "^3.0.6",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"chokidar": "3.6.0", "chokidar": "3.6.0",
"concurrently": "^9.1.2", "concurrently": "^9.1.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"cpy-cli": "^5.0.0", "cpy-cli": "^5.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cspell": "^9.1.5", "cspell": "^9.1.3",
"cypress": "^14.5.4", "cypress": "^14.5.1",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.21", "cypress-split": "^1.24.14",
"esbuild": "^0.25.9", "esbuild": "^0.25.0",
"eslint": "^9.26.0", "eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-cypress": "^4.3.0", "eslint-plugin-cypress": "^4.3.0",
@@ -107,29 +107,29 @@
"eslint-plugin-unicorn": "^59.0.1", "eslint-plugin-unicorn": "^59.0.1",
"express": "^5.1.0", "express": "^5.1.0",
"globals": "^16.0.0", "globals": "^16.0.0",
"globby": "^14.1.0", "globby": "^14.0.2",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^30.0.5", "jest": "^30.0.4",
"jison": "^0.4.18", "jison": "^0.4.18",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"langium-cli": "3.3.0", "langium-cli": "3.3.0",
"lint-staged": "^16.1.5", "lint-staged": "^16.1.2",
"markdown-table": "^3.0.4", "markdown-table": "^3.0.4",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^3.5.3", "prettier": "^3.5.2",
"prettier-plugin-jsdoc": "^1.3.3", "prettier-plugin-jsdoc": "^1.3.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.3", "rollup-plugin-visualizer": "^6.0.3",
"start-server-and-test": "^2.0.13", "start-server-and-test": "^2.0.10",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"tsx": "^4.7.3", "tsx": "^4.7.3",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"typescript-eslint": "^8.38.0", "typescript-eslint": "^8.38.0",
"vite": "^7.0.6", "vite": "^7.0.3",
"vite-plugin-istanbul": "^7.0.0", "vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.0.9" "vitest": "^3.0.6"
}, },
"nyc": { "nyc": {
"report-dir": "coverage/cypress" "report-dir": "coverage/cypress"

View File

@@ -37,7 +37,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.1", "@braintree/sanitize-url": "^7.0.4",
"d3": "^7.9.0", "d3": "^7.9.0",
"khroma": "^2.1.0" "khroma": "^2.1.0"
}, },

View File

@@ -154,6 +154,7 @@
### Minor Changes ### Minor Changes
- [#6408](https://github.com/mermaid-js/mermaid/pull/6408) [`ad65313`](https://github.com/mermaid-js/mermaid/commit/ad653138e16765d095613a6e5de86dc5e52ac8f0) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: restore curve type configuration functionality for flowcharts. This fixes the issue where curve type settings were not being applied when configured through any of the following methods: - [#6408](https://github.com/mermaid-js/mermaid/pull/6408) [`ad65313`](https://github.com/mermaid-js/mermaid/commit/ad653138e16765d095613a6e5de86dc5e52ac8f0) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: restore curve type configuration functionality for flowcharts. This fixes the issue where curve type settings were not being applied when configured through any of the following methods:
- Config - Config
- Init directive (%%{ init: { 'flowchart': { 'curve': '...' } } }%%) - Init directive (%%{ init: { 'flowchart': { 'curve': '...' } } }%%)
- LinkStyle command (linkStyle default interpolate ...) - LinkStyle command (linkStyle default interpolate ...)
@@ -172,12 +173,14 @@
### Minor Changes ### Minor Changes
- [#6187](https://github.com/mermaid-js/mermaid/pull/6187) [`7809b5a`](https://github.com/mermaid-js/mermaid/commit/7809b5a93fae127f45727071f5ff14325222c518) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Flowchart new syntax for node metadata bugs - [#6187](https://github.com/mermaid-js/mermaid/pull/6187) [`7809b5a`](https://github.com/mermaid-js/mermaid/commit/7809b5a93fae127f45727071f5ff14325222c518) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Flowchart new syntax for node metadata bugs
- Incorrect label mapping for nodes when using `&` - Incorrect label mapping for nodes when using `&`
- Syntax error when `}` with trailing spaces before new line - Syntax error when `}` with trailing spaces before new line
- [#6136](https://github.com/mermaid-js/mermaid/pull/6136) [`ec0d9c3`](https://github.com/mermaid-js/mermaid/commit/ec0d9c389aa6018043187654044c1e0b5aa4f600) Thanks [@knsv](https://github.com/knsv)! - Adding support for animation of flowchart edges - [#6136](https://github.com/mermaid-js/mermaid/pull/6136) [`ec0d9c3`](https://github.com/mermaid-js/mermaid/commit/ec0d9c389aa6018043187654044c1e0b5aa4f600) Thanks [@knsv](https://github.com/knsv)! - Adding support for animation of flowchart edges
- [#6373](https://github.com/mermaid-js/mermaid/pull/6373) [`05bdf0e`](https://github.com/mermaid-js/mermaid/commit/05bdf0e20e2629fe77513218fbd4e28e65f75882) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Upgrade Requirement and ER diagram to use the common renderer flow - [#6373](https://github.com/mermaid-js/mermaid/pull/6373) [`05bdf0e`](https://github.com/mermaid-js/mermaid/commit/05bdf0e20e2629fe77513218fbd4e28e65f75882) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Upgrade Requirement and ER diagram to use the common renderer flow
- Added support for directions - Added support for directions
- Added support for hand drawn look - Added support for hand drawn look
@@ -226,6 +229,7 @@
- [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type - [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type
- [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes: - [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes:
- Updates the class diagram to the new unified way of rendering. - Updates the class diagram to the new unified way of rendering.
- Includes a new "classBox" shape to be used in diagrams - Includes a new "classBox" shape to be used in diagrams
- Other updates such as: - Other updates such as:

View File

@@ -67,8 +67,8 @@
] ]
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.1.1", "@braintree/sanitize-url": "^7.0.4",
"@iconify/utils": "^2.3.0", "@iconify/utils": "^2.1.33",
"@mermaid-js/parser": "workspace:^", "@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3", "@types/d3": "^7.4.3",
"cytoscape": "^3.29.3", "cytoscape": "^3.29.3",
@@ -89,7 +89,7 @@
"uuid": "^11.1.0" "uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"@adobe/jsonschema2md": "^8.0.5", "@adobe/jsonschema2md": "^8.0.2",
"@iconify/types": "^2.0.0", "@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.9", "@types/cytoscape": "^3.21.9",
"@types/cytoscape-fcose": "^2.2.4", "@types/cytoscape-fcose": "^2.2.4",
@@ -105,30 +105,30 @@
"@types/stylis": "^4.2.7", "@types/stylis": "^4.2.7",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"canvas": "^3.1.2", "canvas": "^3.1.0",
"chokidar": "3.6.0", "chokidar": "3.6.0",
"concurrently": "^9.1.2", "concurrently": "^9.1.2",
"csstree-validator": "^4.0.1", "csstree-validator": "^4.0.1",
"globby": "^14.1.0", "globby": "^14.0.2",
"jison": "^0.4.18", "jison": "^0.4.18",
"js-base64": "^3.7.8", "js-base64": "^3.7.7",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"json-schema-to-typescript": "^15.0.4", "json-schema-to-typescript": "^15.0.4",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^3.5.3", "prettier": "^3.5.2",
"remark": "^15.0.1", "remark": "^15.0.1",
"remark-frontmatter": "^5.0.0", "remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"start-server-and-test": "^2.0.13", "start-server-and-test": "^2.0.10",
"type-fest": "^4.35.0", "type-fest": "^4.35.0",
"typedoc": "^0.27.9", "typedoc": "^0.27.8",
"typedoc-plugin-markdown": "^4.4.2", "typedoc-plugin-markdown": "^4.4.2",
"typescript": "~5.7.3", "typescript": "~5.7.3",
"unist-util-flatmap": "^1.0.0", "unist-util-flatmap": "^1.0.0",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"vitepress": "^1.6.4", "vitepress": "^1.0.2",
"vitepress-plugin-search": "1.0.4-alpha.22" "vitepress-plugin-search": "1.0.4-alpha.22"
}, },
"files": [ "files": [

View File

@@ -14,7 +14,7 @@
// Special states for recognizing aliases // Special states for recognizing aliases
// A special state for grabbing text up to the first comment/newline // A special state for grabbing text up to the first comment/newline
%x ID ALIAS LINE %x ID ALIAS LINE CONFIG CONFIG_DATA
%x acc_title %x acc_title
%x acc_descr %x acc_descr
@@ -28,6 +28,11 @@
\%%(?!\{)[^\n]* /* skip comments */ \%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM'; [0-9]+(?=[ \n]+) return 'NUM';
<ID>\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; }
<CONFIG>[^\}]+ { return 'CONFIG_CONTENT'; }
<CONFIG>\} { this.popState(); this.popState(); return 'CONFIG_END'; }
<ID>[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
<ID>[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
"box" { this.begin('LINE'); return 'box'; } "box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; } "participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; } "actor" { this.begin('ID'); return 'participant_actor'; }
@@ -231,6 +236,8 @@ participant_statement
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$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;} | 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;} | 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
| 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;}
; ;
note_statement note_statement
@@ -301,6 +308,23 @@ signal
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
; ;
actor_with_config
: ACTOR config_object
{
$$ = {
type: 'addParticipant',
actor: $1,
config: $2
};
}
;
config_object
: CONFIG_START CONFIG_CONTENT CONFIG_END
{
$$ = $2.trim();
}
;
// actor // actor
// : actor_participant // : actor_participant
// | actor_actor // | actor_actor
@@ -313,7 +337,7 @@ signaltype
: SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; }
| DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; }
| SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; }
| BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; } | BIDIRECTIONAL_SOLID_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_SOLID; }
| DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; }
| BIDIRECTIONAL_DOTTED_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_DOTTED; } | BIDIRECTIONAL_DOTTED_ARROW { $$ = yy.LINETYPE.BIDIRECTIONAL_DOTTED; }
| SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; }

View File

@@ -1,4 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js'; import { getConfig } from '../../diagram-api/diagramAPI.js';
import * as yaml from 'js-yaml';
import type { DiagramDB } from '../../diagram-api/types.js'; import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js'; import { ImperativeState } from '../../utils/imperativeState.js';
@@ -13,6 +14,7 @@ import {
setDiagramTitle, setDiagramTitle,
} from '../common/commonDb.js'; } from '../common/commonDb.js';
import type { Actor, AddMessageParams, Box, Message, Note } from './types.js'; import type { Actor, AddMessageParams, Box, Message, Note } from './types.js';
import type { ParticipantMetaData } from '../../types.js';
interface SequenceState { interface SequenceState {
prevActor?: string; prevActor?: string;
@@ -75,6 +77,17 @@ const PLACEMENT = {
OVER: 2, OVER: 2,
} as const; } as const;
export const PARTICIPANT_TYPE = {
ACTOR: 'actor',
BOUNDARY: 'boundary',
COLLECTIONS: 'collections',
CONTROL: 'control',
DATABASE: 'database',
ENTITY: 'entity',
PARTICIPANT: 'participant',
QUEUE: 'queue',
} as const;
export class SequenceDB implements DiagramDB { export class SequenceDB implements DiagramDB {
private readonly state = new ImperativeState<SequenceState>(() => ({ private readonly state = new ImperativeState<SequenceState>(() => ({
prevActor: undefined, prevActor: undefined,
@@ -119,9 +132,22 @@ export class SequenceDB implements DiagramDB {
id: string, id: string,
name: string, name: string,
description: { text: string; wrap?: boolean | null; type: string }, description: { text: string; wrap?: boolean | null; type: string },
type: string type: string,
metadata?: any
) { ) {
let assignedBox = this.state.records.currentBox; let assignedBox = this.state.records.currentBox;
let doc;
if (metadata !== undefined) {
let yamlData;
// detect if shapeData contains a newline character
if (!metadata.includes('\n')) {
yamlData = '{\n' + metadata + '\n}';
} else {
yamlData = metadata + '\n';
}
doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as ParticipantMetaData;
}
type = doc?.type ?? type;
const old = this.state.records.actors.get(id); const old = this.state.records.actors.get(id);
if (old) { if (old) {
// If already set and trying to set to a new one throw error // If already set and trying to set to a new one throw error
@@ -518,7 +544,7 @@ export class SequenceDB implements DiagramDB {
}); });
break; break;
case 'addParticipant': case 'addParticipant':
this.addActor(param.actor, param.actor, param.description, param.draw); this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
break; break;
case 'createParticipant': case 'createParticipant':
if (this.state.records.actors.has(param.actor)) { if (this.state.records.actors.has(param.actor)) {
@@ -527,7 +553,7 @@ export class SequenceDB implements DiagramDB {
); );
} }
this.state.records.lastCreated = param.actor; this.state.records.lastCreated = param.actor;
this.addActor(param.actor, param.actor, param.description, param.draw); this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
this.state.records.createdActors.set(param.actor, this.state.records.messages.length); this.state.records.createdActors.set(param.actor, this.state.records.messages.length);
break; break;
case 'destroyParticipant': case 'destroyParticipant':

View File

@@ -2058,4 +2058,272 @@ Bob->>Alice:Got it!
expect(messages[0].from).toBe('Alice'); expect(messages[0].from).toBe('Alice');
expect(messages[0].to).toBe('Bob'); expect(messages[0].to).toBe('Bob');
}); });
describe('when parsing extended participant syntax', () => {
it('should parse participants with different quote styles and whitespace', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob@{ "type" : "database" }
participant Carl@{ type: "database" }
participant David@{ "type" : 'database' }
participant Eve@{ type: 'database' }
participant Favela@{ "type" : "database" }
Bob->>+Alice: Hi Alice
Alice->>+Bob: Hi Bob
`);
const actors = diagram.db.getActors();
expect(actors.get('Alice').type).toBe('database');
expect(actors.get('Alice').description).toBe('Alice');
expect(actors.get('Bob').type).toBe('database');
expect(actors.get('Bob').description).toBe('Bob');
expect(actors.get('Carl').type).toBe('database');
expect(actors.get('Carl').description).toBe('Carl');
expect(actors.get('David').type).toBe('database');
expect(actors.get('David').description).toBe('David');
expect(actors.get('Eve').type).toBe('database');
expect(actors.get('Eve').description).toBe('Eve');
expect(actors.get('Favela').type).toBe('database');
expect(actors.get('Favela').description).toBe('Favela');
// Verify messages were parsed correctly
const messages = diagram.db.getMessages();
expect(messages.length).toBe(4); // 2 messages + 2 activation messages
expect(messages[0].from).toBe('Bob');
expect(messages[0].to).toBe('Alice');
expect(messages[0].message).toBe('Hi Alice');
expect(messages[2].from).toBe('Alice'); // Second message (index 2 due to activation)
expect(messages[2].to).toBe('Bob');
expect(messages[2].message).toBe('Hi Bob');
});
it('should parse mixed participant types with extended syntax', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant lead
participant dsa@{ "type" : "queue" }
API->>+Database: getUserb
Database-->>-API: userb
dsa --> Database: hello
`);
// Verify actors were created
const actors = diagram.db.getActors();
expect(actors.get('lead').type).toBe('participant');
expect(actors.get('lead').description).toBe('lead');
// Participant with extended syntax
expect(actors.get('dsa').type).toBe('queue');
expect(actors.get('dsa').description).toBe('dsa');
// Implicitly created actors (from messages)
expect(actors.get('API').type).toBe('participant');
expect(actors.get('API').description).toBe('API');
expect(actors.get('Database').type).toBe('participant');
expect(actors.get('Database').description).toBe('Database');
// Verify messages were parsed correctly
const messages = diagram.db.getMessages();
expect(messages.length).toBe(5); // 3 messages + 2 activation messages
// First message with activation
expect(messages[0].from).toBe('API');
expect(messages[0].to).toBe('Database');
expect(messages[0].message).toBe('getUserb');
expect(messages[0].activate).toBe(true);
// Second message with deactivation
expect(messages[2].from).toBe('Database');
expect(messages[2].to).toBe('API');
expect(messages[2].message).toBe('userb');
// Third message
expect(messages[4].from).toBe('dsa');
expect(messages[4].to).toBe('Database');
expect(messages[4].message).toBe('hello');
});
it('should fail for malformed JSON in participant definition', async () => {
const invalidDiagram = `
sequenceDiagram
participant D@{ "type: "entity" }
participant E@{ "type": "dat
abase }
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it('should fail for missing colon separator', async () => {
const invalidDiagram = `
sequenceDiagram
participant C@{ "type" "control" }
C ->> C: action
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it('should fail for missing closing brace', async () => {
const invalidDiagram = `
sequenceDiagram
participant E@{ "type": "entity"
E ->> E: process
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
});
describe('participant type parsing', () => {
it('should parse boundary participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant boundary@{ "type" : "boundary" }
boundary->boundary: test
`);
const actors = diagram.db.getActors();
expect(actors.get('boundary').type).toBe('boundary');
expect(actors.get('boundary').description).toBe('boundary');
});
it('should parse control participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant C@{ "type" : "control" }
C->C: test
`);
const actors = diagram.db.getActors();
expect(actors.get('C').type).toBe('control');
expect(actors.get('C').description).toBe('C');
});
it('should parse entity participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant E@{ "type" : "entity" }
E->E: test
`);
const actors = diagram.db.getActors();
expect(actors.get('E').type).toBe('entity');
expect(actors.get('E').description).toBe('E');
});
it('should parse database participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant D@{ "type" : "database" }
D->D: test
`);
const actors = diagram.db.getActors();
expect(actors.get('D').type).toBe('database');
expect(actors.get('D').description).toBe('D');
});
it('should parse collections participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant L@{ "type" : "collections" }
L->L: test
`);
const actors = diagram.db.getActors();
expect(actors.get('L').type).toBe('collections');
expect(actors.get('L').description).toBe('L');
});
it('should parse queue participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Q@{ "type" : "queue" }
Q->Q: test
`);
const actors = diagram.db.getActors();
expect(actors.get('Q').type).toBe('queue');
expect(actors.get('Q').description).toBe('Q');
});
});
describe('participant type parsing', () => {
it('should parse actor participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant A@{ "type" : "queue" }
A->A: test
`);
const actors = diagram.db.getActors();
expect(actors.get('A').type).toBe('queue');
expect(actors.get('A').description).toBe('A');
});
it('should parse participant participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant P@{ "type" : "database" }
P->P: test
`);
const actors = diagram.db.getActors();
expect(actors.get('P').type).toBe('database');
expect(actors.get('P').description).toBe('P');
});
it('should parse boundary using actor keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob@{ "type" : "control" }
Alice->>Bob: Hello Bob, how are you?
`);
const actors = diagram.db.getActors();
expect(actors.get('Alice').type).toBe('collections');
expect(actors.get('Bob').type).toBe('control');
expect(actors.get('Bob').description).toBe('Bob');
});
it('should parse control using participant keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant C@{ "type" : "control" }
C->C: test
`);
const actors = diagram.db.getActors();
expect(actors.get('C').type).toBe('control');
expect(actors.get('C').description).toBe('C');
});
it('should parse entity using actor keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant E@{ "type" : "entity" }
E->E: test
`);
const actors = diagram.db.getActors();
expect(actors.get('E').type).toBe('entity');
expect(actors.get('E').description).toBe('E');
});
});
}); });

View File

@@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js';
import utils from '../../utils.js'; import utils from '../../utils.js';
import { configureSvgSize } from '../../setupGraphViewbox.js'; import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { Diagram } from '../../Diagram.js'; import type { Diagram } from '../../Diagram.js';
import { PARTICIPANT_TYPE } from './sequenceDb.js';
let conf = {}; let conf = {};
@@ -746,11 +747,19 @@ function adjustCreatedDestroyedData(
msgModel.startx = msgModel.startx - adjustment; msgModel.startx = msgModel.startx - adjustment;
} }
} }
const actorArray = [
PARTICIPANT_TYPE.ACTOR,
PARTICIPANT_TYPE.CONTROL,
PARTICIPANT_TYPE.ENTITY,
PARTICIPANT_TYPE.DATABASE,
];
// if it is a create message // if it is a create message
if (createdActors.get(msg.to) == index) { if (createdActors.get(msg.to) == index) {
const actor = actors.get(msg.to); const actor = actors.get(msg.to);
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; const adjustment = actorArray.includes(actor.type)
? ACTOR_TYPE_WIDTH / 2 + 3
: actor.width / 2 + 3;
receiverAdjustment(actor, adjustment); receiverAdjustment(actor, adjustment);
actor.starty = lineStartY - actor.height / 2; actor.starty = lineStartY - actor.height / 2;
bounds.bumpVerticalPos(actor.height / 2); bounds.bumpVerticalPos(actor.height / 2);
@@ -759,7 +768,7 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.from) == index) { else if (destroyedActors.get(msg.from) == index) {
const actor = actors.get(msg.from); const actor = actors.get(msg.from);
if (conf.mirrorActors) { if (conf.mirrorActors) {
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2; const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
senderAdjustment(actor, adjustment); senderAdjustment(actor, adjustment);
} }
actor.stopy = lineStartY - actor.height / 2; actor.stopy = lineStartY - actor.height / 2;
@@ -769,7 +778,9 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.to) == index) { else if (destroyedActors.get(msg.to) == index) {
const actor = actors.get(msg.to); const actor = actors.get(msg.to);
if (conf.mirrorActors) { if (conf.mirrorActors) {
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3; const adjustment = actorArray.includes(actor.type)
? ACTOR_TYPE_WIDTH / 2 + 3
: actor.width / 2 + 3;
receiverAdjustment(actor, adjustment); receiverAdjustment(actor, adjustment);
} }
actor.stopy = lineStartY - actor.height / 2; actor.stopy = lineStartY - actor.height / 2;
@@ -1087,10 +1098,11 @@ export const draw = async function (_text: string, id: string, _version: string,
for (const box of bounds.models.boxes) { for (const box of bounds.models.boxes) {
box.height = bounds.getVerticalPos() - box.y; box.height = bounds.getVerticalPos() - box.y;
bounds.insert(box.x, box.y, box.x + box.width, box.height); bounds.insert(box.x, box.y, box.x + box.width, box.height);
box.startx = box.x; const boxPadding = conf.boxMargin * 2;
box.starty = box.y; box.startx = box.x - boxPadding;
box.stopx = box.startx + box.width; box.starty = box.y - boxPadding * 0.25;
box.stopy = box.starty + box.height; box.stopx = box.startx + box.width + 2 * boxPadding;
box.stopy = box.starty + box.height + boxPadding * 0.75;
box.stroke = 'rgb(0,0,0, 0.5)'; box.stroke = 'rgb(0,0,0, 0.5)';
svgDraw.drawBox(diagram, box, conf); svgDraw.drawBox(diagram, box, conf);
} }
@@ -1355,6 +1367,9 @@ async function calculateActorMargins(
return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0)); return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0));
}, 0); }, 0);
const standardBoxPadding = conf.boxMargin * 8;
totalWidth += standardBoxPadding;
totalWidth -= 2 * conf.boxTextMargin; totalWidth -= 2 * conf.boxTextMargin;
if (box.wrap) { if (box.wrap) {
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);

View File

@@ -12,6 +12,11 @@ const getStyles = (options) =>
.actor-line { .actor-line {
stroke: ${options.actorLineColor}; stroke: ${options.actorLineColor};
} }
.innerArc {
stroke-width: 1.5;
stroke-dasharray: none;
}
.messageLine0 { .messageLine0 {
stroke-width: 1.5; stroke-width: 1.5;
@@ -115,6 +120,7 @@ const getStyles = (options) =>
fill: ${options.actorBkg}; fill: ${options.actorBkg};
stroke-width: 2px; stroke-width: 2px;
} }
`; `;
export default getStyles; export default getStyles;

View File

@@ -415,6 +415,600 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
return height; return height;
}; };
/**
* Draws an actor in the diagram with the attached line
*
* @param {any} elem - The diagram we'll draw to.
* @param {any} actor - The actor to draw.
* @param {any} conf - DrawText implementation discriminator object
* @param {boolean} isFooter - If the actor is the footer one
*/
const drawActorTypeCollections = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height;
const boxplusLineGroup = elem.append('g').lower();
var g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
var cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// DRAW STACKED RECTANGLES
const offset = 6;
const shadowRect = {
...rect,
x: rect.x + (isFooter ? -offset : -offset),
y: rect.y + (isFooter ? +offset : +offset),
class: 'actor',
};
const rectElem = drawRect(g, rect); // draw main rectangle on top
drawRect(g, shadowRect);
actor.rectData = rect;
if (actor.properties?.icon) {
const iconSrc = actor.properties.icon.trim();
if (iconSrc.charAt(0) === '@') {
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
} else {
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
}
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x - offset,
rect.y + offset,
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
let height = actor.height;
if (rectElem.node) {
const bounds = rectElem.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeQueue = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height;
const boxplusLineGroup = elem.append('g').lower();
let g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
let cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// Cylinder dimensions
const ry = rect.height / 2;
const rx = ry / (2.5 + rect.height / 50);
// Cylinder base group
const cylinderGroup = g.append('g');
const cylinderArc = g.append('g');
// Main cylinder body
cylinderGroup
.append('path')
.attr(
'd',
`M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 0,${rect.height}
h ${rect.width - 2 * rx}
a ${rx},${ry} 0 0 0 0,-${rect.height}
Z
`
)
.attr('class', cssclass);
cylinderArc
.append('path')
.attr(
'd',
`M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 0,${rect.height}`
)
.attr('stroke', '#666')
.attr('stroke-width', '1px')
.attr('class', cssclass);
cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`);
cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`);
actor.rectData = rect;
if (actor.properties?.icon) {
const iconSrc = actor.properties.icon.trim();
const iconX = rect.x + rect.width - 20;
const iconY = rect.y + 10;
if (iconSrc.charAt(0) === '@') {
svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1));
} else {
svgDrawCommon.drawImage(g, iconX, iconY, iconSrc);
}
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x,
rect.y,
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
let height = actor.height;
const lastPath = cylinderGroup.select('path:last-child');
if (lastPath.node()) {
const bounds = lastPath.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeControl = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 75;
const line = elem.append('g').lower();
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
const cx = actor.x + actor.width / 2;
const cy = actorY + 30;
const r = 18;
actElem
.append('defs')
.append('marker')
.attr('id', 'filled-head-control')
.attr('refX', 11)
.attr('refY', 5.8)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', '172.5')
.append('path')
.attr('d', 'M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z');
// Draw the base circle
actElem
.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', r)
.attr('fill', '#eaeaf7')
.attr('stroke', '#666')
.attr('stroke-width', 1.2);
// Draw looping arrow as arc path
actElem
.append('line')
.attr('marker-end', 'url(#filled-head-control)')
.attr('transform', `translate(${cx}, ${cy - r})`);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0);
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + r + (isFooter ? 5 : 10),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
return actor.height;
};
const drawActorTypeEntity = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 75;
const line = elem.append('g').lower();
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
const cx = actor.x + actor.width / 2;
const cy = actorY + (!isFooter ? 25 : 10);
const r = 18;
actElem
.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', r)
.attr('width', actor.width)
.attr('height', actor.height);
actElem
.append('line')
.attr('x1', cx - r)
.attr('x2', cx + r)
.attr('y1', cy + r)
.attr('y2', cy + r)
.attr('stroke', '#333')
.attr('stroke-width', 2);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + (conf?.sequence?.labelBoxHeight ?? 0);
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY + r - 5) / 2),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
if (!isFooter) {
actElem.attr('transform', `translate(${0}, ${r / 2})`);
} else {
actElem.attr('transform', `translate(${0}, ${r / 2})`);
}
return actor.height;
};
const drawActorTypeDatabase = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height + 2 * conf.boxTextMargin;
const boxplusLineGroup = elem.append('g').lower();
let g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
let cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// Cylinder dimensions
rect.x = actor.x;
rect.y = actorY;
const w = rect.width / 4;
const h = rect.width / 4;
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
// Cylinder base group
const cylinderGroup = g.append('g');
const d = `
M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 ${w},0
a ${rx},${ry} 0 0 0 -${w},0
l 0,${h - 2 * ry}
a ${rx},${ry} 0 0 0 ${w},0
l 0,-${h - 2 * ry}
`;
// Draw the main cylinder body
cylinderGroup
.append('path')
.attr('d', d)
.attr('fill', '#eaeaea')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('class', cssclass);
if (!isFooter) {
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${(rect.height + ry) / 4})`);
} else {
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`);
}
actor.rectData = rect;
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x,
rect.y + (!isFooter ? (rect.height + ry) / 2 : (rect.height + h) / 4),
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
const lastPath = cylinderGroup.select('path:last-child');
if (lastPath.node()) {
const bounds = lastPath.node().getBBox();
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
}
return actor.height;
};
const drawActorTypeBoundary = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 80;
const radius = 30;
const line = elem.append('g').lower();
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
actElem
.append('line')
.attr('id', 'actor-man-torso' + actorCnt)
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
.attr('y1', actorY + 10)
.attr('x2', actor.x + actor.width / 2 - 15)
.attr('y2', actorY + 10);
actElem
.append('line')
.attr('id', 'actor-man-arms' + actorCnt)
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
.attr('y1', actorY + 0) // starting Y
.attr('x2', actor.x + actor.width / 2 - radius * 2.5)
.attr('y2', actorY + 20); // ending Y (26px long, adjust as needed)
actElem
.append('circle')
.attr('cx', actor.x + actor.width / 2)
.attr('cy', actorY + 10)
.attr('r', radius);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + (!isFooter ? radius / 2 + 3 : radius / 2 - 4),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
if (!isFooter) {
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
} else {
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
}
return actor.height;
};
const drawActorTypeActor = function (elem, actor, conf, isFooter) { const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty; const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2; const center = actor.x + actor.width / 2;
@@ -516,6 +1110,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) {
return await drawActorTypeActor(elem, actor, conf, isFooter); return await drawActorTypeActor(elem, actor, conf, isFooter);
case 'participant': case 'participant':
return await drawActorTypeParticipant(elem, actor, conf, isFooter); return await drawActorTypeParticipant(elem, actor, conf, isFooter);
case 'boundary':
return await drawActorTypeBoundary(elem, actor, conf, isFooter);
case 'control':
return await drawActorTypeControl(elem, actor, conf, isFooter);
case 'entity':
return await drawActorTypeEntity(elem, actor, conf, isFooter);
case 'database':
return await drawActorTypeDatabase(elem, actor, conf, isFooter);
case 'collections':
return await drawActorTypeCollections(elem, actor, conf, isFooter);
case 'queue':
return await drawActorTypeQueue(elem, actor, conf, isFooter);
} }
}; };

View File

@@ -1,6 +1,91 @@
import { arc as d3arc, select } from 'd3'; import { arc as d3arc, select } from 'd3';
import { createText } from '../../rendering-util/createText.js';
import DOMPurify from 'dompurify';
const MAX_SECTIONS = 12; const MAX_SECTIONS = 12;
/**
* Process HTML content in node descriptions
* @param {object} textElem - The SVG element to append text to
* @param {object} node - The node object containing description and dimensions
* @param {object} conf - Configuration object
* @param {boolean} isVirtual - Whether this is for virtual height calculation
*/
const processHtmlContent = async function (textElem, node, conf, isVirtual = false) {
// Create temporary text to get initial dimensions
const sanitizedHtml = DOMPurify.sanitize(node.descr, { ALLOWED_TAGS: [] });
const tempText = textElem
.append('text')
.text(sanitizedHtml)
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.call(wrap, node.width);
if (!isVirtual) {
tempText.attr('visibility', 'hidden');
}
const bbox = tempText.node().getBBox();
tempText.remove();
// Create the actual HTML content
const textObj = await createText(
textElem,
node.descr,
{
useHtmlLabels: true,
width: node.width,
classes: 'timeline-node-label',
isNode: true,
},
conf
);
if (!isVirtual) {
select(textObj).attr('transform', 'translate(0, 0)');
}
// Process the foreign object
const foreignObject = textElem.select('foreignObject');
if (foreignObject.node()) {
foreignObject.attr('width', `${10 * node.width}px`).attr('height', `${10 * node.width}px`);
const div = foreignObject.select('div');
if (div.node()) {
div
.style('display', 'table-cell')
.style('white-space', 'nowrap')
.style('line-height', '1.5')
.style('max-width', node.width + 'px')
.style('text-align', 'center');
let divBBox = div.node().getBoundingClientRect();
if (divBBox.width === node.width) {
div
.style('display', 'table')
.style('white-space', 'break-spaces')
.style('width', node.width + 'px');
divBBox = div.node().getBoundingClientRect();
}
foreignObject.attr('width', node.width).attr('height', divBBox.height);
if (!isVirtual) {
foreignObject.attr('x', -node.width / 2).attr('y', 3);
div.style('width', node.width + 'px');
}
bbox.height = divBBox.height;
}
}
return bbox;
};
export const drawRect = function (elem, rectData) { export const drawRect = function (elem, rectData) {
const rectElem = elem.append('rect'); const rectElem = elem.append('rect');
rectElem.attr('x', rectData.x); rectElem.attr('x', rectData.x);
@@ -409,6 +494,9 @@ const _drawTextCandidateFunc = (function () {
.style('display', 'table-cell') .style('display', 'table-cell')
.style('text-align', 'center') .style('text-align', 'center')
.style('vertical-align', 'middle') .style('vertical-align', 'middle')
.style('word-wrap', 'break-word')
.style('overflow-wrap', 'break-word')
.style('white-space', 'normal')
.text(content); .text(content);
byTspan(content, body, x, y, width, height, textAttrs, conf); byTspan(content, body, x, y, width, height, textAttrs, conf);
@@ -493,7 +581,7 @@ function wrap(text, width) {
}); });
} }
export const drawNode = function (elem, node, fullSection, conf) { export const drawNode = async function (elem, node, fullSection, conf) {
const section = (fullSection % MAX_SECTIONS) - 1; const section = (fullSection % MAX_SECTIONS) - 1;
const nodeElem = elem.append('g'); const nodeElem = elem.append('g');
node.section = section; node.section = section;
@@ -506,19 +594,28 @@ export const drawNode = function (elem, node, fullSection, conf) {
// Create the wrapped text element // Create the wrapped text element
const textElem = nodeElem.append('g'); const textElem = nodeElem.append('g');
const txt = textElem const hasHtml = /<[a-z][\S\s]*>/i.test(node.descr);
.append('text')
.text(node.descr) if (hasHtml) {
.attr('dy', '1em') const bbox = await processHtmlContent(textElem, node, conf, false);
.attr('alignment-baseline', 'middle') node.height = bbox.height + node.padding;
.attr('dominant-baseline', 'middle') node.height = Math.max(node.height, node.maxHeight);
.attr('text-anchor', 'middle') node.width = node.width + 2 * node.padding;
.call(wrap, node.width); } else {
const bbox = txt.node().getBBox(); const txt = textElem
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize; .append('text')
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; .text(node.descr)
node.height = Math.max(node.height, node.maxHeight); .attr('dy', '1em')
node.width = node.width + 2 * node.padding; .attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.call(wrap, node.width);
const bbox = txt.node().getBBox();
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
node.height = Math.max(node.height, node.maxHeight);
node.width = node.width + 2 * node.padding;
}
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')'); textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
@@ -528,17 +625,25 @@ export const drawNode = function (elem, node, fullSection, conf) {
return node; return node;
}; };
export const getVirtualNodeHeight = function (elem, node, conf) { export const getVirtualNodeHeight = async function (elem, node, conf) {
const textElem = elem.append('g'); const textElem = elem.append('g');
const txt = textElem
.append('text') const hasHtml = /<[a-z][\S\s]*>/i.test(node.descr);
.text(node.descr)
.attr('dy', '1em') let bbox;
.attr('alignment-baseline', 'middle') if (hasHtml) {
.attr('dominant-baseline', 'middle') bbox = await processHtmlContent(textElem, node, conf, true);
.attr('text-anchor', 'middle') } else {
.call(wrap, node.width); const txt = textElem
const bbox = txt.node().getBBox(); .append('text')
.text(node.descr)
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.call(wrap, node.width);
bbox = txt.node().getBBox();
}
const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize; const fontSize = conf.fontSize?.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
textElem.remove(); textElem.remove();
return bbox.height + fontSize * 1.1 * 0.5 + node.padding; return bbox.height + fontSize * 1.1 * 0.5 + node.padding;

View File

@@ -25,7 +25,7 @@ interface TimelineTask {
score: number; score: number;
events: string[]; events: string[];
} }
export const draw = function (text: string, id: string, version: string, diagObj: Diagram) { export const draw = async function (text: string, id: string, version: string, diagObj: Diagram) {
//1. Fetch the configuration //1. Fetch the configuration
const conf = getConfig(); const conf = getConfig();
const LEFT_MARGIN = conf.timeline?.leftMargin ?? 50; const LEFT_MARGIN = conf.timeline?.leftMargin ?? 50;
@@ -76,7 +76,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
let hasSections = true; let hasSections = true;
//Calculate the max height of the sections //Calculate the max height of the sections
sections.forEach(function (section: string) { for (const section of sections) {
const sectionNode: Block<string, number> = { const sectionNode: Block<string, number> = {
number: sectionNumber, number: sectionNumber,
descr: section, descr: section,
@@ -85,10 +85,10 @@ export const draw = function (text: string, id: string, version: string, diagObj
padding: 20, padding: 20,
maxHeight: maxSectionHeight, maxHeight: maxSectionHeight,
}; };
const sectionHeight = svgDraw.getVirtualNodeHeight(svg, sectionNode, conf); const sectionHeight = await svgDraw.getVirtualNodeHeight(svg, sectionNode, conf);
log.debug('sectionHeight before draw', sectionHeight); log.debug('sectionHeight before draw', sectionHeight);
maxSectionHeight = Math.max(maxSectionHeight, sectionHeight + 20); maxSectionHeight = Math.max(maxSectionHeight, sectionHeight + 20);
}); }
//tasks length and maxEventCount //tasks length and maxEventCount
let maxEventCount = 0; let maxEventCount = 0;
@@ -106,7 +106,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
padding: 20, padding: 20,
maxHeight: maxTaskHeight, maxHeight: maxTaskHeight,
}; };
const taskHeight = svgDraw.getVirtualNodeHeight(svg, taskNode, conf); const taskHeight = await svgDraw.getVirtualNodeHeight(svg, taskNode, conf);
log.debug('taskHeight before draw', taskHeight); log.debug('taskHeight before draw', taskHeight);
maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20); maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20);
@@ -123,9 +123,8 @@ export const draw = function (text: string, id: string, version: string, diagObj
padding: 20, padding: 20,
maxHeight: 50, maxHeight: 50,
}; };
maxEventLineLengthTemp += svgDraw.getVirtualNodeHeight(svg, eventNode, conf); maxEventLineLengthTemp += await svgDraw.getVirtualNodeHeight(svg, eventNode, conf);
} }
// Add spacing between events (10px per event except the last one)
if (task.events.length > 0) { if (task.events.length > 0) {
maxEventLineLengthTemp += (task.events.length - 1) * 10; maxEventLineLengthTemp += (task.events.length - 1) * 10;
} }
@@ -136,7 +135,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
log.debug('maxTaskHeight before draw', maxTaskHeight); log.debug('maxTaskHeight before draw', maxTaskHeight);
if (sections && sections.length > 0) { if (sections && sections.length > 0) {
sections.forEach((section) => { for (const section of sections) {
//filter task where tasks.section == section //filter task where tasks.section == section
const tasksForSection = tasks.filter((task) => task.section === section); const tasksForSection = tasks.filter((task) => task.section === section);
@@ -150,7 +149,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
}; };
log.debug('sectionNode', sectionNode); log.debug('sectionNode', sectionNode);
const sectionNodeWrapper = svg.append('g'); const sectionNodeWrapper = svg.append('g');
const node = svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf); const node = await svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf);
log.debug('sectionNode output', node); log.debug('sectionNode output', node);
sectionNodeWrapper.attr('transform', `translate(${masterX}, ${sectionBeginY})`); sectionNodeWrapper.attr('transform', `translate(${masterX}, ${sectionBeginY})`);
@@ -159,7 +158,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
//draw tasks for this section //draw tasks for this section
if (tasksForSection.length > 0) { if (tasksForSection.length > 0) {
drawTasks( await drawTasks(
svg, svg,
tasksForSection, tasksForSection,
sectionNumber, sectionNumber,
@@ -178,11 +177,11 @@ export const draw = function (text: string, id: string, version: string, diagObj
masterY = sectionBeginY; masterY = sectionBeginY;
sectionNumber++; sectionNumber++;
}); }
} else { } else {
//draw tasks //draw tasks
hasSections = false; hasSections = false;
drawTasks( await drawTasks(
svg, svg,
tasks, tasks,
sectionNumber, sectionNumber,
@@ -236,7 +235,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
// addSVGAccessibilityFields(diagObj.db, diagram, id); // addSVGAccessibilityFields(diagObj.db, diagram, id);
}; };
export const drawTasks = function ( export const drawTasks = async function (
diagram: Selection<SVGElement, unknown, null, undefined>, diagram: Selection<SVGElement, unknown, null, undefined>,
tasks: TimelineTask[], tasks: TimelineTask[],
sectionColor: number, sectionColor: number,
@@ -265,7 +264,7 @@ export const drawTasks = function (
// create task wrapper // create task wrapper
const taskWrapper = diagram.append('g').attr('class', 'taskWrapper'); const taskWrapper = diagram.append('g').attr('class', 'taskWrapper');
const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf); const node = await svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf);
const taskHeight = node.height; const taskHeight = node.height;
//log task height //log task height
log.debug('taskHeight after draw', taskHeight); log.debug('taskHeight after draw', taskHeight);
@@ -282,7 +281,7 @@ export const drawTasks = function (
//add margin to task //add margin to task
masterY += 100; masterY += 100;
lineLength = lineLength =
lineLength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf); lineLength + (await drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf));
masterY -= 100; masterY -= 100;
lineWrapper lineWrapper
@@ -307,7 +306,7 @@ export const drawTasks = function (
masterY = masterY - 10; masterY = masterY - 10;
}; };
export const drawEvents = function ( export const drawEvents = async function (
diagram: Selection<SVGElement, unknown, null, undefined>, diagram: Selection<SVGElement, unknown, null, undefined>,
events: string[], events: string[],
sectionColor: number, sectionColor: number,
@@ -334,7 +333,7 @@ export const drawEvents = function (
log.debug('eventNode', eventNode); log.debug('eventNode', eventNode);
// create event wrapper // create event wrapper
const eventWrapper = diagram.append('g').attr('class', 'eventWrapper'); const eventWrapper = diagram.append('g').attr('class', 'eventWrapper');
const node = svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf); const node = await svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf);
const eventHeight = node.height; const eventHeight = node.height;
maxEventHeight = maxEventHeight + eventHeight; maxEventHeight = maxEventHeight + eventHeight;
eventWrapper.attr('transform', `translate(${masterX}, ${masterY})`); eventWrapper.attr('transform', `translate(${masterX}, ${masterY})`);

View File

@@ -17,6 +17,7 @@ While directives allow you to change most of the default configuration settings,
Mermaid basically supports two types of configuration options to be overridden by directives. Mermaid basically supports two types of configuration options to be overridden by directives.
1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are: 1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are:
- theme - theme
- fontFamily - fontFamily
- logLevel - logLevel

View File

@@ -23,6 +23,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
- **Plugins** - A plugin system for extending the functionality of Mermaid. - **Plugins** - A plugin system for extending the functionality of Mermaid.
Official Mermaid Chart plugins: Official Mermaid Chart plugins:
- [Mermaid Chart GPT](https://chatgpt.com/g/g-684cc36f30208191b21383b88650a45d-mermaid-chart-diagrams-and-charts) - [Mermaid Chart GPT](https://chatgpt.com/g/g-684cc36f30208191b21383b88650a45d-mermaid-chart-diagrams-and-charts)
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview) - [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud) - [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)

View File

@@ -33,11 +33,13 @@ The Mermaid Chart team is excited to introduce a new Visual Editor for Flowchart
Learn more: Learn more:
- Visual Editor For Flowcharts - Visual Editor For Flowcharts
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts) - [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts)
- [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0) - [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0)
- Visual Editor For Sequence diagrams - Visual Editor For Sequence diagrams
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams) - [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams)
- [Demo video](https://youtu.be/imc2u5_N6Dc) - [Demo video](https://youtu.be/imc2u5_N6Dc)

View File

@@ -21,21 +21,21 @@
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"jiti": "^2.4.2", "jiti": "^2.4.2",
"mermaid": "workspace:^", "mermaid": "workspace:^",
"vue": "^3.5.19" "vue": "^3.4.38"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/carbon": "^1.2.13", "@iconify-json/carbon": "^1.1.37",
"@unocss/reset": "^66.0.0", "@unocss/reset": "^66.0.0",
"@vite-pwa/vitepress": "^1.0.0", "@vite-pwa/vitepress": "^1.0.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.0",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"https-localhost": "^4.7.1", "https-localhost": "^4.7.1",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"unocss": "^66.0.0", "unocss": "^66.0.0",
"unplugin-vue-components": "^28.4.1", "unplugin-vue-components": "^28.4.0",
"vite": "^6.1.1", "vite": "^6.1.1",
"vite-plugin-pwa": "^1.0.3", "vite-plugin-pwa": "^1.0.0",
"vitepress": "1.6.4", "vitepress": "1.6.3",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
} }
} }

View File

@@ -83,6 +83,7 @@ The following unfinished features are not supported in the short term.
- [ ] Legend - [ ] Legend
- [x] System Context - [x] System Context
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link) - [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
- [x] Person_Ext - [x] Person_Ext
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link) - [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
@@ -96,6 +97,7 @@ The following unfinished features are not supported in the short term.
- [x] System_Boundary - [x] System_Boundary
- [x] Container diagram - [x] Container diagram
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ContainerDb - [x] ContainerDb
- [x] ContainerQueue - [x] ContainerQueue
@@ -105,6 +107,7 @@ The following unfinished features are not supported in the short term.
- [x] Container_Boundary(alias, label, ?tags, $link) - [x] Container_Boundary(alias, label, ?tags, $link)
- [x] Component diagram - [x] Component diagram
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] ComponentDb - [x] ComponentDb
- [x] ComponentQueue - [x] ComponentQueue
@@ -113,15 +116,18 @@ The following unfinished features are not supported in the short term.
- [x] ComponentQueue_Ext - [x] ComponentQueue_Ext
- [x] Dynamic diagram - [x] Dynamic diagram
- [x] RelIndex(index, from, to, label, ?tags, $link) - [x] RelIndex(index, from, to, label, ?tags, $link)
- [x] Deployment diagram - [x] Deployment diagram
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link) - [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node() - [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node() - [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node() - [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
- [x] Relationship Types - [x] Relationship Types
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link) - [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
- [x] BiRel (bidirectional relationship) - [x] BiRel (bidirectional relationship)
- [x] Rel_U, Rel_Up - [x] Rel_U, Rel_Up

View File

@@ -590,11 +590,17 @@ flowchart TD
- `b` - `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image. - **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image. - **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are: - **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the width (`w`) accordingly to the height (`h`). If not defined, this will default to `off` Possible values are:
- `on` - `on`
- `off` - `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging. If you want to resize an image, but keep the same aspect ratio, set `h`, and set `constraint: on` to constrain the aspect ratio. E.g.
```mermaid
flowchart TD
%% My image with a constrained aspect ratio
A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" }
```
## Links between nodes ## Links between nodes

View File

@@ -46,6 +46,78 @@ sequenceDiagram
Bob->>Alice: Hi Alice Bob->>Alice: Hi Alice
``` ```
### Boundary
If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "boundary" }
participant Bob
Alice->>Bob: Request from boundary
Bob->>Alice: Response to boundary
```
### Control
If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob
Alice->>Bob: Control request
Bob->>Alice: Control response
```
### Entity
If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "entity" }
participant Bob
Alice->>Bob: Entity request
Bob->>Alice: Entity response
```
### Database
If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob
Alice->>Bob: DB query
Bob->>Alice: DB result
```
### Collections
If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob
Alice->>Bob: Collections request
Bob->>Alice: Collections response
```
### Queue
If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob
Alice->>Bob: Queue message
Bob->>Alice: Queue response
```
### Aliases ### Aliases
The actor can have a convenient identifier and a descriptive label. The actor can have a convenient identifier and a descriptive label.

View File

@@ -13,6 +13,18 @@ export interface NodeMetaData {
ticket?: string; ticket?: string;
} }
export interface ParticipantMetaData {
type?:
| 'actor'
| 'participant'
| 'boundary'
| 'control'
| 'entity'
| 'database'
| 'collections'
| 'queue';
}
export interface EdgeMetaData { export interface EdgeMetaData {
animation?: 'fast' | 'slow'; animation?: 'fast' | 'slow';
animate?: boolean; animate?: boolean;

View File

@@ -44,16 +44,20 @@ ValueConverter -->> Package: Return AST
``` ```
- When to override `TokenBuilder`? - When to override `TokenBuilder`?
- To override keyword rules. - To override keyword rules.
- To override terminal rules that need a custom function. - To override terminal rules that need a custom function.
- To manually reorder the list of rules. - To manually reorder the list of rules.
- When to override `Lexer`? - When to override `Lexer`?
- To modify input before tokenizing. - To modify input before tokenizing.
- To insert/modify tokens that cannot or have not been parsed. - To insert/modify tokens that cannot or have not been parsed.
- When to override `LangiumParser`? - When to override `LangiumParser`?
- To insert or modify attributes that can't be parsed. - To insert or modify attributes that can't be parsed.
- When to override `ValueConverter`? - When to override `ValueConverter`?
- To modify the returned value from the parser. - To modify the returned value from the parser.

View File

@@ -154,6 +154,7 @@
### Minor Changes ### Minor Changes
- [#6408](https://github.com/mermaid-js/mermaid/pull/6408) [`ad65313`](https://github.com/mermaid-js/mermaid/commit/ad653138e16765d095613a6e5de86dc5e52ac8f0) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: restore curve type configuration functionality for flowcharts. This fixes the issue where curve type settings were not being applied when configured through any of the following methods: - [#6408](https://github.com/mermaid-js/mermaid/pull/6408) [`ad65313`](https://github.com/mermaid-js/mermaid/commit/ad653138e16765d095613a6e5de86dc5e52ac8f0) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: restore curve type configuration functionality for flowcharts. This fixes the issue where curve type settings were not being applied when configured through any of the following methods:
- Config - Config
- Init directive (%%{ init: { 'flowchart': { 'curve': '...' } } }%%) - Init directive (%%{ init: { 'flowchart': { 'curve': '...' } } }%%)
- LinkStyle command (linkStyle default interpolate ...) - LinkStyle command (linkStyle default interpolate ...)
@@ -172,12 +173,14 @@
### Minor Changes ### Minor Changes
- [#6187](https://github.com/mermaid-js/mermaid/pull/6187) [`7809b5a`](https://github.com/mermaid-js/mermaid/commit/7809b5a93fae127f45727071f5ff14325222c518) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Flowchart new syntax for node metadata bugs - [#6187](https://github.com/mermaid-js/mermaid/pull/6187) [`7809b5a`](https://github.com/mermaid-js/mermaid/commit/7809b5a93fae127f45727071f5ff14325222c518) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Flowchart new syntax for node metadata bugs
- Incorrect label mapping for nodes when using `&` - Incorrect label mapping for nodes when using `&`
- Syntax error when `}` with trailing spaces before new line - Syntax error when `}` with trailing spaces before new line
- [#6136](https://github.com/mermaid-js/mermaid/pull/6136) [`ec0d9c3`](https://github.com/mermaid-js/mermaid/commit/ec0d9c389aa6018043187654044c1e0b5aa4f600) Thanks [@knsv](https://github.com/knsv)! - Adding support for animation of flowchart edges - [#6136](https://github.com/mermaid-js/mermaid/pull/6136) [`ec0d9c3`](https://github.com/mermaid-js/mermaid/commit/ec0d9c389aa6018043187654044c1e0b5aa4f600) Thanks [@knsv](https://github.com/knsv)! - Adding support for animation of flowchart edges
- [#6373](https://github.com/mermaid-js/mermaid/pull/6373) [`05bdf0e`](https://github.com/mermaid-js/mermaid/commit/05bdf0e20e2629fe77513218fbd4e28e65f75882) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Upgrade Requirement and ER diagram to use the common renderer flow - [#6373](https://github.com/mermaid-js/mermaid/pull/6373) [`05bdf0e`](https://github.com/mermaid-js/mermaid/commit/05bdf0e20e2629fe77513218fbd4e28e65f75882) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Upgrade Requirement and ER diagram to use the common renderer flow
- Added support for directions - Added support for directions
- Added support for hand drawn look - Added support for hand drawn look
@@ -226,6 +229,7 @@
- [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type - [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type
- [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes: - [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes:
- Updates the class diagram to the new unified way of rendering. - Updates the class diagram to the new unified way of rendering.
- Includes a new "classBox" shape to be used in diagrams - Includes a new "classBox" shape to be used in diagrams
- Other updates such as: - Other updates such as:

3406
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff