mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-12 03:49:43 +02:00
Compare commits
218 Commits
@mermaid-j
...
renovate/p
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cf08ba0ef8 | ||
![]() |
baf4093e8d | ||
![]() |
027d7b6368 | ||
![]() |
b80ea26a2b | ||
![]() |
f88986a87d | ||
![]() |
e16f0848ab | ||
![]() |
e7811886c3 | ||
![]() |
32eda8565c | ||
![]() |
3840451fda | ||
![]() |
cfe9238882 | ||
![]() |
c1f2d052be | ||
![]() |
bce40e180a | ||
![]() |
f47dec3680 | ||
![]() |
88dc4beade | ||
![]() |
e9232088c0 | ||
![]() |
e96614ab86 | ||
![]() |
73115cb416 | ||
![]() |
480438bd52 | ||
![]() |
34fc8bddc4 | ||
![]() |
4dd89e439f | ||
![]() |
150177c449 | ||
![]() |
bf58ed2b53 | ||
![]() |
827ced0014 | ||
![]() |
133d46bde2 | ||
![]() |
e1017266ac | ||
![]() |
404fdaf2ff | ||
![]() |
2e1d156d66 | ||
![]() |
e863ad1547 | ||
![]() |
e231b692fd | ||
![]() |
68c365f906 | ||
![]() |
494c7294cb | ||
![]() |
fb20ee99eb | ||
![]() |
1a22154a3a | ||
![]() |
2972bf25bf | ||
![]() |
6b1a7a9e1a | ||
![]() |
33bc4a0b4e | ||
![]() |
c6f25167a2 | ||
![]() |
a150f92fb0 | ||
![]() |
5d31ded7a0 | ||
![]() |
0ed31bfa2c | ||
![]() |
51b9185a6b | ||
![]() |
b219497847 | ||
![]() |
7e96c89be5 | ||
![]() |
16a8d0e794 | ||
![]() |
7bb9981d8a | ||
![]() |
ea3d38bf64 | ||
![]() |
8f628b85e5 | ||
![]() |
defc922acd | ||
![]() |
88ae8d1f2b | ||
![]() |
8c7c9ac38a | ||
![]() |
0e146d50f7 | ||
![]() |
454e1e3927 | ||
![]() |
4f9875fd4e | ||
![]() |
82800a2c84 | ||
![]() |
27e700debd | ||
![]() |
01e47333d5 | ||
![]() |
d47ba7c2d1 | ||
![]() |
b1c4eb3f5c | ||
![]() |
869709a75f | ||
![]() |
310fcd2292 | ||
![]() |
85e9ca2a0f | ||
![]() |
65d225cb2c | ||
![]() |
04b6fc1280 | ||
![]() |
21eddc3f23 | ||
![]() |
f46a151075 | ||
![]() |
b7e9d02b7c | ||
![]() |
0ef3130510 | ||
![]() |
862d40cc3a | ||
![]() |
4b63214a72 | ||
![]() |
4937ebc058 | ||
![]() |
00f5700320 | ||
![]() |
e32dc8513f | ||
![]() |
50127f3ffe | ||
![]() |
29bb0e3dca | ||
![]() |
1221de4c2d | ||
![]() |
c41e08cb7a | ||
![]() |
4760ed8893 | ||
![]() |
31ecf31c2e | ||
![]() |
b52766653c | ||
![]() |
6d9fad01a9 | ||
![]() |
8322a63598 | ||
![]() |
075e1b5e1f | ||
![]() |
3c9bd7be29 | ||
![]() |
6995248443 | ||
![]() |
93467a6fce | ||
![]() |
95d48e3497 | ||
![]() |
202172135d | ||
![]() |
b94ab243a8 | ||
![]() |
11c8848e1f | ||
![]() |
231fcc700f | ||
![]() |
8ba7520acc | ||
![]() |
e0a5a2489d | ||
![]() |
bd400a5130 | ||
![]() |
d35f84f337 | ||
![]() |
8dadb853a0 | ||
![]() |
e438e035bc | ||
![]() |
2bc5b6d2fa | ||
![]() |
a700e8bf97 | ||
![]() |
7091792694 | ||
![]() |
efd94b705d | ||
![]() |
2cfebef122 | ||
![]() |
9ec989e633 | ||
![]() |
61d9143acb | ||
![]() |
c88f74a6ee | ||
![]() |
6377d6f64d | ||
![]() |
1b0bc05fc2 | ||
![]() |
45edeb9307 | ||
![]() |
211974b2b7 | ||
![]() |
1f5ad3e315 | ||
![]() |
d7848e8a3d | ||
![]() |
c0e2d4a23b | ||
![]() |
89b9f0df70 | ||
![]() |
e9011567bd | ||
![]() |
0429970d58 | ||
![]() |
ecad9cee6c | ||
![]() |
1e8a9f76a9 | ||
![]() |
e42fdf1c54 | ||
![]() |
c75566ddc3 | ||
![]() |
7bdcf93412 | ||
![]() |
d86e46b705 | ||
![]() |
71e09bcaef | ||
![]() |
7e9577dffd | ||
![]() |
cba659d097 | ||
![]() |
f7a0844a31 | ||
![]() |
2817383714 | ||
![]() |
180dc7bdff | ||
![]() |
cc9581842d | ||
![]() |
d782e4bb17 | ||
![]() |
ba9ad9385b | ||
![]() |
91edfa40f7 | ||
![]() |
c8b00bb929 | ||
![]() |
57eadbf6af | ||
![]() |
a906adce26 | ||
![]() |
227cef05b3 | ||
![]() |
a6d26ef6c3 | ||
![]() |
80c6faf4d5 | ||
![]() |
2b3f94eb7d | ||
![]() |
9f6ee53382 | ||
![]() |
3248bf3da4 | ||
![]() |
dd36046e23 | ||
![]() |
1507435e15 | ||
![]() |
e7a7ff8a2a | ||
![]() |
68fc68c239 | ||
![]() |
769b362005 | ||
![]() |
e4d3aa4610 | ||
![]() |
716548548a | ||
![]() |
4bece53a3c | ||
![]() |
68c01b76bf | ||
![]() |
28717e108d | ||
![]() |
297be4a868 | ||
![]() |
fb6ace73b5 | ||
![]() |
688d9b383d | ||
![]() |
e68424d748 | ||
![]() |
bf362673fc | ||
![]() |
d042b21b12 | ||
![]() |
677ff82d13 | ||
![]() |
204a9a338f | ||
![]() |
981829a426 | ||
![]() |
327a5aa9fd | ||
![]() |
6a6a39ff33 | ||
![]() |
b296db9a33 | ||
![]() |
01ce84d8ee | ||
![]() |
f48e663d4c | ||
![]() |
a4aa2bd355 | ||
![]() |
b51b9d50c2 | ||
![]() |
848f69a75c | ||
![]() |
99dbeba407 | ||
![]() |
d525acc05b | ||
![]() |
b61780f735 | ||
![]() |
1d3681053b | ||
![]() |
93df13898f | ||
![]() |
074f18dfb8 | ||
![]() |
d7308b0f43 | ||
![]() |
2f1860386a | ||
![]() |
f0bca7da55 | ||
![]() |
6fcdf5bfcc | ||
![]() |
e2ce0450c1 | ||
![]() |
c95c64139d | ||
![]() |
a7f12f1baa | ||
![]() |
2a8653de2b | ||
![]() |
a92c3bb251 | ||
![]() |
3677abe9e5 | ||
![]() |
95847ad236 | ||
![]() |
e0152fb873 | ||
![]() |
2298b96d8e | ||
![]() |
5db83365b6 | ||
![]() |
4915545429 | ||
![]() |
341a81a113 | ||
![]() |
8a62b4cace | ||
![]() |
334fe87bc6 | ||
![]() |
283e7810d2 | ||
![]() |
237d01d510 | ||
![]() |
ccafc20917 | ||
![]() |
d5cb4eaa59 | ||
![]() |
425fb7ee33 | ||
![]() |
cd6f8e5a24 | ||
![]() |
afeb761296 | ||
![]() |
3abcfbb8d2 | ||
![]() |
ee82694645 | ||
![]() |
012530e98e | ||
![]() |
a4a27611dd | ||
![]() |
5055ade44e | ||
![]() |
b61bec8faf | ||
![]() |
76d073b027 | ||
![]() |
cc476d59d1 | ||
![]() |
f0445b74d1 | ||
![]() |
ba52eef257 | ||
![]() |
c13ce2a5c0 | ||
![]() |
d2463f41b5 | ||
![]() |
eadb343292 | ||
![]() |
e7208622f7 | ||
![]() |
fbae611406 | ||
![]() |
34027bc589 | ||
![]() |
f2eef37599 | ||
![]() |
1e3ea13323 | ||
![]() |
4c8c48cde9 | ||
![]() |
c8e50276e8 | ||
![]() |
1e6419a63f |
@@ -33,6 +33,11 @@ export const packageOptions = {
|
|||||||
packageName: 'mermaid-layout-elk',
|
packageName: 'mermaid-layout-elk',
|
||||||
file: 'layouts.ts',
|
file: 'layouts.ts',
|
||||||
},
|
},
|
||||||
|
'mermaid-layout-tidy-tree': {
|
||||||
|
name: 'mermaid-layout-tidy-tree',
|
||||||
|
packageName: 'mermaid-layout-tidy-tree',
|
||||||
|
file: 'index.ts',
|
||||||
|
},
|
||||||
examples: {
|
examples: {
|
||||||
name: 'mermaid-examples',
|
name: 'mermaid-examples',
|
||||||
packageName: 'examples',
|
packageName: 'examples',
|
||||||
|
5
.changeset/clean-wolves-turn.md
Normal file
5
.changeset/clean-wolves-turn.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Render newlines as spaces in class diagrams
|
5
.changeset/crazy-loops-matter.md
Normal file
5
.changeset/crazy-loops-matter.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Handle arrows correctly when auto number is enabled
|
5
.changeset/four-eyes-wish.md
Normal file
5
.changeset/four-eyes-wish.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: Ensure edge label color is applied when using classDef with edge IDs
|
5
.changeset/hungry-baths-glow.md
Normal file
5
.changeset/hungry-baths-glow.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.
|
7
.changeset/hungry-guests-drive.md
Normal file
7
.changeset/hungry-guests-drive.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
'@mermaid-js/layout-tidy-tree': minor
|
||||||
|
'@mermaid-js/layout-elk': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
|
9
.changeset/revert-marked-dependency.md
Normal file
9
.changeset/revert-marked-dependency.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
'mermaid': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: revert marked dependency from ^15.0.7 to ^16.0.0
|
||||||
|
|
||||||
|
- Reverted marked package version to ^16.0.0 for better compatibility
|
||||||
|
- This is a dependency update that maintains API compatibility
|
||||||
|
- All tests pass with the updated version
|
@@ -5,6 +5,7 @@ bmatrix
|
|||||||
braintree
|
braintree
|
||||||
catmull
|
catmull
|
||||||
compositTitleSize
|
compositTitleSize
|
||||||
|
cose
|
||||||
curv
|
curv
|
||||||
doublecircle
|
doublecircle
|
||||||
elems
|
elems
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
BRANDES
|
BRANDES
|
||||||
|
Buzan
|
||||||
circo
|
circo
|
||||||
handDrawn
|
handDrawn
|
||||||
KOEPF
|
KOEPF
|
||||||
|
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/init@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||||
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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/autobuild@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||||
|
|
||||||
# ℹ️ 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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/analyze@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||||
|
5
.github/workflows/e2e-applitools.yml
vendored
5
.github/workflows/e2e-applitools.yml
vendored
@@ -23,9 +23,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
e2e-applitools:
|
e2e-applitools:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
|
||||||
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
|
|
||||||
options: --user 1001
|
|
||||||
steps:
|
steps:
|
||||||
- if: ${{ ! env.USE_APPLI }}
|
- if: ${{ ! env.USE_APPLI }}
|
||||||
name: Warn if not using Applitools
|
name: Warn if not using Applitools
|
||||||
@@ -56,7 +53,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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
id: cypress
|
id: cypress
|
||||||
with:
|
with:
|
||||||
start: pnpm run dev
|
start: pnpm run dev
|
||||||
|
6
.github/workflows/e2e-timings.yml
vendored
6
.github/workflows/e2e-timings.yml
vendored
@@ -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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
with:
|
with:
|
||||||
runTests: false
|
runTests: false
|
||||||
|
|
||||||
- name: Cypress run
|
- name: Cypress run
|
||||||
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
id: cypress
|
id: cypress
|
||||||
with:
|
with:
|
||||||
install: false
|
install: false
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Commit and create pull request
|
- name: Commit and create pull request
|
||||||
uses: peter-evans/create-pull-request@cb4d3bfce175d44325c6b7697f81e0afe8a79bdf
|
uses: peter-evans/create-pull-request@18e469570b1cf0dfc11d60ec121099f8ff3e617a
|
||||||
with:
|
with:
|
||||||
add-paths: |
|
add-paths: |
|
||||||
cypress/timings.json
|
cypress/timings.json
|
||||||
|
10
.github/workflows/e2e.yml
vendored
10
.github/workflows/e2e.yml
vendored
@@ -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@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||||
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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
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@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||||
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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||||
id: cypress
|
id: cypress
|
||||||
with:
|
with:
|
||||||
install: false
|
install: false
|
||||||
|
2
.github/workflows/link-checker.yml
vendored
2
.github/workflows/link-checker.yml
vendored
@@ -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@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||||
with:
|
with:
|
||||||
path: .lycheecache
|
path: .lycheecache
|
||||||
key: cache-lychee-${{ github.sha }}
|
key: cache-lychee-${{ github.sha }}
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
|
uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10
|
||||||
with:
|
with:
|
||||||
version: pnpm changeset:version
|
version: pnpm changeset:version
|
||||||
publish: pnpm changeset:publish
|
publish: pnpm changeset:publish
|
||||||
|
6
.github/workflows/scorecard.yml
vendored
6
.github/workflows/scorecard.yml
vendored
@@ -20,18 +20,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run analysis
|
- name: Run analysis
|
||||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||||
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@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
uses: github/codeql-action/upload-sarif@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
2
.github/workflows/update-browserlist.yml
vendored
2
.github/workflows/update-browserlist.yml
vendored
@@ -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@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
with:
|
with:
|
||||||
branch: update-browserslist
|
branch: update-browserslist
|
||||||
title: Update Browserslist
|
title: Update Browserslist
|
||||||
|
2
.github/workflows/validate-lockfile.yml
vendored
2
.github/workflows/validate-lockfile.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
# 2) No unwanted vitepress paths
|
# 2) No unwanted vitepress paths
|
||||||
if grep -qF 'packages/mermaid/src/vitepress' pnpm-lock.yaml; then
|
if grep -qF 'packages/mermaid/src/vitepress' pnpm-lock.yaml; then
|
||||||
issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run `rm -rf packages/mermaid/src/vitepress && pnpm install` to regenerate.")
|
issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run \`rm -rf packages/mermaid/src/vitepress && pnpm install\` to regenerate.")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3) Lockfile only changes when package.json changes
|
# 3) Lockfile only changes when package.json changes
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ node_modules/
|
|||||||
coverage/
|
coverage/
|
||||||
.idea/
|
.idea/
|
||||||
.pnpm-store/
|
.pnpm-store/
|
||||||
|
.instructions/
|
||||||
|
|
||||||
dist
|
dist
|
||||||
v8-compile-cache-0
|
v8-compile-cache-0
|
||||||
|
@@ -524,5 +524,18 @@ describe('Class diagram', () => {
|
|||||||
`,
|
`,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
it('should handle an empty class body with empty braces', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` classDiagram
|
||||||
|
class FooBase~T~ {}
|
||||||
|
class Bar {
|
||||||
|
+Zip
|
||||||
|
+Zap()
|
||||||
|
}
|
||||||
|
FooBase <|-- Ba
|
||||||
|
`,
|
||||||
|
{ flowchart: { defaultRenderer: 'elk' } }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1186,4 +1186,17 @@ end
|
|||||||
imgSnapshotTest(graph, { htmlLabels: false });
|
imgSnapshotTest(graph, { htmlLabels: false });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('V2 - 17: should apply class def colour to edge label', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` graph LR
|
||||||
|
id1(Start) link@-- "Label" -->id2(Stop)
|
||||||
|
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
|
|
||||||
|
class id2 myClass
|
||||||
|
classDef myClass fill:#bbf,stroke:#f66,stroke-width:2px,color:white,stroke-dasharray: 5 5
|
||||||
|
class link myClass
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
79
cypress/integration/rendering/mindmap-tidy-tree.spec.js
Normal file
79
cypress/integration/rendering/mindmap-tidy-tree.spec.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||||
|
|
||||||
|
describe('Mindmap Tidy Tree', () => {
|
||||||
|
it('1-tidy-tree: should render a simple mindmap without children', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` ---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('2-tidy-tree: should render a simple mindmap', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` ---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('3-tidy-tree: should render a mindmap with different shapes', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` ---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
id)I am a cloud(
|
||||||
|
id))I am a bang((
|
||||||
|
Tools
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('4-tidy-tree: should render a mindmap with children', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
` ---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
((This is a mindmap))
|
||||||
|
child1
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3
|
||||||
|
grandchild 5
|
||||||
|
grandchild 6
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@@ -159,12 +159,10 @@ root
|
|||||||
});
|
});
|
||||||
it('square shape', () => {
|
it('square shape', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
|
||||||
root[
|
root[
|
||||||
The root
|
The root
|
||||||
]
|
]`,
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -172,12 +170,10 @@ mindmap
|
|||||||
});
|
});
|
||||||
it('rounded rect shape', () => {
|
it('rounded rect shape', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
|
||||||
root((
|
root((
|
||||||
The root
|
The root
|
||||||
))
|
))`,
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -185,12 +181,10 @@ mindmap
|
|||||||
});
|
});
|
||||||
it('circle shape', () => {
|
it('circle shape', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
|
||||||
root(
|
root(
|
||||||
The root
|
The root
|
||||||
)
|
)`,
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -198,10 +192,8 @@ mindmap
|
|||||||
});
|
});
|
||||||
it('default shape', () => {
|
it('default shape', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
The root`,
|
||||||
The root
|
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -209,12 +201,10 @@ mindmap
|
|||||||
});
|
});
|
||||||
it('adding children', () => {
|
it('adding children', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
|
||||||
The root
|
The root
|
||||||
child1
|
child1
|
||||||
child2
|
child2`,
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -222,13 +212,11 @@ mindmap
|
|||||||
});
|
});
|
||||||
it('adding grand children', () => {
|
it('adding grand children', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`mindmap
|
||||||
mindmap
|
|
||||||
The root
|
The root
|
||||||
child1
|
child1
|
||||||
child2
|
child2
|
||||||
child3
|
child3`,
|
||||||
`,
|
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
shouldHaveRoot
|
shouldHaveRoot
|
||||||
@@ -240,25 +228,21 @@ mindmap
|
|||||||
`mindmap
|
`mindmap
|
||||||
id1[\`**Start** with
|
id1[\`**Start** with
|
||||||
a second line 😎\`]
|
a second line 😎\`]
|
||||||
id2[\`The dog in **the** hog... a *very long text* about it
|
id2[\`The dog in **the** hog... a *very long text* about it Word!\`]`
|
||||||
Word!\`]
|
|
||||||
`
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Include char sequence "graph" in text (#6795)', () => {
|
describe('Include char sequence "graph" in text (#6795)', () => {
|
||||||
it('has a label with char sequence "graph"', () => {
|
it('has a label with char sequence "graph"', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
` mindmap
|
||||||
mindmap
|
|
||||||
root
|
root
|
||||||
Photograph
|
Photograph
|
||||||
Waterfall
|
Waterfall
|
||||||
Landscape
|
Landscape
|
||||||
Geography
|
Geography
|
||||||
Mountains
|
Mountains
|
||||||
Rocks
|
Rocks`,
|
||||||
`,
|
|
||||||
{ flowchart: { defaultRenderer: 'elk' } }
|
{ flowchart: { defaultRenderer: 'elk' } }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
659
cypress/integration/rendering/sequencediagram-v2.spec.js
Normal file
659
cypress/integration/rendering/sequencediagram-v2.spec.js
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -130,6 +130,76 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
<pre id="diagram4" class="mermaid">
|
<pre id="diagram4" class="mermaid">
|
||||||
---
|
---
|
||||||
config:
|
config:
|
||||||
@@ -191,8 +261,145 @@ treemap
|
|||||||
"Item B2": 25
|
"Item B2": 25
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram4" class="mermaid2">
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
flowchart TB
|
||||||
|
A --> n0["1"]
|
||||||
|
A --> n1["2"]
|
||||||
|
A --> n2["3"]
|
||||||
|
A --> n3["4"] --> Q & R & S & T
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart TB
|
||||||
|
A --> n0["1"]
|
||||||
|
A --> n1["2"]
|
||||||
|
A --> n2["3"]
|
||||||
|
A --> n3["4"] --> Q & R & S & T
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
flowchart LR
|
flowchart LR
|
||||||
AB["apa@apa@"] --> B(("`apa@apa`"))
|
root{mindmap} --- Origins --- Europe
|
||||||
|
Origins --> Asia
|
||||||
|
root --- Background --- Rich
|
||||||
|
Background --- Poor
|
||||||
|
subgraph apa
|
||||||
|
Background
|
||||||
|
Poor
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart LR
|
||||||
|
root{mindmap} --- Origins --- Europe
|
||||||
|
Origins --> Asia
|
||||||
|
root --- Background --- Rich
|
||||||
|
Background --- Poor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram4" class="mermaid2">
|
<pre id="diagram4" class="mermaid2">
|
||||||
flowchart
|
flowchart
|
||||||
@@ -274,6 +481,44 @@ config:
|
|||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram4" class="mermaid2">
|
<pre id="diagram4" class="mermaid2">
|
||||||
---
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart LR
|
||||||
|
a
|
||||||
|
subgraph s0["APA"]
|
||||||
|
subgraph s8["APA"]
|
||||||
|
subgraph s1["APA"]
|
||||||
|
D{"X"}
|
||||||
|
E[Q]
|
||||||
|
end
|
||||||
|
subgraph s3["BAPA"]
|
||||||
|
F[Q]
|
||||||
|
I
|
||||||
|
end
|
||||||
|
D --> I
|
||||||
|
D --> I
|
||||||
|
D --> I
|
||||||
|
|
||||||
|
I{"X"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
flowchart LR
|
||||||
|
a
|
||||||
|
D{"Use the editor"}
|
||||||
|
|
||||||
|
D -- Mermaid js --> I{"fa:fa-code Text"}
|
||||||
|
D-->I
|
||||||
|
D-->I
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid2">
|
||||||
|
---
|
||||||
config:
|
config:
|
||||||
layout: elk
|
layout: elk
|
||||||
---
|
---
|
||||||
|
376
cypress/platform/mindmap-layouts.html
Normal file
376
cypress/platform/mindmap-layouts.html
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Mermaid Quick Test Page</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
id)I am a cloud(
|
||||||
|
id))I am a bang((
|
||||||
|
Tools
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
id)I am a cloud(
|
||||||
|
id))I am a bang((
|
||||||
|
Tools
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
id)I am a cloud(
|
||||||
|
id))I am a bang((
|
||||||
|
Tools
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
id)I am a cloud(
|
||||||
|
id))I am a bang((
|
||||||
|
Tools
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
((This is a mindmap))
|
||||||
|
child1
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3
|
||||||
|
grandchild 5
|
||||||
|
grandchild 6
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: dagre
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
((This is a mindmap))
|
||||||
|
child1
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3
|
||||||
|
grandchild 5
|
||||||
|
grandchild 6
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
((This is a mindmap))
|
||||||
|
child1
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3
|
||||||
|
grandchild 5
|
||||||
|
grandchild 6
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: cose-bilkent
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
((This is a mindmap))
|
||||||
|
child1
|
||||||
|
grandchild 1
|
||||||
|
grandchild 2
|
||||||
|
child2
|
||||||
|
grandchild 3
|
||||||
|
grandchild 4
|
||||||
|
child3
|
||||||
|
grandchild 5
|
||||||
|
grandchild 6
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from '/mermaid.esm.mjs';
|
||||||
|
import tidytree from '/mermaid-layout-tidy-tree.esm.mjs';
|
||||||
|
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.registerLayoutLoaders(tidytree);
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'default',
|
||||||
|
logLevel: 3,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -1,5 +1,6 @@
|
|||||||
import externalExample from './mermaid-example-diagram.esm.mjs';
|
import externalExample from './mermaid-example-diagram.esm.mjs';
|
||||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||||
|
import tidyTree from './mermaid-layout-tidy-tree.esm.mjs';
|
||||||
import zenUml from './mermaid-zenuml.esm.mjs';
|
import zenUml from './mermaid-zenuml.esm.mjs';
|
||||||
import mermaid from './mermaid.esm.mjs';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
|
||||||
@@ -65,6 +66,7 @@ const contentLoaded = async function () {
|
|||||||
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
|
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
|
||||||
|
|
||||||
mermaid.registerLayoutLoaders(layouts);
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.registerLayoutLoaders(tidyTree);
|
||||||
mermaid.initialize(graphObj.mermaid);
|
mermaid.initialize(graphObj.mermaid);
|
||||||
/**
|
/**
|
||||||
* CC-BY-4.0
|
* CC-BY-4.0
|
||||||
|
@@ -2,219 +2,227 @@
|
|||||||
"durations": [
|
"durations": [
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/configuration.spec.js",
|
"spec": "cypress/integration/other/configuration.spec.js",
|
||||||
"duration": 6297
|
"duration": 5841
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/external-diagrams.spec.js",
|
"spec": "cypress/integration/other/external-diagrams.spec.js",
|
||||||
"duration": 2187
|
"duration": 2138
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/ghsa.spec.js",
|
"spec": "cypress/integration/other/ghsa.spec.js",
|
||||||
"duration": 3509
|
"duration": 3370
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/iife.spec.js",
|
"spec": "cypress/integration/other/iife.spec.js",
|
||||||
"duration": 2218
|
"duration": 2052
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/interaction.spec.js",
|
"spec": "cypress/integration/other/interaction.spec.js",
|
||||||
"duration": 12104
|
"duration": 12243
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/rerender.spec.js",
|
"spec": "cypress/integration/other/rerender.spec.js",
|
||||||
"duration": 2151
|
"duration": 2065
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/other/xss.spec.js",
|
"spec": "cypress/integration/other/xss.spec.js",
|
||||||
"duration": 33064
|
"duration": 31288
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/appli.spec.js",
|
"spec": "cypress/integration/rendering/appli.spec.js",
|
||||||
"duration": 3488
|
"duration": 3421
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/architecture.spec.ts",
|
"spec": "cypress/integration/rendering/architecture.spec.ts",
|
||||||
"duration": 106
|
"duration": 97
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/block.spec.js",
|
"spec": "cypress/integration/rendering/block.spec.js",
|
||||||
"duration": 18317
|
"duration": 18500
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/c4.spec.js",
|
"spec": "cypress/integration/rendering/c4.spec.js",
|
||||||
"duration": 5592
|
"duration": 5793
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
|
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
|
||||||
"duration": 39358
|
"duration": 40966
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
|
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
|
||||||
"duration": 37160
|
"duration": 39176
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
|
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
|
||||||
"duration": 23660
|
"duration": 23468
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
|
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
|
||||||
"duration": 36866
|
"duration": 38291
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/classDiagram.spec.js",
|
"spec": "cypress/integration/rendering/classDiagram.spec.js",
|
||||||
"duration": 17334
|
"duration": 16949
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
|
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
|
||||||
"duration": 9871
|
"duration": 9480
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/current.spec.js",
|
"spec": "cypress/integration/rendering/current.spec.js",
|
||||||
"duration": 2833
|
"duration": 2753
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
|
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
|
||||||
"duration": 85321
|
"duration": 88028
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/erDiagram.spec.js",
|
"spec": "cypress/integration/rendering/erDiagram.spec.js",
|
||||||
"duration": 15673
|
"duration": 15615
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
|
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
|
||||||
"duration": 3724
|
"duration": 3706
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
|
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
|
||||||
"duration": 41178
|
"duration": 43905
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
|
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
|
||||||
"duration": 29966
|
"duration": 31217
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
|
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
|
||||||
"duration": 7689
|
"duration": 7531
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
|
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
|
||||||
"duration": 24709
|
"duration": 25423
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
|
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
|
||||||
"duration": 45565
|
"duration": 49664
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/flowchart.spec.js",
|
"spec": "cypress/integration/rendering/flowchart.spec.js",
|
||||||
"duration": 31144
|
"duration": 32525
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/gantt.spec.js",
|
"spec": "cypress/integration/rendering/gantt.spec.js",
|
||||||
"duration": 20808
|
"duration": 20915
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/gitGraph.spec.js",
|
"spec": "cypress/integration/rendering/gitGraph.spec.js",
|
||||||
"duration": 49985
|
"duration": 53556
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/iconShape.spec.ts",
|
"spec": "cypress/integration/rendering/iconShape.spec.ts",
|
||||||
"duration": 273272
|
"duration": 283038
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/imageShape.spec.ts",
|
"spec": "cypress/integration/rendering/imageShape.spec.ts",
|
||||||
"duration": 55880
|
"duration": 59434
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/info.spec.ts",
|
"spec": "cypress/integration/rendering/info.spec.ts",
|
||||||
"duration": 3271
|
"duration": 3101
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/journey.spec.js",
|
"spec": "cypress/integration/rendering/journey.spec.js",
|
||||||
"duration": 7293
|
"duration": 7099
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/kanban.spec.ts",
|
"spec": "cypress/integration/rendering/kanban.spec.ts",
|
||||||
"duration": 7861
|
"duration": 7567
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/katex.spec.js",
|
"spec": "cypress/integration/rendering/katex.spec.js",
|
||||||
"duration": 3922
|
"duration": 3817
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
|
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
|
||||||
"duration": 2726
|
"duration": 2624
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": "cypress/integration/rendering/mindmap-tidy-tree.spec.js",
|
||||||
|
"duration": 4246
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/mindmap.spec.ts",
|
"spec": "cypress/integration/rendering/mindmap.spec.ts",
|
||||||
"duration": 11670
|
"duration": 11967
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/newShapes.spec.ts",
|
"spec": "cypress/integration/rendering/newShapes.spec.ts",
|
||||||
"duration": 146020
|
"duration": 151914
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
|
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
|
||||||
"duration": 114244
|
"duration": 116698
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/packet.spec.ts",
|
"spec": "cypress/integration/rendering/packet.spec.ts",
|
||||||
"duration": 5036
|
"duration": 4967
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/pie.spec.ts",
|
"spec": "cypress/integration/rendering/pie.spec.ts",
|
||||||
"duration": 6545
|
"duration": 6700
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
|
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
|
||||||
"duration": 9097
|
"duration": 8963
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/radar.spec.js",
|
"spec": "cypress/integration/rendering/radar.spec.js",
|
||||||
"duration": 5676
|
"duration": 5540
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/requirement.spec.js",
|
"spec": "cypress/integration/rendering/requirement.spec.js",
|
||||||
"duration": 2795
|
"duration": 2782
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
|
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
|
||||||
"duration": 51660
|
"duration": 54797
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/sankey.spec.ts",
|
"spec": "cypress/integration/rendering/sankey.spec.ts",
|
||||||
"duration": 6957
|
"duration": 6914
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": "cypress/integration/rendering/sequencediagram-v2.spec.js",
|
||||||
|
"duration": 20481
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
|
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
|
||||||
"duration": 36026
|
"duration": 38490
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
|
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
|
||||||
"duration": 29551
|
"duration": 30766
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
|
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
|
||||||
"duration": 17364
|
"duration": 16705
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/theme.spec.js",
|
"spec": "cypress/integration/rendering/theme.spec.js",
|
||||||
"duration": 30209
|
"duration": 30928
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/timeline.spec.ts",
|
"spec": "cypress/integration/rendering/timeline.spec.ts",
|
||||||
"duration": 8699
|
"duration": 8424
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/treemap.spec.ts",
|
"spec": "cypress/integration/rendering/treemap.spec.ts",
|
||||||
"duration": 12168
|
"duration": 12533
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/xyChart.spec.js",
|
"spec": "cypress/integration/rendering/xyChart.spec.js",
|
||||||
"duration": 21453
|
"duration": 21197
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"spec": "cypress/integration/rendering/zenuml.spec.js",
|
"spec": "cypress/integration/rendering/zenuml.spec.js",
|
||||||
"duration": 3577
|
"duration": 3455
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
|
@@ -29,7 +29,8 @@ 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** > [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
|
> **💡 Tip**
|
||||||
|
> [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
|
||||||
|
@@ -33,7 +33,8 @@ mindmap
|
|||||||
|
|
||||||
## Join the Development
|
## Join the Development
|
||||||
|
|
||||||
> **💡 Tip** > **Check out our** [**detailed contribution guide**](./contributing.md).
|
> **💡 Tip**
|
||||||
|
> **Check out our** [**detailed contribution guide**](./contributing.md).
|
||||||
|
|
||||||
Where to start:
|
Where to start:
|
||||||
|
|
||||||
@@ -47,7 +48,8 @@ Where to start:
|
|||||||
|
|
||||||
## A Question Or a Suggestion?
|
## A Question Or a Suggestion?
|
||||||
|
|
||||||
> **💡 Tip** > **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
|
> **💡 Tip**
|
||||||
|
> **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).
|
||||||
|
|
||||||
|
@@ -22,7 +22,6 @@ 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
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217)
|
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712)
|
||||||
2. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
|
2. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
|
||||||
3. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
|
3. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
|
||||||
4. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)
|
4. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)
|
||||||
|
40
docs/config/layouts.md
Normal file
40
docs/config/layouts.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/layouts.md](../../packages/mermaid/src/docs/config/layouts.md).
|
||||||
|
|
||||||
|
# Layouts
|
||||||
|
|
||||||
|
This page lists the available layout algorithms supported in Mermaid diagrams.
|
||||||
|
|
||||||
|
## Supported Layouts
|
||||||
|
|
||||||
|
- **elk**: [ELK (Eclipse Layout Kernel)](https://www.eclipse.org/elk/)
|
||||||
|
- **tidy-tree**: Tidy tree layout for hierarchical diagrams [Tidy Tree Configuration](/config/tidy-tree)
|
||||||
|
- **cose-bilkent**: Cose Bilkent layout for force-directed graphs
|
||||||
|
- **dagre**: Dagre layout for layered graphs
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
You can specify the layout in your diagram's YAML config or initialization options. For example:
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
B-->C;
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
B-->C;
|
||||||
|
```
|
@@ -19,6 +19,7 @@
|
|||||||
- [addDirective](functions/addDirective.md)
|
- [addDirective](functions/addDirective.md)
|
||||||
- [getConfig](functions/getConfig.md)
|
- [getConfig](functions/getConfig.md)
|
||||||
- [getSiteConfig](functions/getSiteConfig.md)
|
- [getSiteConfig](functions/getSiteConfig.md)
|
||||||
|
- [getUserDefinedConfig](functions/getUserDefinedConfig.md)
|
||||||
- [reset](functions/reset.md)
|
- [reset](functions/reset.md)
|
||||||
- [sanitize](functions/sanitize.md)
|
- [sanitize](functions/sanitize.md)
|
||||||
- [saveConfigFromInitialize](functions/saveConfigFromInitialize.md)
|
- [saveConfigFromInitialize](functions/saveConfigFromInitialize.md)
|
||||||
|
19
docs/config/setup/config/functions/getUserDefinedConfig.md
Normal file
19
docs/config/setup/config/functions/getUserDefinedConfig.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md).
|
||||||
|
|
||||||
|
[**mermaid**](../../README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Function: getUserDefinedConfig()
|
||||||
|
|
||||||
|
> **getUserDefinedConfig**(): [`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
|
||||||
|
|
||||||
|
Defined in: [packages/mermaid/src/config.ts:252](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L252)
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
[`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
|
@@ -10,10 +10,6 @@
|
|||||||
|
|
||||||
# mermaid
|
# mermaid
|
||||||
|
|
||||||
## Classes
|
|
||||||
|
|
||||||
- [UnknownDiagramError](classes/UnknownDiagramError.md)
|
|
||||||
|
|
||||||
## Interfaces
|
## Interfaces
|
||||||
|
|
||||||
- [DetailedError](interfaces/DetailedError.md)
|
- [DetailedError](interfaces/DetailedError.md)
|
||||||
@@ -27,6 +23,7 @@
|
|||||||
- [RenderOptions](interfaces/RenderOptions.md)
|
- [RenderOptions](interfaces/RenderOptions.md)
|
||||||
- [RenderResult](interfaces/RenderResult.md)
|
- [RenderResult](interfaces/RenderResult.md)
|
||||||
- [RunOptions](interfaces/RunOptions.md)
|
- [RunOptions](interfaces/RunOptions.md)
|
||||||
|
- [UnknownDiagramError](interfaces/UnknownDiagramError.md)
|
||||||
|
|
||||||
## Type Aliases
|
## Type Aliases
|
||||||
|
|
||||||
|
@@ -1,159 +0,0 @@
|
|||||||
> **Warning**
|
|
||||||
>
|
|
||||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
|
||||||
>
|
|
||||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md).
|
|
||||||
|
|
||||||
[**mermaid**](../../README.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Class: UnknownDiagramError
|
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
|
|
||||||
|
|
||||||
## Extends
|
|
||||||
|
|
||||||
- `Error`
|
|
||||||
|
|
||||||
## Constructors
|
|
||||||
|
|
||||||
### new UnknownDiagramError()
|
|
||||||
|
|
||||||
> **new UnknownDiagramError**(`message`): [`UnknownDiagramError`](UnknownDiagramError.md)
|
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/errors.ts:2](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L2)
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
##### message
|
|
||||||
|
|
||||||
`string`
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
[`UnknownDiagramError`](UnknownDiagramError.md)
|
|
||||||
|
|
||||||
#### Overrides
|
|
||||||
|
|
||||||
`Error.constructor`
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### cause?
|
|
||||||
|
|
||||||
> `optional` **cause**: `unknown`
|
|
||||||
|
|
||||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
|
|
||||||
|
|
||||||
#### Inherited from
|
|
||||||
|
|
||||||
`Error.cause`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### message
|
|
||||||
|
|
||||||
> **message**: `string`
|
|
||||||
|
|
||||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
|
|
||||||
|
|
||||||
#### Inherited from
|
|
||||||
|
|
||||||
`Error.message`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### name
|
|
||||||
|
|
||||||
> **name**: `string`
|
|
||||||
|
|
||||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
|
|
||||||
|
|
||||||
#### Inherited from
|
|
||||||
|
|
||||||
`Error.name`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### stack?
|
|
||||||
|
|
||||||
> `optional` **stack**: `string`
|
|
||||||
|
|
||||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
|
|
||||||
|
|
||||||
#### Inherited from
|
|
||||||
|
|
||||||
`Error.stack`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### prepareStackTrace()?
|
|
||||||
|
|
||||||
> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any`
|
|
||||||
|
|
||||||
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:143
|
|
||||||
|
|
||||||
Optional override for formatting stack traces
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
##### err
|
|
||||||
|
|
||||||
`Error`
|
|
||||||
|
|
||||||
##### stackTraces
|
|
||||||
|
|
||||||
`CallSite`\[]
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`any`
|
|
||||||
|
|
||||||
#### See
|
|
||||||
|
|
||||||
<https://v8.dev/docs/stack-trace-api#customizing-stack-traces>
|
|
||||||
|
|
||||||
#### Inherited from
|
|
||||||
|
|
||||||
`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`
|
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# Interface: ExternalDiagramDefinition
|
# Interface: ExternalDiagramDefinition
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L94)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L96)
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:94](https://github.com/me
|
|||||||
|
|
||||||
> **detector**: `DiagramDetector`
|
> **detector**: `DiagramDetector`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L96)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L98)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/me
|
|||||||
|
|
||||||
> **id**: `string`
|
> **id**: `string`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:95](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L95)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L97)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -34,4 +34,4 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:95](https://github.com/me
|
|||||||
|
|
||||||
> **loader**: `DiagramLoader`
|
> **loader**: `DiagramLoader`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L97)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:99](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L99)
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# Interface: LayoutData
|
# Interface: LayoutData
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:145](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L145)
|
Defined in: [packages/mermaid/src/rendering-util/types.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L168)
|
||||||
|
|
||||||
## Indexable
|
## Indexable
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:145](https://github.co
|
|||||||
|
|
||||||
> **config**: [`MermaidConfig`](MermaidConfig.md)
|
> **config**: [`MermaidConfig`](MermaidConfig.md)
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L148)
|
Defined in: [packages/mermaid/src/rendering-util/types.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L171)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:148](https://github.co
|
|||||||
|
|
||||||
> **edges**: `Edge`\[]
|
> **edges**: `Edge`\[]
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
|
Defined in: [packages/mermaid/src/rendering-util/types.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L170)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -38,4 +38,4 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:147](https://github.co
|
|||||||
|
|
||||||
> **nodes**: `Node`\[]
|
> **nodes**: `Node`\[]
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)
|
Defined in: [packages/mermaid/src/rendering-util/types.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L169)
|
||||||
|
@@ -32,7 +32,7 @@ page.
|
|||||||
|
|
||||||
### detectType()
|
### detectType()
|
||||||
|
|
||||||
> **detectType**: (`text`, `config`?) => `string`
|
> **detectType**: (`text`, `config?`) => `string`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/mermaid.ts:449](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L449)
|
Defined in: [packages/mermaid/src/mermaid.ts:449](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L449)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ An array of objects with the id of the diagram.
|
|||||||
|
|
||||||
### ~~init()~~
|
### ~~init()~~
|
||||||
|
|
||||||
> **init**: (`config`?, `nodes`?, `callback`?) => `Promise`<`void`>
|
> **init**: (`config?`, `nodes?`, `callback?`) => `Promise`<`void`>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
|
Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/
|
|||||||
|
|
||||||
[`MermaidConfig`](MermaidConfig.md)
|
[`MermaidConfig`](MermaidConfig.md)
|
||||||
|
|
||||||
**Deprecated**, please set configuration in [initialize](Mermaid.md#initialize).
|
**Deprecated**, please set configuration in [initialize](#initialize).
|
||||||
|
|
||||||
##### nodes?
|
##### nodes?
|
||||||
|
|
||||||
@@ -141,13 +141,13 @@ Called once for each rendered diagram's id.
|
|||||||
|
|
||||||
#### Deprecated
|
#### Deprecated
|
||||||
|
|
||||||
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
|
Use [initialize](#initialize) and [run](#run) instead.
|
||||||
|
|
||||||
Renders the mermaid diagrams
|
Renders the mermaid diagrams
|
||||||
|
|
||||||
#### Deprecated
|
#### Deprecated
|
||||||
|
|
||||||
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
|
Use [initialize](#initialize) and [run](#run) instead.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ Configuration object for mermaid.
|
|||||||
|
|
||||||
### ~~mermaidAPI~~
|
### ~~mermaidAPI~~
|
||||||
|
|
||||||
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>; `render`: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
|
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }; `render`: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
|
Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
|
||||||
|
|
||||||
@@ -184,73 +184,81 @@ Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/
|
|||||||
|
|
||||||
#### Deprecated
|
#### Deprecated
|
||||||
|
|
||||||
Use [parse](Mermaid.md#parse) and [render](Mermaid.md#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
|
Use [parse](#parse) and [render](#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### parse()
|
### parse()
|
||||||
|
|
||||||
> **parse**: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>
|
> **parse**: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
|
Defined in: [packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
|
||||||
|
|
||||||
|
#### Call Signature
|
||||||
|
|
||||||
|
> (`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>
|
||||||
|
|
||||||
Parse the text and validate the syntax.
|
Parse the text and validate the syntax.
|
||||||
|
|
||||||
#### Parameters
|
##### Parameters
|
||||||
|
|
||||||
##### text
|
###### text
|
||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
The mermaid diagram definition.
|
The mermaid diagram definition.
|
||||||
|
|
||||||
##### parseOptions
|
###### parseOptions
|
||||||
|
|
||||||
[`ParseOptions`](ParseOptions.md) & `object`
|
[`ParseOptions`](ParseOptions.md) & `object`
|
||||||
|
|
||||||
Options for parsing.
|
Options for parsing.
|
||||||
|
|
||||||
#### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<`false` | [`ParseResult`](ParseResult.md)>
|
`Promise`<`false` | [`ParseResult`](ParseResult.md)>
|
||||||
|
|
||||||
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||||
|
|
||||||
#### See
|
##### See
|
||||||
|
|
||||||
[ParseOptions](ParseOptions.md)
|
[ParseOptions](ParseOptions.md)
|
||||||
|
|
||||||
#### Throws
|
##### Throws
|
||||||
|
|
||||||
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||||
|
|
||||||
|
#### Call Signature
|
||||||
|
|
||||||
|
> (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>
|
||||||
|
|
||||||
Parse the text and validate the syntax.
|
Parse the text and validate the syntax.
|
||||||
|
|
||||||
#### Parameters
|
##### Parameters
|
||||||
|
|
||||||
##### text
|
###### text
|
||||||
|
|
||||||
`string`
|
`string`
|
||||||
|
|
||||||
The mermaid diagram definition.
|
The mermaid diagram definition.
|
||||||
|
|
||||||
##### parseOptions?
|
###### parseOptions?
|
||||||
|
|
||||||
[`ParseOptions`](ParseOptions.md)
|
[`ParseOptions`](ParseOptions.md)
|
||||||
|
|
||||||
Options for parsing.
|
Options for parsing.
|
||||||
|
|
||||||
#### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<[`ParseResult`](ParseResult.md)>
|
`Promise`<[`ParseResult`](ParseResult.md)>
|
||||||
|
|
||||||
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||||
|
|
||||||
#### See
|
##### See
|
||||||
|
|
||||||
[ParseOptions](ParseOptions.md)
|
[ParseOptions](ParseOptions.md)
|
||||||
|
|
||||||
#### Throws
|
##### Throws
|
||||||
|
|
||||||
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||||
|
|
||||||
@@ -332,7 +340,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:444](https://github.com/mermaid-js/
|
|||||||
|
|
||||||
### render()
|
### render()
|
||||||
|
|
||||||
> **render**: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>
|
> **render**: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
|
Defined in: [packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
|
||||||
|
|
||||||
|
@@ -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:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
|
||||||
|
|
||||||
## 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:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L93)
|
||||||
|
|
||||||
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.
|
||||||
|
@@ -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:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96)
|
||||||
|
|
||||||
## 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:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L104)
|
||||||
|
|
||||||
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:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100)
|
||||||
|
|
||||||
The diagram type, e.g. 'flowchart', 'sequence', etc.
|
The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||||
|
@@ -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:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114)
|
||||||
|
|
||||||
## 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:132](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L132)
|
||||||
|
|
||||||
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:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L122)
|
||||||
|
|
||||||
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:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118)
|
||||||
|
|
||||||
The svg code for the rendered graph.
|
The svg code for the rendered graph.
|
||||||
|
65
docs/config/setup/mermaid/interfaces/UnknownDiagramError.md
Normal file
65
docs/config/setup/mermaid/interfaces/UnknownDiagramError.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md).
|
||||||
|
|
||||||
|
[**mermaid**](../../README.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Interface: UnknownDiagramError
|
||||||
|
|
||||||
|
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
|
||||||
|
|
||||||
|
## Extends
|
||||||
|
|
||||||
|
- `Error`
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### cause?
|
||||||
|
|
||||||
|
> `optional` **cause**: `unknown`
|
||||||
|
|
||||||
|
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`Error.cause`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### message
|
||||||
|
|
||||||
|
> **message**: `string`
|
||||||
|
|
||||||
|
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`Error.message`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
> **name**: `string`
|
||||||
|
|
||||||
|
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`Error.name`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### stack?
|
||||||
|
|
||||||
|
> `optional` **stack**: `string`
|
||||||
|
|
||||||
|
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`Error.stack`
|
@@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
# Type Alias: InternalHelpers
|
# Type Alias: InternalHelpers
|
||||||
|
|
||||||
> **InternalHelpers**: _typeof_ `internalHelpers`
|
> **InternalHelpers** = _typeof_ `internalHelpers`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/internals.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/internals.ts#L33)
|
Defined in: [packages/mermaid/src/internals.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/internals.ts#L33)
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# Type Alias: ParseErrorFunction()
|
# Type Alias: ParseErrorFunction()
|
||||||
|
|
||||||
> **ParseErrorFunction**: (`err`, `hash`?) => `void`
|
> **ParseErrorFunction** = (`err`, `hash?`) => `void`
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/Diagram.ts:10](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/Diagram.ts#L10)
|
Defined in: [packages/mermaid/src/Diagram.ts:10](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/Diagram.ts#L10)
|
||||||
|
|
||||||
|
@@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
# Type Alias: SVG
|
# Type Alias: SVG
|
||||||
|
|
||||||
> **SVG**: `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
|
> **SVG** = `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L126)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L128)
|
||||||
|
@@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
# Type Alias: SVGGroup
|
# Type Alias: SVGGroup
|
||||||
|
|
||||||
> **SVGGroup**: `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
|
> **SVGGroup** = `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L128)
|
Defined in: [packages/mermaid/src/diagram-api/types.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L130)
|
||||||
|
89
docs/config/tidy-tree.md
Normal file
89
docs/config/tidy-tree.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
> **Warning**
|
||||||
|
>
|
||||||
|
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||||
|
>
|
||||||
|
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/tidy-tree.md](../../packages/mermaid/src/docs/config/tidy-tree.md).
|
||||||
|
|
||||||
|
# Tidy-tree Layout
|
||||||
|
|
||||||
|
The **tidy-tree** layout arranges nodes in a hierarchical, tree-like structure. It is especially useful for diagrams where parent-child relationships are important, such as mindmaps.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Organizes nodes in a tidy, non-overlapping tree
|
||||||
|
- Ideal for mindmaps and hierarchical data
|
||||||
|
- Automatically adjusts spacing for readability
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
```
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
- Currently, tidy-tree is primarily supported for mindmap diagrams.
|
@@ -29,7 +29,6 @@ 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)
|
||||||
|
@@ -35,13 +35,11 @@ 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)
|
||||||
|
@@ -139,7 +139,6 @@ 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)
|
||||||
@@ -153,7 +152,6 @@ 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
|
||||||
@@ -163,7 +161,6 @@ 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
|
||||||
@@ -172,18 +169,15 @@ 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
|
||||||
|
@@ -326,7 +326,9 @@ Below is a comprehensive list of the newly introduced shapes and their correspon
|
|||||||
|
|
||||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||||
|
| Bang | Bang | `bang` | Bang | `bang` |
|
||||||
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||||
|
| Cloud | Cloud | `cloud` | cloud | `cloud` |
|
||||||
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
||||||
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||||
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
||||||
@@ -983,11 +985,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
|
||||||
|
|
||||||
|
@@ -360,7 +360,8 @@ gantt
|
|||||||
weekday monday
|
weekday monday
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Warning** > `millisecond` and `second` support was added in v10.3.0
|
> **Warning**
|
||||||
|
> `millisecond` and `second` support was added in v10.3.0
|
||||||
|
|
||||||
## Output in compact mode
|
## Output in compact mode
|
||||||
|
|
||||||
|
@@ -314,3 +314,22 @@ You can also refer the [implementation in the live editor](https://github.com/me
|
|||||||
cspell:locale en,en-gb
|
cspell:locale en,en-gb
|
||||||
cspell:ignore Buzan
|
cspell:ignore Buzan
|
||||||
--->
|
--->
|
||||||
|
|
||||||
|
## Layouts
|
||||||
|
|
||||||
|
Mermaid also supports a Tidy Tree layout for mindmaps.
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap is a long thing))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
```
|
||||||
|
|
||||||
|
Instructions to add and register tidy-tree layout are present in [Tidy Tree Configuration](/config/tidy-tree)
|
||||||
|
@@ -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.
|
||||||
|
@@ -138,7 +138,7 @@ xychart
|
|||||||
|
|
||||||
## Chart Theme Variables
|
## Chart Theme Variables
|
||||||
|
|
||||||
Themes for xychart resides inside xychart attribute so to set the variables use this syntax:
|
Themes for xychart reside inside the `xychart` attribute, allowing customization through the following syntax:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
@@ -163,6 +163,52 @@ config:
|
|||||||
| yAxisLineColor | Color of the y-axis line |
|
| yAxisLineColor | Color of the y-axis line |
|
||||||
| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |
|
| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |
|
||||||
|
|
||||||
|
### Setting Colors for Lines and Bars
|
||||||
|
|
||||||
|
To set the color for lines and bars, use the `plotColorPalette` parameter. Colors in the palette will correspond sequentially to the elements in your chart (e.g., first bar/line will use the first color specified in the palette).
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
themeVariables:
|
||||||
|
xyChart:
|
||||||
|
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
|
||||||
|
---
|
||||||
|
xychart
|
||||||
|
title "Different Colors in xyChart"
|
||||||
|
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
|
||||||
|
y-axis "valuesY" 0 --> 50
|
||||||
|
%% Black line
|
||||||
|
line [10,20,30,40]
|
||||||
|
%% Blue bar
|
||||||
|
bar [20,30,25,35]
|
||||||
|
%% Green bar
|
||||||
|
bar [15,25,20,30]
|
||||||
|
%% Red line
|
||||||
|
line [5,15,25,35]
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
themeVariables:
|
||||||
|
xyChart:
|
||||||
|
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
|
||||||
|
---
|
||||||
|
xychart
|
||||||
|
title "Different Colors in xyChart"
|
||||||
|
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
|
||||||
|
y-axis "valuesY" 0 --> 50
|
||||||
|
%% Black line
|
||||||
|
line [10,20,30,40]
|
||||||
|
%% Blue bar
|
||||||
|
bar [20,30,25,35]
|
||||||
|
%% Green bar
|
||||||
|
bar [15,25,20,30]
|
||||||
|
%% Red line
|
||||||
|
line [5,15,25,35]
|
||||||
|
```
|
||||||
|
|
||||||
## Example on config and theme
|
## Example on config and theme
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
|
@@ -17,6 +17,7 @@ export default tseslint.config(
|
|||||||
...tseslint.configs.stylisticTypeChecked,
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
|
'**/*.d.ts',
|
||||||
'**/dist/',
|
'**/dist/',
|
||||||
'**/node_modules/',
|
'**/node_modules/',
|
||||||
'.git/',
|
'.git/',
|
||||||
|
42
package.json
42
package.json
@@ -64,35 +64,35 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@applitools/eyes-cypress": "^3.44.9",
|
"@applitools/eyes-cypress": "^3.44.9",
|
||||||
"@argos-ci/cypress": "^5.0.2",
|
"@argos-ci/cypress": "^5.0.7",
|
||||||
"@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.2",
|
"@rollup/plugin-typescript": "^12.1.4",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.3",
|
||||||
"@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.15",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/mdast": "^4.0.4",
|
"@types/mdast": "^4.0.4",
|
||||||
"@types/node": "^22.13.5",
|
"@types/node": "^22.13.17",
|
||||||
"@types/rollup-plugin-visualizer": "^5.0.3",
|
"@types/rollup-plugin-visualizer": "^5.0.3",
|
||||||
"@vitest/coverage-v8": "^3.0.6",
|
"@vitest/coverage-v8": "^3.0.9",
|
||||||
"@vitest/spy": "^3.0.6",
|
"@vitest/spy": "^3.0.9",
|
||||||
"@vitest/ui": "^3.0.6",
|
"@vitest/ui": "^3.0.9",
|
||||||
"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.3",
|
"cspell": "^9.1.5",
|
||||||
"cypress": "^14.5.1",
|
"cypress": "^14.5.4",
|
||||||
"cypress-image-snapshot": "^4.0.1",
|
"cypress-image-snapshot": "^4.0.1",
|
||||||
"cypress-split": "^1.24.14",
|
"cypress-split": "^1.24.21",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.9",
|
||||||
"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.0.2",
|
"globby": "^14.1.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jest": "^30.0.4",
|
"jest": "^30.0.5",
|
||||||
"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.2",
|
"lint-staged": "^16.1.6",
|
||||||
"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.2",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-jsdoc": "^1.3.2",
|
"prettier-plugin-jsdoc": "^1.3.3",
|
||||||
"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.10",
|
"start-server-and-test": "^2.0.13",
|
||||||
"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.3",
|
"vite": "^7.0.6",
|
||||||
"vite-plugin-istanbul": "^7.0.0",
|
"vite-plugin-istanbul": "^7.0.0",
|
||||||
"vitest": "^3.0.6"
|
"vitest": "^3.0.9"
|
||||||
},
|
},
|
||||||
"nyc": {
|
"nyc": {
|
||||||
"report-dir": "coverage/cypress"
|
"report-dir": "coverage/cypress"
|
||||||
|
@@ -37,7 +37,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.0.4",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"khroma": "^2.1.0"
|
"khroma": "^2.1.0"
|
||||||
},
|
},
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This package provides a layout engine for Mermaid based on the [ELK](https://www.eclipse.org/elk/) layout engine.
|
This package provides a layout engine for Mermaid based on the [ELK](https://www.eclipse.org/elk/) layout engine.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The ELK Layout engine will not be available in all providers that support mermaid by default.
|
> The ELK Layout engine will not be available in all providers that support mermaid by default.
|
||||||
> The websites will have to install the `@mermaid-js/layout-elk` package to use the ELK layout engine.
|
> The websites will have to install the `@mermaid-js/layout-elk` package to use the ELK layout engine.
|
||||||
|
|
||||||
@@ -69,4 +69,4 @@ mermaid.registerLayoutLoaders(elkLayouts);
|
|||||||
- `elk.mrtree`: Multi-root tree layout
|
- `elk.mrtree`: Multi-root tree layout
|
||||||
- `elk.sporeOverlap`: Spore overlap layout
|
- `elk.sporeOverlap`: Spore overlap layout
|
||||||
|
|
||||||
<!-- TODO: Add images for these layouts, as GitHub doesn't support natively -->
|
<!-- TODO: Add images for these layouts, as GitHub doesn't support natively. -->
|
||||||
|
9
packages/mermaid-layout-elk/src/find-common-ancestor.d.ts
vendored
Normal file
9
packages/mermaid-layout-elk/src/find-common-ancestor.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface TreeData {
|
||||||
|
parentById: Record<string, string>;
|
||||||
|
childrenById: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
export declare const findCommonAncestor: (
|
||||||
|
id1: string,
|
||||||
|
id2: string,
|
||||||
|
{ parentById }: TreeData
|
||||||
|
) => string;
|
@@ -4,7 +4,8 @@ import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from '
|
|||||||
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
|
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
|
||||||
|
|
||||||
type Node = LayoutData['nodes'][number];
|
type Node = LayoutData['nodes'][number];
|
||||||
|
// Used to calculate distances in order to avoid floating number rounding issues when comparing floating numbers
|
||||||
|
const epsilon = 0.0001;
|
||||||
interface LabelData {
|
interface LabelData {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -13,11 +14,20 @@ interface LabelData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface NodeWithVertex extends Omit<Node, 'domId'> {
|
interface NodeWithVertex extends Omit<Node, 'domId'> {
|
||||||
children?: unknown[];
|
children?: LayoutData['nodes'];
|
||||||
labelData?: LabelData;
|
labelData?: LabelData;
|
||||||
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
|
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
|
||||||
}
|
}
|
||||||
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
function distance(p1?: Point, p2?: Point): number {
|
||||||
|
if (!p1 || !p2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
||||||
|
}
|
||||||
export const render = async (
|
export const render = async (
|
||||||
data4Layout: LayoutData,
|
data4Layout: LayoutData,
|
||||||
svg: SVG,
|
svg: SVG,
|
||||||
@@ -51,15 +61,30 @@ export const render = async (
|
|||||||
|
|
||||||
// Add the element to the DOM
|
// Add the element to the DOM
|
||||||
if (!node.isGroup) {
|
if (!node.isGroup) {
|
||||||
|
// Create a clean node object for ELK with only the properties it expects
|
||||||
const child: NodeWithVertex = {
|
const child: NodeWithVertex = {
|
||||||
...node,
|
id: node.id,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
// Store the original node data for later use
|
||||||
|
label: node.label,
|
||||||
|
isGroup: node.isGroup,
|
||||||
|
shape: node.shape,
|
||||||
|
padding: node.padding,
|
||||||
|
cssClasses: node.cssClasses,
|
||||||
|
cssStyles: node.cssStyles,
|
||||||
|
look: node.look,
|
||||||
|
// Include parentId for subgraph processing
|
||||||
|
parentId: node.parentId,
|
||||||
};
|
};
|
||||||
graph.children.push(child);
|
graph.children.push(child);
|
||||||
nodeDb[node.id] = child;
|
nodeDb[node.id] = child;
|
||||||
|
|
||||||
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
|
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
|
||||||
const boundingBox = childNodeEl.node()!.getBBox();
|
const boundingBox = childNodeEl.node()!.getBBox();
|
||||||
|
// Store the domId separately for rendering, not in the ELK graph
|
||||||
child.domId = childNodeEl;
|
child.domId = childNodeEl;
|
||||||
|
child.calcIntersect = node.calcIntersect;
|
||||||
child.width = boundingBox.width;
|
child.width = boundingBox.width;
|
||||||
child.height = boundingBox.height;
|
child.height = boundingBox.height;
|
||||||
} else {
|
} else {
|
||||||
@@ -459,302 +484,6 @@ export const render = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function intersectLine(
|
|
||||||
p1: { y: number; x: number },
|
|
||||||
p2: { y: number; x: number },
|
|
||||||
q1: { x: any; y: any },
|
|
||||||
q2: { x: any; y: any }
|
|
||||||
) {
|
|
||||||
log.debug('UIO intersectLine', p1, p2, q1, q2);
|
|
||||||
// Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
|
|
||||||
// p7 and p473.
|
|
||||||
|
|
||||||
// let a1, a2, b1, b2, c1, c2;
|
|
||||||
// let r1, r2, r3, r4;
|
|
||||||
// let denom, offset, num;
|
|
||||||
// let x, y;
|
|
||||||
|
|
||||||
// Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
|
|
||||||
// b1 y + c1 = 0.
|
|
||||||
const a1 = p2.y - p1.y;
|
|
||||||
const b1 = p1.x - p2.x;
|
|
||||||
const c1 = p2.x * p1.y - p1.x * p2.y;
|
|
||||||
|
|
||||||
// Compute r3 and r4.
|
|
||||||
const r3 = a1 * q1.x + b1 * q1.y + c1;
|
|
||||||
const r4 = a1 * q2.x + b1 * q2.y + c1;
|
|
||||||
|
|
||||||
const epsilon = 1e-6;
|
|
||||||
|
|
||||||
// Check signs of r3 and r4. If both point 3 and point 4 lie on
|
|
||||||
// same side of line 1, the line segments do not intersect.
|
|
||||||
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
|
|
||||||
return /*DON'T_INTERSECT*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
|
|
||||||
const a2 = q2.y - q1.y;
|
|
||||||
const b2 = q1.x - q2.x;
|
|
||||||
const c2 = q2.x * q1.y - q1.x * q2.y;
|
|
||||||
|
|
||||||
// Compute r1 and r2
|
|
||||||
const r1 = a2 * p1.x + b2 * p1.y + c2;
|
|
||||||
const r2 = a2 * p2.x + b2 * p2.y + c2;
|
|
||||||
|
|
||||||
// Check signs of r1 and r2. If both point 1 and point 2 lie
|
|
||||||
// on same side of second line segment, the line segments do
|
|
||||||
// not intersect.
|
|
||||||
if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) {
|
|
||||||
return /*DON'T_INTERSECT*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line segments intersect: compute intersection point.
|
|
||||||
const denom = a1 * b2 - a2 * b1;
|
|
||||||
if (denom === 0) {
|
|
||||||
return /*COLLINEAR*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = Math.abs(denom / 2);
|
|
||||||
|
|
||||||
// The denom/2 is to get rounding instead of truncating. It
|
|
||||||
// is added or subtracted to the numerator, depending upon the
|
|
||||||
// sign of the numerator.
|
|
||||||
let num = b1 * c2 - b2 * c1;
|
|
||||||
const x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
|
|
||||||
|
|
||||||
num = a2 * c1 - a1 * c2;
|
|
||||||
const y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
|
|
||||||
|
|
||||||
return { x: x, y: y };
|
|
||||||
}
|
|
||||||
|
|
||||||
function sameSign(r1: number, r2: number) {
|
|
||||||
return r1 * r2 > 0;
|
|
||||||
}
|
|
||||||
const diamondIntersection = (
|
|
||||||
bounds: { x: any; y: any; width: any; height: any },
|
|
||||||
outsidePoint: { x: number; y: number },
|
|
||||||
insidePoint: any
|
|
||||||
) => {
|
|
||||||
const x1 = bounds.x;
|
|
||||||
const y1 = bounds.y;
|
|
||||||
|
|
||||||
const w = bounds.width; //+ bounds.padding;
|
|
||||||
const h = bounds.height; // + bounds.padding;
|
|
||||||
|
|
||||||
const polyPoints = [
|
|
||||||
{ x: x1, y: y1 - h / 2 },
|
|
||||||
{ x: x1 + w / 2, y: y1 },
|
|
||||||
{ x: x1, y: y1 + h / 2 },
|
|
||||||
{ x: x1 - w / 2, y: y1 },
|
|
||||||
];
|
|
||||||
log.debug(
|
|
||||||
`APA16 diamondIntersection calc abc89:
|
|
||||||
outsidePoint: ${JSON.stringify(outsidePoint)}
|
|
||||||
insidePoint : ${JSON.stringify(insidePoint)}
|
|
||||||
node-bounds : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
|
|
||||||
JSON.stringify(polyPoints)
|
|
||||||
);
|
|
||||||
|
|
||||||
const intersections = [];
|
|
||||||
|
|
||||||
let minX = Number.POSITIVE_INFINITY;
|
|
||||||
let minY = Number.POSITIVE_INFINITY;
|
|
||||||
|
|
||||||
polyPoints.forEach(function (entry) {
|
|
||||||
minX = Math.min(minX, entry.x);
|
|
||||||
minY = Math.min(minY, entry.y);
|
|
||||||
});
|
|
||||||
|
|
||||||
const left = x1 - w / 2 - minX;
|
|
||||||
const top = y1 - h / 2 - minY;
|
|
||||||
|
|
||||||
for (let i = 0; i < polyPoints.length; i++) {
|
|
||||||
const p1 = polyPoints[i];
|
|
||||||
const p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
|
|
||||||
const intersect = intersectLine(
|
|
||||||
bounds,
|
|
||||||
outsidePoint,
|
|
||||||
{ x: left + p1.x, y: top + p1.y },
|
|
||||||
{ x: left + p2.x, y: top + p2.y }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (intersect) {
|
|
||||||
intersections.push(intersect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!intersections.length) {
|
|
||||||
return bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug('UIO intersections', intersections);
|
|
||||||
|
|
||||||
if (intersections.length > 1) {
|
|
||||||
// More intersections, find the one nearest to edge end point
|
|
||||||
intersections.sort(function (p, q) {
|
|
||||||
const pdx = p.x - outsidePoint.x;
|
|
||||||
const pdy = p.y - outsidePoint.y;
|
|
||||||
const distp = Math.sqrt(pdx * pdx + pdy * pdy);
|
|
||||||
|
|
||||||
const qdx = q.x - outsidePoint.x;
|
|
||||||
const qdy = q.y - outsidePoint.y;
|
|
||||||
const distq = Math.sqrt(qdx * qdx + qdy * qdy);
|
|
||||||
|
|
||||||
return distp < distq ? -1 : distp === distq ? 0 : 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return intersections[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
const intersection = (
|
|
||||||
node: { x: any; y: any; width: number; height: number },
|
|
||||||
outsidePoint: { x: number; y: number },
|
|
||||||
insidePoint: { x: number; y: number }
|
|
||||||
) => {
|
|
||||||
log.debug(`intersection calc abc89:
|
|
||||||
outsidePoint: ${JSON.stringify(outsidePoint)}
|
|
||||||
insidePoint : ${JSON.stringify(insidePoint)}
|
|
||||||
node : x:${node.x} y:${node.y} w:${node.width} h:${node.height}`);
|
|
||||||
const x = node.x;
|
|
||||||
const y = node.y;
|
|
||||||
|
|
||||||
const dx = Math.abs(x - insidePoint.x);
|
|
||||||
// const dy = Math.abs(y - insidePoint.y);
|
|
||||||
const w = node.width / 2;
|
|
||||||
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
|
|
||||||
const h = node.height / 2;
|
|
||||||
|
|
||||||
const Q = Math.abs(outsidePoint.y - insidePoint.y);
|
|
||||||
const R = Math.abs(outsidePoint.x - insidePoint.x);
|
|
||||||
|
|
||||||
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
|
|
||||||
// Intersection is top or bottom of rect.
|
|
||||||
const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
|
|
||||||
r = (R * q) / Q;
|
|
||||||
const res = {
|
|
||||||
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
|
|
||||||
y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (r === 0) {
|
|
||||||
res.x = outsidePoint.x;
|
|
||||||
res.y = outsidePoint.y;
|
|
||||||
}
|
|
||||||
if (R === 0) {
|
|
||||||
res.x = outsidePoint.x;
|
|
||||||
}
|
|
||||||
if (Q === 0) {
|
|
||||||
res.y = outsidePoint.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res); // cspell: disable-line
|
|
||||||
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
// Intersection on sides of rect
|
|
||||||
if (insidePoint.x < outsidePoint.x) {
|
|
||||||
r = outsidePoint.x - w - x;
|
|
||||||
} else {
|
|
||||||
// r = outsidePoint.x - w - x;
|
|
||||||
r = x - w - outsidePoint.x;
|
|
||||||
}
|
|
||||||
const q = (Q * r) / R;
|
|
||||||
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w;
|
|
||||||
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
|
|
||||||
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
|
|
||||||
// let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
|
|
||||||
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
|
|
||||||
log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
|
|
||||||
if (r === 0) {
|
|
||||||
_x = outsidePoint.x;
|
|
||||||
_y = outsidePoint.y;
|
|
||||||
}
|
|
||||||
if (R === 0) {
|
|
||||||
_x = outsidePoint.x;
|
|
||||||
}
|
|
||||||
if (Q === 0) {
|
|
||||||
_y = outsidePoint.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { x: _x, y: _y };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const outsideNode = (
|
|
||||||
node: { x: any; y: any; width: number; height: number },
|
|
||||||
point: { x: number; y: number }
|
|
||||||
) => {
|
|
||||||
const x = node.x;
|
|
||||||
const y = node.y;
|
|
||||||
const dx = Math.abs(point.x - x);
|
|
||||||
const dy = Math.abs(point.y - y);
|
|
||||||
const w = node.width / 2;
|
|
||||||
const h = node.height / 2;
|
|
||||||
if (dx >= w || dy >= h) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* This function will page a path and node where the last point(s) in the path is inside the node
|
|
||||||
* and return an update path ending by the border of the node.
|
|
||||||
*/
|
|
||||||
const cutPathAtIntersect = (
|
|
||||||
_points: any[],
|
|
||||||
bounds: { x: any; y: any; width: any; height: any; padding: any },
|
|
||||||
isDiamond: boolean
|
|
||||||
) => {
|
|
||||||
log.debug('APA18 cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
|
|
||||||
const points: any[] = [];
|
|
||||||
let lastPointOutside = _points[0];
|
|
||||||
let isInside = false;
|
|
||||||
_points.forEach((point: any) => {
|
|
||||||
// check if point is inside the boundary rect
|
|
||||||
if (!outsideNode(bounds, point) && !isInside) {
|
|
||||||
// First point inside the rect found
|
|
||||||
// Calc the intersection coord between the point and the last point outside the rect
|
|
||||||
let inter;
|
|
||||||
|
|
||||||
if (isDiamond) {
|
|
||||||
const inter2 = diamondIntersection(bounds, lastPointOutside, point);
|
|
||||||
const distance = Math.sqrt(
|
|
||||||
(lastPointOutside.x - inter2.x) ** 2 + (lastPointOutside.y - inter2.y) ** 2
|
|
||||||
);
|
|
||||||
if (distance > 1) {
|
|
||||||
inter = inter2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!inter) {
|
|
||||||
inter = intersection(bounds, lastPointOutside, point);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check case where the intersection is the same as the last point
|
|
||||||
let pointPresent = false;
|
|
||||||
points.forEach((p) => {
|
|
||||||
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
|
|
||||||
});
|
|
||||||
// if (!pointPresent) {
|
|
||||||
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
|
|
||||||
points.push(inter);
|
|
||||||
} else {
|
|
||||||
log.debug('abc88 no intersect', inter, points);
|
|
||||||
}
|
|
||||||
// points.push(inter);
|
|
||||||
isInside = true;
|
|
||||||
} else {
|
|
||||||
// Outside
|
|
||||||
log.debug('abc88 outside', point, lastPointOutside, points);
|
|
||||||
lastPointOutside = point;
|
|
||||||
// points.push(point);
|
|
||||||
if (!isInside) {
|
|
||||||
points.push(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return points;
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore - ELK is not typed
|
// @ts-ignore - ELK is not typed
|
||||||
const elk = new ELK();
|
const elk = new ELK();
|
||||||
const element = svg.select('g');
|
const element = svg.select('g');
|
||||||
@@ -869,11 +598,16 @@ export const render = async (
|
|||||||
delete node.height;
|
delete node.height;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
elkGraph.edges.forEach((edge: any) => {
|
log.debug('APA01 processing edges, count:', elkGraph.edges.length);
|
||||||
|
elkGraph.edges.forEach((edge: any, index: number) => {
|
||||||
|
log.debug('APA01 processing edge', index, ':', edge);
|
||||||
const source = edge.sources[0];
|
const source = edge.sources[0];
|
||||||
const target = edge.targets[0];
|
const target = edge.targets[0];
|
||||||
|
log.debug('APA01 source:', source, 'target:', target);
|
||||||
|
log.debug('APA01 nodeDb[source]:', nodeDb[source]);
|
||||||
|
log.debug('APA01 nodeDb[target]:', nodeDb[target]);
|
||||||
|
|
||||||
if (nodeDb[source].parentId !== nodeDb[target].parentId) {
|
if (nodeDb[source] && nodeDb[target] && nodeDb[source].parentId !== nodeDb[target].parentId) {
|
||||||
const ancestorId = findCommonAncestor(source, target, parentLookupDb);
|
const ancestorId = findCommonAncestor(source, target, parentLookupDb);
|
||||||
// an edge that breaks a subgraph has been identified, set configuration accordingly
|
// an edge that breaks a subgraph has been identified, set configuration accordingly
|
||||||
setIncludeChildrenPolicy(source, ancestorId);
|
setIncludeChildrenPolicy(source, ancestorId);
|
||||||
@@ -881,7 +615,37 @@ export const render = async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const g = await elk.layout(elkGraph);
|
log.debug('APA01 before');
|
||||||
|
log.debug('APA01 elkGraph structure:', JSON.stringify(elkGraph, null, 2));
|
||||||
|
log.debug('APA01 elkGraph.children length:', elkGraph.children?.length);
|
||||||
|
log.debug('APA01 elkGraph.edges length:', elkGraph.edges?.length);
|
||||||
|
|
||||||
|
// Validate that all edge references exist as nodes
|
||||||
|
elkGraph.edges?.forEach((edge: any, index: number) => {
|
||||||
|
log.debug(`APA01 validating edge ${index}:`, edge);
|
||||||
|
if (edge.sources) {
|
||||||
|
edge.sources.forEach((sourceId: any) => {
|
||||||
|
const sourceExists = elkGraph.children?.some((child: any) => child.id === sourceId);
|
||||||
|
log.debug(`APA01 source ${sourceId} exists:`, sourceExists);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (edge.targets) {
|
||||||
|
edge.targets.forEach((targetId: any) => {
|
||||||
|
const targetExists = elkGraph.children?.some((child: any) => child.id === targetId);
|
||||||
|
log.debug(`APA01 target ${targetId} exists:`, targetExists);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let g;
|
||||||
|
try {
|
||||||
|
g = await elk.layout(elkGraph);
|
||||||
|
log.debug('APA01 after - success');
|
||||||
|
log.debug('APA01 layout result:', JSON.stringify(g, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
log.error('APA01 ELK layout error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
// debugger;
|
// debugger;
|
||||||
await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
|
await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
|
||||||
@@ -969,42 +733,37 @@ export const render = async (
|
|||||||
startNode.innerHTML
|
startNode.innerHTML
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (startNode.shape === 'diamond' || startNode.shape === 'diam') {
|
|
||||||
edge.points.unshift({
|
|
||||||
x: startNode.offset.posX + startNode.width / 2,
|
|
||||||
y: startNode.offset.posY + startNode.height / 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (endNode.shape === 'diamond' || endNode.shape === 'diam') {
|
|
||||||
edge.points.push({
|
|
||||||
x: endNode.offset.posX + endNode.width / 2,
|
|
||||||
y: endNode.offset.posY + endNode.height / 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
edge.points = cutPathAtIntersect(
|
if (startNode.calcIntersect) {
|
||||||
edge.points.reverse(),
|
const intersection = startNode.calcIntersect(
|
||||||
{
|
{
|
||||||
x: startNode.offset.posX + startNode.width / 2,
|
x: startNode.offset.posX + startNode.width / 2,
|
||||||
y: startNode.offset.posY + startNode.height / 2,
|
y: startNode.offset.posY + startNode.height / 2,
|
||||||
width: sw,
|
width: startNode.width,
|
||||||
height: startNode.height,
|
height: startNode.height,
|
||||||
padding: startNode.padding,
|
},
|
||||||
},
|
edge.points[0]
|
||||||
startNode.shape === 'diamond' || startNode.shape === 'diam'
|
);
|
||||||
).reverse();
|
|
||||||
|
|
||||||
edge.points = cutPathAtIntersect(
|
if (distance(intersection, edge.points[0]) > epsilon) {
|
||||||
edge.points,
|
edge.points.unshift(intersection);
|
||||||
{
|
}
|
||||||
x: endNode.offset.posX + endNode.width / 2,
|
}
|
||||||
y: endNode.offset.posY + endNode.height / 2,
|
if (endNode.calcIntersect) {
|
||||||
width: ew,
|
const intersection = endNode.calcIntersect(
|
||||||
height: endNode.height,
|
{
|
||||||
padding: endNode.padding,
|
x: endNode.offset.posX + endNode.width / 2,
|
||||||
},
|
y: endNode.offset.posY + endNode.height / 2,
|
||||||
endNode.shape === 'diamond' || endNode.shape === 'diam'
|
width: endNode.width,
|
||||||
);
|
height: endNode.height,
|
||||||
|
},
|
||||||
|
edge.points[edge.points.length - 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distance(intersection, edge.points[edge.points.length - 1]) > epsilon) {
|
||||||
|
edge.points.push(intersection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const paths = insertEdge(
|
const paths = insertEdge(
|
||||||
edgesEl,
|
edgesEl,
|
||||||
@@ -1015,7 +774,6 @@ export const render = async (
|
|||||||
endNode,
|
endNode,
|
||||||
data4Layout.diagramId
|
data4Layout.diagramId
|
||||||
);
|
);
|
||||||
log.info('APA12 edge points after insert', JSON.stringify(edge.points));
|
|
||||||
|
|
||||||
edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
|
edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
|
||||||
edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;
|
edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;
|
||||||
|
@@ -5,6 +5,6 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"types": ["vitest/importMeta", "vitest/globals"]
|
"types": ["vitest/importMeta", "vitest/globals"]
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.ts"],
|
"include": ["./src/**/*.ts", "./src/**/*.d.ts"],
|
||||||
"typeRoots": ["./src/types"]
|
"typeRoots": ["./src/types"]
|
||||||
}
|
}
|
||||||
|
59
packages/mermaid-layout-tidy-tree/README.md
Normal file
59
packages/mermaid-layout-tidy-tree/README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# @mermaid-js/layout-tidy-tree
|
||||||
|
|
||||||
|
This package provides a bidirectional tidy tree layout engine for Mermaid based on the non-layered-tidy-tree-layout algorithm.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The Tidy Tree Layout engine will not be available in all providers that support mermaid by default.
|
||||||
|
> The websites will have to install the @mermaid-js/layout-tidy-tree package to use the Tidy Tree layout engine.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
```
|
||||||
|
|
||||||
|
### With bundlers
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @mermaid-js/layout-tidy-tree
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import mermaid from 'mermaid';
|
||||||
|
import tidyTreeLayouts from '@mermaid-js/layout-tidy-tree';
|
||||||
|
|
||||||
|
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||||
|
```
|
||||||
|
|
||||||
|
### With CDN
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||||
|
import tidyTreeLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-tidy-tree@0/dist/mermaid-layout-tidy-tree.esm.min.mjs';
|
||||||
|
|
||||||
|
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tidy Tree Layout Overview
|
||||||
|
|
||||||
|
tidy-tree: The bidirectional tidy tree layout
|
||||||
|
|
||||||
|
The bidirectional tidy tree layout algorithm creates two separate trees that grow horizontally in opposite directions from a central root node:
|
||||||
|
Left tree: grows horizontally to the left (children alternate: 1st, 3rd, 5th...)
|
||||||
|
Right tree: grows horizontally to the right (children alternate: 2nd, 4th, 6th...)
|
||||||
|
|
||||||
|
This creates a balanced, symmetric layout that is ideal for mindmaps, organizational charts, and other tree-based diagrams.
|
||||||
|
|
||||||
|
Layout Structure:
|
||||||
|
[Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
[GrandChild] [GrandChild] [GrandChild] [GrandChild]
|
46
packages/mermaid-layout-tidy-tree/package.json
Normal file
46
packages/mermaid-layout-tidy-tree/package.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "@mermaid-js/layout-tidy-tree",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Tidy-tree layout engine for mermaid",
|
||||||
|
"module": "dist/mermaid-layout-tidy-tree.core.mjs",
|
||||||
|
"types": "dist/layouts.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/mermaid-layout-tidy-tree.core.mjs",
|
||||||
|
"types": "./dist/layouts.d.ts"
|
||||||
|
},
|
||||||
|
"./": "./"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"diagram",
|
||||||
|
"markdown",
|
||||||
|
"tidy-tree",
|
||||||
|
"mermaid",
|
||||||
|
"layout"
|
||||||
|
],
|
||||||
|
"scripts": {},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mermaid-js/mermaid"
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
"Knut Sveidqvist",
|
||||||
|
"Sidharth Vinod"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"d3": "^7.9.0",
|
||||||
|
"non-layered-tidy-tree-layout": "^2.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/d3": "^7.4.3",
|
||||||
|
"mermaid": "workspace:^"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"mermaid": "^11.0.2"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
50
packages/mermaid-layout-tidy-tree/src/index.ts
Normal file
50
packages/mermaid-layout-tidy-tree/src/index.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Bidirectional Tidy-Tree Layout Algorithm for Generic Diagrams
|
||||||
|
*
|
||||||
|
* This module provides a layout algorithm implementation using the
|
||||||
|
* non-layered-tidy-tree-layout algorithm for positioning nodes and edges
|
||||||
|
* in tree structures with a bidirectional approach.
|
||||||
|
*
|
||||||
|
* The algorithm creates two separate trees that grow horizontally in opposite
|
||||||
|
* directions from a central root node:
|
||||||
|
* - Left tree: grows horizontally to the left (children alternate: 1st, 3rd, 5th...)
|
||||||
|
* - Right tree: grows horizontally to the right (children alternate: 2nd, 4th, 6th...)
|
||||||
|
*
|
||||||
|
* This creates a balanced, symmetric layout that is ideal for mindmaps,
|
||||||
|
* organizational charts, and other tree-based diagrams.
|
||||||
|
*
|
||||||
|
* The algorithm follows the unified rendering pattern and can be used
|
||||||
|
* by any diagram type that provides compatible LayoutData.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render function for the bidirectional tidy-tree layout algorithm
|
||||||
|
*
|
||||||
|
* This function follows the unified rendering pattern used by all layout algorithms.
|
||||||
|
* It takes LayoutData, inserts nodes into DOM, runs the bidirectional tidy-tree layout algorithm,
|
||||||
|
* and renders the positioned elements to the SVG.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Alternates root children between left and right trees
|
||||||
|
* - Left tree grows horizontally to the left (rotated 90° counterclockwise)
|
||||||
|
* - Right tree grows horizontally to the right (rotated 90° clockwise)
|
||||||
|
* - Uses tidy-tree algorithm for optimal spacing within each tree
|
||||||
|
* - Creates symmetric, balanced layouts
|
||||||
|
* - Maintains proper edge connections between all nodes
|
||||||
|
*
|
||||||
|
* Layout Structure:
|
||||||
|
* ```
|
||||||
|
* [Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]
|
||||||
|
* ↓ ↓ ↓ ↓
|
||||||
|
* [GrandChild] [GrandChild] [GrandChild] [GrandChild]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param layoutData - Layout data containing nodes, edges, and configuration
|
||||||
|
* @param svg - SVG element to render to
|
||||||
|
* @param helpers - Internal helper functions for rendering
|
||||||
|
* @param options - Rendering options
|
||||||
|
*/
|
||||||
|
export { default } from './layouts.js';
|
||||||
|
export * from './types.js';
|
||||||
|
export * from './layout.js';
|
||||||
|
export { render } from './render.js';
|
409
packages/mermaid-layout-tidy-tree/src/layout.test.ts
Normal file
409
packages/mermaid-layout-tidy-tree/src/layout.test.ts
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { executeTidyTreeLayout, validateLayoutData } from './layout.js';
|
||||||
|
import type { LayoutResult } from './types.js';
|
||||||
|
import type { LayoutData, MermaidConfig } from 'mermaid';
|
||||||
|
|
||||||
|
// Mock non-layered-tidy-tree-layout
|
||||||
|
vi.mock('non-layered-tidy-tree-layout', () => ({
|
||||||
|
BoundingBox: vi.fn().mockImplementation(() => ({})),
|
||||||
|
Layout: vi.fn().mockImplementation(() => ({
|
||||||
|
layout: vi.fn().mockImplementation((treeData) => {
|
||||||
|
const result = { ...treeData };
|
||||||
|
|
||||||
|
if (result.id?.toString().startsWith('virtual-root')) {
|
||||||
|
result.x = 0;
|
||||||
|
result.y = 0;
|
||||||
|
} else {
|
||||||
|
result.x = 100;
|
||||||
|
result.y = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.children) {
|
||||||
|
result.children.forEach((child: any, index: number) => {
|
||||||
|
child.x = 50 + index * 100;
|
||||||
|
child.y = 100;
|
||||||
|
|
||||||
|
if (child.children) {
|
||||||
|
child.children.forEach((grandchild: any, gIndex: number) => {
|
||||||
|
grandchild.x = 25 + gIndex * 50;
|
||||||
|
grandchild.y = 200;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
boundingBox: {
|
||||||
|
left: 0,
|
||||||
|
right: 200,
|
||||||
|
top: 0,
|
||||||
|
bottom: 250,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Tidy-Tree Layout Algorithm', () => {
|
||||||
|
let mockConfig: MermaidConfig;
|
||||||
|
let mockLayoutData: LayoutData;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockConfig = {
|
||||||
|
theme: 'default',
|
||||||
|
} as MermaidConfig;
|
||||||
|
|
||||||
|
mockLayoutData = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'root',
|
||||||
|
label: 'Root',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
width: 100,
|
||||||
|
height: 50,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'child1',
|
||||||
|
label: 'Child 1',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
width: 80,
|
||||||
|
height: 40,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'child2',
|
||||||
|
label: 'Child 2',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
width: 80,
|
||||||
|
height: 40,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'child3',
|
||||||
|
label: 'Child 3',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
width: 80,
|
||||||
|
height: 40,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'child4',
|
||||||
|
label: 'Child 4',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect',
|
||||||
|
width: 80,
|
||||||
|
height: 40,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: 'root_child1',
|
||||||
|
start: 'root',
|
||||||
|
end: 'child1',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'root_child2',
|
||||||
|
start: 'root',
|
||||||
|
end: 'child2',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'root_child3',
|
||||||
|
start: 'root',
|
||||||
|
end: 'child3',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'root_child4',
|
||||||
|
start: 'root',
|
||||||
|
end: 'child4',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
config: mockConfig,
|
||||||
|
direction: 'TB',
|
||||||
|
type: 'test',
|
||||||
|
diagramId: 'test-diagram',
|
||||||
|
markers: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateLayoutData', () => {
|
||||||
|
it('should validate correct layout data', () => {
|
||||||
|
expect(() => validateLayoutData(mockLayoutData)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for missing data', () => {
|
||||||
|
expect(() => validateLayoutData(null as any)).toThrow('Layout data is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for missing config', () => {
|
||||||
|
const invalidData = { ...mockLayoutData, config: null as any };
|
||||||
|
expect(() => validateLayoutData(invalidData)).toThrow('Configuration is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid nodes array', () => {
|
||||||
|
const invalidData = { ...mockLayoutData, nodes: null as any };
|
||||||
|
expect(() => validateLayoutData(invalidData)).toThrow('Nodes array is required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid edges array', () => {
|
||||||
|
const invalidData = { ...mockLayoutData, edges: null as any };
|
||||||
|
expect(() => validateLayoutData(invalidData)).toThrow('Edges array is required');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('executeTidyTreeLayout function', () => {
|
||||||
|
it('should execute layout algorithm successfully', async () => {
|
||||||
|
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.nodes).toBeDefined();
|
||||||
|
expect(result.edges).toBeDefined();
|
||||||
|
expect(Array.isArray(result.nodes)).toBe(true);
|
||||||
|
expect(Array.isArray(result.edges)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return positioned nodes with coordinates', async () => {
|
||||||
|
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||||
|
|
||||||
|
expect(result.nodes.length).toBeGreaterThan(0);
|
||||||
|
result.nodes.forEach((node) => {
|
||||||
|
expect(node.x).toBeDefined();
|
||||||
|
expect(node.y).toBeDefined();
|
||||||
|
expect(typeof node.x).toBe('number');
|
||||||
|
expect(typeof node.y).toBe('number');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return positioned edges with coordinates', async () => {
|
||||||
|
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||||
|
|
||||||
|
expect(result.edges.length).toBeGreaterThan(0);
|
||||||
|
result.edges.forEach((edge) => {
|
||||||
|
expect(edge.startX).toBeDefined();
|
||||||
|
expect(edge.startY).toBeDefined();
|
||||||
|
expect(edge.midX).toBeDefined();
|
||||||
|
expect(edge.midY).toBeDefined();
|
||||||
|
expect(edge.endX).toBeDefined();
|
||||||
|
expect(edge.endY).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty layout data gracefully', async () => {
|
||||||
|
const emptyData: LayoutData = {
|
||||||
|
...mockLayoutData,
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(executeTidyTreeLayout(emptyData)).rejects.toThrow(
|
||||||
|
'No nodes found in layout data'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for missing nodes', async () => {
|
||||||
|
const invalidData = { ...mockLayoutData, nodes: [] };
|
||||||
|
|
||||||
|
await expect(executeTidyTreeLayout(invalidData)).rejects.toThrow(
|
||||||
|
'No nodes found in layout data'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty edges (single node tree)', async () => {
|
||||||
|
const singleNodeData = {
|
||||||
|
...mockLayoutData,
|
||||||
|
edges: [],
|
||||||
|
nodes: [mockLayoutData.nodes[0]],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await executeTidyTreeLayout(singleNodeData);
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.nodes).toHaveLength(1);
|
||||||
|
expect(result.edges).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create bidirectional dual-tree layout with alternating left/right children', async () => {
|
||||||
|
const result = await executeTidyTreeLayout(mockLayoutData);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.nodes).toHaveLength(5);
|
||||||
|
|
||||||
|
const rootNode = result.nodes.find((node) => node.id === 'root');
|
||||||
|
expect(rootNode).toBeDefined();
|
||||||
|
expect(rootNode!.x).toBe(0);
|
||||||
|
expect(rootNode!.y).toBe(20);
|
||||||
|
|
||||||
|
const child1 = result.nodes.find((node) => node.id === 'child1');
|
||||||
|
const child2 = result.nodes.find((node) => node.id === 'child2');
|
||||||
|
const child3 = result.nodes.find((node) => node.id === 'child3');
|
||||||
|
const child4 = result.nodes.find((node) => node.id === 'child4');
|
||||||
|
|
||||||
|
expect(child1).toBeDefined();
|
||||||
|
expect(child2).toBeDefined();
|
||||||
|
expect(child3).toBeDefined();
|
||||||
|
expect(child4).toBeDefined();
|
||||||
|
|
||||||
|
expect(child1!.x).toBeLessThan(rootNode!.x);
|
||||||
|
expect(child2!.x).toBeGreaterThan(rootNode!.x);
|
||||||
|
expect(child3!.x).toBeLessThan(rootNode!.x);
|
||||||
|
expect(child4!.x).toBeGreaterThan(rootNode!.x);
|
||||||
|
|
||||||
|
expect(child1!.x).toBeLessThan(-100);
|
||||||
|
expect(child3!.x).toBeLessThan(-100);
|
||||||
|
|
||||||
|
expect(child2!.x).toBeGreaterThan(100);
|
||||||
|
expect(child4!.x).toBeGreaterThan(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly transpose coordinates to prevent high nodes from covering nodes above them', async () => {
|
||||||
|
const testData = {
|
||||||
|
...mockLayoutData,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'root',
|
||||||
|
label: 'Root',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 100,
|
||||||
|
height: 50,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tall-child',
|
||||||
|
label: 'Tall Child',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 80,
|
||||||
|
height: 120,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'short-child',
|
||||||
|
label: 'Short Child',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 80,
|
||||||
|
height: 30,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: 'root_tall',
|
||||||
|
start: 'root',
|
||||||
|
end: 'tall-child',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'root_short',
|
||||||
|
start: 'root',
|
||||||
|
end: 'short-child',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await executeTidyTreeLayout(testData);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.nodes).toHaveLength(3);
|
||||||
|
|
||||||
|
const rootNode = result.nodes.find((node) => node.id === 'root');
|
||||||
|
const tallChild = result.nodes.find((node) => node.id === 'tall-child');
|
||||||
|
const shortChild = result.nodes.find((node) => node.id === 'short-child');
|
||||||
|
|
||||||
|
expect(rootNode).toBeDefined();
|
||||||
|
expect(tallChild).toBeDefined();
|
||||||
|
expect(shortChild).toBeDefined();
|
||||||
|
|
||||||
|
expect(tallChild!.x).not.toBe(shortChild!.x);
|
||||||
|
|
||||||
|
expect(tallChild!.width).toBe(80);
|
||||||
|
expect(tallChild!.height).toBe(120);
|
||||||
|
expect(shortChild!.width).toBe(80);
|
||||||
|
expect(shortChild!.height).toBe(30);
|
||||||
|
|
||||||
|
const yDifference = Math.abs(tallChild!.y - shortChild!.y);
|
||||||
|
expect(yDifference).toBeGreaterThanOrEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
629
packages/mermaid-layout-tidy-tree/src/layout.ts
Normal file
629
packages/mermaid-layout-tidy-tree/src/layout.ts
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
import type { LayoutData } from 'mermaid';
|
||||||
|
import type { Bounds, Point } from 'mermaid/src/types.js';
|
||||||
|
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
|
||||||
|
import type {
|
||||||
|
Edge,
|
||||||
|
LayoutResult,
|
||||||
|
Node,
|
||||||
|
PositionedEdge,
|
||||||
|
PositionedNode,
|
||||||
|
TidyTreeNode,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the tidy-tree layout algorithm on generic layout data
|
||||||
|
*
|
||||||
|
* This function takes layout data and uses the non-layered-tidy-tree-layout
|
||||||
|
* algorithm to calculate optimal node positions for tree structures.
|
||||||
|
*
|
||||||
|
* @param data - The layout data containing nodes, edges, and configuration
|
||||||
|
* @param config - Mermaid configuration object
|
||||||
|
* @returns Promise resolving to layout result with positioned nodes and edges
|
||||||
|
*/
|
||||||
|
export function executeTidyTreeLayout(data: LayoutData): Promise<LayoutResult> {
|
||||||
|
let intersectionShift = 50;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!data.nodes || !Array.isArray(data.nodes) || data.nodes.length === 0) {
|
||||||
|
throw new Error('No nodes found in layout data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.edges || !Array.isArray(data.edges)) {
|
||||||
|
data.edges = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { leftTree, rightTree, rootNode } = convertToDualTreeFormat(data);
|
||||||
|
|
||||||
|
const gap = 20;
|
||||||
|
const bottomPadding = 40;
|
||||||
|
intersectionShift = 30;
|
||||||
|
|
||||||
|
const bb = new BoundingBox(gap, bottomPadding);
|
||||||
|
const layout = new Layout(bb);
|
||||||
|
|
||||||
|
let leftResult = null;
|
||||||
|
let rightResult = null;
|
||||||
|
|
||||||
|
if (leftTree) {
|
||||||
|
const leftLayoutResult = layout.layout(leftTree);
|
||||||
|
leftResult = leftLayoutResult.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightTree) {
|
||||||
|
const rightLayoutResult = layout.layout(rightTree);
|
||||||
|
rightResult = rightLayoutResult.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionedNodes = combineAndPositionTrees(rootNode, leftResult, rightResult);
|
||||||
|
const positionedEdges = calculateEdgePositions(
|
||||||
|
data.edges,
|
||||||
|
positionedNodes,
|
||||||
|
intersectionShift
|
||||||
|
);
|
||||||
|
resolve({
|
||||||
|
nodes: positionedNodes,
|
||||||
|
edges: positionedEdges,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert LayoutData to dual-tree format (left and right trees)
|
||||||
|
*
|
||||||
|
* This function builds two separate tree structures from the nodes and edges,
|
||||||
|
* alternating children between left and right trees.
|
||||||
|
*/
|
||||||
|
function convertToDualTreeFormat(data: LayoutData): {
|
||||||
|
leftTree: TidyTreeNode | null;
|
||||||
|
rightTree: TidyTreeNode | null;
|
||||||
|
rootNode: TidyTreeNode;
|
||||||
|
} {
|
||||||
|
const { nodes, edges } = data;
|
||||||
|
|
||||||
|
const nodeMap = new Map<string, Node>();
|
||||||
|
nodes.forEach((node) => nodeMap.set(node.id, node));
|
||||||
|
|
||||||
|
const children = new Map<string, string[]>();
|
||||||
|
const parents = new Map<string, string>();
|
||||||
|
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
const parentId = edge.start;
|
||||||
|
const childId = edge.end;
|
||||||
|
|
||||||
|
if (parentId && childId) {
|
||||||
|
if (!children.has(parentId)) {
|
||||||
|
children.set(parentId, []);
|
||||||
|
}
|
||||||
|
children.get(parentId)!.push(childId);
|
||||||
|
parents.set(childId, parentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootNodeData = nodes.find((node) => !parents.has(node.id));
|
||||||
|
if (!rootNodeData && nodes.length === 0) {
|
||||||
|
throw new Error('No nodes available to create tree');
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualRoot = rootNodeData ?? nodes[0];
|
||||||
|
|
||||||
|
const rootNode: TidyTreeNode = {
|
||||||
|
id: actualRoot.id,
|
||||||
|
width: actualRoot.width ?? 100,
|
||||||
|
height: actualRoot.height ?? 50,
|
||||||
|
_originalNode: actualRoot,
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootChildren = children.get(actualRoot.id) ?? [];
|
||||||
|
const leftChildren: string[] = [];
|
||||||
|
const rightChildren: string[] = [];
|
||||||
|
|
||||||
|
rootChildren.forEach((childId, index) => {
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
leftChildren.push(childId);
|
||||||
|
} else {
|
||||||
|
rightChildren.push(childId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const leftTree = leftChildren.length > 0 ? buildSubTree(leftChildren, children, nodeMap) : null;
|
||||||
|
|
||||||
|
const rightTree =
|
||||||
|
rightChildren.length > 0 ? buildSubTree(rightChildren, children, nodeMap) : null;
|
||||||
|
|
||||||
|
return { leftTree, rightTree, rootNode };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a subtree from a list of root children
|
||||||
|
* For horizontal trees, we need to transpose width/height since the tree will be rotated 90°
|
||||||
|
*/
|
||||||
|
function buildSubTree(
|
||||||
|
rootChildren: string[],
|
||||||
|
children: Map<string, string[]>,
|
||||||
|
nodeMap: Map<string, Node>
|
||||||
|
): TidyTreeNode {
|
||||||
|
const virtualRoot: TidyTreeNode = {
|
||||||
|
id: `virtual-root-${Math.random()}`,
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
children: rootChildren
|
||||||
|
.map((childId) => nodeMap.get(childId))
|
||||||
|
.filter((child): child is Node => child !== undefined)
|
||||||
|
.map((child) => convertNodeToTidyTreeTransposed(child, children, nodeMap)),
|
||||||
|
};
|
||||||
|
|
||||||
|
return virtualRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively convert a node and its children to tidy-tree format
|
||||||
|
* This version transposes width/height for horizontal tree layout
|
||||||
|
*/
|
||||||
|
function convertNodeToTidyTreeTransposed(
|
||||||
|
node: Node,
|
||||||
|
children: Map<string, string[]>,
|
||||||
|
nodeMap: Map<string, Node>
|
||||||
|
): TidyTreeNode {
|
||||||
|
const childIds = children.get(node.id) ?? [];
|
||||||
|
const childNodes = childIds
|
||||||
|
.map((childId) => nodeMap.get(childId))
|
||||||
|
.filter((child): child is Node => child !== undefined)
|
||||||
|
.map((child) => convertNodeToTidyTreeTransposed(child, children, nodeMap));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
width: node.height ?? 50,
|
||||||
|
height: node.width ?? 100,
|
||||||
|
children: childNodes.length > 0 ? childNodes : undefined,
|
||||||
|
_originalNode: node,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Combine and position the left and right trees around the root node
|
||||||
|
* Creates a bidirectional layout where left tree grows left and right tree grows right
|
||||||
|
*/
|
||||||
|
function combineAndPositionTrees(
|
||||||
|
rootNode: TidyTreeNode,
|
||||||
|
leftResult: TidyTreeNode | null,
|
||||||
|
rightResult: TidyTreeNode | null
|
||||||
|
): PositionedNode[] {
|
||||||
|
const positionedNodes: PositionedNode[] = [];
|
||||||
|
|
||||||
|
const rootX = 0;
|
||||||
|
const rootY = 0;
|
||||||
|
|
||||||
|
const treeSpacing = rootNode.width / 2 + 30;
|
||||||
|
const leftTreeNodes: PositionedNode[] = [];
|
||||||
|
const rightTreeNodes: PositionedNode[] = [];
|
||||||
|
|
||||||
|
if (leftResult?.children) {
|
||||||
|
positionLeftTreeBidirectional(leftResult.children, leftTreeNodes, rootX - treeSpacing, rootY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightResult?.children) {
|
||||||
|
positionRightTreeBidirectional(
|
||||||
|
rightResult.children,
|
||||||
|
rightTreeNodes,
|
||||||
|
rootX + treeSpacing,
|
||||||
|
rootY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let leftTreeCenterY = 0;
|
||||||
|
let rightTreeCenterY = 0;
|
||||||
|
|
||||||
|
if (leftTreeNodes.length > 0) {
|
||||||
|
const leftTreeXPositions = [...new Set(leftTreeNodes.map((node) => node.x))].sort(
|
||||||
|
(a, b) => b - a
|
||||||
|
);
|
||||||
|
const firstLevelLeftX = leftTreeXPositions[0];
|
||||||
|
const firstLevelLeftNodes = leftTreeNodes.filter((node) => node.x === firstLevelLeftX);
|
||||||
|
|
||||||
|
if (firstLevelLeftNodes.length > 0) {
|
||||||
|
const leftMinY = Math.min(
|
||||||
|
...firstLevelLeftNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
const leftMaxY = Math.max(
|
||||||
|
...firstLevelLeftNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
leftTreeCenterY = (leftMinY + leftMaxY) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightTreeNodes.length > 0) {
|
||||||
|
const rightTreeXPositions = [...new Set(rightTreeNodes.map((node) => node.x))].sort(
|
||||||
|
(a, b) => a - b
|
||||||
|
);
|
||||||
|
const firstLevelRightX = rightTreeXPositions[0];
|
||||||
|
const firstLevelRightNodes = rightTreeNodes.filter((node) => node.x === firstLevelRightX);
|
||||||
|
|
||||||
|
if (firstLevelRightNodes.length > 0) {
|
||||||
|
const rightMinY = Math.min(
|
||||||
|
...firstLevelRightNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
const rightMaxY = Math.max(
|
||||||
|
...firstLevelRightNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
rightTreeCenterY = (rightMinY + rightMaxY) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftTreeOffset = -leftTreeCenterY;
|
||||||
|
const rightTreeOffset = -rightTreeCenterY;
|
||||||
|
|
||||||
|
positionedNodes.push({
|
||||||
|
id: String(rootNode.id),
|
||||||
|
x: rootX,
|
||||||
|
y: rootY + 20,
|
||||||
|
section: 'root',
|
||||||
|
width: rootNode._originalNode?.width ?? rootNode.width,
|
||||||
|
height: rootNode._originalNode?.height ?? rootNode.height,
|
||||||
|
originalNode: rootNode._originalNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
const leftTreeNodesWithOffset = leftTreeNodes.map((node) => ({
|
||||||
|
id: node.id,
|
||||||
|
x: node.x - (node.width ?? 0) / 2,
|
||||||
|
y: node.y + leftTreeOffset + (node.height ?? 0) / 2,
|
||||||
|
section: 'left' as const,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
originalNode: node.originalNode,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const rightTreeNodesWithOffset = rightTreeNodes.map((node) => ({
|
||||||
|
id: node.id,
|
||||||
|
x: node.x + (node.width ?? 0) / 2,
|
||||||
|
y: node.y + rightTreeOffset + (node.height ?? 0) / 2,
|
||||||
|
section: 'right' as const,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
originalNode: node.originalNode,
|
||||||
|
}));
|
||||||
|
|
||||||
|
positionedNodes.push(...leftTreeNodesWithOffset);
|
||||||
|
positionedNodes.push(...rightTreeNodesWithOffset);
|
||||||
|
|
||||||
|
return positionedNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position nodes from the left tree in a bidirectional layout (grows to the left)
|
||||||
|
* Rotates the tree 90 degrees counterclockwise so it grows horizontally to the left
|
||||||
|
*/
|
||||||
|
function positionLeftTreeBidirectional(
|
||||||
|
nodes: TidyTreeNode[],
|
||||||
|
positionedNodes: PositionedNode[],
|
||||||
|
offsetX: number,
|
||||||
|
offsetY: number
|
||||||
|
): void {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const distanceFromRoot = node.y ?? 0;
|
||||||
|
const verticalPosition = node.x ?? 0;
|
||||||
|
|
||||||
|
const originalWidth = node._originalNode?.width ?? 100;
|
||||||
|
const originalHeight = node._originalNode?.height ?? 50;
|
||||||
|
|
||||||
|
const adjustedY = offsetY + verticalPosition;
|
||||||
|
|
||||||
|
positionedNodes.push({
|
||||||
|
id: String(node.id),
|
||||||
|
x: offsetX - distanceFromRoot,
|
||||||
|
y: adjustedY,
|
||||||
|
width: originalWidth,
|
||||||
|
height: originalHeight,
|
||||||
|
originalNode: node._originalNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
positionLeftTreeBidirectional(node.children, positionedNodes, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position nodes from the right tree in a bidirectional layout (grows to the right)
|
||||||
|
* Rotates the tree 90 degrees clockwise so it grows horizontally to the right
|
||||||
|
*/
|
||||||
|
function positionRightTreeBidirectional(
|
||||||
|
nodes: TidyTreeNode[],
|
||||||
|
positionedNodes: PositionedNode[],
|
||||||
|
offsetX: number,
|
||||||
|
offsetY: number
|
||||||
|
): void {
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const distanceFromRoot = node.y ?? 0;
|
||||||
|
const verticalPosition = node.x ?? 0;
|
||||||
|
|
||||||
|
const originalWidth = node._originalNode?.width ?? 100;
|
||||||
|
const originalHeight = node._originalNode?.height ?? 50;
|
||||||
|
|
||||||
|
const adjustedY = offsetY + verticalPosition;
|
||||||
|
|
||||||
|
positionedNodes.push({
|
||||||
|
id: String(node.id),
|
||||||
|
x: offsetX + distanceFromRoot,
|
||||||
|
y: adjustedY,
|
||||||
|
width: originalWidth,
|
||||||
|
height: originalHeight,
|
||||||
|
originalNode: node._originalNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
positionRightTreeBidirectional(node.children, positionedNodes, offsetX, offsetY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the intersection point of a line with a circle
|
||||||
|
* @param circle - Circle coordinates and radius
|
||||||
|
* @param lineStart - Starting point of the line
|
||||||
|
* @param lineEnd - Ending point of the line
|
||||||
|
* @returns The intersection point
|
||||||
|
*/
|
||||||
|
function computeCircleEdgeIntersection(circle: Bounds, lineStart: Point, lineEnd: Point): Point {
|
||||||
|
const radius = Math.min(circle.width, circle.height) / 2;
|
||||||
|
|
||||||
|
const dx = lineEnd.x - lineStart.x;
|
||||||
|
const dy = lineEnd.y - lineStart.y;
|
||||||
|
const length = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (length === 0) {
|
||||||
|
return lineStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nx = dx / length;
|
||||||
|
const ny = dy / length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: circle.x - nx * radius,
|
||||||
|
y: circle.y - ny * radius,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersection(node: PositionedNode, outsidePoint: Point, insidePoint: Point): Point {
|
||||||
|
const x = node.x;
|
||||||
|
const y = node.y;
|
||||||
|
|
||||||
|
if (!node.width || !node.height) {
|
||||||
|
return { x: outsidePoint.x, y: outsidePoint.y };
|
||||||
|
}
|
||||||
|
const dx = Math.abs(x - insidePoint.x);
|
||||||
|
const w = node?.width / 2;
|
||||||
|
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
|
||||||
|
const h = node.height / 2;
|
||||||
|
|
||||||
|
const Q = Math.abs(outsidePoint.y - insidePoint.y);
|
||||||
|
const R = Math.abs(outsidePoint.x - insidePoint.x);
|
||||||
|
|
||||||
|
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
|
||||||
|
// Intersection is top or bottom of rect.
|
||||||
|
const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
|
||||||
|
r = (R * q) / Q;
|
||||||
|
const res = {
|
||||||
|
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
|
||||||
|
y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (r === 0) {
|
||||||
|
res.x = outsidePoint.x;
|
||||||
|
res.y = outsidePoint.y;
|
||||||
|
}
|
||||||
|
if (R === 0) {
|
||||||
|
res.x = outsidePoint.x;
|
||||||
|
}
|
||||||
|
if (Q === 0) {
|
||||||
|
res.y = outsidePoint.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
if (insidePoint.x < outsidePoint.x) {
|
||||||
|
r = outsidePoint.x - w - x;
|
||||||
|
} else {
|
||||||
|
r = x - w - outsidePoint.x;
|
||||||
|
}
|
||||||
|
const q = (Q * r) / R;
|
||||||
|
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
|
||||||
|
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
|
||||||
|
|
||||||
|
if (r === 0) {
|
||||||
|
_x = outsidePoint.x;
|
||||||
|
_y = outsidePoint.y;
|
||||||
|
}
|
||||||
|
if (R === 0) {
|
||||||
|
_x = outsidePoint.x;
|
||||||
|
}
|
||||||
|
if (Q === 0) {
|
||||||
|
_y = outsidePoint.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: _x, y: _y };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate edge positions based on positioned nodes
|
||||||
|
* Now includes tree membership and node dimensions for precise edge calculations
|
||||||
|
* Edges now stop at shape boundaries instead of extending to centers
|
||||||
|
*/
|
||||||
|
function calculateEdgePositions(
|
||||||
|
edges: Edge[],
|
||||||
|
positionedNodes: PositionedNode[],
|
||||||
|
intersectionShift: number
|
||||||
|
): PositionedEdge[] {
|
||||||
|
const nodeInfo = new Map<string, PositionedNode>();
|
||||||
|
positionedNodes.forEach((node) => {
|
||||||
|
nodeInfo.set(node.id, node);
|
||||||
|
});
|
||||||
|
|
||||||
|
return edges.map((edge) => {
|
||||||
|
const sourceNode = nodeInfo.get(edge.start ?? '');
|
||||||
|
const targetNode = nodeInfo.get(edge.end ?? '');
|
||||||
|
|
||||||
|
if (!sourceNode || !targetNode) {
|
||||||
|
return {
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.start ?? '',
|
||||||
|
target: edge.end ?? '',
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
midX: 0,
|
||||||
|
midY: 0,
|
||||||
|
endX: 0,
|
||||||
|
endY: 0,
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
sourceSection: undefined,
|
||||||
|
targetSection: undefined,
|
||||||
|
sourceWidth: undefined,
|
||||||
|
sourceHeight: undefined,
|
||||||
|
targetWidth: undefined,
|
||||||
|
targetHeight: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceCenter = { x: sourceNode.x, y: sourceNode.y };
|
||||||
|
const targetCenter = { x: targetNode.x, y: targetNode.y };
|
||||||
|
|
||||||
|
const isSourceRound = ['circle', 'cloud', 'bang'].includes(
|
||||||
|
sourceNode.originalNode?.shape ?? ''
|
||||||
|
);
|
||||||
|
const isTargetRound = ['circle', 'cloud', 'bang'].includes(
|
||||||
|
targetNode.originalNode?.shape ?? ''
|
||||||
|
);
|
||||||
|
|
||||||
|
let startPos = isSourceRound
|
||||||
|
? computeCircleEdgeIntersection(
|
||||||
|
{
|
||||||
|
x: sourceNode.x,
|
||||||
|
y: sourceNode.y,
|
||||||
|
width: sourceNode.width ?? 100,
|
||||||
|
height: sourceNode.height ?? 100,
|
||||||
|
},
|
||||||
|
targetCenter,
|
||||||
|
sourceCenter
|
||||||
|
)
|
||||||
|
: intersection(sourceNode, sourceCenter, targetCenter);
|
||||||
|
|
||||||
|
let endPos = isTargetRound
|
||||||
|
? computeCircleEdgeIntersection(
|
||||||
|
{
|
||||||
|
x: targetNode.x,
|
||||||
|
y: targetNode.y,
|
||||||
|
width: targetNode.width ?? 100,
|
||||||
|
height: targetNode.height ?? 100,
|
||||||
|
},
|
||||||
|
sourceCenter,
|
||||||
|
targetCenter
|
||||||
|
)
|
||||||
|
: intersection(targetNode, targetCenter, sourceCenter);
|
||||||
|
|
||||||
|
const midX = (startPos.x + endPos.x) / 2;
|
||||||
|
const midY = (startPos.y + endPos.y) / 2;
|
||||||
|
|
||||||
|
const points = [startPos];
|
||||||
|
if (sourceNode.section === 'left') {
|
||||||
|
points.push({
|
||||||
|
x: sourceNode.x - (sourceNode.width ?? 0) / 2 - intersectionShift,
|
||||||
|
y: sourceNode.y,
|
||||||
|
});
|
||||||
|
} else if (sourceNode.section === 'right') {
|
||||||
|
points.push({
|
||||||
|
x: sourceNode.x + (sourceNode.width ?? 0) / 2 + intersectionShift,
|
||||||
|
y: sourceNode.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (targetNode.section === 'left') {
|
||||||
|
points.push({
|
||||||
|
x: targetNode.x + (targetNode.width ?? 0) / 2 + intersectionShift,
|
||||||
|
y: targetNode.y,
|
||||||
|
});
|
||||||
|
} else if (targetNode.section === 'right') {
|
||||||
|
points.push({
|
||||||
|
x: targetNode.x - (targetNode.width ?? 0) / 2 - intersectionShift,
|
||||||
|
y: targetNode.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
points.push(endPos);
|
||||||
|
|
||||||
|
const secondPoint = points.length > 1 ? points[1] : targetCenter;
|
||||||
|
startPos = isSourceRound
|
||||||
|
? computeCircleEdgeIntersection(
|
||||||
|
{
|
||||||
|
x: sourceNode.x,
|
||||||
|
y: sourceNode.y,
|
||||||
|
width: sourceNode.width ?? 100,
|
||||||
|
height: sourceNode.height ?? 100,
|
||||||
|
},
|
||||||
|
secondPoint,
|
||||||
|
sourceCenter
|
||||||
|
)
|
||||||
|
: intersection(sourceNode, secondPoint, sourceCenter);
|
||||||
|
points[0] = startPos;
|
||||||
|
|
||||||
|
const secondLastPoint = points.length > 1 ? points[points.length - 2] : sourceCenter;
|
||||||
|
endPos = isTargetRound
|
||||||
|
? computeCircleEdgeIntersection(
|
||||||
|
{
|
||||||
|
x: targetNode.x,
|
||||||
|
y: targetNode.y,
|
||||||
|
width: targetNode.width ?? 100,
|
||||||
|
height: targetNode.height ?? 100,
|
||||||
|
},
|
||||||
|
secondLastPoint,
|
||||||
|
targetCenter
|
||||||
|
)
|
||||||
|
: intersection(targetNode, secondLastPoint, targetCenter);
|
||||||
|
points[points.length - 1] = endPos;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.start ?? '',
|
||||||
|
target: edge.end ?? '',
|
||||||
|
startX: startPos.x,
|
||||||
|
startY: startPos.y,
|
||||||
|
midX,
|
||||||
|
midY,
|
||||||
|
endX: endPos.x,
|
||||||
|
endY: endPos.y,
|
||||||
|
points,
|
||||||
|
sourceSection: sourceNode?.section,
|
||||||
|
targetSection: targetNode?.section,
|
||||||
|
sourceWidth: sourceNode?.width,
|
||||||
|
sourceHeight: sourceNode?.height,
|
||||||
|
targetWidth: targetNode?.width,
|
||||||
|
targetHeight: targetNode?.height,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate layout data structure
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns True if data is valid, throws error otherwise
|
||||||
|
*/
|
||||||
|
export function validateLayoutData(data: LayoutData): boolean {
|
||||||
|
if (!data) {
|
||||||
|
throw new Error('Layout data is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.config) {
|
||||||
|
throw new Error('Configuration is required in layout data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(data.nodes)) {
|
||||||
|
throw new Error('Nodes array is required in layout data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(data.edges)) {
|
||||||
|
throw new Error('Edges array is required in layout data');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
13
packages/mermaid-layout-tidy-tree/src/layouts.ts
Normal file
13
packages/mermaid-layout-tidy-tree/src/layouts.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { LayoutLoaderDefinition } from 'mermaid';
|
||||||
|
|
||||||
|
const loader = async () => await import(`./render.js`);
|
||||||
|
|
||||||
|
const tidyTreeLayout: LayoutLoaderDefinition[] = [
|
||||||
|
{
|
||||||
|
name: 'tidy-tree',
|
||||||
|
loader,
|
||||||
|
algorithm: 'tidy-tree',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default tidyTreeLayout;
|
18
packages/mermaid-layout-tidy-tree/src/non-layered-tidy-tree-layout.d.ts
vendored
Normal file
18
packages/mermaid-layout-tidy-tree/src/non-layered-tidy-tree-layout.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
declare module 'non-layered-tidy-tree-layout' {
|
||||||
|
export class BoundingBox {
|
||||||
|
constructor(gap: number, bottomPadding: number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Layout {
|
||||||
|
constructor(boundingBox: BoundingBox);
|
||||||
|
layout(data: any): {
|
||||||
|
result: any;
|
||||||
|
boundingBox: {
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
top: number;
|
||||||
|
bottom: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
180
packages/mermaid-layout-tidy-tree/src/render.ts
Normal file
180
packages/mermaid-layout-tidy-tree/src/render.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import type { InternalHelpers, LayoutData, RenderOptions, SVG } from 'mermaid';
|
||||||
|
import { executeTidyTreeLayout } from './layout.js';
|
||||||
|
|
||||||
|
interface NodeWithPosition {
|
||||||
|
id: string;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
domId?: any;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render function for bidirectional tidy-tree layout algorithm
|
||||||
|
*
|
||||||
|
* This follows the same pattern as ELK and dagre renderers:
|
||||||
|
* 1. Insert nodes into DOM to get their actual dimensions
|
||||||
|
* 2. Run the bidirectional tidy-tree layout algorithm to calculate positions
|
||||||
|
* 3. Position the nodes and edges based on layout results
|
||||||
|
*
|
||||||
|
* The bidirectional layout creates two trees that grow horizontally in opposite
|
||||||
|
* directions from a central root node:
|
||||||
|
* - Left tree: grows horizontally to the left (children: 1st, 3rd, 5th...)
|
||||||
|
* - Right tree: grows horizontally to the right (children: 2nd, 4th, 6th...)
|
||||||
|
*/
|
||||||
|
export const render = async (
|
||||||
|
data4Layout: LayoutData,
|
||||||
|
svg: SVG,
|
||||||
|
{
|
||||||
|
insertCluster,
|
||||||
|
insertEdge,
|
||||||
|
insertEdgeLabel,
|
||||||
|
insertMarkers,
|
||||||
|
insertNode,
|
||||||
|
log,
|
||||||
|
positionEdgeLabel,
|
||||||
|
}: InternalHelpers,
|
||||||
|
{ algorithm: _algorithm }: RenderOptions
|
||||||
|
) => {
|
||||||
|
const nodeDb: Record<string, NodeWithPosition> = {};
|
||||||
|
const clusterDb: Record<string, any> = {};
|
||||||
|
|
||||||
|
const element = svg.select('g');
|
||||||
|
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||||
|
|
||||||
|
const subGraphsEl = element.insert('g').attr('class', 'subgraphs');
|
||||||
|
const edgePaths = element.insert('g').attr('class', 'edgePaths');
|
||||||
|
const edgeLabels = element.insert('g').attr('class', 'edgeLabels');
|
||||||
|
const nodes = element.insert('g').attr('class', 'nodes');
|
||||||
|
// Step 1: Insert nodes into DOM to get their actual dimensions
|
||||||
|
log.debug('Inserting nodes into DOM for dimension calculation');
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
data4Layout.nodes.map(async (node) => {
|
||||||
|
if (node.isGroup) {
|
||||||
|
const clusterNode: NodeWithPosition = {
|
||||||
|
...node,
|
||||||
|
id: node.id,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
};
|
||||||
|
clusterDb[node.id] = clusterNode;
|
||||||
|
nodeDb[node.id] = clusterNode;
|
||||||
|
|
||||||
|
await insertCluster(subGraphsEl, node);
|
||||||
|
} else {
|
||||||
|
const nodeWithPosition: NodeWithPosition = {
|
||||||
|
...node,
|
||||||
|
id: node.id,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
};
|
||||||
|
nodeDb[node.id] = nodeWithPosition;
|
||||||
|
|
||||||
|
const nodeEl = await insertNode(nodes, node, {
|
||||||
|
config: data4Layout.config,
|
||||||
|
dir: data4Layout.direction || 'TB',
|
||||||
|
});
|
||||||
|
|
||||||
|
const boundingBox = nodeEl.node()!.getBBox();
|
||||||
|
nodeWithPosition.width = boundingBox.width;
|
||||||
|
nodeWithPosition.height = boundingBox.height;
|
||||||
|
nodeWithPosition.domId = nodeEl;
|
||||||
|
|
||||||
|
log.debug(`Node ${node.id} dimensions: ${boundingBox.width}x${boundingBox.height}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// Step 2: Run the bidirectional tidy-tree layout algorithm
|
||||||
|
log.debug('Running bidirectional tidy-tree layout algorithm');
|
||||||
|
|
||||||
|
const updatedLayoutData = {
|
||||||
|
...data4Layout,
|
||||||
|
nodes: data4Layout.nodes.map((node) => {
|
||||||
|
const nodeWithDimensions = nodeDb[node.id];
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
width: nodeWithDimensions.width ?? node.width ?? 100,
|
||||||
|
height: nodeWithDimensions.height ?? node.height ?? 50,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const layoutResult = await executeTidyTreeLayout(updatedLayoutData);
|
||||||
|
// Step 3: Position the nodes based on bidirectional layout results
|
||||||
|
log.debug('Positioning nodes based on bidirectional layout results');
|
||||||
|
|
||||||
|
layoutResult.nodes.forEach((positionedNode) => {
|
||||||
|
const node = nodeDb[positionedNode.id];
|
||||||
|
if (node?.domId) {
|
||||||
|
// Position the node at the calculated coordinates from bidirectional layout
|
||||||
|
// The layout algorithm has already calculated positions for:
|
||||||
|
// - Root node at center (0, 0)
|
||||||
|
// - Left tree nodes with negative x coordinates (growing left)
|
||||||
|
// - Right tree nodes with positive x coordinates (growing right)
|
||||||
|
node.domId.attr('transform', `translate(${positionedNode.x}, ${positionedNode.y})`);
|
||||||
|
// Store the final position
|
||||||
|
node.x = positionedNode.x;
|
||||||
|
node.y = positionedNode.y;
|
||||||
|
// Step 3: Position the nodes based on bidirectional layout results
|
||||||
|
log.debug(`Positioned node ${node.id} at (${positionedNode.x}, ${positionedNode.y})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug('Inserting and positioning edges');
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
data4Layout.edges.map(async (edge) => {
|
||||||
|
await insertEdgeLabel(edgeLabels, edge);
|
||||||
|
|
||||||
|
const startNode = nodeDb[edge.start ?? ''];
|
||||||
|
const endNode = nodeDb[edge.end ?? ''];
|
||||||
|
|
||||||
|
if (startNode && endNode) {
|
||||||
|
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
||||||
|
|
||||||
|
if (positionedEdge) {
|
||||||
|
log.debug('APA01 positionedEdge', positionedEdge);
|
||||||
|
const edgeWithPath = {
|
||||||
|
...edge,
|
||||||
|
points: positionedEdge.points,
|
||||||
|
};
|
||||||
|
const paths = insertEdge(
|
||||||
|
edgePaths,
|
||||||
|
edgeWithPath,
|
||||||
|
clusterDb,
|
||||||
|
data4Layout.type,
|
||||||
|
startNode,
|
||||||
|
endNode,
|
||||||
|
data4Layout.diagramId
|
||||||
|
);
|
||||||
|
|
||||||
|
positionEdgeLabel(edgeWithPath, paths);
|
||||||
|
} else {
|
||||||
|
const edgeWithPath = {
|
||||||
|
...edge,
|
||||||
|
points: [
|
||||||
|
{ x: startNode.x ?? 0, y: startNode.y ?? 0 },
|
||||||
|
{ x: endNode.x ?? 0, y: endNode.y ?? 0 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const paths = insertEdge(
|
||||||
|
edgePaths,
|
||||||
|
edgeWithPath,
|
||||||
|
clusterDb,
|
||||||
|
data4Layout.type,
|
||||||
|
startNode,
|
||||||
|
endNode,
|
||||||
|
data4Layout.diagramId
|
||||||
|
);
|
||||||
|
positionEdgeLabel(edgeWithPath, paths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
log.debug('Bidirectional tidy-tree rendering completed');
|
||||||
|
};
|
69
packages/mermaid-layout-tidy-tree/src/types.ts
Normal file
69
packages/mermaid-layout-tidy-tree/src/types.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import type { LayoutData } from 'mermaid';
|
||||||
|
|
||||||
|
export type Node = LayoutData['nodes'][number];
|
||||||
|
export type Edge = LayoutData['edges'][number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positioned node after layout calculation
|
||||||
|
*/
|
||||||
|
export interface PositionedNode {
|
||||||
|
id: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
section?: 'root' | 'left' | 'right';
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
originalNode?: Node;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positioned edge after layout calculation
|
||||||
|
*/
|
||||||
|
export interface PositionedEdge {
|
||||||
|
id: string;
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
startX: number;
|
||||||
|
startY: number;
|
||||||
|
midX: number;
|
||||||
|
midY: number;
|
||||||
|
endX: number;
|
||||||
|
endY: number;
|
||||||
|
sourceSection?: 'root' | 'left' | 'right';
|
||||||
|
targetSection?: 'root' | 'left' | 'right';
|
||||||
|
sourceWidth?: number;
|
||||||
|
sourceHeight?: number;
|
||||||
|
targetWidth?: number;
|
||||||
|
targetHeight?: number;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of layout algorithm execution
|
||||||
|
*/
|
||||||
|
export interface LayoutResult {
|
||||||
|
nodes: PositionedNode[];
|
||||||
|
edges: PositionedEdge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tidy-tree node structure compatible with non-layered-tidy-tree-layout
|
||||||
|
*/
|
||||||
|
export interface TidyTreeNode {
|
||||||
|
id: string | number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
children?: TidyTreeNode[];
|
||||||
|
_originalNode?: Node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tidy-tree layout configuration
|
||||||
|
*/
|
||||||
|
export interface TidyTreeLayoutConfig {
|
||||||
|
gap: number;
|
||||||
|
bottomPadding: number;
|
||||||
|
}
|
10
packages/mermaid-layout-tidy-tree/tsconfig.json
Normal file
10
packages/mermaid-layout-tidy-tree/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"types": ["vitest/importMeta", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts", "./src/**/*.d.ts"],
|
||||||
|
"typeRoots": ["./src/types"]
|
||||||
|
}
|
@@ -1,11 +1,5 @@
|
|||||||
# mermaid
|
# mermaid
|
||||||
|
|
||||||
## 11.10.1
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- [#6886](https://github.com/mermaid-js/mermaid/pull/6886) [`e0b45c2`](https://github.com/mermaid-js/mermaid/commit/e0b45c2d2b41c2a9038bf87646fa3ccd7560eb20) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Handle arrows correctly when auto number is enabled
|
|
||||||
|
|
||||||
## 11.10.0
|
## 11.10.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
@@ -160,7 +154,6 @@
|
|||||||
### 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 ...)
|
||||||
@@ -179,14 +172,12 @@
|
|||||||
### 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
|
||||||
|
|
||||||
@@ -235,7 +226,6 @@
|
|||||||
- [#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:
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid",
|
"name": "mermaid",
|
||||||
"version": "11.10.1",
|
"version": "11.10.0",
|
||||||
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
|
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "./dist/mermaid.core.mjs",
|
"module": "./dist/mermaid.core.mjs",
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^7.0.4",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"@iconify/utils": "^2.1.33",
|
"@iconify/utils": "^3.0.1",
|
||||||
"@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",
|
||||||
@@ -77,19 +77,19 @@
|
|||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"d3-sankey": "^0.12.3",
|
"d3-sankey": "^0.12.3",
|
||||||
"dagre-d3-es": "7.0.11",
|
"dagre-d3-es": "7.0.11",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.18",
|
||||||
"dompurify": "^3.2.5",
|
"dompurify": "^3.2.5",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.22",
|
||||||
"khroma": "^2.1.0",
|
"khroma": "^2.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^16.0.0",
|
"marked": "^16.2.1",
|
||||||
"roughjs": "^4.6.6",
|
"roughjs": "^4.6.6",
|
||||||
"stylis": "^4.3.6",
|
"stylis": "^4.3.6",
|
||||||
"ts-dedent": "^2.2.0",
|
"ts-dedent": "^2.2.0",
|
||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@adobe/jsonschema2md": "^8.0.2",
|
"@adobe/jsonschema2md": "^8.0.5",
|
||||||
"@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.0",
|
"canvas": "^3.1.2",
|
||||||
"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.0.2",
|
"globby": "^14.1.0",
|
||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.8",
|
||||||
"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.2",
|
"prettier": "^3.5.3",
|
||||||
"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.10",
|
"start-server-and-test": "^2.0.13",
|
||||||
"type-fest": "^4.35.0",
|
"type-fest": "^4.35.0",
|
||||||
"typedoc": "^0.27.8",
|
"typedoc": "^0.28.12",
|
||||||
"typedoc-plugin-markdown": "^4.4.2",
|
"typedoc-plugin-markdown": "^4.8.1",
|
||||||
"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.0.2",
|
"vitepress": "^1.6.4",
|
||||||
"vitepress-plugin-search": "1.0.4-alpha.22"
|
"vitepress-plugin-search": "1.0.4-alpha.22"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
@@ -171,7 +171,9 @@ This Markdown should be kept.
|
|||||||
expect(buildShapeDoc()).toMatchInlineSnapshot(`
|
expect(buildShapeDoc()).toMatchInlineSnapshot(`
|
||||||
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||||
|
| Bang | Bang | \`bang\` | Bang | \`bang\` |
|
||||||
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
||||||
|
| Cloud | Cloud | \`cloud\` | cloud | \`cloud\` |
|
||||||
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
||||||
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
||||||
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
||||||
|
@@ -78,3 +78,187 @@ describe('when working with site config', () => {
|
|||||||
expect(config_4.altFontFamily).toBeUndefined();
|
expect(config_4.altFontFamily).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getUserDefinedConfig', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
configApi.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty object when no user config is defined', () => {
|
||||||
|
const userConfig = configApi.getUserDefinedConfig();
|
||||||
|
expect(userConfig).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return config from initialize only', () => {
|
||||||
|
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial' };
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
|
||||||
|
const userConfig = configApi.getUserDefinedConfig();
|
||||||
|
expect(userConfig).toEqual(initConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return config from directives only', () => {
|
||||||
|
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
|
||||||
|
const directive2: MermaidConfig = { theme: 'forest' };
|
||||||
|
|
||||||
|
configApi.addDirective(directive1);
|
||||||
|
configApi.addDirective(directive2);
|
||||||
|
|
||||||
|
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"fontFamily": "Arial",
|
||||||
|
"fontSize": 14,
|
||||||
|
"layout": "elk",
|
||||||
|
"theme": "forest",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should combine initialize config and directives', () => {
|
||||||
|
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial', layout: 'dagre' };
|
||||||
|
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
|
||||||
|
const directive2: MermaidConfig = { theme: 'forest' };
|
||||||
|
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
configApi.addDirective(directive1);
|
||||||
|
configApi.addDirective(directive2);
|
||||||
|
|
||||||
|
const userConfig = configApi.getUserDefinedConfig();
|
||||||
|
expect(userConfig).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"fontFamily": "Arial",
|
||||||
|
"fontSize": 14,
|
||||||
|
"layout": "elk",
|
||||||
|
"theme": "forest",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nested config objects properly', () => {
|
||||||
|
const initConfig: MermaidConfig = {
|
||||||
|
flowchart: { nodeSpacing: 50, rankSpacing: 100 },
|
||||||
|
theme: 'default',
|
||||||
|
};
|
||||||
|
const directive: MermaidConfig = {
|
||||||
|
flowchart: { nodeSpacing: 75, curve: 'basis' },
|
||||||
|
mindmap: { padding: 20 },
|
||||||
|
};
|
||||||
|
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
configApi.addDirective(directive);
|
||||||
|
|
||||||
|
const userConfig = configApi.getUserDefinedConfig();
|
||||||
|
expect(userConfig).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"flowchart": {
|
||||||
|
"curve": "basis",
|
||||||
|
"nodeSpacing": 75,
|
||||||
|
"rankSpacing": 100,
|
||||||
|
},
|
||||||
|
"mindmap": {
|
||||||
|
"padding": 20,
|
||||||
|
},
|
||||||
|
"theme": "default",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex nested overrides', () => {
|
||||||
|
const initConfig: MermaidConfig = {
|
||||||
|
flowchart: {
|
||||||
|
nodeSpacing: 50,
|
||||||
|
rankSpacing: 100,
|
||||||
|
curve: 'linear',
|
||||||
|
},
|
||||||
|
theme: 'default',
|
||||||
|
};
|
||||||
|
const directive1: MermaidConfig = {
|
||||||
|
flowchart: {
|
||||||
|
nodeSpacing: 75,
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
};
|
||||||
|
const directive2: MermaidConfig = {
|
||||||
|
flowchart: {
|
||||||
|
curve: 'basis',
|
||||||
|
nodeSpacing: 100,
|
||||||
|
},
|
||||||
|
mindmap: {
|
||||||
|
padding: 15,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
configApi.addDirective(directive1);
|
||||||
|
configApi.addDirective(directive2);
|
||||||
|
|
||||||
|
const userConfig = configApi.getUserDefinedConfig();
|
||||||
|
expect(userConfig).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"flowchart": {
|
||||||
|
"curve": "basis",
|
||||||
|
"nodeSpacing": 100,
|
||||||
|
"rankSpacing": 100,
|
||||||
|
},
|
||||||
|
"fontSize": 12,
|
||||||
|
"mindmap": {
|
||||||
|
"padding": 15,
|
||||||
|
},
|
||||||
|
"theme": "default",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return independent copies (not references)', () => {
|
||||||
|
const initConfig: MermaidConfig = { theme: 'dark', flowchart: { nodeSpacing: 50 } };
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
|
||||||
|
const userConfig1 = configApi.getUserDefinedConfig();
|
||||||
|
const userConfig2 = configApi.getUserDefinedConfig();
|
||||||
|
|
||||||
|
userConfig1.theme = 'neutral';
|
||||||
|
userConfig1.flowchart!.nodeSpacing = 999;
|
||||||
|
|
||||||
|
expect(userConfig2).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"flowchart": {
|
||||||
|
"nodeSpacing": 50,
|
||||||
|
},
|
||||||
|
"theme": "dark",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle edge cases with undefined values', () => {
|
||||||
|
const initConfig: MermaidConfig = { theme: 'dark', layout: undefined };
|
||||||
|
const directive: MermaidConfig = { fontSize: 14, fontFamily: undefined };
|
||||||
|
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
configApi.addDirective(directive);
|
||||||
|
|
||||||
|
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"fontSize": 14,
|
||||||
|
"layout": undefined,
|
||||||
|
"theme": "dark",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retain config from initialize after reset', () => {
|
||||||
|
const initConfig: MermaidConfig = { theme: 'dark' };
|
||||||
|
const directive: MermaidConfig = { layout: 'elk' };
|
||||||
|
|
||||||
|
configApi.saveConfigFromInitialize(initConfig);
|
||||||
|
configApi.addDirective(directive);
|
||||||
|
|
||||||
|
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"layout": "elk",
|
||||||
|
"theme": "dark",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
configApi.reset();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -248,3 +248,17 @@ const checkConfig = (config: MermaidConfig) => {
|
|||||||
issueWarning('LAZY_LOAD_DEPRECATED');
|
issueWarning('LAZY_LOAD_DEPRECATED');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getUserDefinedConfig = (): MermaidConfig => {
|
||||||
|
let userConfig: MermaidConfig = {};
|
||||||
|
|
||||||
|
if (configFromInitialize) {
|
||||||
|
userConfig = assignWithDepth(userConfig, configFromInitialize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const d of directives) {
|
||||||
|
userConfig = assignWithDepth(userConfig, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userConfig;
|
||||||
|
};
|
||||||
|
@@ -1075,6 +1075,10 @@ export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
|
|||||||
export interface MindmapDiagramConfig extends BaseDiagramConfig {
|
export interface MindmapDiagramConfig extends BaseDiagramConfig {
|
||||||
padding?: number;
|
padding?: number;
|
||||||
maxNodeWidth?: number;
|
maxNodeWidth?: number;
|
||||||
|
/**
|
||||||
|
* Layout algorithm to use for positioning mindmap nodes
|
||||||
|
*/
|
||||||
|
layoutAlgorithm?: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* The object containing configurations specific for kanban diagrams
|
* The object containing configurations specific for kanban diagrams
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
// tests to check that comments are removed
|
|
||||||
|
|
||||||
import { cleanupComments } from './comments.js';
|
import { cleanupComments } from './comments.js';
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
@@ -10,12 +8,12 @@ describe('comments', () => {
|
|||||||
%% This is a comment
|
%% This is a comment
|
||||||
%% This is another comment
|
%% This is another comment
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
%% This is a comment
|
%% This is a comment
|
||||||
`;
|
`;
|
||||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||||
"graph TD
|
"graph TD
|
||||||
A-->B
|
A-->B
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -29,9 +27,9 @@ graph TD
|
|||||||
%%{ init: {'theme': 'space before init'}}%%
|
%%{ init: {'theme': 'space before init'}}%%
|
||||||
%%{init: {'theme': 'space after ending'}}%%
|
%%{init: {'theme': 'space after ending'}}%%
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
|
|
||||||
B-->C
|
B-->C
|
||||||
%% This is a comment
|
%% This is a comment
|
||||||
`;
|
`;
|
||||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||||
@@ -39,9 +37,9 @@ graph TD
|
|||||||
%%{ init: {'theme': 'space before init'}}%%
|
%%{ init: {'theme': 'space before init'}}%%
|
||||||
%%{init: {'theme': 'space after ending'}}%%
|
%%{init: {'theme': 'space after ending'}}%%
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
|
|
||||||
B-->C
|
B-->C
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -50,14 +48,14 @@ graph TD
|
|||||||
const text = `
|
const text = `
|
||||||
%% This is a comment
|
%% This is a comment
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
%% This is a comment
|
%% This is a comment
|
||||||
C-->D
|
C-->D
|
||||||
`;
|
`;
|
||||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||||
"graph TD
|
"graph TD
|
||||||
A-->B
|
A-->B
|
||||||
C-->D
|
C-->D
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -70,11 +68,11 @@ graph TD
|
|||||||
|
|
||||||
%% This is a comment
|
%% This is a comment
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
`;
|
`;
|
||||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||||
"graph TD
|
"graph TD
|
||||||
A-->B
|
A-->B
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -82,12 +80,12 @@ graph TD
|
|||||||
it('should remove comments at end of text with no newline', () => {
|
it('should remove comments at end of text with no newline', () => {
|
||||||
const text = `
|
const text = `
|
||||||
graph TD
|
graph TD
|
||||||
A-->B
|
A-->B
|
||||||
%% This is a comment`;
|
%% This is a comment`;
|
||||||
|
|
||||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||||
"graph TD
|
"graph TD
|
||||||
A-->B
|
A-->B
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@@ -3,6 +3,7 @@ import type * as d3 from 'd3';
|
|||||||
import type { SetOptional, SetRequired } from 'type-fest';
|
import type { SetOptional, SetRequired } from 'type-fest';
|
||||||
import type { Diagram } from '../Diagram.js';
|
import type { Diagram } from '../Diagram.js';
|
||||||
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
|
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||||
|
import type { DiagramOrientation } from '../diagrams/git/gitGraphTypes.js';
|
||||||
|
|
||||||
export interface DiagramMetadata {
|
export interface DiagramMetadata {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -35,7 +36,8 @@ export interface DiagramDB {
|
|||||||
getAccTitle?: () => string;
|
getAccTitle?: () => string;
|
||||||
setAccDescription?: (description: string) => void;
|
setAccDescription?: (description: string) => void;
|
||||||
getAccDescription?: () => string;
|
getAccDescription?: () => string;
|
||||||
|
getDirection?: () => string | undefined;
|
||||||
|
setDirection?: (dir: DiagramOrientation) => void;
|
||||||
setDisplayMode?: (title: string) => void;
|
setDisplayMode?: (title: string) => void;
|
||||||
bindFunctions?: (element: Element) => void;
|
bindFunctions?: (element: Element) => void;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import type { Position } from 'cytoscape';
|
import type { LayoutOptions, Position } from 'cytoscape';
|
||||||
import cytoscape from 'cytoscape';
|
import cytoscape from 'cytoscape';
|
||||||
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
|
|
||||||
import fcose from 'cytoscape-fcose';
|
import fcose from 'cytoscape-fcose';
|
||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
|
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
|
||||||
@@ -41,7 +40,7 @@ registerIconPacks([
|
|||||||
icons: architectureIcons,
|
icons: architectureIcons,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
cytoscape.use(fcose);
|
cytoscape.use(fcose as any);
|
||||||
|
|
||||||
function addServices(services: ArchitectureService[], cy: cytoscape.Core, db: ArchitectureDB) {
|
function addServices(services: ArchitectureService[], cy: cytoscape.Core, db: ArchitectureDB) {
|
||||||
services.forEach((service) => {
|
services.forEach((service) => {
|
||||||
@@ -429,7 +428,7 @@ function layoutArchitecture(
|
|||||||
},
|
},
|
||||||
alignmentConstraint,
|
alignmentConstraint,
|
||||||
relativePlacementConstraint,
|
relativePlacementConstraint,
|
||||||
} as FcoseLayoutOptions);
|
} as LayoutOptions);
|
||||||
|
|
||||||
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
|
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
|
||||||
layout.one('layoutstop', () => {
|
layout.one('layoutstop', () => {
|
||||||
|
@@ -1070,6 +1070,14 @@ describe('given a class diagram with members and methods ', function () {
|
|||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
|
it('should handle an empty class body with {}', function () {
|
||||||
|
const str = 'classDiagram\nclass EmptyClass {}';
|
||||||
|
parser.parse(str);
|
||||||
|
const actual = parser.yy.getClass('EmptyClass');
|
||||||
|
expect(actual.label).toBe('EmptyClass');
|
||||||
|
expect(actual.members.length).toBe(0);
|
||||||
|
expect(actual.methods.length).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -293,6 +293,7 @@ classStatement
|
|||||||
: classIdentifier
|
: classIdentifier
|
||||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||||
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
|
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
|
||||||
|
| classIdentifier STRUCT_START STRUCT_STOP {}
|
||||||
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
|
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -301,8 +302,15 @@ classIdentifier
|
|||||||
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
emptyBody
|
||||||
|
:
|
||||||
|
| SPACE emptyBody
|
||||||
|
| NEWLINE emptyBody
|
||||||
|
;
|
||||||
|
|
||||||
annotationStatement
|
annotationStatement
|
||||||
:ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
|
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
|
||||||
;
|
;
|
||||||
|
|
||||||
members
|
members
|
||||||
|
297
packages/mermaid/src/diagrams/mindmap/mindmapDb.getData.test.ts
Normal file
297
packages/mermaid/src/diagrams/mindmap/mindmapDb.getData.test.ts
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { MindmapDB } from './mindmapDb.js';
|
||||||
|
import type { MindmapLayoutNode, MindmapLayoutEdge } from './mindmapDb.js';
|
||||||
|
import type { Edge } from '../../rendering-util/types.js';
|
||||||
|
|
||||||
|
// Mock the getConfig function
|
||||||
|
vi.mock('../../diagram-api/diagramAPI.js', () => ({
|
||||||
|
getConfig: vi.fn(() => ({
|
||||||
|
mindmap: {
|
||||||
|
layoutAlgorithm: 'cose-bilkent',
|
||||||
|
padding: 10,
|
||||||
|
maxNodeWidth: 200,
|
||||||
|
useMaxWidth: true,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('MindmapDb getData function', () => {
|
||||||
|
let db: MindmapDB;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
db = new MindmapDB();
|
||||||
|
// Clear the database before each test
|
||||||
|
db.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getData', () => {
|
||||||
|
it('should return empty data when no mindmap is set', () => {
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
expect(result.nodes).toEqual([]);
|
||||||
|
expect(result.edges).toEqual([]);
|
||||||
|
expect(result.config).toBeDefined();
|
||||||
|
expect(result.rootNode).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return structured data for simple mindmap', () => {
|
||||||
|
// Create a simple mindmap structure
|
||||||
|
db.addNode(0, 'root', 'Root Node', 0);
|
||||||
|
db.addNode(1, 'child1', 'Child 1', 0);
|
||||||
|
db.addNode(1, 'child2', 'Child 2', 0);
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
expect(result.nodes).toHaveLength(3);
|
||||||
|
expect(result.edges).toHaveLength(2);
|
||||||
|
expect(result.config).toBeDefined();
|
||||||
|
expect(result.rootNode).toBeDefined();
|
||||||
|
|
||||||
|
// Check root node
|
||||||
|
const rootNode = (result.nodes as MindmapLayoutNode[]).find((n) => n.id === '0');
|
||||||
|
expect(rootNode).toBeDefined();
|
||||||
|
expect(rootNode?.label).toBe('Root Node');
|
||||||
|
expect(rootNode?.level).toBe(0);
|
||||||
|
|
||||||
|
// Check child nodes
|
||||||
|
const child1 = (result.nodes as MindmapLayoutNode[]).find((n) => n.id === '1');
|
||||||
|
expect(child1).toBeDefined();
|
||||||
|
expect(child1?.label).toBe('Child 1');
|
||||||
|
expect(child1?.level).toBe(1);
|
||||||
|
|
||||||
|
// Check edges
|
||||||
|
expect(result.edges).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
start: '0',
|
||||||
|
end: '1',
|
||||||
|
depth: 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return structured data for hierarchical mindmap', () => {
|
||||||
|
// Create a hierarchical mindmap structure
|
||||||
|
db.addNode(0, 'root', 'Root Node', 0);
|
||||||
|
db.addNode(1, 'child1', 'Child 1', 0);
|
||||||
|
db.addNode(2, 'grandchild1', 'Grandchild 1', 0);
|
||||||
|
db.addNode(2, 'grandchild2', 'Grandchild 2', 0);
|
||||||
|
db.addNode(1, 'child2', 'Child 2', 0);
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
expect(result.nodes).toHaveLength(5);
|
||||||
|
expect(result.edges).toHaveLength(4);
|
||||||
|
|
||||||
|
// Check that all levels are represented
|
||||||
|
const levels = result.nodes.map((n) => (n as MindmapLayoutNode).level);
|
||||||
|
expect(levels).toContain(0); // root
|
||||||
|
expect(levels).toContain(1); // children
|
||||||
|
expect(levels).toContain(2); // grandchildren
|
||||||
|
|
||||||
|
// Check edge relationships
|
||||||
|
const edgeRelations = result.edges.map(
|
||||||
|
(e) => `${(e as MindmapLayoutEdge).start}->${(e as MindmapLayoutEdge).end}`
|
||||||
|
);
|
||||||
|
expect(edgeRelations).toContain('0->1'); // root to child1
|
||||||
|
expect(edgeRelations).toContain('1->2'); // child1 to grandchild1
|
||||||
|
expect(edgeRelations).toContain('1->3'); // child1 to grandchild2
|
||||||
|
expect(edgeRelations).toContain('0->4'); // root to child2
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve node properties in processed data', () => {
|
||||||
|
// Add a node with specific properties
|
||||||
|
db.addNode(0, 'root', 'Root Node', 2); // type 2 = rectangle
|
||||||
|
|
||||||
|
// Set additional properties
|
||||||
|
const mindmap = db.getMindmap();
|
||||||
|
if (mindmap) {
|
||||||
|
mindmap.width = 150;
|
||||||
|
mindmap.height = 75;
|
||||||
|
mindmap.padding = 15;
|
||||||
|
mindmap.section = 1;
|
||||||
|
mindmap.class = 'custom-class';
|
||||||
|
mindmap.icon = 'star';
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
expect(result.nodes).toHaveLength(1);
|
||||||
|
const node = result.nodes[0] as MindmapLayoutNode;
|
||||||
|
|
||||||
|
expect(node.type).toBe(2);
|
||||||
|
expect(node.width).toBe(150);
|
||||||
|
expect(node.height).toBe(75);
|
||||||
|
expect(node.padding).toBe(15);
|
||||||
|
expect(node.section).toBeUndefined(); // Root node has undefined section
|
||||||
|
expect(node.cssClasses).toBe('mindmap-node section-root section--1 custom-class');
|
||||||
|
expect(node.icon).toBe('star');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate unique edge IDs', () => {
|
||||||
|
db.addNode(0, 'root', 'Root Node', 0);
|
||||||
|
db.addNode(1, 'child1', 'Child 1', 0);
|
||||||
|
db.addNode(1, 'child2', 'Child 2', 0);
|
||||||
|
db.addNode(1, 'child3', 'Child 3', 0);
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
const edgeIds = result.edges.map((e: Edge) => e.id);
|
||||||
|
const uniqueIds = new Set(edgeIds);
|
||||||
|
|
||||||
|
expect(edgeIds).toHaveLength(3);
|
||||||
|
expect(uniqueIds.size).toBe(3); // All IDs should be unique
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nodes with missing optional properties', () => {
|
||||||
|
db.addNode(0, 'root', 'Root Node', 0);
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
const node = result.nodes[0] as MindmapLayoutNode;
|
||||||
|
|
||||||
|
// Should handle undefined/missing properties gracefully
|
||||||
|
expect(node.section).toBeUndefined(); // Root node has undefined section
|
||||||
|
expect(node.cssClasses).toBe('mindmap-node section-root section--1'); // Root node gets special classes
|
||||||
|
expect(node.icon).toBeUndefined();
|
||||||
|
expect(node.x).toBeUndefined();
|
||||||
|
expect(node.y).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assign correct section classes based on sibling position', () => {
|
||||||
|
// Create the example mindmap structure:
|
||||||
|
// A
|
||||||
|
// a0
|
||||||
|
// aa0
|
||||||
|
// a1
|
||||||
|
// aaa
|
||||||
|
// a2
|
||||||
|
db.addNode(0, 'A', 'A', 0); // Root
|
||||||
|
db.addNode(1, 'a0', 'a0', 0); // First child of root
|
||||||
|
db.addNode(2, 'aa0', 'aa0', 0); // Child of a0
|
||||||
|
db.addNode(1, 'a1', 'a1', 0); // Second child of root
|
||||||
|
db.addNode(2, 'aaa', 'aaa', 0); // Child of a1
|
||||||
|
db.addNode(1, 'a2', 'a2', 0); // Third child of root
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
// Find nodes by their labels
|
||||||
|
const nodeA = result.nodes.find((n) => n.label === 'A') as MindmapLayoutNode;
|
||||||
|
const nodeA0 = result.nodes.find((n) => n.label === 'a0') as MindmapLayoutNode;
|
||||||
|
const nodeAa0 = result.nodes.find((n) => n.label === 'aa0') as MindmapLayoutNode;
|
||||||
|
const nodeA1 = result.nodes.find((n) => n.label === 'a1') as MindmapLayoutNode;
|
||||||
|
const nodeAaa = result.nodes.find((n) => n.label === 'aaa') as MindmapLayoutNode;
|
||||||
|
const nodeA2 = result.nodes.find((n) => n.label === 'a2') as MindmapLayoutNode;
|
||||||
|
|
||||||
|
// Check section assignments
|
||||||
|
expect(nodeA.section).toBeUndefined(); // Root has undefined section
|
||||||
|
expect(nodeA0.section).toBe(0); // First child of root
|
||||||
|
expect(nodeAa0.section).toBe(0); // Inherits from parent a0
|
||||||
|
expect(nodeA1.section).toBe(1); // Second child of root
|
||||||
|
expect(nodeAaa.section).toBe(1); // Inherits from parent a1
|
||||||
|
expect(nodeA2.section).toBe(2); // Third child of root
|
||||||
|
|
||||||
|
// Check CSS classes
|
||||||
|
expect(nodeA.cssClasses).toBe('mindmap-node section-root section--1');
|
||||||
|
expect(nodeA0.cssClasses).toBe('mindmap-node section-0');
|
||||||
|
expect(nodeAa0.cssClasses).toBe('mindmap-node section-0');
|
||||||
|
expect(nodeA1.cssClasses).toBe('mindmap-node section-1');
|
||||||
|
expect(nodeAaa.cssClasses).toBe('mindmap-node section-1');
|
||||||
|
expect(nodeA2.cssClasses).toBe('mindmap-node section-2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve custom classes while adding section classes', () => {
|
||||||
|
db.addNode(0, 'root', 'Root Node', 0);
|
||||||
|
db.addNode(1, 'child', 'Child Node', 0);
|
||||||
|
|
||||||
|
// Add custom classes to nodes
|
||||||
|
const mindmap = db.getMindmap();
|
||||||
|
if (mindmap) {
|
||||||
|
mindmap.class = 'custom-root-class';
|
||||||
|
if (mindmap.children?.[0]) {
|
||||||
|
mindmap.children[0].class = 'custom-child-class';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
const rootNode = result.nodes.find((n) => n.label === 'Root Node') as MindmapLayoutNode;
|
||||||
|
const childNode = result.nodes.find((n) => n.label === 'Child Node') as MindmapLayoutNode;
|
||||||
|
|
||||||
|
// Should include both section classes and custom classes
|
||||||
|
expect(rootNode.cssClasses).toBe('mindmap-node section-root section--1 custom-root-class');
|
||||||
|
expect(childNode.cssClasses).toBe('mindmap-node section-0 custom-child-class');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create any fake root nodes', () => {
|
||||||
|
// Create a simple mindmap
|
||||||
|
db.addNode(0, 'A', 'A', 0);
|
||||||
|
db.addNode(1, 'a0', 'a0', 0);
|
||||||
|
db.addNode(1, 'a1', 'a1', 0);
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
// Check that we only have the expected nodes
|
||||||
|
expect(result.nodes).toHaveLength(3);
|
||||||
|
expect(result.nodes.map((n) => n.label)).toEqual(['A', 'a0', 'a1']);
|
||||||
|
|
||||||
|
// Check that there's no node with label "mindmap" or any other fake root
|
||||||
|
const mindmapNode = result.nodes.find((n) => n.label === 'mindmap');
|
||||||
|
expect(mindmapNode).toBeUndefined();
|
||||||
|
|
||||||
|
// Verify the root node has the correct classes
|
||||||
|
const rootNode = result.nodes.find((n) => n.label === 'A') as MindmapLayoutNode;
|
||||||
|
expect(rootNode.cssClasses).toBe('mindmap-node section-root section--1');
|
||||||
|
expect(rootNode.level).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assign correct section classes to edges', () => {
|
||||||
|
// Create the example mindmap structure:
|
||||||
|
// A
|
||||||
|
// a0
|
||||||
|
// aa0
|
||||||
|
// a1
|
||||||
|
// aaa
|
||||||
|
// a2
|
||||||
|
db.addNode(0, 'A', 'A', 0); // Root
|
||||||
|
db.addNode(1, 'a0', 'a0', 0); // First child of root
|
||||||
|
db.addNode(2, 'aa0', 'aa0', 0); // Child of a0
|
||||||
|
db.addNode(1, 'a1', 'a1', 0); // Second child of root
|
||||||
|
db.addNode(2, 'aaa', 'aaa', 0); // Child of a1
|
||||||
|
db.addNode(1, 'a2', 'a2', 0); // Third child of root
|
||||||
|
|
||||||
|
const result = db.getData();
|
||||||
|
|
||||||
|
// Should have 5 edges: A->a0, a0->aa0, A->a1, a1->aaa, A->a2
|
||||||
|
expect(result.edges).toHaveLength(5);
|
||||||
|
|
||||||
|
// Find edges by their start and end nodes
|
||||||
|
const edgeA_a0 = result.edges.find(
|
||||||
|
(e) => e.start === '0' && e.end === '1'
|
||||||
|
) as MindmapLayoutEdge;
|
||||||
|
const edgeA0_aa0 = result.edges.find(
|
||||||
|
(e) => e.start === '1' && e.end === '2'
|
||||||
|
) as MindmapLayoutEdge;
|
||||||
|
const edgeA_a1 = result.edges.find(
|
||||||
|
(e) => e.start === '0' && e.end === '3'
|
||||||
|
) as MindmapLayoutEdge;
|
||||||
|
const edgeA1_aaa = result.edges.find(
|
||||||
|
(e) => e.start === '3' && e.end === '4'
|
||||||
|
) as MindmapLayoutEdge;
|
||||||
|
const edgeA_a2 = result.edges.find(
|
||||||
|
(e) => e.start === '0' && e.end === '5'
|
||||||
|
) as MindmapLayoutEdge;
|
||||||
|
|
||||||
|
// Check edge classes
|
||||||
|
expect(edgeA_a0.classes).toBe('edge section-edge-0 edge-depth-1'); // A->a0: section-0, depth-1
|
||||||
|
expect(edgeA0_aa0.classes).toBe('edge section-edge-0 edge-depth-2'); // a0->aa0: section-0, depth-2
|
||||||
|
expect(edgeA_a1.classes).toBe('edge section-edge-1 edge-depth-1'); // A->a1: section-1, depth-1
|
||||||
|
expect(edgeA1_aaa.classes).toBe('edge section-edge-1 edge-depth-2'); // a1->aaa: section-1, depth-2
|
||||||
|
expect(edgeA_a2.classes).toBe('edge section-edge-2 edge-depth-1'); // A->a2: section-2, depth-1
|
||||||
|
|
||||||
|
// Check section assignments match the child nodes
|
||||||
|
expect(edgeA_a0.section).toBe(0);
|
||||||
|
expect(edgeA0_aa0.section).toBe(0);
|
||||||
|
expect(edgeA_a1.section).toBe(1);
|
||||||
|
expect(edgeA1_aaa.section).toBe(1);
|
||||||
|
expect(edgeA_a2.section).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,9 +1,26 @@
|
|||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
import type { D3Element } from '../../types.js';
|
import type { D3Element } from '../../types.js';
|
||||||
import { sanitizeText } from '../../diagrams/common/common.js';
|
import { sanitizeText } from '../../diagrams/common/common.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import type { MindmapNode } from './mindmapTypes.js';
|
import type { MindmapNode } from './mindmapTypes.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
|
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
|
||||||
|
import { getUserDefinedConfig } from '../../config.js';
|
||||||
|
|
||||||
|
// Extend Node type for mindmap-specific properties
|
||||||
|
export type MindmapLayoutNode = Node & {
|
||||||
|
level: number;
|
||||||
|
nodeId: string;
|
||||||
|
type: number;
|
||||||
|
section?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extend Edge type for mindmap-specific properties
|
||||||
|
export type MindmapLayoutEdge = Edge & {
|
||||||
|
depth: number;
|
||||||
|
section?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const nodeType = {
|
const nodeType = {
|
||||||
DEFAULT: 0,
|
DEFAULT: 0,
|
||||||
@@ -27,7 +44,6 @@ export class MindmapDB {
|
|||||||
this.nodeType = nodeType;
|
this.nodeType = nodeType;
|
||||||
this.clear();
|
this.clear();
|
||||||
this.getType = this.getType.bind(this);
|
this.getType = this.getType.bind(this);
|
||||||
this.getMindmap = this.getMindmap.bind(this);
|
|
||||||
this.getElementById = this.getElementById.bind(this);
|
this.getElementById = this.getElementById.bind(this);
|
||||||
this.getParent = this.getParent.bind(this);
|
this.getParent = this.getParent.bind(this);
|
||||||
this.getMindmap = this.getMindmap.bind(this);
|
this.getMindmap = this.getMindmap.bind(this);
|
||||||
@@ -156,6 +172,223 @@ export class MindmapDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign section numbers to nodes based on their position relative to root
|
||||||
|
* @param node - The mindmap node to process
|
||||||
|
* @param sectionNumber - The section number to assign (undefined for root)
|
||||||
|
*/
|
||||||
|
public assignSections(node: MindmapNode, sectionNumber?: number): void {
|
||||||
|
// For root node, section should be undefined (not -1)
|
||||||
|
if (node.level === 0) {
|
||||||
|
node.section = undefined;
|
||||||
|
} else {
|
||||||
|
// For non-root nodes, assign the section number
|
||||||
|
node.section = sectionNumber;
|
||||||
|
}
|
||||||
|
// For root node's children, assign section numbers based on their index
|
||||||
|
// For other nodes, inherit parent's section number
|
||||||
|
if (node.children) {
|
||||||
|
for (const [index, child] of node.children.entries()) {
|
||||||
|
const childSectionNumber = node.level === 0 ? index : sectionNumber;
|
||||||
|
this.assignSections(child, childSectionNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert mindmap tree structure to flat array of nodes
|
||||||
|
* @param node - The mindmap node to process
|
||||||
|
* @param processedNodes - Array to collect processed nodes
|
||||||
|
*/
|
||||||
|
public flattenNodes(node: MindmapNode, processedNodes: MindmapLayoutNode[]): void {
|
||||||
|
// Build CSS classes for the node
|
||||||
|
const cssClasses = ['mindmap-node'];
|
||||||
|
|
||||||
|
// Add section-specific classes
|
||||||
|
if (node.level === 0) {
|
||||||
|
// Root node gets special classes
|
||||||
|
cssClasses.push('section-root', 'section--1');
|
||||||
|
} else if (node.section !== undefined) {
|
||||||
|
// Child nodes get section class based on their section number
|
||||||
|
cssClasses.push(`section-${node.section}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any custom classes from the node
|
||||||
|
if (node.class) {
|
||||||
|
cssClasses.push(node.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = cssClasses.join(' ');
|
||||||
|
|
||||||
|
// Map mindmap node type to valid shape name
|
||||||
|
const getShapeFromType = (type: number) => {
|
||||||
|
switch (type) {
|
||||||
|
case nodeType.CIRCLE:
|
||||||
|
return 'mindmapCircle';
|
||||||
|
case nodeType.RECT:
|
||||||
|
return 'rect';
|
||||||
|
case nodeType.ROUNDED_RECT:
|
||||||
|
return 'rounded';
|
||||||
|
case nodeType.CLOUD:
|
||||||
|
return 'cloud';
|
||||||
|
case nodeType.BANG:
|
||||||
|
return 'bang';
|
||||||
|
case nodeType.HEXAGON:
|
||||||
|
return 'hexagon';
|
||||||
|
case nodeType.DEFAULT:
|
||||||
|
return 'defaultMindmapNode';
|
||||||
|
case nodeType.NO_BORDER:
|
||||||
|
default:
|
||||||
|
return 'rect';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processedNode: MindmapLayoutNode = {
|
||||||
|
id: node.id.toString(),
|
||||||
|
domId: 'node_' + node.id.toString(),
|
||||||
|
label: node.descr,
|
||||||
|
isGroup: false,
|
||||||
|
shape: getShapeFromType(node.type),
|
||||||
|
width: node.width,
|
||||||
|
height: node.height ?? 0,
|
||||||
|
padding: node.padding,
|
||||||
|
cssClasses: classes,
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
icon: node.icon,
|
||||||
|
x: node.x,
|
||||||
|
y: node.y,
|
||||||
|
// Mindmap-specific properties
|
||||||
|
level: node.level,
|
||||||
|
nodeId: node.nodeId,
|
||||||
|
type: node.type,
|
||||||
|
section: node.section,
|
||||||
|
};
|
||||||
|
|
||||||
|
processedNodes.push(processedNode);
|
||||||
|
|
||||||
|
// Recursively process children
|
||||||
|
if (node.children) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
this.flattenNodes(child, processedNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate edges from parent-child relationships in mindmap tree
|
||||||
|
* @param node - The mindmap node to process
|
||||||
|
* @param edges - Array to collect edges
|
||||||
|
*/
|
||||||
|
public generateEdges(node: MindmapNode, edges: MindmapLayoutEdge[]): void {
|
||||||
|
if (!node.children) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const child of node.children) {
|
||||||
|
// Build CSS classes for the edge
|
||||||
|
let edgeClasses = 'edge';
|
||||||
|
|
||||||
|
// Add section-specific classes based on the child's section
|
||||||
|
if (child.section !== undefined) {
|
||||||
|
edgeClasses += ` section-edge-${child.section}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add depth class based on the parent's level + 1 (depth of the edge)
|
||||||
|
const edgeDepth = node.level + 1;
|
||||||
|
edgeClasses += ` edge-depth-${edgeDepth}`;
|
||||||
|
|
||||||
|
const edge: MindmapLayoutEdge = {
|
||||||
|
id: `edge_${node.id}_${child.id}`,
|
||||||
|
start: node.id.toString(),
|
||||||
|
end: child.id.toString(),
|
||||||
|
type: 'normal',
|
||||||
|
curve: 'basis',
|
||||||
|
thickness: 'normal',
|
||||||
|
look: 'default',
|
||||||
|
classes: edgeClasses,
|
||||||
|
// Store mindmap-specific data
|
||||||
|
depth: node.level,
|
||||||
|
section: child.section,
|
||||||
|
};
|
||||||
|
|
||||||
|
edges.push(edge);
|
||||||
|
|
||||||
|
// Recursively process child edges
|
||||||
|
this.generateEdges(child, edges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get structured data for layout algorithms
|
||||||
|
* Following the pattern established by ER diagrams
|
||||||
|
* @returns Structured data containing nodes, edges, and config
|
||||||
|
*/
|
||||||
|
public getData(): LayoutData {
|
||||||
|
const mindmapRoot = this.getMindmap();
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
const userDefinedConfig = getUserDefinedConfig();
|
||||||
|
const hasUserDefinedLayout = userDefinedConfig.layout !== undefined;
|
||||||
|
|
||||||
|
const finalConfig = config;
|
||||||
|
if (!hasUserDefinedLayout) {
|
||||||
|
finalConfig.layout = 'cose-bilkent';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mindmapRoot) {
|
||||||
|
return {
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
config: finalConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
log.debug('getData: mindmapRoot', mindmapRoot, config);
|
||||||
|
|
||||||
|
// Assign section numbers to all nodes based on their position relative to root
|
||||||
|
this.assignSections(mindmapRoot);
|
||||||
|
|
||||||
|
// Convert tree structure to flat arrays
|
||||||
|
const processedNodes: MindmapLayoutNode[] = [];
|
||||||
|
const processedEdges: MindmapLayoutEdge[] = [];
|
||||||
|
|
||||||
|
this.flattenNodes(mindmapRoot, processedNodes);
|
||||||
|
this.generateEdges(mindmapRoot, processedEdges);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
`getData: processed ${processedNodes.length} nodes and ${processedEdges.length} edges`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create shapes map for ELK compatibility
|
||||||
|
const shapes = new Map<string, any>();
|
||||||
|
for (const node of processedNodes) {
|
||||||
|
shapes.set(node.id, {
|
||||||
|
shape: node.shape,
|
||||||
|
width: node.width,
|
||||||
|
height: node.height,
|
||||||
|
padding: node.padding,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: processedNodes,
|
||||||
|
edges: processedEdges,
|
||||||
|
config: finalConfig,
|
||||||
|
// Store the root node for mindmap-specific layout algorithms
|
||||||
|
rootNode: mindmapRoot,
|
||||||
|
// Properties required by dagre layout algorithm
|
||||||
|
markers: ['point'], // Mindmaps don't use markers
|
||||||
|
direction: 'TB', // Top-to-bottom direction for mindmaps
|
||||||
|
nodeSpacing: 50, // Default spacing between nodes
|
||||||
|
rankSpacing: 50, // Default spacing between ranks
|
||||||
|
// Add shapes for ELK compatibility
|
||||||
|
shapes: Object.fromEntries(shapes),
|
||||||
|
// Additional properties that layout algorithms might expect
|
||||||
|
type: 'mindmap',
|
||||||
|
diagramId: 'mindmap-' + v4(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose logger to grammar
|
||||||
public getLogger() {
|
public getLogger() {
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
@@ -1,200 +1,83 @@
|
|||||||
import cytoscape from 'cytoscape';
|
|
||||||
// @ts-expect-error No types available
|
|
||||||
import coseBilkent from 'cytoscape-cose-bilkent';
|
|
||||||
import { select } from 'd3';
|
|
||||||
import type { MermaidConfig } from '../../config.type.js';
|
|
||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
|
||||||
import type { DrawDefinition } from '../../diagram-api/types.js';
|
import type { DrawDefinition } from '../../diagram-api/types.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import type { D3Element } from '../../types.js';
|
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
||||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||||
import type { FilledMindMapNode, MindmapNode } from './mindmapTypes.js';
|
import type { LayoutData } from '../../rendering-util/types.js';
|
||||||
import { drawNode, positionNode } from './svgDraw.js';
|
import type { FilledMindMapNode } from './mindmapTypes.js';
|
||||||
import defaultConfig from '../../defaultConfig.js';
|
import defaultConfig from '../../defaultConfig.js';
|
||||||
import type { MindmapDB } from './mindmapDb.js';
|
import type { MindmapDB } from './mindmapDb.js';
|
||||||
// Inject the layout algorithm into cytoscape
|
|
||||||
cytoscape.use(coseBilkent);
|
|
||||||
|
|
||||||
async function drawNodes(
|
/**
|
||||||
db: MindmapDB,
|
* Update the layout data with actual node dimensions after drawing
|
||||||
svg: D3Element,
|
*/
|
||||||
mindmap: FilledMindMapNode,
|
function _updateNodeDimensions(data4Layout: LayoutData, mindmapRoot: FilledMindMapNode) {
|
||||||
section: number,
|
const updateNode = (node: FilledMindMapNode) => {
|
||||||
conf: MermaidConfig
|
// Find the corresponding node in the layout data
|
||||||
) {
|
const layoutNode = data4Layout.nodes.find((n) => n.id === node.id.toString());
|
||||||
await drawNode(db, svg, mindmap, section, conf);
|
if (layoutNode) {
|
||||||
if (mindmap.children) {
|
// Update with the actual dimensions calculated by drawNode
|
||||||
await Promise.all(
|
layoutNode.width = node.width;
|
||||||
mindmap.children.map((child, index) =>
|
layoutNode.height = node.height;
|
||||||
drawNodes(db, svg, child, section < 0 ? index : section, conf)
|
log.debug('Updated node dimensions:', node.id, 'width:', node.width, 'height:', node.height);
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'cytoscape' {
|
|
||||||
interface EdgeSingular {
|
|
||||||
_private: {
|
|
||||||
bodyBounds: unknown;
|
|
||||||
rscratch: {
|
|
||||||
startX: number;
|
|
||||||
startY: number;
|
|
||||||
midX: number;
|
|
||||||
midY: number;
|
|
||||||
endX: number;
|
|
||||||
endY: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
|
|
||||||
cy.edges().map((edge, id) => {
|
|
||||||
const data = edge.data();
|
|
||||||
if (edge[0]._private.bodyBounds) {
|
|
||||||
const bounds = edge[0]._private.rscratch;
|
|
||||||
log.trace('Edge: ', id, data);
|
|
||||||
edgesEl
|
|
||||||
.insert('path')
|
|
||||||
.attr(
|
|
||||||
'd',
|
|
||||||
`M ${bounds.startX},${bounds.startY} L ${bounds.midX},${bounds.midY} L${bounds.endX},${bounds.endY} `
|
|
||||||
)
|
|
||||||
.attr('class', 'edge section-edge-' + data.section + ' edge-depth-' + data.depth);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
|
// Recursively update children
|
||||||
cy.add({
|
node.children?.forEach(updateNode);
|
||||||
group: 'nodes',
|
};
|
||||||
data: {
|
|
||||||
id: mindmap.id.toString(),
|
|
||||||
labelText: mindmap.descr,
|
|
||||||
height: mindmap.height,
|
|
||||||
width: mindmap.width,
|
|
||||||
level: level,
|
|
||||||
nodeId: mindmap.id,
|
|
||||||
padding: mindmap.padding,
|
|
||||||
type: mindmap.type,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
x: mindmap.x!,
|
|
||||||
y: mindmap.y!,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (mindmap.children) {
|
|
||||||
mindmap.children.forEach((child) => {
|
|
||||||
addNodes(child, cy, conf, level + 1);
|
|
||||||
cy.add({
|
|
||||||
group: 'edges',
|
|
||||||
data: {
|
|
||||||
id: `${mindmap.id}_${child.id}`,
|
|
||||||
source: mindmap.id,
|
|
||||||
target: child.id,
|
|
||||||
depth: level,
|
|
||||||
section: child.section,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> {
|
updateNode(mindmapRoot);
|
||||||
return new Promise((resolve) => {
|
|
||||||
// Add temporary render element
|
|
||||||
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
|
|
||||||
const cy = cytoscape({
|
|
||||||
container: document.getElementById('cy'), // container to render in
|
|
||||||
style: [
|
|
||||||
{
|
|
||||||
selector: 'edge',
|
|
||||||
style: {
|
|
||||||
'curve-style': 'bezier',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
// Remove element after layout
|
|
||||||
renderEl.remove();
|
|
||||||
addNodes(node, cy, conf, 0);
|
|
||||||
|
|
||||||
// Make cytoscape care about the dimensions of the nodes
|
|
||||||
cy.nodes().forEach(function (n) {
|
|
||||||
n.layoutDimensions = () => {
|
|
||||||
const data = n.data();
|
|
||||||
return { w: data.width, h: data.height };
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.layout({
|
|
||||||
name: 'cose-bilkent',
|
|
||||||
// @ts-ignore Types for cose-bilkent are not correct?
|
|
||||||
quality: 'proof',
|
|
||||||
styleEnabled: false,
|
|
||||||
animate: false,
|
|
||||||
}).run();
|
|
||||||
cy.ready((e) => {
|
|
||||||
log.info('Ready', e);
|
|
||||||
resolve(cy);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function positionNodes(db: MindmapDB, cy: cytoscape.Core) {
|
|
||||||
cy.nodes().map((node, id) => {
|
|
||||||
const data = node.data();
|
|
||||||
data.x = node.position().x;
|
|
||||||
data.y = node.position().y;
|
|
||||||
positionNode(db, data);
|
|
||||||
const el = db.getElementById(data.nodeId);
|
|
||||||
log.info('id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data);
|
|
||||||
el.attr(
|
|
||||||
'transform',
|
|
||||||
`translate(${node.position().x - data.width / 2}, ${node.position().y - data.height / 2})`
|
|
||||||
);
|
|
||||||
el.attr('attr', `apa-${id})`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
|
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
|
||||||
log.debug('Rendering mindmap diagram\n' + text);
|
log.debug('Rendering mindmap diagram\n' + text);
|
||||||
|
|
||||||
|
// Draw the nodes first to get their dimensions, then update the layout data
|
||||||
const db = diagObj.db as MindmapDB;
|
const db = diagObj.db as MindmapDB;
|
||||||
|
|
||||||
|
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
|
||||||
|
// into the Layout data format
|
||||||
|
const data4Layout = db.getData();
|
||||||
|
|
||||||
|
// Create the root SVG - the element is the div containing the SVG element
|
||||||
|
const svg = getDiagramElement(id, data4Layout.config.securityLevel);
|
||||||
|
|
||||||
|
data4Layout.type = diagObj.type;
|
||||||
|
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(data4Layout.config.layout, {
|
||||||
|
fallback: 'cose-bilkent',
|
||||||
|
});
|
||||||
|
|
||||||
|
data4Layout.diagramId = id;
|
||||||
|
|
||||||
const mm = db.getMindmap();
|
const mm = db.getMindmap();
|
||||||
if (!mm) {
|
if (!mm) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conf = getConfig();
|
data4Layout.nodes.forEach((node) => {
|
||||||
conf.htmlLabels = false;
|
if (node.shape === 'rounded') {
|
||||||
|
node.radius = 15;
|
||||||
|
node.taper = 15;
|
||||||
|
node.stroke = 'none';
|
||||||
|
node.width = 0;
|
||||||
|
node.padding = 15;
|
||||||
|
} else if (node.shape === 'circle') {
|
||||||
|
node.padding = 10;
|
||||||
|
} else if (node.shape === 'rect') {
|
||||||
|
node.width = 0;
|
||||||
|
node.padding = 10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const svg = selectSvgElement(id);
|
// Use the unified rendering system
|
||||||
|
await render(data4Layout, svg);
|
||||||
|
|
||||||
// Draw the graph and start with drawing the nodes without proper position
|
// Setup the view box and size of the svg element using config from data4Layout
|
||||||
// this gives us the size of the nodes and we can set the positions later
|
setupViewPortForSVG(
|
||||||
|
|
||||||
const edgesElem = svg.append('g');
|
|
||||||
edgesElem.attr('class', 'mindmap-edges');
|
|
||||||
const nodesElem = svg.append('g');
|
|
||||||
nodesElem.attr('class', 'mindmap-nodes');
|
|
||||||
await drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
|
|
||||||
|
|
||||||
// Next step is to layout the mindmap, giving each node a position
|
|
||||||
|
|
||||||
const cy = await layoutMindmap(mm, conf);
|
|
||||||
|
|
||||||
// After this we can draw, first the edges and the then nodes with the correct position
|
|
||||||
drawEdges(edgesElem, cy);
|
|
||||||
positionNodes(db, cy);
|
|
||||||
|
|
||||||
// Setup the view box and size of the svg element
|
|
||||||
setupGraphViewbox(
|
|
||||||
undefined,
|
|
||||||
svg,
|
svg,
|
||||||
conf.mindmap?.padding ?? defaultConfig.mindmap.padding,
|
data4Layout.config.mindmap?.padding ?? defaultConfig.mindmap.padding,
|
||||||
conf.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
|
'mindmapDiagram',
|
||||||
|
data4Layout.config.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -64,6 +64,12 @@ const getStyles: DiagramStylesProvider = (options) =>
|
|||||||
.section-root text {
|
.section-root text {
|
||||||
fill: ${options.gitBranchLabel0};
|
fill: ${options.gitBranchLabel0};
|
||||||
}
|
}
|
||||||
|
.section-root span {
|
||||||
|
color: ${options.gitBranchLabel0};
|
||||||
|
}
|
||||||
|
.section-2 span {
|
||||||
|
color: ${options.gitBranchLabel0};
|
||||||
|
}
|
||||||
.icon-container {
|
.icon-container {
|
||||||
height:100%;
|
height:100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@@ -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; }
|
||||||
|
@@ -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':
|
||||||
|
@@ -1368,7 +1368,7 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
|
|||||||
it('should handle box without description', async () => {
|
it('should handle box without description', async () => {
|
||||||
const diagram = await Diagram.fromText(`
|
const diagram = await Diagram.fromText(`
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
box Aqua
|
box aqua
|
||||||
participant a as Alice
|
participant a as Alice
|
||||||
participant b as Bob
|
participant b as Bob
|
||||||
end
|
end
|
||||||
@@ -1384,7 +1384,7 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
|
|||||||
const boxes = diagram.db.getBoxes();
|
const boxes = diagram.db.getBoxes();
|
||||||
expect(boxes[0].name).toBeFalsy();
|
expect(boxes[0].name).toBeFalsy();
|
||||||
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
|
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
|
||||||
expect(boxes[0].fill).toEqual('Aqua');
|
expect(boxes[0].fill).toEqual('aqua');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle simple actor creation', async () => {
|
it('should handle simple actor creation', async () => {
|
||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -203,6 +203,7 @@ function sidebarConfig() {
|
|||||||
{ text: 'Accessibility', link: '/config/accessibility' },
|
{ text: 'Accessibility', link: '/config/accessibility' },
|
||||||
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
|
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
|
||||||
{ text: 'FAQ', link: '/config/faq' },
|
{ text: 'FAQ', link: '/config/faq' },
|
||||||
|
{ text: 'Layouts', link: '/config/layouts' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -17,7 +17,6 @@ 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
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217)
|
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712)
|
||||||
1. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
|
1. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
|
||||||
1. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
|
1. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
|
||||||
1. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)
|
1. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)
|
||||||
|
24
packages/mermaid/src/docs/config/layouts.md
Normal file
24
packages/mermaid/src/docs/config/layouts.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Layouts
|
||||||
|
|
||||||
|
This page lists the available layout algorithms supported in Mermaid diagrams.
|
||||||
|
|
||||||
|
## Supported Layouts
|
||||||
|
|
||||||
|
- **elk**: [ELK (Eclipse Layout Kernel)](https://www.eclipse.org/elk/)
|
||||||
|
- **tidy-tree**: Tidy tree layout for hierarchical diagrams [Tidy Tree Configuration](/config/tidy-tree)
|
||||||
|
- **cose-bilkent**: Cose Bilkent layout for force-directed graphs
|
||||||
|
- **dagre**: Dagre layout for layered graphs
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
You can specify the layout in your diagram's YAML config or initialization options. For example:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: elk
|
||||||
|
---
|
||||||
|
graph TD;
|
||||||
|
A-->B;
|
||||||
|
B-->C;
|
||||||
|
```
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user