Compare commits

..

161 Commits

Author SHA1 Message Date
omkarht
10bb1f9261 fix: my sample commit summary here
Some optional description over here if you need to add more info

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 19:41:28 +05:30
omkarht
f0c422ba37 fix: sample commit 2
on-behalf-of: @Mermaid-Chart hello@mermaidchart.com
2025-07-15 19:38:27 +05:30
omkarht
d1acc182cf fix:sample change
on-behalf-of: @Mermaid-Chart hello@mermaidchart.com
2025-07-15 19:28:52 +05:30
darshanr0107
d4e341368a fix: my test commit 7
Some optional description over here if you need to add more info

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 19:24:45 +05:30
darshanr0107
9773e6e1e2 fix: test commit from darshan
Some optional description over here if you need to add more info

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 19:14:14 +05:30
darshanr0107
5277af895b fix: test commit 3
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 18:58:58 +05:30
darshanr0107
f2803ed901 fix: my commit summary here
Some optional description over here if you need to add more info

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 18:14:31 +05:30
darshanr0107
e883140971 Merge branch 'test-commits' of https://github.com/mermaid-js/mermaid into test-commits 2025-07-15 18:09:26 +05:30
darshanr0107
a085db10cd fix: test commit 2
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 18:09:11 +05:30
omkarht
e73105bcec fix: test commit
on-behalf-of: @Mermaid-Chart hello@mermaidchart.com
2025-07-15 18:03:38 +05:30
darshanr0107
fb80afe3ae fix: test commit 1
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 17:33:36 +05:30
shubham-mermaid
4cb80e22ee fix: another test commit 5
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 17:28:15 +05:30
shubham-mermaid
e584c79fcc fix: another test commit 4
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 17:26:19 +05:30
shubham-mermaid
3ddbdbbb30 fix: another test commit 2
Some optional description over here if you need to add more info

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-15 17:25:05 +05:30
shubham-mermaid
9d50014705 fix: another test commit
on-behalf-of: @Mermaid-Chart
2025-07-15 17:20:58 +05:30
shubham-mermaid
a8ee7dca30 fix: test commit
this is test commit to verify  template

on-behalf-of: Mermaid-Chart
2025-07-15 17:17:42 +05:30
Sidharth Vinod
1f3f8da0f7 Merge pull request #6749 from mermaid-js/chore/downgrade-chokidar-to-3.6.0
chore: downgrade chokidar to 3.6.0
2025-07-15 16:40:56 +05:30
Sidharth Vinod
b11f40e8ce Merge branch 'develop' into chore/downgrade-chokidar-to-3.6.0
* develop:
  chore: Update jsdom
  Update @argos-ci/cypress to v5.0.2
  Update @argos-ci/cypress to v5.0.1
  chore: Upgrade cypress
  chore: Update @argos-ci/cypress to v5
  chore(test): Finish refactoring of jsdomit tests
  docs(tests): Documentation for `jsdomIt` tests
  test(refactor): Provide SVG selection in `jsdomIt` function
  test(refactor): Use real-ish rendering instead of heavy mocking in unit tests
2025-07-15 16:40:18 +05:30
Sidharth Vinod
1fe045e638 chore: pin chokidar version 2025-07-15 16:36:13 +05:30
Sidharth Vinod
1c750ffc70 Merge pull request #6675 from quilicicf/chore/6627_improve-rendering-tests
Improve unit tests to allow pseudo-render tests
2025-07-15 09:57:48 +00:00
Sidharth Vinod
b0ec93f29c chore: Update jsdom 2025-07-15 13:54:45 +05:30
Sidharth Vinod
d7b8ed2c5a Merge remote-tracking branch 'origin/develop' into pr/quilicicf/6675
* origin/develop:
  Update @argos-ci/cypress to v5.0.2
  Update @argos-ci/cypress to v5.0.1
  chore: Upgrade cypress
  chore: Update @argos-ci/cypress to v5
2025-07-15 13:34:11 +05:30
Sidharth Vinod
d7f1f12549 Merge pull request #6748 from mermaid-js/sidv/updateArgosCy
chore: Update @argos-ci/cypress to v5
2025-07-14 23:35:38 +05:30
Sidharth Vinod
6e56869566 Update @argos-ci/cypress to v5.0.2 2025-07-14 23:22:40 +05:30
Sidharth Vinod
852cb35f0a Update @argos-ci/cypress to v5.0.1 2025-07-14 20:05:57 +05:30
omkarht
a28965064d chore: downgrade chokidar to 3.6.0 2025-07-14 19:34:02 +05:30
Sidharth Vinod
355eeeb9cc chore: Upgrade cypress 2025-07-14 18:33:09 +05:30
Sidharth Vinod
5449d6a447 chore: Update @argos-ci/cypress to v5
This fixes the random test renaming issue
2025-07-14 18:15:18 +05:30
quilicicf
34e91f8b65 chore(test): Finish refactoring of jsdomit tests 2025-07-14 14:04:53 +02:00
quilicicf
627ee1f34d docs(tests): Documentation for jsdomIt tests 2025-07-14 14:04:53 +02:00
quilicicf
28840ebd84 test(refactor): Provide SVG selection in jsdomIt function 2025-07-14 14:04:53 +02:00
quilicicf
4145879003 test(refactor): Use real-ish rendering instead of heavy mocking in unit tests
* Creates a utility method `jsdomIt` that overrides `it` from `vitest` and fakes a browser environment by :
  * Creating a DOM with `jsdom` (and `canvas`)
  * Adding (for the duration of the test) that DOM's `window` and `document` on `global`
  * Monkey-patching DOM methods that require a rendering engine (`Element.getBBox` & `Element.getComputedLength`)
* Removes all d3 mocking since it can now work normally in `jsdomIt` tests
* Re-writes existing rendering tests to :
  * Use `jsdomIt`
  * Get rid of most of the involved mocking
  * Run `expect` calls on the generated SVG instead

Inspired by d3's own test code mocking : https://github.com/d3/d3-selection/blob/v3.0.0/test/jsdom.js
2025-07-14 14:04:52 +02:00
shubham-mermaid
f76e27db70 Merge pull request #6747 from mermaid-js/6619-sequence-title-box-safari
6619: Prevent title from overlapping with box top border in Safari
2025-07-14 09:13:53 +00:00
darshanr0107
3e3ae08930 add changeset 2025-07-14 14:17:00 +05:30
darshanr0107
966c112eb1 prevent title from overlapping with box top border in Safari 2025-07-14 14:11:06 +05:30
shubham-mermaid
55527e70c2 Merge pull request #6745 from mermaid-js/renovate/autofix-ci-action-digest
chore(deps): update autofix-ci/action digest to 635ffb0
2025-07-14 06:47:45 +00:00
renovate[bot]
447d1cf988 chore(deps): update autofix-ci/action digest to 635ffb0 2025-07-14 00:55:26 +00:00
Alois Klink
fad6676d18 Merge pull request #6715 from Syn3ugar/feature/timelineRenderer-conf
fix: update leftMargin value
2025-07-10 10:37:48 +00:00
Syn3ugar
637680d4d9 fix(timeline): load leftMargin from config
The `timeline.leftMargin` value in the docs/schema was not being
correctly loaded for timeline diagrams.
2025-07-10 18:28:15 +08:00
shubham-mermaid
5f6f5110fd Merge pull request #6718 from MoonBall/feat/modify-blockDiagram-log
Modify block diagram's logger to global log
2025-07-10 08:00:39 +00:00
Sidharth Vinod
af47269342 Merge pull request #6615 from mermaid-js/renovate/major-all-major
fix(deps): update all major dependencies (major)
2025-07-09 20:05:52 +00:00
renovate[bot]
d3c0893937 fix(deps): update all major dependencies 2025-07-09 19:53:14 +00:00
Sidharth Vinod
75ef9bc681 fix: Make PR labeler stricter 2025-07-10 01:21:42 +05:30
Sidharth Vinod
45edc91591 Merge pull request #6510 from mermaid-js/sidv/packet-release
Move `packet` diagram out of beta
2025-07-09 19:35:19 +00:00
Sidharth Vinod
f4edd19371 chore: Add auto labeler for Sponsored work 2025-07-10 01:00:10 +05:30
Sidharth Vinod
b611a13e04 chore: Add test for packet-beta 2025-07-10 00:44:07 +05:30
Sidharth Vinod
7a38eb715d chore: Changeset 2025-07-10 00:43:02 +05:30
Sidharth Vinod
2715ddb338 chore: Update packet-beta to packet 2025-07-10 00:41:02 +05:30
Sidharth Vinod
ca2eca58c9 Merge branch 'develop' into sidv/packet-release
* develop: (266 commits)
  chore: update E2E timings
  docs: Update changeset
  fix: Add useful error message to test
  chore: Add treemap example
  chore: Add treemap example
  chore: Add peerDependency to @mermaid-js/examples
  Version Packages
  docs: Update changeset
  update in changeset
  updated validaor and tests to use treemap
  Updated parser to use treemap
  chore: Fail build in CI on type errors
  fix text going outside node
  Version Packages
  Updated phpbb url  to support 403 in lychee
  Updated lychee for working url phpbb.com
  resolve PR comment
  resolve PR comments
  chore: update E2E timings
  add changeset
  ...
2025-07-10 00:38:41 +05:30
shubham-mermaid
b4fae2d096 Merge pull request #6716 from mermaid-js/6664-text-going-outside-node
6664: Text rendering issue in flowchart note elements
2025-07-09 13:18:26 +00:00
shubham-mermaid
6dd9af0dd4 Merge branch 'develop' into 6664-text-going-outside-node 2025-07-09 18:34:15 +05:30
shubham-mermaid
3151241559 Merge branch 'develop' into feat/modify-blockDiagram-log 2025-07-09 13:47:24 +05:30
Sidharth Vinod
dda9c9b46e Merge pull request #6731 from mermaid-js/update-timings
Update E2E Timings
2025-07-08 13:23:06 +05:30
github-actions[bot]
cdd1a70b67 chore: update E2E timings 2025-07-08 04:14:48 +00:00
Sidharth Vinod
d8bd4dea93 Merge pull request #6453 from mermaid-js/sidv/examples
Add metadata for diagrams
2025-07-08 01:11:26 +05:30
Sidharth Vinod
6deb476182 docs: Update changeset 2025-07-08 01:01:47 +05:30
Sidharth Vinod
ed297ee235 fix: Add useful error message to test 2025-07-08 00:55:44 +05:30
Sidharth Vinod
03c1201fcb chore: Add treemap example 2025-07-08 00:55:24 +05:30
Sidharth Vinod
3ca317c5a0 chore: Add treemap example 2025-07-08 00:45:47 +05:30
Sidharth Vinod
4d83263388 chore: Add peerDependency to @mermaid-js/examples
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-07-08 00:44:07 +05:30
Sidharth Vinod
254e5cbd51 Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid:
2025-07-08 00:39:56 +05:30
Sidharth Vinod
a58dd3c6ce Merge branch 'develop' into sidv/examples 2025-07-08 00:38:03 +05:30
Sidharth Vinod
10b7bb568f Merge branch 'develop' into sidv/examples
* develop: (198 commits)
  Version Packages
  docs: Update changeset
  update in changeset
  updated validaor and tests to use treemap
  Updated parser to use treemap
  chore: Fail build in CI on type errors
  Version Packages
  Updated phpbb url  to support 403 in lychee
  Updated lychee for working url phpbb.com
  resolve PR comment
  resolve PR comments
  chore: update E2E timings
  add changeset
  log warning when duplicate commit IDs are encountered
  Added updated chatgpt  plugin url and removed failing url
  Added classes and outernodes in imperativestate
  Updated code as per suggestions
  fixed unwanted vitepress from lockfile
  Updated as per suggestions
  Added step to install pnpm and echo statements for debugging.
  ...
2025-07-08 00:36:08 +05:30
Sidharth Vinod
1aa2870224 Merge pull request #6727 from mermaid-js/changeset-release/master
Version Packages
2025-07-07 20:52:47 +05:30
github-actions[bot]
8fbcbb6dc9 Version Packages 2025-07-07 15:21:07 +00:00
Sidharth Vinod
3e545d7925 docs: Update changeset 2025-07-07 20:49:04 +05:30
Sidharth Vinod
75d2a259ed Merge pull request #6726 from mermaid-js/develop
Pre Release
2025-07-07 20:29:25 +05:30
shubham-mermaid
746280b3e2 Merge pull request #6724 from mermaid-js/sidv/failOnTypeError
chore: Fail build in CI on type errors
2025-07-07 14:08:28 +00:00
Sidharth Vinod
fa4e30bb15 Merge pull request #6725 from mermaid-js/use-treemap-in-languim
Use treemap in languim
2025-07-07 19:23:03 +05:30
shubham-mermaid
950b107dd4 update in changeset 2025-07-07 19:15:50 +05:30
shubham-mermaid
35b84761a9 updated validaor and tests to use treemap 2025-07-07 18:54:38 +05:30
shubham-mermaid
0da2922ee7 Updated parser to use treemap 2025-07-07 18:50:53 +05:30
Sidharth Vinod
85eba01663 chore: Fail build in CI on type errors 2025-07-07 18:02:23 +05:30
Gang Chen
79ba50216a Modify block diagram's logger to global log 2025-07-06 01:09:36 +08:00
darshanr0107
9655d07adf fix text going outside node 2025-07-04 16:11:30 +05:30
Ashish Jain
2b3cb6a362 Merge pull request #6713 from mermaid-js/changeset-release/master
Version Packages
2025-07-03 15:51:36 +02:00
github-actions[bot]
89ac2932c4 Version Packages 2025-07-03 13:28:11 +00:00
Ashish Jain
e828609749 Merge pull request #6695 from mermaid-js/develop
Pre-Release
2025-07-03 15:24:36 +02:00
Ashish Jain
91f141f772 Merge pull request #6711 from mermaid-js/update-lychee-for-phpbb
Updated phpbb url  to support 403 in lychee
2025-07-03 12:53:41 +00:00
shubham-mermaid
c77b968f1e Updated phpbb url to support 403 in lychee 2025-07-03 18:12:12 +05:30
Ashish Jain
ba13981905 Updated lychee for working url phpbb.com 2025-07-03 12:56:09 +02:00
Alois Klink
a7a94b95e1 Merge pull request #6707 from mermaid-js/4838-gitgraph-chronology-duplicate-id
4838: Log warning for duplicate commit IDs in gitGraph
2025-07-03 10:34:25 +00:00
darshanr0107
31f141c61a Merge branch 'develop' into 4838-gitgraph-chronology-duplicate-id 2025-07-03 15:39:40 +05:30
darshanr0107
fb017bebfd resolve PR comment
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-07-03 15:39:26 +05:30
darshanr0107
648698a43a resolve PR comments 2025-07-03 13:57:36 +05:30
Sidharth Vinod
a5e4729c76 Merge pull request #6700 from mermaid-js/update-timings
Update E2E Timings
2025-07-03 12:52:12 +05:30
github-actions[bot]
c884def5fc chore: update E2E timings 2025-07-03 04:14:58 +00:00
darshanr0107
592c5bb880 add changeset 2025-07-02 12:16:17 +05:30
darshanr0107
da0c6c6c32 log warning when duplicate commit IDs are encountered 2025-07-02 11:59:20 +05:30
Knut Sveidqvist
d9396eedd6 Merge pull request #6698 from mermaid-js/update-urls-in-docs
Added updated chatgpt  plugin url and removed failing url
2025-06-27 12:46:28 +00:00
shubham-mermaid
5f9601b6a8 Merge branch 'develop' into update-urls-in-docs 2025-06-27 17:53:04 +05:30
shubham-mermaid
378f8ece0c Added updated chatgpt plugin url and removed failing url 2025-06-27 17:50:45 +05:30
shubham-mermaid
148a42a31a Merge pull request #6590 from mermaid-js/knsv-treemap
Adding support for the new diagram type nested treemap
2025-06-27 10:31:32 +00:00
shubham-mermaid
8980ca4526 Added classes and outernodes in imperativestate 2025-06-27 15:18:53 +05:30
shubham-mermaid
caa04aad8b Updated code as per suggestions 2025-06-27 13:48:44 +05:30
shubham-mermaid
3dc06ea9bd fixed unwanted vitepress from lockfile 2025-06-26 19:27:40 +05:30
shubham-mermaid
c311c1ba5d Merge branch 'develop' into knsv-treemap 2025-06-26 18:40:43 +05:30
Sidharth Vinod
11a86d9c06 Merge pull request #6689 from mermaid-js/#6669-pnpm-lock-workflow-validation-
Added step to install pnpm
2025-06-26 12:59:17 +00:00
shubham-mermaid
037bec189c Updated as per suggestions 2025-06-26 17:43:06 +05:30
shubham-mermaid
9b164dd185 Added step to install pnpm and echo statements for debugging. 2025-06-26 14:41:03 +05:30
shubham-mermaid
4ee124bf91 Merge branch 'develop' into knsv-treemap 2025-06-26 13:04:47 +05:30
Knut Sveidqvist
dc3ed65c18 Commented out skipped tests 2025-05-20 15:26:27 +02:00
Knut Sveidqvist
43092e6e11 Fixed rendering issues found while testing, setting to treemap-beta 2025-05-20 15:22:49 +02:00
Knut Sveidqvist
878e77acab Updates after review comments 2025-05-20 12:56:31 +02:00
Knut Sveidqvist
63827db60d Added padding as configuration 2025-05-15 10:59:47 +02:00
Knut Sveidqvist
41108358f6 Adding support for title and accessibilities 2025-05-15 10:44:46 +02:00
Ashish Jain
f970fc8bea Merge branch 'develop' into knsv-treemap 2025-05-13 14:39:53 +02:00
Knut Sveidqvist
db461a4c6c Updated lockfile 2025-05-12 19:21:25 +02:00
Knut Sveidqvist
fe33d5fb53 Merge remote-tracking branch 'origin/develop' into knsv-treemap 2025-05-12 19:18:04 +02:00
Knut Sveidqvist
64c4d4197b Prettier fix for json file 2025-05-12 18:30:06 +02:00
Knut Sveidqvist
ed7bab76f2 Fixed linting issues in markdown file 2025-05-12 18:27:40 +02:00
Knut Sveidqvist
f338802642 Adding changeset 2025-05-12 18:05:43 +02:00
Knut Sveidqvist
445595f942 Handling longer summaries for sections and lint fixes for the renderer 2025-05-12 17:57:23 +02:00
Knut Sveidqvist
66ce617bea Added support for valueFormat directive - linting 2025-05-12 17:47:00 +02:00
Knut Sveidqvist
3e07a5acff Added support for valueFormat directive 2025-05-12 17:46:19 +02:00
Knut Sveidqvist
df1e739194 Added support for valueFormat directive 2025-05-12 17:40:23 +02:00
Knut Sveidqvist
2def5a0768 Added visual tests 2025-05-12 17:16:29 +02:00
Knut Sveidqvist
2746bccef3 Added rendering and documentation for treemap - linting 2025-05-12 15:54:06 +02:00
Knut Sveidqvist
f0c3dfe3b3 Added rendering and documentation for treemap 2025-05-12 15:47:58 +02:00
Knut Sveidqvist
680d65114c Added class suppoort to the grammar 2025-05-09 13:47:45 +02:00
Knut Sveidqvist
4f8f929340 adjusted layout and theme support 2025-05-08 16:32:40 +02:00
Knut Sveidqvist
3629e8e480 Update treemap renderer for improved padding and layout adjustments 2025-05-08 14:20:05 +02:00
Knut Sveidqvist
ff48c2e1da adjusted layout WIP 2025-05-08 13:42:50 +02:00
Knut Sveidqvist
1bd13b50f1 added proper hierarchy from parsed data 2025-05-08 11:19:19 +02:00
Knut Sveidqvist
e0a075ecca Adding treemap 2025-05-07 18:16:00 +02:00
Knut Sveidqvist
40eb0cc240 Passing tests 2025-05-07 16:15:14 +02:00
Sidharth Vinod
954f6cc8fc Merge branch 'develop' into sidv/examples
* develop: (189 commits)
  chore(deps): update dependency vite to v6.1.6 [security]
  corrected font size text
  docs(theme): fix table formatting by removing extra line between fontFamily and fontSize
  chore(deps): update dependency eslint-plugin-unicorn to v59
  chore(deps): update dependency vite to v6.1.5 [security]
  docs(theme): document fontFamily customization in themeVariables
  chore(deps): update eslint
  chore(deps): update peter-evans/create-pull-request digest to 3b1f4bf
  chore(deps): update dependency vite to v6.1.5 [security]
  Added the changeset
  Deleted unused code
  Deleted unused method
  chore: update E2E timings
  added changeset and unit test
  fix(sequenceDiagram): allow empty message after colon (Fixes #6518)
  chore(deps): update wei/curl digest to 012398a
  `layout-elk` package documentation misleading version
  [autofix.ci] apply automated fixes
  code review - wording and removals
  [autofix.ci] apply automated fixes
  ...
2025-05-02 02:32:50 +05:30
Sidharth Vinod
19884294bc chore: Move packet diagram out of beta 2025-04-20 14:09:27 +05:30
Sidharth Vinod
e6b63fd70a chore: Bump beta version to publish 2025-04-20 13:48:11 +05:30
autofix-ci[bot]
8a84ede164 [autofix.ci] apply automated fixes 2025-04-17 16:17:01 +00:00
Sidharth Vinod
d79b7b2d97 Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid: (110 commits)
  [autofix.ci] apply automated fixes
  chore: Change `mermaid` blocks to `mermaid-example` in docs
  chore: Remove duplicate `mermaid` blocks
  chore: update E2E timings
  [autofix.ci] apply automated fixes
  chore: Switch from `%%{init` to `config:`
  chore: Add details to e2e-timings
  chore: Ignore timeout domains
  [autofix.ci] apply automated fixes
  [autofix.ci] apply automated fixes
  Create neat-moose-compare.md
  chore: Add changeset
  updated tests
  modified description of showDataLabel in config schema and added it to docs
  fix(deps): update all major dependencies
  chore(deps): update peter-evans/create-pull-request digest to a7b20e1
  fix(deps): update dependency dompurify to ^3.2.5
  [autofix.ci] apply automated fixes
  Update integrations-community.md
  [autofix.ci] apply automated fixes
  ...
2025-04-17 21:42:14 +05:30
Sidharth Vinod
844f879f63 fix: Examples unit test 2025-04-17 21:42:05 +05:30
Sidharth Vinod
cf789d2c91 Merge branch 'develop' into sidv/examples 2025-04-17 09:06:50 -07:00
Sidharth Vinod
4936ef5c30 chore: Split changeset
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-17 21:32:10 +05:30
Sidharth Vinod
f006718e56 chore: Change getDiagramData to getRegisteredDiagramsMetadata
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-17 21:30:48 +05:30
Sidharth Vinod
d720776918 Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid:
  Update packages/examples/tsconfig.json
2025-04-17 21:28:07 +05:30
Sidharth Vinod
f43398dd44 Update packages/examples/tsconfig.json
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-17 08:57:53 -07:00
Sidharth Vinod
43ad451940 fix: Remove unnecessary export from package.json
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-17 21:26:46 +05:30
Sidharth Vinod
94c099caa1 docs: Update kanban example
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-17 21:26:10 +05:30
Sidharth Vinod
3e6f680df2 chore: Reorder fields in examples 2025-04-08 20:14:36 +05:30
Sidharth Vinod
2e5d955e77 Update packages/examples/src/examples/kanban.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-08 04:39:41 -07:00
Sidharth Vinod
7733faf6c4 Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid:
  chore: update E2E timings
  chore: Update permission for timings action
  chore: Fix PR action in e2e-timings
  chore: Fix branch in e2e-timings
2025-04-08 17:08:13 +05:30
Sidharth Vinod
b053a88993 docs: Add steps to add examples for new diagrams 2025-04-08 17:07:56 +05:30
Sidharth Vinod
7c77c46ede Merge branch 'develop' into sidv/examples 2025-04-08 00:14:49 -07:00
Sidharth Vinod
da6937f474 temp: Revert all changes is cypress folder 2025-04-08 10:08:06 +05:30
Sidharth Vinod
cbb496da79 Merge branch 'develop' into sidv/examples
* develop:
  docs: improve elk section of ER docs
  Fix formatting of ELK example in ERD + clarify usage
2025-04-08 10:05:04 +05:30
Sidharth Vinod
43e66a6089 chore: Unskip example test 2025-04-05 14:02:58 +05:30
Sidharth Vinod
5acbd7e762 docs: Add changeset 2025-04-05 13:30:08 +05:30
Sidharth Vinod
febae345fc chore: Fix file extension 2025-04-05 09:25:31 +05:30
Sidharth Vinod
28bdbbca1a chore: Fix examples exports 2025-04-05 09:17:52 +05:30
Sidharth Vinod
4b896fa22e Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid:
  [autofix.ci] apply automated fixes
2025-04-05 09:11:26 +05:30
Sidharth Vinod
a25ee49edd test: Add visual testing for examples 2025-04-05 09:11:18 +05:30
Sidharth Vinod
695b5b2fb2 fix: Build script 2025-04-05 09:10:58 +05:30
autofix-ci[bot]
9b77af540b [autofix.ci] apply automated fixes 2025-04-05 03:33:20 +00:00
Sidharth Vinod
7829138fb2 chore: Update lockfile 2025-04-05 08:58:31 +05:30
Sidharth Vinod
7e7a4fc665 chore: Move examples to different package 2025-04-05 08:53:37 +05:30
Sidharth Vinod
34e6112fea Merge branch 'sidv/examples' of https://github.com/mermaid-js/mermaid into sidv/examples
* 'sidv/examples' of https://github.com/mermaid-js/mermaid:
  [autofix.ci] apply automated fixes
2025-04-03 17:10:45 +05:30
Sidharth Vinod
0c759d0075 chore: Add isDefault to examples 2025-04-03 17:10:33 +05:30
autofix-ci[bot]
5b7c1aad9e [autofix.ci] apply automated fixes 2025-04-03 10:53:10 +00:00
Sidharth Vinod
865c453547 feat: Add title and examples for diagrams 2025-04-03 16:16:44 +05:30
Sidharth Vinod
c0b14021b7 feat: Add support for examples in diagram definition 2025-04-03 11:15:45 +05:30
150 changed files with 6397 additions and 6952 deletions

View File

@@ -33,4 +33,9 @@ export const packageOptions = {
packageName: 'mermaid-layout-elk',
file: 'layouts.ts',
},
examples: {
name: 'mermaid-examples',
packageName: 'examples',
file: 'index.ts',
},
} as const satisfies Record<string, PackageOptions>;

View File

@@ -10,13 +10,16 @@ const buildType = (packageName: string) => {
console.log(out.toString());
}
} catch (e) {
console.error(e);
if (e.stdout.length > 0) {
console.error(e.stdout.toString());
}
if (e.stderr.length > 0) {
console.error(e.stderr.toString());
}
// Exit the build process if we are in CI
if (process.env.CI) {
throw new Error(`Failed to build types for ${packageName}`);
}
}
};

View File

@@ -1,5 +0,0 @@
---
'mermaid': minor
---
feat: Added IpSepCoLa algorithm for layout

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Add `getRegisteredDiagramsMetadata` to `mermaid`, which returns all the registered diagram IDs in mermaid

View File

@@ -0,0 +1,5 @@
---
'@mermaid-js/examples': minor
---
feat: Add examples for diagrams in the `@mermaid-js/examples` package

View File

@@ -0,0 +1,7 @@
---
'@mermaid-js/examples': patch
'mermaid': patch
'@mermaid-js/parser': patch
---
chore: Move packet diagram out of beta

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: adjust sequence diagram title positioning to prevent overlap with top border in Safari

View File

@@ -0,0 +1,7 @@
---
'mermaid': patch
---
fix(timeline): fix loading `leftMargin` from config
The `timeline.leftMargin` config value should now correctly control the size of the left margin, instead of being ignored.

View File

@@ -88,6 +88,7 @@ NODIR
NSTR
outdir
Qcontrolx
QSTR
reinit
rels
reqs

View File

@@ -12,7 +12,6 @@ gantt
gitgraph
gzipped
handDrawn
ipsepcola
kanban
marginx
marginy

3
.github/lychee.toml vendored
View File

@@ -46,6 +46,9 @@ exclude = [
# Drupal 403
"https://(www.)?drupal.org",
# Phbpp 403
"https://(www.)?phpbb.com",
# Swimm returns 404, even though the link is valid
"https://docs.swimm.io",

View File

@@ -42,4 +42,4 @@ jobs:
working-directory: ./packages/mermaid
run: pnpm run docs:build
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef # main
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # main

View File

@@ -148,4 +148,4 @@ jobs:
name: mermaid-codecov
fail_ci_if_error: false
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
token: 6845cc80-77ee-4e17-85a1-026cd95e0766

View File

@@ -29,3 +29,29 @@ jobs:
disable-releaser: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Add "Sponsored by MermaidChart" label
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const { data: commits } = await github.rest.pulls.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const isSponsored = commits.every(
(c) => c.commit.author.email?.endsWith('@mermaidchart.com')
);
if (isSponsored) {
console.log('PR is sponsored. Adding label.');
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['Sponsored by MermaidChart'],
});
}

View File

@@ -52,4 +52,4 @@ jobs:
name: mermaid-codecov
fail_ci_if_error: false
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
token: 6845cc80-77ee-4e17-85a1-026cd95e0766

View File

@@ -21,6 +21,8 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
- name: Validate pnpm-lock.yaml entries
id: validate # give this step an ID so we can reference its outputs
run: |

View File

@@ -1,13 +0,0 @@
import { MockedD3 } from '../packages/mermaid/src/tests/MockedD3.js';
export const select = function () {
return new MockedD3();
};
export const selectAll = function () {
return new MockedD3();
};
export const curveBasis = 'basis';
export const curveLinear = 'linear';
export const curveCardinal = 'cardinal';

View File

@@ -26,7 +26,10 @@ export default eyesPlugin(
config.env.useArgos = process.env.RUN_VISUAL_TEST === 'true';
if (config.env.useArgos) {
registerArgosTask(on, config);
registerArgosTask(on, config, {
// Enable upload to Argos only when it runs on CI.
uploadToArgos: !!process.env.CI,
});
} else {
addMatchImageSnapshotPlugin(on, config);
}

View File

@@ -1,153 +0,0 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
describe('Flowchart IPSepCoLa', () => {
it('1-ipsepCola: should render a simple flowchart', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`
);
});
it('2-ipsepCola: handle bidirectional edges', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart TD
subgraph D
A --> B
A --> B
B --> A
B --> A
end
`
);
});
it('3-ipsepCola: handle multiple self loops', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart
a --> a
a --> a
a --> a
a --> a
`
);
});
it('4-ipsepCola: handle state diagram example', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
`
);
});
it('5-ipsepCola: handle multiple subgraphs with edges between them', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart LR
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2
`
);
});
it('6-ipsepCola: handle class diagram example', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
classDiagram
class AuthService {
+login(username: string, password: string): boolean
+logout(): void
+register(): void
}
class User {
-username: string
-password: string
-role: Role
+changePassword(): void
}
class Role {
-name: string
-permissions: string[]
+hasPermission(): boolean
}
AuthService --> User
User --> Role
`
);
});
it('7-ipsepCola: should render a decision flowchart', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart TD
Start([Start]) --> Prep[Preparation Step]
Prep --> Split{Ready to Process?}
Split -->|Yes| T1[Task A]
Split -->|Yes| T2[Task B]
T1 --> Merge
T2 --> Merge
Merge((Join Results)) --> Finalize[Finalize Process]
Finalize --> End([End])
`
);
});
it('8-ipsepCola: handle nested subgraphs', () => {
imgSnapshotTest(
`---
config:
layout: ipsepCola
---
flowchart LR
subgraph main
subgraph subcontainer
subcontainer-child
end
subcontainer-child--> subcontainer-sibling
end
`
);
});
});

View File

@@ -1,7 +1,7 @@
import { imgSnapshotTest } from '../../helpers/util';
describe('packet structure', () => {
it('should render a simple packet diagram', () => {
it('should render a simple packet-beta diagram', () => {
imgSnapshotTest(
`packet-beta
title Hello world
@@ -10,9 +10,18 @@ describe('packet structure', () => {
);
});
it('should render a simple packet diagram', () => {
imgSnapshotTest(
`packet
title Hello world
0-10: "hello"
`
);
});
it('should render a simple packet diagram without ranges', () => {
imgSnapshotTest(
`packet-beta
`packet
0: "h"
1: "i"
`
@@ -21,7 +30,7 @@ describe('packet structure', () => {
it('should render a complex packet diagram', () => {
imgSnapshotTest(
`packet-beta
`packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
@@ -52,7 +61,7 @@ describe('packet structure', () => {
packet:
showBits: false
---
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"

View File

@@ -0,0 +1,382 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
describe('Treemap Diagram', () => {
it('1: should render a basic treemap', () => {
imgSnapshotTest(
`treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
it('2: should render a hierarchical treemap', () => {
imgSnapshotTest(
`treemap-beta
"Products"
"Electronics"
"Phones": 50
"Computers": 30
"Accessories": 20
"Clothing"
"Men's"
"Shirts": 10
"Pants": 15
"Women's"
"Dresses": 20
"Skirts": 10
`,
{}
);
});
it('3: should render a treemap with styling using classDef', () => {
imgSnapshotTest(
`treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
`,
{}
);
});
it('4: should handle long text that wraps', () => {
imgSnapshotTest(
`treemap-beta
"Main Category"
"This is a very long item name that should wrap to the next line when rendered in the treemap diagram": 50
"Short item": 20
`,
{}
);
});
it('5: should render with a forest theme', () => {
imgSnapshotTest(
`---
config:
theme: forest
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
it('6: should handle multiple levels of nesting', () => {
imgSnapshotTest(
`treemap-beta
"Level 1"
"Level 2A"
"Level 3A": 10
"Level 3B": 15
"Level 2B"
"Level 3C": 20
"Level 3D"
"Level 4A": 5
"Level 4B": 5
`,
{}
);
});
it('7: should handle classDef with multiple styles', () => {
imgSnapshotTest(
`treemap-beta
"Main"
"A": 20
"B":::important
"B1": 10
"B2": 15
"C": 5:::secondary
classDef important fill:#f96,stroke:#333,stroke-width:2px;
classDef secondary fill:#6cf,stroke:#333,stroke-dasharray:5 5;
`,
{}
);
});
it('8: should handle dollar value formatting with thousands separator', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: "$0,0"
---
treemap
"Budget"
"Operations"
"Salaries": 700000
"Equipment": 200000
"Supplies": 100000
"Marketing"
"Advertising": 400000
"Events": 100000
`,
{}
);
});
it('8a: should handle percentage formatting', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: ".1%"
---
treemap-beta
"Market Share"
"Company A": 0.35
"Company B": 0.25
"Company C": 0.15
"Others": 0.25
`,
{}
);
});
it('8b: should handle decimal formatting', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: ".2f"
---
treemap-beta
"Metrics"
"Conversion Rate": 0.0567
"Bounce Rate": 0.6723
"Click-through Rate": 0.1289
"Engagement": 0.4521
`,
{}
);
});
it('8c: should handle dollar sign with decimal places', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: "$.2f"
---
treemap-beta
"Product Prices"
"Basic": 19.99
"Standard": 49.99
"Premium": 99.99
"Enterprise": 199.99
`,
{}
);
});
it('8d: should handle dollar sign with thousands separator and decimal places', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: "$,.2f"
---
treemap-beta
"Revenue"
"Q1": 1250345.75
"Q2": 1645789.25
"Q3": 1845123.50
"Q4": 2145678.75
`,
{}
);
});
it('8e: should handle simple thousands separator', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: ","
---
treemap-beta
"User Counts"
"Active Users": 1250345
"New Signups": 45789
"Churned": 12350
"Converted": 78975
`,
{}
);
});
it('8f: should handle valueFormat set via directive with dollar and thousands separator', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: "$,.0f"
---
treemap-beta
"Sales by Region"
"North": 1234567
"South": 7654321
"East": 4567890
"West": 9876543
`,
{}
);
});
it('8g: should handle scientific notation format', () => {
imgSnapshotTest(
`---
config:
treemap:
valueFormat: ".2e"
---
treemap-beta
"Scientific Values"
"Value 1": 1234567
"Value 2": 0.0000123
"Value 3": 1000000000
`,
{}
);
});
it('9: should handle a complex example with multiple features', () => {
imgSnapshotTest(
`---
config:
theme: dark
treemap:
valueFormat: "$0,0"
---
treemap-beta
"Company Budget"
"Engineering":::engineering
"Frontend": 300000
"Backend": 400000
"DevOps": 200000
"Marketing":::marketing
"Digital": 250000
"Print": 100000
"Events": 150000
"Sales":::sales
"Direct": 500000
"Channel": 300000
classDef engineering fill:#6b9bc3,stroke:#333;
classDef marketing fill:#c36b9b,stroke:#333;
classDef sales fill:#c3a66b,stroke:#333;
`,
{}
);
});
it('10: should render the example from documentation', () => {
imgSnapshotTest(
`
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
`,
{}
);
});
it('11: should handle comments', () => {
imgSnapshotTest(
`
treemap-beta
%% This is a comment
"Category A"
"Item A1": 10
"Item A2": 20
%% Another comment
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
/*
it.skip('12: should render a treemap with title', () => {
imgSnapshotTest(
`
treemap-beta
title Treemap with Title
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
it.skip('13: should render a treemap with accessibility attributes', () => {
imgSnapshotTest(
`
treemap-beta
accTitle: Accessible Treemap Title
accDescr: This is a description of the treemap for accessibility purposes
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
it.skip('14: should render a treemap with title and accessibility attributes', () => {
imgSnapshotTest(
`
treemap
title Treemap with Title and Accessibility
accTitle: Accessible Treemap Title
accDescr: This is a description of the treemap for accessibility purposes
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
`,
{}
);
});
*/
});

View File

@@ -1,601 +0,0 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: #333; */
font-family: 'Arial';
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
svg {
border: 3px solid #300;
margin: 10px;
}
pre {
width: 100%;
}
</style>
</head>
<body>
<pre id="diagram4" class="mermaid">
flowchart
A --> B
subgraph hello
C --> D
end
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
A --> B
A --> B
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
A[hello] --> A
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
subgraph C
c
end
A --> C
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
subgraph C
c
end
A --> c
</pre
>
<pre id="diagram4" class="mermaid">
flowchart TD
subgraph D
A --> B
A --> B
B --> A
B --> A
end
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
subgraph B2
A --> B --> C
B --> D
end
B2 --> X
</pre
>
<pre id="diagram4" class="mermaid">
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
</pre
>
<pre id="diagram4" class="mermaid">
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
</pre
>
<pre id="diagram4" class="mermaid">
flowchart TD
P1
P1 -->P1.5
subgraph P1.5
P2
P2.5(( A ))
P3
end
P2 --> P4
P3 --> P6
P1.5 --> P5
</pre>
<pre id="diagram4" class="mermaid">
%% Length of edges
flowchart TD
L1 --- L2
L2 --- C
M1 ---> C
R1 .-> R2
R2 <.-> C
C -->|Label 1| E1
C <-- Label 2 ---> E2
C ----> E3
C <-...-> E4
C ======> E5
</pre>
<pre id="diagram4" class="mermaid">
%% Stadium shape
flowchart TD
A([stadium shape test])
A -->|Get money| B([Go shopping])
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
C -->|One| D([Laptop])
C -->|Two| E([iPhone])
C -->|Three| F([Car<br/>wroom wroom])
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
class C someclass;
</pre>
<pre id="diagram4" class="mermaid">
%% should render escaped without html labels
flowchart TD
a["<strong>Haiya</strong>"]---->b
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs in reverse order
flowchart LR
a -->b
subgraph A
B
end
subgraph B
b
end
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs in several levels
flowchart LR
b-->B
a-->c
subgraph O
A
end
subgraph B
c
end
subgraph A
a
b
B
end
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with edges in and out
flowchart LR
internet
nat
routeur
lb1
lb2
compute1
compute2
subgraph project
routeur
nat
subgraph subnet1
compute1
lb1
end
subgraph subnet2
compute2
lb2
end
end
internet --> routeur
routeur --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links
flowchart LR
subgraph main
subgraph subcontainer
subcontainer-child
end
subcontainer-child--> subcontainer-sibling
end
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with ingoing links
flowchart LR
subgraph one[One]
subgraph sub_one[Sub One]
_sub_one
end
subgraph sub_two[Sub Two]
_sub_two
end
_one
end
%% here, either the first or the second one
sub_one --> sub_two
_one --> b
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links 3
flowchart LR
subgraph container_Beta
process_C-->Process_D
end
subgraph container_Alpha
process_A-->process_B
process_A-->|messages|process_C
end
process_B-->|via_AWSBatch|container_Beta
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links 4
flowchart LR
subgraph A
a -->b
end
subgraph B
b
end
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links 2
flowchart LR
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links 5
flowchart LR
subgraph container_Beta
process_C-->Process_D
end
subgraph container_Alpha
process_A-->process_B
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|process_C
end
</pre>
<pre id="diagram4" class="mermaid">
%% More subgraphs
flowchart LR
subgraph two
b1
end
subgraph three
c2
end
three --> two
two --> c2
note[There are two links in this diagram]
</pre>
<pre id="diagram4" class="mermaid">
%% nested subgraphs with outgoing links 5
flowchart LR
A[red text] -->|default style| B(blue text)
C([red text]) -->|default style| D[[blue text]]
E[(red text)] -->|default style| F((blue text))
G>red text] -->|default style| H{blue text}
I{{red text}} -->|default style| J[/blue text/]
K[\\ red text\\] -->|default style| L[/blue text\\]
M[\\ red text/] -->|default style| N[blue text];
O(((red text))) -->|default style| P(((blue text)));
linkStyle default color:Sienna;
style A stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style B stroke:#0000ff,fill:#ccccff,color:#0000ff;
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style D stroke:#0000ff,fill:#ccccff,color:#0000ff;
style E stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style F stroke:#0000ff,fill:#ccccff,color:#0000ff;
style G stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style H stroke:#0000ff,fill:#ccccff,color:#0000ff;
style I stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style J stroke:#0000ff,fill:#ccccff,color:#0000ff;
style K stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style L stroke:#0000ff,fill:#ccccff,color:#0000ff;
style M stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style N stroke:#0000ff,fill:#ccccff,color:#0000ff;
style O stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style P stroke:#0000ff,fill:#ccccff,color:#0000ff;
</pre>
<pre id="diagram4" class="mermaid">
%% labels on edges in a cluster
flowchart RL
subgraph one
a1 -- I am a long label --> a2
a1 -- Another long label --> a2
end
</pre>
<pre id="diagram4" class="mermaid">
%% labels on edges in a cluster
flowchart RL
subgraph one
a1[Iam a node with a super long label] -- I am a long label --> a2[I am another node with a mega long label]
a1 -- Another long label --> a2
a3 --> a1 & a2 & a3 & a4
a1 --> a4
end
</pre>
<pre id="diagram4" class="mermaid">
%% labels on edges in a cluster
flowchart RL
subgraph one
a1[Iam a node with a super long label]
a2[I am another node with a mega long label]
a3[I am a node with a super long label]
a4[I am another node with a mega long label]
a1 -- Another long label --> a2
a3 --> a1 & a2 & a3 & a4
a1 --> a4
end
</pre>
<pre id="diagram4" class="mermaid">
%% labels on edges in a cluster
flowchart RL
a1[I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. ]
a2[I am another node with a mega long label]
a3[I am a node with a super long label]
a4[I am another node with a mega long label]
a1 & a2 & a3 & a4 --> a5 & a6 & a7 & a8 & a9 & a10
</pre>
<pre id="diagram4" class="mermaid">
flowchart
subgraph Z
subgraph X
a --> b
end
subgraph Y
c --> d
end
end
Y --> X
X --> P
P --> Y
</pre
>
<pre id="diagram4" class="mermaid">
flowchart
a --> b
b --> c
b --> d
c --> a
</pre
>
<pre id="diagram3" class="mermaid">
flowchart TD
Start([Start]) --> Prep[Preparation Step]
Prep --> Split{Ready to Process?}
Split -->|Yes| T1[Task A]
Split -->|Yes| T2[Task B]
T1 --> Merge
T2 --> Merge
Merge((Join Results)) --> Finalize[Finalize Process]
Finalize --> End([End])
</pre
>
<pre id="diagram4" class="mermaid">
flowchart TD
A[Start Build] --> B[Compile Source]
B --> C[Test Suite]
C --> D{Tests Passed?}
D -->|No| E[Notify Developer]
E --> A
D -->|Yes| F[Build Docker Image]
subgraph Deploy Pipeline
F --> G[Deploy to Staging]
G --> H[Run Integration Tests]
H --> I{Tests Passed?}
I -->|No| J[Rollback & Alert]
I -->|Yes| K[Deploy to Production]
end
K --> L([Success])
</pre
>
<pre class="mermaid">
classDiagram
class Controller {
+handleRequest(): void
}
class View {
+render(): void
}
class Model {
+getData(): any
+setData(data: any): void
}
Controller --> Model
Controller --> View
Model --> View : notifyChange()
</pre
>
<pre class="mermaid">
classDiagram
class AuthService {
+login(username: string, password: string): boolean
+logout(): void
+register(): void
}
class User {
-username: string
-password: string
-role: Role
+changePassword(): void
}
class Role {
-name: string
-permissions: string[]
+hasPermission(): boolean
}
AuthService --> User
User --> Role
</pre
>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
const staticBellIconPack = {
prefix: 'fa6-regular',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () => staticBellIconPack,
},
]);
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
};
window.callback = function () {
alert('A callback was triggered');
};
function callback() {
alert('It worked');
}
await mermaid.initialize({
theme: 'redux-dark',
// theme: 'default',
// theme: 'forest',
handDrawnSeed: 12,
look: 'classic ',
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
// layout: 'dagre',
layout: 'ipsepCola',
// layout: 'elk',
// layout: 'sugiyama',
// htmlLabels: false,
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
// fontFamily: 'courier',
fontFamily: 'arial',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
kanban: {
htmlLabels: false,
},
fontSize: 12,
logLevel: 0,
securityLevel: 'loose',
callback,
});
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>

View File

@@ -32,8 +32,26 @@
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:wght@300..1000&display=swap"
rel="stylesheet"
/>
<style>
.recursive-mermaid {
font-family: 'Recursive', sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings:
'slnt' 0,
'CASL' 0,
'CRSV' 0.5,
'MONO' 0;
}
body {
/* background: rgb(221, 208, 208); */
/* background: #333; */
@@ -45,7 +63,9 @@
h1 {
color: grey;
}
.mermaid {
border: 1px solid red;
}
.mermaid2 {
display: none;
}
@@ -83,6 +103,11 @@
width: 100%;
}
.class2 {
fill: red;
fill-opacity: 1;
}
/* tspan {
font-size: 6px !important;
} */
@@ -106,19 +131,63 @@
<body>
<pre id="diagram4" class="mermaid">
treemap
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
treemap:
valueFormat: '$0,0'
---
treemap
"Budget"
"Operations"
"Salaries": 7000
"Equipment": 2000
"Supplies": 1000
"Marketing"
"Advertising": 4000
"Events": 1000
</pre
>
<pre id="diagram4" class="mermaid">
treemap
title Accessible Treemap Title
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
AB["apa@apa@"] --> B(("`apa@apa`"))
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart
D(("for D"))
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@==> B
e1@{ animate: true}
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
@@ -438,7 +507,7 @@ kanban
alert('It worked');
}
await mermaid.initialize({
// theme: 'base',
// theme: 'forest',
// theme: 'default',
// theme: 'forest',
// handDrawnSeed: 12,
@@ -449,11 +518,7 @@ kanban
// layout: 'fixed',
// htmlLabels: false,
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
// fontFamily: 'courier',
fontFamily: 'arial',
fontFamily: "'Recursive', sans-serif",
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',

View File

@@ -75,7 +75,6 @@
end
D --> D
end
C1 --> C1
end
</pre>

View File

@@ -2,35 +2,35 @@
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 6130
"duration": 5672
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 1974
"duration": 1990
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3308
"duration": 3186
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 1877
"duration": 1948
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 10902
"duration": 11938
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 1836
"duration": 1932
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 26467
"duration": 27237
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 3129
"duration": 3170
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
@@ -38,147 +38,147 @@
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 16230
"duration": 17390
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 5231
"duration": 5296
},
{
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
"duration": 38113
"duration": 39004
},
{
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
"duration": 36423
"duration": 37653
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 22509
"duration": 23278
},
{
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
"duration": 34933
"duration": 36645
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 14681
"duration": 15418
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 8877
"duration": 9684
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 2517
"duration": 2570
},
{
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
"duration": 81226
"duration": 84687
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 14211
"duration": 14819
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 3355
"duration": 3371
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 38857
"duration": 39925
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 28570
"duration": 34694
},
{
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
"duration": 6902
"duration": 7137
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 23075
"duration": 24740
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 40514
"duration": 42077
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 28611
"duration": 30642
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 16605
"duration": 18085
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 47636
"duration": 50107
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 262219
"duration": 276279
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 54111
"duration": 56505
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 3006
"duration": 3036
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 6858
"duration": 6889
},
{
"spec": "cypress/integration/rendering/kanban.spec.ts",
"duration": 7281
"duration": 7353
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 3579
"duration": 3580
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 2448
"duration": 2508
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 10618
"duration": 10939
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 140874
"duration": 149102
},
{
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
"duration": 108015
"duration": 113987
},
{
"spec": "cypress/integration/rendering/packet.spec.ts",
"duration": 4241
"duration": 4060
},
{
"spec": "cypress/integration/rendering/pie.spec.ts",
"duration": 5645
"duration": 5715
},
{
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
"duration": 8524
"duration": 8945
},
{
"spec": "cypress/integration/rendering/radar.spec.js",
"duration": 5203
"duration": 5337
},
{
"spec": "cypress/integration/rendering/requirement.spec.js",
"duration": 2635
"duration": 2643
},
{
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
"duration": 50512
"duration": 52072
},
{
"spec": "cypress/integration/rendering/sankey.spec.ts",
@@ -186,31 +186,35 @@
},
{
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
"duration": 34559
"duration": 35721
},
{
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
"duration": 24421
"duration": 26030
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 15316
"duration": 16333
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 28240
"duration": 29287
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 6808
"duration": 8491
},
{
"spec": "cypress/integration/rendering/treemap.spec.ts",
"duration": 12291
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 19359
"duration": 20651
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 3164
"duration": 3218
}
]
}

View File

@@ -4,7 +4,7 @@
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"

View File

@@ -17,7 +17,7 @@
<div class="diagrams">
<pre class="mermaid">
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
@@ -44,7 +44,7 @@
packet:
showBits: false
---
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
@@ -70,7 +70,7 @@
config:
theme: forest
---
packet-beta
packet
title Forest theme
0-15: "Source Port"
16-31: "Destination Port"
@@ -97,7 +97,7 @@
config:
theme: dark
---
packet-beta
packet
title Dark theme
0-15: "Source Port"
16-31: "Destination Port"

75
demos/treemap.html Normal file
View File

@@ -0,0 +1,75 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mermaid Treemap Diagram Demo</title>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<style>
body {
font-family: 'Montserrat', sans-serif;
margin: 0 auto;
max-width: 900px;
padding: 20px;
}
.mermaid {
margin: 30px 0;
}
h1,
h2 {
color: #333;
}
pre {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>Treemap Diagram Demo</h1>
<p>This is a demo of the new treemap diagram type in Mermaid.</p>
<h2>Basic Treemap Example</h2>
<pre class="mermaid">
treemap
"Root"
"Branch 1"
"Leaf 1.1": 10
"Leaf 1.2": 15
"Branch 2"
"Branch 2.1"
"Leaf 2.1.1": 20
"Leaf 2.1.2": 25
"Leaf 2.2": 25
"Leaf 2.3": 30
</pre>
<h2>Technology Stack Treemap Example</h2>
<pre class="mermaid">
treemap
"Technology Stack"
"Frontend"
"React": 35
"CSS": 15
"HTML": 10
"Backend"
"Node.js": 25
"Express": 10
"MongoDB": 15
"DevOps"
"Docker": 10
"Kubernetes": 15
"CI/CD": 5
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'forest',
logLevel: 1,
securityLevel: 'loose',
});
</script>
</body>
</html>

View File

@@ -301,7 +301,7 @@ If you are adding a feature, you will definitely need to add tests. Depending on
Unit tests are tests that test a single function or module. They are the easiest to write and the fastest to run.
Unit tests are mandatory for all code except the renderers. (The renderers are tested with integration tests.)
Unit tests are mandatory for all code except the layout tests. (The layouts are tested with integration tests.)
We use [Vitest](https://vitest.dev) to run unit tests.
@@ -327,6 +327,30 @@ When using Docker prepend your command with `./run`:
./run pnpm test
```
##### Testing the DOM
One can use `jsdomIt` to test any part of Mermaid that interacts with the DOM, as long as it is not related to the layout.
The function `jsdomIt` ([developed in utils.ts](../../tests/util.ts)) overrides `it` from `vitest`, and creates a pseudo-browser environment that works almost like the real deal for the duration of the test. It uses JSDOM to create a DOM, and adds objects `window` and `document` to `global` to mock the browser environment.
> \[!NOTE]
> The layout cannot work in `jsdomIt` tests because JSDOM has no rendering engine, hence the pseudo-browser environment.
Example :
```typescript
import { ensureNodeFromSelector, jsdomIt } from './tests/util.js';
jsdomIt('should add element "thing" in the SVG', ({ svg }) => {
// Code in this block runs in a pseudo-browser environment
addThing(svg); // The svg item is the D3 selection of the SVG node
const svgNode = ensureNodeFromSelector('svg'); // Retrieve the DOM node using the DOM API
expect(svgNode.querySelector('thing')).not.toBeNull(); // Test the structure of the SVG
});
```
They can be used to test any method that interacts with the DOM, including for testing renderers. For renderers, additional integration testing is necessary to test the layout though.
#### Integration / End-to-End (E2E) Tests
These test the rendering and visual appearance of the diagrams.

View File

@@ -111,3 +111,13 @@ const themes = {
```
The actual options and values for the colors are defined in **src/theme/theme-\[xyz].js**. If you provide the options your diagram needs in the existing theme files then the theming will work smoothly without hiccups.
## Examples
The `@mermaid-js/examples` package contains a collection of examples that are used by tools like mermaid.live to help users get started with the new diagram.
You can duplicate an existing diagram example file, eg: `packages/examples/src/examples/flowchart.ts`, and modify it with details specific to your diagram.
Then you can import the example in the `packages/examples/src/index.ts` file and add it to the `examples` array.
Each diagram should have at least one example, and that should be marked as default. It is good to add more examples to showcase different features of the diagram.

View File

@@ -12,4 +12,4 @@
> `const` **configKeys**: `Set`<`string`>
Defined in: [packages/mermaid/src/defaultConfig.ts:278](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L278)
Defined in: [packages/mermaid/src/defaultConfig.ts:290](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L290)

View File

@@ -10,7 +10,7 @@
# Interface: ExternalDiagramDefinition
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)
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)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:99](https://github.com/me
> **detector**: `DiagramDetector`
Defined in: [packages/mermaid/src/diagram-api/types.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L101)
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)
---
@@ -26,7 +26,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:101](https://github.com/m
> **id**: `string`
Defined in: [packages/mermaid/src/diagram-api/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L100)
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)
---
@@ -34,4 +34,4 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:100](https://github.com/m
> **loader**: `DiagramLoader`
Defined in: [packages/mermaid/src/diagram-api/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L102)
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)

View File

@@ -10,7 +10,7 @@
# Interface: LayoutData
Defined in: [packages/mermaid/src/rendering-util/types.ts:159](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L159)
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)
## Indexable
@@ -22,7 +22,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:159](https://github.co
> **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/rendering-util/types.ts:162](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L162)
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)
---
@@ -30,7 +30,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:162](https://github.co
> **edges**: `Edge`\[]
Defined in: [packages/mermaid/src/rendering-util/types.ts:161](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L161)
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)
---
@@ -38,4 +38,4 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:161](https://github.co
> **nodes**: `Node`\[]
Defined in: [packages/mermaid/src/rendering-util/types.ts:160](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L160)
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)

View File

@@ -10,7 +10,7 @@
# Interface: Mermaid
Defined in: [packages/mermaid/src/mermaid.ts:418](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L418)
Defined in: [packages/mermaid/src/mermaid.ts:429](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L429)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:418](https://github.com/mermaid-js/
> **contentLoaded**: () => `void`
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:447](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L447)
\##contentLoaded Callback function that is called when page is loaded. This functions fetches
configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
@@ -34,7 +34,7 @@ page.
> **detectType**: (`text`, `config`?) => `string`
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:449](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L449)
Detects the type of the graph text.
@@ -86,11 +86,28 @@ A graph definition key
---
### getRegisteredDiagramsMetadata()
> **getRegisteredDiagramsMetadata**: () => `Pick`<[`ExternalDiagramDefinition`](ExternalDiagramDefinition.md), `"id"`>\[]
Defined in: [packages/mermaid/src/mermaid.ts:451](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L451)
Gets the metadata for all registered diagrams.
Currently only the id is returned.
#### Returns
`Pick`<[`ExternalDiagramDefinition`](ExternalDiagramDefinition.md), `"id"`>\[]
An array of objects with the id of the diagram.
---
### ~~init()~~
> **init**: (`config`?, `nodes`?, `callback`?) => `Promise`<`void`>
Defined in: [packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
## init
@@ -138,7 +155,7 @@ Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
> **initialize**: (`config`) => `void`
Defined in: [packages/mermaid/src/mermaid.ts:435](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L435)
Defined in: [packages/mermaid/src/mermaid.ts:446](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L446)
Used to set configurations for mermaid.
This function should be called before the run function.
@@ -161,7 +178,7 @@ Configuration object for mermaid.
> **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:425](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L425)
Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
**`Internal`**
@@ -175,7 +192,7 @@ Use [parse](Mermaid.md#parse) and [render](Mermaid.md#render) instead. Please [o
> **parse**: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>
Defined in: [packages/mermaid/src/mermaid.ts:426](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L426)
Defined in: [packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
Parse the text and validate the syntax.
@@ -243,7 +260,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
> `optional` **parseError**: [`ParseErrorFunction`](../type-aliases/ParseErrorFunction.md)
Defined in: [packages/mermaid/src/mermaid.ts:420](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L420)
Defined in: [packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
---
@@ -251,7 +268,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:420](https://github.com/mermaid-js/
> **registerExternalDiagrams**: (`diagrams`, `opts`) => `Promise`<`void`>
Defined in: [packages/mermaid/src/mermaid.ts:434](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L434)
Defined in: [packages/mermaid/src/mermaid.ts:445](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L445)
Used to register external diagram types.
@@ -281,7 +298,7 @@ If opts.lazyLoad is false, the diagrams will be loaded immediately.
> **registerIconPacks**: (`iconLoaders`) => `void`
Defined in: [packages/mermaid/src/mermaid.ts:439](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L439)
Defined in: [packages/mermaid/src/mermaid.ts:450](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L450)
#### Parameters
@@ -299,7 +316,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:439](https://github.com/mermaid-js/
> **registerLayoutLoaders**: (`loaders`) => `void`
Defined in: [packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
Defined in: [packages/mermaid/src/mermaid.ts:444](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L444)
#### Parameters
@@ -317,7 +334,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/
> **render**: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>
Defined in: [packages/mermaid/src/mermaid.ts:427](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L427)
Defined in: [packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
#### Parameters
@@ -349,7 +366,7 @@ Deprecated for external use.
> **run**: (`options`) => `Promise`<`void`>
Defined in: [packages/mermaid/src/mermaid.ts:432](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L432)
Defined in: [packages/mermaid/src/mermaid.ts:443](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L443)
## run
@@ -393,7 +410,7 @@ Optional runtime configs
> **setParseErrorHandler**: (`parseErrorHandler`) => `void`
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:448](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L448)
## setParseErrorHandler Alternative to directly setting parseError using:
@@ -424,4 +441,4 @@ New parseError() callback.
> **startOnLoad**: `boolean`
Defined in: [packages/mermaid/src/mermaid.ts:419](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L419)
Defined in: [packages/mermaid/src/mermaid.ts:430](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L430)

View File

@@ -12,4 +12,4 @@
> **SVG**: `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
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)
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)

View File

@@ -12,4 +12,4 @@
> **SVGGroup**: `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
Defined in: [packages/mermaid/src/diagram-api/types.ts:132](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L132)
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)

View File

@@ -12,4 +12,4 @@
> `const` **default**: [`Mermaid`](../interfaces/Mermaid.md)
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:454](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L454)

View File

@@ -245,7 +245,7 @@ Communication tools and platforms
| GitHub + Mermaid | - | [🦊🔗](https://addons.mozilla.org/firefox/addon/github-mermaid/) | - | - | [🐙🔗](https://github.com/BackMarket/github-mermaid-extension) |
| Asciidoctor Live Preview | [🎡🔗](https://chromewebstore.google.com/detail/asciidoctorjs-live-previe/iaalpfgpbocpdfblpnhhgllgbdbchmia) | - | - | [🌀🔗](https://microsoftedge.microsoft.com/addons/detail/asciidoctorjs-live-previ/pefkelkanablhjdekgdahplkccnbdggd?hl=en-US) | - |
| Diagram Tab | - | - | - | - | [🐙🔗](https://github.com/khafast/diagramtab) |
| Markdown Diagrams | [🎡🔗](https://chromewebstore.google.com/detail/markdown-diagrams/pmoglnmodacnbbofbgcagndelmgaclel) | [🦊🔗](https://addons.mozilla.org/en-US/firefox/addon/markdown-diagrams/) | [🔴🔗](https://addons.opera.com/en/extensions/details/markdown-diagrams/) | [🌀🔗](https://microsoftedge.microsoft.com/addons/detail/markdown-diagrams/hceenoomhhdkjjijnmlclkpenkapfihe) | [🐙🔗](https://github.com/marcozaccari/markdown-diagrams-browser-extension/tree/master/doc/examples) |
| Markdown Diagrams | [🎡🔗](https://chromewebstore.google.com/detail/markdown-diagrams/pmoglnmodacnbbofbgcagndelmgaclel) | [🦊🔗](https://addons.mozilla.org/en-US/firefox/addon/markdown-diagrams/) | - | [🌀🔗](https://microsoftedge.microsoft.com/addons/detail/markdown-diagrams/hceenoomhhdkjjijnmlclkpenkapfihe) | [🐙🔗](https://github.com/marcozaccari/markdown-diagrams-browser-extension/tree/master/doc/examples) |
| Markdown Viewer | - | [🦊🔗](https://addons.mozilla.org/en-US/firefox/addon/markdown-viewer-chrome/) | - | - | [🐙🔗](https://github.com/simov/markdown-viewer) |
| Extensions for Mermaid | - | - | [🔴🔗](https://addons.opera.com/en/extensions/details/extensions-for-mermaid/) | - | [🐙🔗](https://github.com/Stefan-S/mermaid-extension) |
| Chrome Diagrammer | [🎡🔗](https://chromewebstore.google.com/detail/chrome-diagrammer/bkpbgjmkomfoakfklcjeoegkklgjnnpk) | - | - | - | - |

View File

@@ -30,7 +30,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
Official Mermaid Chart plugins:
- [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart)
- [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)
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart)

View File

@@ -1,177 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/development.md](../../packages/mermaid/src/docs/layouts/development.md).
# 🛠️ How to Create a New Layout Algorithm in Mermaid
Mermaid supports pluggable layout engines, and contributors can add custom layout algorithms to support specialized rendering needs such as clustered layouts, nested structures, or domain-specific visualizations.
This guide outlines the steps required to **create and integrate a new layout algorithm** into the Mermaid codebase.
---
## 📦 Prerequisites
Before starting, ensure the following:
- You have [Node.js](https://nodejs.org/) installed.
- You have [pnpm](https://pnpm.io/) installed globally:
```bash
npm install -g pnpm
```
---
## 🔄 Step-by-Step Integration
### 1. Clone the Mermaid Repository
```bash
git clone https://github.com/mermaid-js/mermaid.git
cd mermaid
```
### 2. Install Dependencies
Mermaid uses `pnpm` for dependency management:
```bash
pnpm i
```
### 3. Start the Development Server
This will spin up a local dev environment with hot reload:
```bash
pnpm dev
```
---
## 🧠 Implementing Your Custom Layout Algorithm
### 4. Create Your Layout Folder
Navigate to the relevant source directory and create a folder for your new algorithm:
```bash
cd packages/mermaid/src/layout
mkdir myCustomLayout
touch myCustomLayout/index.ts
```
> 📁 You can organize supporting files, utils, and types inside this folder.
### 5. Register the Layout Algorithm
Open the file:
```
packages/mermaid/src/rendering-util/render.ts
```
Inside the function `registerDefaultLayoutLoaders`, find the `layoutLoaders` array. Add your layout here:
```ts
registerDefaultLayoutLoaders([
...,
{
id: 'myCustomLayout',
loader: () => import('../layout/myCustomLayout'),
},
]);
```
This tells Mermaid how to load your layout dynamically by name (`id`).
---
## 🧪 Testing Your Algorithm
### 6. Create a Test File
To visually test your layout implementation, create a test HTML file in:
```
cypress/platform/
```
Example:
```bash
touch cypress/platform/myCustomLayoutTest.html
```
Inside the file, load your diagram like this:
```html
<!DOCTYPE html>
<html>
<head>
<script type="module">
import mermaid from '/dist/mermaid.esm.mjs';
mermaid.initialize({
startOnLoad: true,
theme: 'default',
layout: 'myCustomLayout', // Use your layout here
});
</script>
</head>
<body>
<div class="mermaid">graph TD A[Node A] --> B[Node B] B --> C[Node C]</div>
</body>
</html>
```
### 7. Open in Browser
After running `pnpm dev`, open your test in the browser:
```
http://localhost:9000/myCustomLayoutTest.html
```
You should see your diagram rendered using your new layout engine.
---
## 📝 Tips
- Keep your layout algorithm modular and readable.
- Use TypeScript types and helper functions for better structure.
- Add comments and constraints where necessary.
- If applicable, create a unit test and add a visual test for Cypress.
---
## 📚 Example File Structure
```
packages/
└── mermaid/
└── src/
└── layout/
└── myCustomLayout/
├── index.ts
├── utils.ts
└── types.ts
```
---
## ✅ Final Checklist
- [ ] All dependencies installed via `pnpm i`
- [ ] Layout folder and files created under `src/layout/`
- [ ] Entry registered in `registerDefaultLayoutLoaders`
- [ ] HTML test file added under `cypress/platform/`
- [ ] Diagram renders as expected at `localhost:9000`
- [ ] Code is linted and documented
---
> 💡 Youre now ready to build advanced layout algorithms and contribute to Mermaid's growing visualization capabilities!

View File

@@ -1,138 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/introduction.md](../../packages/mermaid/src/docs/layouts/introduction.md).
# 📊 Layout Algorithms in Mermaid
Mermaid is a popular JavaScript-based diagramming tool that supports auto-layout for graphs using pluggable layout engines. Layout algorithms play a critical role in rendering nodes and edges in a clean, readable, and meaningful way. Mermaid currently uses engines like **Dagre** and **ELK**, and will soon introduce a powerful new layout engine: **IPSep-CoLa**.
---
## 🔹 Dagre Layout
**Dagre** is a layout engine inspired by the **Sugiyama algorithm**, optimized for directed acyclic graphs (DAGs). It arranges nodes in layers and computes edge routing to minimize crossings and improve readability.
### Key Features:
- **Layered (Sugiyama-style) layout**: Ideal for top-down or left-to-right flow.
- **Edge routing**: Attempts to reduce edge crossings and bends.
- **Ranking**: Vertices are assigned ranks to group related elements into the same level.
- **Lightweight and fast**: Suitable for small to medium-sized graphs.
### Technical Overview:
- Works in four stages:
1. **Cycle Removal**
2. **Layer Assignment**
3. **Node Ordering**
4. **Coordinate Assignment**
- Outputs crisp layouts where edge direction is clear and logical.
### Limitations:
- No native support for **grouped or nested structures**.
- Not ideal for graphs with **non-hierarchical** or **dense cyclic connections**.
- Limited edge label placement capabilities.
---
## 🔸 ELK (Eclipse Layout Kernel)
**ELK** is a modular, extensible layout framework developed as part of the Eclipse ecosystem. It supports a wide variety of graph types and layout strategies.
### Key Features:
- **Multiple layout styles**: Hierarchical, force-based, layered, orthogonal, etc.
- **Support for ports**: Allows fine-grained edge anchoring on specific sides of nodes.
- **Group and hierarchy awareness**: Ideal for nested and compartmentalized diagrams.
- **Rich configuration**: Offers control over spacing, edge routing, direction, padding, and more.
### Technical Overview:
- Uses a **model-driven approach** with a well-defined intermediate representation (ELK Graph Model).
- Different engines are plugged in depending on the chosen layout strategy.
- Works well with large, complex, and deeply nested graphs.
### Limitations:
- Requires verbose configuration for best results.
- Can be slower than Dagre for small or simple diagrams.
- More complex to integrate and control dynamically.
---
## 🆕 IPSep-CoLa
### 🌐 Introduction
**IPSep-CoLa** stands for **Incremental Procedure for Separation Constraint Layout**, a next-generation layout algorithm tailored for **grouped, nested, and labeled graphs**. It is an enhancement over standard force-directed layouts, offering constraint enforcement and iterative refinement.
It is particularly useful for diagrams where:
- **Group integrity** is important (e.g., modules, clusters).
- **Edge labels** need smart placement.
- **Overlaps** must be prevented even under tight space constraints.
---
### ⚙️ How IPSep-CoLa Works
#### 1. **Constraint-Based Force Simulation**:
It builds on top of standard force-directed approaches (like CoLa), but adds **constraints** to influence the final positions of nodes:
- **Separation constraints**: Minimum distances between nodes, edge labels, and groups.
- **Containment constraints**: Child nodes must stay within the bounds of parent groups.
- **Alignment constraints**: Nodes can be aligned in rows or columns if desired.
#### 2. **Incremental Refinement**:
Unlike one-pass algorithms, IPSep-CoLa works in **phases**:
- Initial layout is produced using a base force simulation.
- The layout is iteratively adjusted using **constraint solvers**.
- Additional forces (spring, collision avoidance, containment) are incrementally added.
#### 3. **Edge Label Handling**:
One of the distinguishing features of IPSep-CoLa is its support for **multi-segment edge routing with mid-edge label positioning**, ensuring labels do not clutter or overlap.
---
### 📌 Use Cases
IPSep-CoLa is ideal for:
- **Hierarchical graphs** with complex nesting (e.g., software architecture, UML diagrams).
- **Clustered views** (e.g., social network groupings).
- **Diagrams with heavy labeling** where label placement affects readability.
- **Diagrams with strict visual structure** needs — maintaining boundaries, margins, or padding.
---
## 🔍 Comparison Table
| Feature | Dagre | ELK | IPSep-CoLa (Upcoming) |
| ------------------------- | ----------- | ------------------- | ------------------------------ |
| Layout Type | Layered DAG | Modular (varied) | Constraint-driven force layout |
| Edge Labeling | ⚠️ Basic | ✅ Yes | ✅ Smart Placement |
| Overlap Avoidance | ⚠️ Partial | ✅ Configurable | ✅ Automatic |
| Layout Performance | ✅ Fast | ⚠️ Medium | ⚠️ Medium |
| Customization Flexibility | ⚠️ Limited | ✅ Extensive | ✅ Moderate to High |
| Best For | Simple DAGs | Complex hierarchies | Grouped and labeled graphs |
---
## 🧾 Summary
Each layout engine in Mermaid serves a different purpose:
- **Dagre** is best for fast, simple, and readable DAGs.
- **ELK** is powerful for modular, layered, or port-based diagrams with a need for rich customization.
- **IPSep-CoLa** will soon offer a flexible, constraint-respecting layout engine that excels at **visual clarity in grouped and complex diagrams**.
The addition of IPSep-CoLa to Mermaid's layout stack represents a significant leap forward in layout control and quality — making it easier than ever to visualize rich, structured, and annotated graphs.
---

View File

@@ -1,46 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/ipsepcola/implementation.md](../../../packages/mermaid/src/docs/layouts/ipsepcola/implementation.md).
## IPSEPCOLA Documentation :
IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs
## How IPSep-CoLa built :
IPSep-CoLa follows a multi-stage process to compute a well-structured layout:
1. Layer Assignment :
The layer assignment algorithm organizes nodes into hierarchical layers to create a structured layout for directed graphs. It begins by detecting and temporarily removing cyclic edges using a depth-first search (DFS) approach, ensuring the graph becomes a Directed Acyclic Graph (DAG) for proper layering. The algorithm then performs a topological sort using Kahn's method, calculating node ranks (layers) based on in-degree counts. Each node's layer is determined by its position in the topological order, with parent nodes always appearing in higher layers than their children to maintain proper flow direction.
The implementation handles special cases like nested nodes by considering parent-child relationships when calculating layers. Nodes without dependencies are placed in layer 0, while subsequent nodes are assigned to layers one level below their nearest parent. The algorithm efficiently processes nodes using a queue system, decrementing in-degrees as it progresses, and ultimately stores the layer information directly in the node objects. Though cyclic edges are removed during processing, they could potentially be reintroduced after layer assignment if needed for visualization purposes.
2. Node ordering:
After assigning layers to nodes, this step organizes nodes horizontally within each layer to minimize edge crossings and create a clean, readable layout. It uses the barycenter method—a technique that positions each node based on the average position of its connected neighbors (either incoming or outgoing). Nodes with no connections are pushed to the end of their layer.
The algorithm works in multiple passes (iterations) to refine the order: first adjusting nodes based on their incoming connections (from the layer above), then outgoing connections (to the layer below). Group nodes (like containers) are handled separately—their position is determined by averaging the positions of their children, ensuring they stay properly aligned with their contents. This approach keeps the layout structured while reducing visual clutter.
3. AssignInitial positions to node :
This step calculates the starting (x, y) positions for each node based on its assigned layer (vertical level) and order (horizontal position). Nodes are spaced evenly—horizontally using nodeSpacing and vertically using layerHeight. For example, a node in layer 2 with order 3 will be placed at (3 \_ nodeSpacing, 2 \_ layerHeight). This creates a grid-like structure where nodes align neatly in rows (layers) and columns (orders).
The initial positioning is simple but crucial—it provides a structured starting point before more advanced adjustments (like reducing edge crossings or compacting the layout) are applied. Group nodes follow the same logic, ensuring they align with their children. This method ensures a readable, organized foundation for further refinement.
4. Force-Directed Simulation with Constraints :
- Spring Forces: Attracts connected nodes to maintain desired edge lengths.
- Repulsion Forces: Pushes nodes apart to prevent overlaps.
- Group Constraints: Ensures child nodes stay near their parent groups.
- Cooling Factor: Gradually reduces movement to stabilize the layout.
5. Incremental Refinement :
- Overlap Resolution: Iteratively adjusts node positions to eliminate overlaps.
- Edge Routing: Computes smooth paths for edges, including curved paths for parallel edges and self-loops.
- Group Boundary Adjustment: Dynamically resizes group containers to fit nested elements.
6. Adjusting the Final Layout :
This step takes the calculated node positions and applies them to the visual elements of the graph. Nodes are placed at their assigned (x, y) coordinates—regular nodes are positioned directly, while group nodes (clusters) are rendered as containers that may include other nodes. Edges (connections between nodes) are drawn based on their start and end points, ensuring they follow the structured layout.
The adjustment phase bridges the mathematical layout with the actual rendering, updating the SVG or canvas elements to reflect the computed positions. This ensures that the graph is not only logically organized but also visually coherent, with proper spacing, alignment, and connections. The result is a clean, readable diagram ready for display.

View File

@@ -1,186 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/ipsepcola/overview.md](../../../packages/mermaid/src/docs/layouts/ipsepcola/overview.md).
## IPSEPCOLA Documentation :
IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs
## Introduction :
IPSep-CoLa (Incremental Procedure for Separation Constraint Layout) is an advanced graph layout algorithm designed to handle complex diagrams with separation constraints, such as grouped nodes, edge labels, and hierarchical structures. Unlike traditional force-directed algorithms, IPSep-CoLa incrementally refines node positions while enforcing geometric constraints to prevent overlaps, maintain group cohesion, and optimize edge routing.
The algorithm is particularly effective for visualizing nested and clustered graphs, where maintaining clear separation between elements is crucial. It combines techniques from force-directed layout, constraint satisfaction, and incremental refinement to produce readable and aesthetically pleasing diagrams.
## How IPSep-CoLa Works :
IPSep-CoLa follows a multi-stage process to compute a well-structured layout:
1. Graph Preprocessing :
Cycle Removal: Detects and temporarily removes cyclic dependencies to enable proper layering.
Layer Assignment: Assigns nodes to hierarchical layers using topological sorting.
Node Ordering: Uses the barycenter heuristic to minimize edge crossings within layers.
2. Force-Directed Simulation with Constraints :
Spring Forces: Attracts connected nodes to maintain desired edge lengths.
Repulsion Forces: Pushes nodes apart to prevent overlaps.
Group Constraints: Ensures child nodes stay near their parent groups.
Cooling Factor: Gradually reduces movement to stabilize the layout.
3. Incremental Refinement :
Overlap Resolution: Iteratively adjusts node positions to eliminate overlaps.
Edge Routing: Computes smooth paths for edges, including curved paths for parallel edges and self-loops.
Group Boundary Adjustment: Dynamically resizes group containers to fit nested elements.
## Key Features :
1. Group-Aware Layout: Maintains separation between nested structures.
2. Edge Label Placement: Uses edge labels as virtual nodes and automatically positions labels inside their parent groups.
3. Stable Convergence: Uses cooling factors and incremental updates for smooth refinement.
4. Support for Self-Loops & Parallel Edges: Avoids visual clutter with intelligent edge routing.
## Use Cases :
1. Hierarchical Diagrams (org charts, flowcharts, decision trees)
2. Network Visualization (dependency graphs, data pipelines)
3. Interactive Graph Editors (real-time layout adjustments)
4. Clustered Data Visualization (UML diagrams, biological networks)
## **Examples**
### **Example 1**
```
---
config:
layout: ipsepCola
---
flowchart TD
CEO --> MKT["Marketing Head"]
CEO --> ENG["Engineering Head"]
ENG --> DEV["Developer"]
ENG --> QA["QA Tester"]
```
### **Example 2**
```
---
config:
layout: ipsepCola
---
flowchart TD
Start["Start"] --> Red{"Is it red?"}
Red -- Yes --> Round{"Is it round?"}
Red -- No --> NotApple["❌ Not an Apple"]
Round -- Yes --> Apple["✅ It's an Apple"]
Round -- No --> NotApple2["❌ Not an Apple"]
```
### **Example 3**
```
---
config:
layout: ipsepCola
---
flowchart TD
A[Module A] --> B[Module B]
A --> C[Module C]
B --> D[Module D]
C --> D
D --> E[Module E]
```
### **Example 4**
```
---
config:
layout: ipsepCola
---
flowchart TD
Source1["📦 Raw Data (CSV)"]
Source2["🌐 API Data"]
Source1 --> Clean["🧹 Clean & Format"]
Source2 --> Clean
Clean --> Transform["🔄 Transform Data"]
Transform --> Load["📥 Load into Data Warehouse"]
Load --> BI["📊 BI Dashboard"]
```
### **Example 5**
```
---
config:
layout: ipsepCola
---
classDiagram
class Person {
-String name
-int age
+greet(): void
}
class Employee {
-int employeeId
+calculateSalary(): float
}
class Manager {
-String department
+assignTask(): void
}
Person <|-- Employee
Employee <|-- Manager
```
### **Example 6**
```
---
config:
layout: ipsepCola
---
flowchart TD
Sunlight["☀️ Sunlight"] --> Leaf["🌿 Leaf"]
Leaf --> Glucose["🍬 Glucose"]
Leaf --> Oxygen["💨 Oxygen"]
```
### **Example 7**
```
---
config:
layout: ipsepCola
---
flowchart TD
Internet["🌐 Internet"] --> Router["📡 Router"]
Router --> Server1["🖥️ Server A"]
Router --> Server2["🖥️ Server B"]
Router --> Laptop["💻 Laptop"]
%% New device joins
Router --> Mobile["📱 Mobile"]
```
## Limitations :
1. Computational Cost: More iterations may be needed for large graphs (>1000 nodes).
2. Parameter Tuning: Requires adjustments for different graph types.
3. Non-Determinism: Small variations may occur between runs due to force simulation.
## Conclusion :
IPSep-CoLa provides a robust solution for constraint-based graph layout, particularly for structured and clustered diagrams. By combining incremental refinement with separation constraints, it achieves readable and well-organized visualizations. Future improvements could include GPU acceleration and adaptive parameter tuning for large-scale graphs.

View File

@@ -17,7 +17,7 @@ This diagram type is particularly useful for developers, network engineers, educ
## Syntax
```
packet-beta
packet
start: "Block name" %% Single-bit block
start-end: "Block name" %% Multi-bit blocks
... More Fields ...
@@ -28,7 +28,7 @@ start-end: "Block name" %% Multi-bit blocks
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
```
packet-beta
packet
+1: "Block name" %% Single-bit block
+8: "Block name" %% 8-bit block
9-15: "Manually set start and end, it's fine to mix and match"
@@ -41,7 +41,7 @@ packet-beta
---
title: "TCP Packet"
---
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
@@ -65,7 +65,7 @@ packet-beta
---
title: "TCP Packet"
---
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
@@ -86,7 +86,7 @@ packet-beta
```
```mermaid-example
packet-beta
packet
title UDP Packet
+16: "Source Port"
+16: "Destination Port"
@@ -96,7 +96,7 @@ title UDP Packet
```
```mermaid
packet-beta
packet
title UDP Packet
+16: "Source Port"
+16: "Destination Port"
@@ -144,7 +144,7 @@ config:
packet:
startByteColor: red
---
packet-beta
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"

353
docs/syntax/treemap.md Normal file
View File

@@ -0,0 +1,353 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/treemap.md](../../packages/mermaid/src/docs/syntax/treemap.md).
# Treemap Diagram
> A treemap diagram displays hierarchical data as a set of nested rectangles. Each branch of the tree is represented by a rectangle, which is then tiled with smaller rectangles representing sub-branches.
> **Warning**
> This is a new diagram type in Mermaid. Its syntax may evolve in future versions.
## Introduction
Treemap diagrams are an effective way to visualize hierarchical data and show proportions between categories and subcategories. The size of each rectangle is proportional to the value it represents, making it easy to compare different parts of a hierarchy.
Treemap diagrams are particularly useful for:
- Visualizing hierarchical data structures
- Comparing proportions between categories
- Displaying large amounts of hierarchical data in a limited space
- Identifying patterns and outliers in hierarchical data
## Syntax
```
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2"
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20
"Leaf 2.2": 25
```
### Node Definition
Nodes in a treemap are defined using the following syntax:
- **Section/Parent nodes**: Defined with quoted text `"Section Name"`
- **Leaf nodes with values**: Defined with quoted text followed by a colon and value `"Leaf Name": value`
- **Hierarchy**: Created using indentation (spaces or tabs)
- **Styling**: Nodes can be styled using the `:::class` syntax
## Examples
### Basic Treemap
```mermaid-example
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
### Hierarchical Treemap
```mermaid-example
treemap-beta
"Products"
"Electronics"
"Phones": 50
"Computers": 30
"Accessories": 20
"Clothing"
"Men's": 40
"Women's": 40
```
```mermaid
treemap-beta
"Products"
"Electronics"
"Phones": 50
"Computers": 30
"Accessories": 20
"Clothing"
"Men's": 40
"Women's": 40
```
### Treemap with Styling
```mermaid-example
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
```
```mermaid
treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2":::class1
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20:::class1
"Leaf 2.2": 25
"Leaf 2.3": 12
classDef class1 fill:red,color:blue,stroke:#FFD600;
```
## Styling and Configuration
Treemap diagrams can be customized using Mermaid's styling and configuration options.
### Using classDef for Styling
You can define custom styles for nodes using the `classDef` syntax, which is a standard feature across many Mermaid diagram types:
```mermaid-example
treemap-beta
"Main"
"A": 20
"B":::important
"B1": 10
"B2": 15
"C": 5
classDef important fill:#f96,stroke:#333,stroke-width:2px;
```
```mermaid
treemap-beta
"Main"
"A": 20
"B":::important
"B1": 10
"B2": 15
"C": 5
classDef important fill:#f96,stroke:#333,stroke-width:2px;
```
### Theme Configuration
You can customize the colors of your treemap using the theme configuration:
```mermaid-example
---
config:
theme: 'forest'
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
---
config:
theme: 'forest'
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
### Diagram Padding
You can adjust the padding around the treemap diagram using the `diagramPadding` configuration option:
```mermaid-example
---
config:
treemap:
diagramPadding: 200
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
```mermaid
---
config:
treemap:
diagramPadding: 200
---
treemap-beta
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"Item B2": 25
```
## Configuration Options
The treemap diagram supports the following configuration options:
| Option | Description | Default |
| -------------- | --------------------------------------------------------------------------- | ------- |
| useMaxWidth | When true, the diagram width is set to 100% and scales with available space | true |
| padding | Internal padding between nodes | 10 |
| diagramPadding | Padding around the entire diagram | 8 |
| showValues | Whether to show values in the treemap | true |
| nodeWidth | Width of nodes | 100 |
| nodeHeight | Height of nodes | 40 |
| borderWidth | Width of borders | 1 |
| valueFontSize | Font size for values | 12 |
| labelFontSize | Font size for labels | 14 |
| valueFormat | Format for values (see Value Formatting section) | ',' |
## Advanced Features
### Value Formatting
Values in treemap diagrams can be formatted to display in different ways using the `valueFormat` configuration option. This option primarily uses [D3's format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats.
Some common format patterns:
- `,` - Thousands separator (default)
- `$` - Add dollar sign
- `.1f` - Show one decimal place
- `.1%` - Show as percentage with one decimal place
- `$0,0` - Dollar sign with thousands separator
- `$.2f` - Dollar sign with 2 decimal places
- `$,.2f` - Dollar sign with thousands separator and 2 decimal places
The treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options.
Example with currency formatting:
```mermaid-example
---
config:
treemap:
valueFormat: '$0,0'
---
treemap-beta
"Budget"
"Operations"
"Salaries": 700000
"Equipment": 200000
"Supplies": 100000
"Marketing"
"Advertising": 400000
"Events": 100000
```
```mermaid
---
config:
treemap:
valueFormat: '$0,0'
---
treemap-beta
"Budget"
"Operations"
"Salaries": 700000
"Equipment": 200000
"Supplies": 100000
"Marketing"
"Advertising": 400000
"Events": 100000
```
Example with percentage formatting:
```mermaid-example
---
config:
treemap:
valueFormat: '$.1%'
---
treemap-beta
"Market Share"
"Company A": 0.35
"Company B": 0.25
"Company C": 0.15
"Others": 0.25
```
```mermaid
---
config:
treemap:
valueFormat: '$.1%'
---
treemap-beta
"Market Share"
"Company A": 0.35
"Company B": 0.25
"Company C": 0.15
"Others": 0.25
```
## Common Use Cases
Treemap diagrams are commonly used for:
1. **Financial Data**: Visualizing budget allocations, market shares, or portfolio compositions
2. **File System Analysis**: Showing disk space usage by folders and files
3. **Population Demographics**: Displaying population distribution across regions and subregions
4. **Product Hierarchies**: Visualizing product categories and their sales volumes
5. **Organizational Structures**: Representing departments and team sizes in a company
## Limitations
- Treemap diagrams work best when the data has a natural hierarchy
- Very small values may be difficult to see or label in a treemap diagram
- Deep hierarchies (many levels) can be challenging to represent clearly
- Treemap diagrams are not well suited for representing data with negative values
## Related Diagrams
If treemap diagrams don't suit your needs, consider these alternatives:
- [**Pie Charts**](./pie.md): For simple proportion comparisons without hierarchy
- **Sunburst Diagrams**: For hierarchical data with a radial layout (yet to be released in Mermaid).
- [**Sankey Diagrams**](./sankey.md): For flow-based hierarchical data
## Notes
The treemap diagram implementation in Mermaid is designed to be simple to use while providing powerful visualization capabilities. As this is a newer diagram type, feedback and feature requests are welcome through the Mermaid GitHub repository.

View File

@@ -37,7 +37,7 @@
"e2e:coverage": "start-server-and-test dev:coverage http://localhost:9000/ cypress",
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
"coverage:merge": "tsx scripts/coverage.ts",
"coverage": "pnpm test:coverage --run && pnpm coverage:merge",
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
"ci": "vitest run",
"test": "pnpm lint && vitest run",
"test:watch": "vitest --watch",
@@ -64,7 +64,7 @@
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.44.9",
"@argos-ci/cypress": "^4.0.3",
"@argos-ci/cypress": "^5.0.2",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.27.12",
"@cspell/eslint-plugin": "^8.19.3",
@@ -83,13 +83,13 @@
"@vitest/spy": "^3.0.6",
"@vitest/ui": "^3.0.6",
"ajv": "^8.17.1",
"chokidar": "^4.0.3",
"chokidar": "3.6.0",
"concurrently": "^9.1.2",
"cors": "^2.8.5",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
"cspell": "^8.6.1",
"cypress": "^14.0.3",
"cspell": "^9.1.3",
"cypress": "^14.5.1",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.14",
"esbuild": "^0.25.0",
@@ -109,25 +109,25 @@
"globals": "^16.0.0",
"globby": "^14.0.2",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest": "^30.0.4",
"jison": "^0.4.18",
"js-yaml": "^4.1.0",
"jsdom": "^26.0.0",
"jsdom": "^26.1.0",
"langium-cli": "3.3.0",
"lint-staged": "^15.2.11",
"lint-staged": "^16.1.2",
"markdown-table": "^3.0.4",
"nyc": "^17.1.0",
"path-browserify": "^1.0.1",
"prettier": "^3.5.2",
"prettier-plugin-jsdoc": "^1.3.2",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^5.14.0",
"rollup-plugin-visualizer": "^6.0.3",
"start-server-and-test": "^2.0.10",
"tslib": "^2.8.1",
"tsx": "^4.7.3",
"typescript": "~5.7.3",
"typescript-eslint": "^8.32.0",
"vite": "^6.1.1",
"vite": "^7.0.3",
"vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.0.6"
},
@@ -139,8 +139,13 @@
"roughjs": "patches/roughjs.patch"
},
"onlyBuiltDependencies": [
"canvas",
"cypress",
"esbuild"
],
"ignoredBuiltDependencies": [
"sharp",
"vue-demi"
]
}
}

View File

@@ -0,0 +1,32 @@
{
"name": "@mermaid-js/examples",
"version": "0.0.1-beta.1",
"description": "Mermaid examples package",
"author": "Sidharth Vinod",
"type": "module",
"module": "./dist/mermaid-examples.core.mjs",
"types": "./dist/mermaid.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/mermaid-examples.core.mjs",
"default": "./dist/mermaid-examples.core.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"clean": "rimraf dist"
},
"dependencies": {},
"devDependencies": {
"mermaid": "workspace:*"
},
"peerDependencies": {
"mermaid": "workspace:~"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1,34 @@
import mermaid from 'mermaid';
import { diagramData } from './index.js';
describe('examples', () => {
beforeAll(async () => {
// To trigger the diagram registration
await mermaid.registerExternalDiagrams([]);
});
it('should have examples for each diagrams', () => {
const skippedDiagrams = [
// These diagrams have no examples
'error',
'info',
'---',
// These diagrams have v2 versions, with examples
'class',
'graph',
'flowchart-elk',
'flowchart',
'state',
];
const diagrams = mermaid
.getRegisteredDiagramsMetadata()
.filter((d) => !skippedDiagrams.includes(d.id));
expect(diagrams.length).toBeGreaterThan(0);
for (const diagram of diagrams) {
const data = diagramData.find((d) => d.id === diagram.id)!;
expect(data, `Example for ${diagram.id} is not defined`).toBeDefined();
expect(data.examples.length).toBeGreaterThan(0);
expect(data.examples.filter((e) => e.isDefault).length).toBe(1);
}
});
});

View File

@@ -0,0 +1,24 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'architecture',
name: 'Architecture Diagram',
description: 'Visualize system architecture and components',
examples: [
{
title: 'Basic System Architecture',
isDefault: true,
code: `architecture-beta
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,27 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'block',
name: 'Block Diagram',
description: 'Create block-based visualizations with beta styling',
examples: [
{
title: 'Basic Block Layout',
isDefault: true,
code: `block-beta
columns 1
db(("DB"))
blockArrowId6<["&nbsp;&nbsp;&nbsp;"]>(down)
block:ID
A
B["A wide one in the middle"]
C
end
space
D
ID --> D
C --> D
style B fill:#969,stroke:#333,stroke-width:4px`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,47 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'c4',
name: 'C4 Diagram',
description:
'Visualize software architecture using the C4 model (Context, Container, Component, Code)',
examples: [
{
title: 'Internet Banking System Context',
isDefault: true,
code: `C4Context
title System Context diagram for Internet Banking System
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
Person(customerB, "Banking Customer B")
Person_Ext(customerC, "Banking Customer C", "desc")
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
System_Boundary(b2, "BankBoundary2") {
System(SystemA, "Banking System A")
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
}
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
Boundary(b3, "BankBoundary3", "boundary") {
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
}
}
}
BiRel(customerA, SystemAA, "Uses")
BiRel(SystemAA, SystemE, "Uses")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
Rel(SystemC, customerA, "Sends e-mails to")`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,34 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'classDiagram',
name: 'Class Diagram',
description: 'Visualize class structures and relationships in object-oriented programming',
examples: [
{
title: 'Basic Class Inheritance',
isDefault: true,
code: `classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,36 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'er',
name: 'Entity Relationship Diagram',
description: 'Visualize database schemas and relationships between entities',
examples: [
{
title: 'Basic ER Schema',
isDefault: true,
code: `erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ ORDER_ITEM : contains
PRODUCT ||--o{ ORDER_ITEM : includes
CUSTOMER {
string id
string name
string email
}
ORDER {
string id
date orderDate
string status
}
PRODUCT {
string id
string name
float price
}
ORDER_ITEM {
int quantity
float price
}`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,19 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'flowchart-v2',
name: 'Flowchart',
description: 'Visualize flowcharts and directed graphs',
examples: [
{
title: 'Basic Flowchart',
isDefault: true,
code: `flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,22 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'gantt',
name: 'Gantt Chart',
description: 'Visualize project schedules and timelines',
examples: [
{
title: 'Basic Project Timeline',
isDefault: true,
code: `gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,28 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'gitGraph',
name: 'Git Graph',
description: 'Visualize Git repository history and branch relationships',
examples: [
{
title: 'Basic Git Flow',
isDefault: true,
code: `gitGraph
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
branch feature
checkout feature
commit
commit
checkout main
merge feature`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,37 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'kanban',
name: 'Kanban Diagram',
description: 'Visualize work items in a Kanban board',
examples: [
{
title: 'Kanban Diagram',
isDefault: true,
code: `---
config:
kanban:
ticketBaseUrl: 'https://github.com/mermaid-js/mermaid/issues/#TICKET#'
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
[In progress]
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: 2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: 2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: 2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,32 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'mindmap',
name: 'Mindmap',
description: 'Visualize ideas and concepts in a tree-like structure',
examples: [
{
title: 'Basic Mindmap',
isDefault: true,
code: `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`,
},
],
} satisfies DiagramMetadata;
// cspell:ignore Buzan

View File

@@ -0,0 +1,34 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'packet',
name: 'Packet Diagram',
description: 'Visualize packet data and network traffic',
examples: [
{
title: 'TCP Packet',
isDefault: true,
code: `---
title: "TCP Packet"
---
packet
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-255: "Data (variable length)"`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,17 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'pie',
name: 'Pie Chart',
description: 'Visualize data as proportional segments of a circle',
examples: [
{
title: 'Basic Pie Chart',
isDefault: true,
code: `pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,27 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'quadrantChart',
name: 'Quadrant Chart',
description: 'Visualize items in a 2x2 matrix based on two variables',
examples: [
{
title: 'Product Positioning',
isDefault: true,
code: `quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,25 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'radar',
name: 'Radar Diagram',
description: 'Visualize data in a radial format',
examples: [
{
title: 'Student Grades',
isDefault: true,
code: `---
title: "Grades"
---
radar-beta
axis m["Math"], s["Science"], e["English"]
axis h["History"], g["Geography"], a["Art"]
curve a["Alice"]{85, 90, 80, 70, 75, 90}
curve b["Bob"]{70, 75, 85, 80, 90, 85}
max 100
min 0
`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,27 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'requirement',
name: 'Requirement Diagram',
description: 'Visualize system requirements and their relationships',
examples: [
{
title: 'Basic Requirements',
code: `requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req`,
isDefault: true,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,88 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'sankey',
name: 'Sankey Diagram',
description: 'Visualize flow quantities between different stages or processes',
examples: [
{
title: 'Energy Flow',
isDefault: true,
code: `---
config:
sankey:
showValues: false
---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
Bio-conversion,Liquid,0.597
Bio-conversion,Losses,26.862
Bio-conversion,Solid,280.322
Bio-conversion,Gas,81.144
Biofuel imports,Liquid,35
Biomass imports,Solid,35
Coal imports,Coal,11.606
Coal reserves,Coal,63.965
Coal,Solid,75.571
District heating,Industry,10.639
District heating,Heating and cooling - commercial,22.505
District heating,Heating and cooling - homes,46.184
Electricity grid,Over generation / exports,104.453
Electricity grid,Heating and cooling - homes,113.726
Electricity grid,H2 conversion,27.14
Electricity grid,Industry,342.165
Electricity grid,Road transport,37.797
Electricity grid,Agriculture,4.412
Electricity grid,Heating and cooling - commercial,40.858
Electricity grid,Losses,56.691
Electricity grid,Rail transport,7.863
Electricity grid,Lighting & appliances - commercial,90.008
Electricity grid,Lighting & appliances - homes,93.494
Gas imports,NGas,40.719
Gas reserves,NGas,82.233
Gas,Heating and cooling - commercial,0.129
Gas,Losses,1.401
Gas,Thermal generation,151.891
Gas,Agriculture,2.096
Gas,Industry,48.58
Geothermal,Electricity grid,7.013
H2 conversion,H2,20.897
H2 conversion,Losses,6.242
H2,Road transport,20.897
Hydro,Electricity grid,6.995
Liquid,Industry,121.066
Liquid,International shipping,128.69
Liquid,Road transport,135.835
Liquid,Domestic aviation,14.458
Liquid,International aviation,206.267
Liquid,Agriculture,3.64
Liquid,National navigation,33.218
Liquid,Rail transport,4.413
Marine algae,Bio-conversion,4.375
NGas,Gas,122.952
Nuclear,Thermal generation,839.978
Oil imports,Oil,504.287
Oil reserves,Oil,107.703
Oil,Liquid,611.99
Other waste,Solid,56.587
Other waste,Bio-conversion,77.81
Pumped heat,Heating and cooling - homes,193.026
Pumped heat,Heating and cooling - commercial,70.672
Solar PV,Electricity grid,59.901
Solar Thermal,Heating and cooling - homes,19.263
Solar,Solar Thermal,19.263
Solar,Solar PV,59.901
Solid,Agriculture,0.882
Solid,Thermal generation,400.12
Solid,Industry,46.477
Thermal generation,Electricity grid,525.531
Thermal generation,Losses,787.129
Thermal generation,District heating,79.329
Tidal,Electricity grid,9.452
UK land based bioenergy,Bio-conversion,182.01
Wave,Electricity grid,19.013
Wind,Electricity grid,289.366`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,18 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'sequence',
name: 'Sequence Diagram',
description: 'Visualize interactions between objects over time',
examples: [
{
title: 'Basic Sequence',
isDefault: true,
code: `sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,20 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'stateDiagram',
name: 'State Diagram',
description: 'Visualize the states and transitions of a system',
examples: [
{
title: 'Basic State Diagram',
code: `stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`,
isDefault: true,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,20 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'timeline',
name: 'Timeline Diagram',
description: 'Visualize events and milestones in chronological order',
examples: [
{
title: 'Project Timeline',
isDefault: true,
code: `timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook
: Google
2005 : YouTube
2006 : Twitter`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,21 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'treemap',
name: 'Treemap',
description: 'Visualize hierarchical data as nested rectangles',
examples: [
{
title: 'Treemap',
isDefault: true,
code: `treemap-beta
"Section 1"
"Leaf 1.1": 12
"Section 1.2"
"Leaf 1.2.1": 12
"Section 2"
"Leaf 2.1": 20
"Leaf 2.2": 25`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,22 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'journey',
name: 'User Journey Diagram',
description: 'Visualize user interactions and experiences with a system',
examples: [
{
title: 'My Working Day',
isDefault: true,
code: `journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,19 @@
import type { DiagramMetadata } from '../types.js';
export default {
id: 'xychart',
name: 'XY Chart',
description: 'Create scatter plots and line charts with customizable axes',
examples: [
{
title: 'Sales Revenue',
isDefault: true,
code: `xychart-beta
title "Sales Revenue"
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]`,
},
],
} satisfies DiagramMetadata;

View File

@@ -0,0 +1,48 @@
import type { DiagramMetadata } from './types.js';
import flowChart from './examples/flowchart.js';
import c4 from './examples/c4.js';
import kanban from './examples/kanban.js';
import classDiagram from './examples/class.js';
import sequenceDiagram from './examples/sequence.js';
import pieDiagram from './examples/pie.js';
import userJourneyDiagram from './examples/user-journey.js';
import mindmapDiagram from './examples/mindmap.js';
import requirementDiagram from './examples/requirement.js';
import radarDiagram from './examples/radar.js';
import stateDiagram from './examples/state.js';
import erDiagram from './examples/er.js';
import gitDiagram from './examples/git.js';
import architectureDiagram from './examples/architecture.js';
import xychartDiagram from './examples/xychart.js';
import sankeyDiagram from './examples/sankey.js';
import ganttDiagram from './examples/gantt.js';
import timelineDiagram from './examples/timeline.js';
import quadrantChart from './examples/quadrant-chart.js';
import packetDiagram from './examples/packet.js';
import blockDiagram from './examples/block.js';
import treemapDiagram from './examples/treemap.js';
export const diagramData: DiagramMetadata[] = [
flowChart,
c4,
kanban,
classDiagram,
sequenceDiagram,
pieDiagram,
userJourneyDiagram,
mindmapDiagram,
requirementDiagram,
radarDiagram,
stateDiagram,
erDiagram,
gitDiagram,
architectureDiagram,
xychartDiagram,
sankeyDiagram,
ganttDiagram,
timelineDiagram,
quadrantChart,
packetDiagram,
blockDiagram,
treemapDiagram,
];

View File

@@ -0,0 +1,12 @@
export interface Example {
title: string;
code: string;
isDefault?: boolean;
}
export interface DiagramMetadata {
id: string;
name: string;
description: string;
examples: Example[];
}

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "Node16",
"moduleResolution": "Node16"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,5 +1,25 @@
# mermaid
## 11.8.1
### Patch Changes
- Updated dependencies [[`0da2922`](https://github.com/mermaid-js/mermaid/commit/0da2922ee7f47959e324ec10d3d21ee70594f557)]:
- @mermaid-js/parser@0.6.1
## 11.8.0
### Minor Changes
- [#6590](https://github.com/mermaid-js/mermaid/pull/6590) [`f338802`](https://github.com/mermaid-js/mermaid/commit/f338802642cdecf5b7ed6c19a20cf2a81effbbee) Thanks [@knsv](https://github.com/knsv)! - Adding support for the new diagram type nested treemap
### Patch Changes
- [#6707](https://github.com/mermaid-js/mermaid/pull/6707) [`592c5bb`](https://github.com/mermaid-js/mermaid/commit/592c5bb880c3b942710a2878d386bcb3eb35c137) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Log a warning when duplicate commit IDs are encountered in gitGraph to help identify and debug rendering issues caused by non-unique IDs.
- Updated dependencies [[`f338802`](https://github.com/mermaid-js/mermaid/commit/f338802642cdecf5b7ed6c19a20cf2a81effbbee)]:
- @mermaid-js/parser@0.6.0
## 11.7.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.7.0",
"version": "11.8.1",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -82,7 +82,7 @@
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"marked": "^15.0.7",
"marked": "^16.0.0",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
"ts-dedent": "^2.2.0",
@@ -105,13 +105,14 @@
"@types/stylis": "^4.2.7",
"@types/uuid": "^10.0.0",
"ajv": "^8.17.1",
"chokidar": "^4.0.3",
"canvas": "^3.1.0",
"chokidar": "3.6.0",
"concurrently": "^9.1.2",
"csstree-validator": "^4.0.1",
"globby": "^14.0.2",
"jison": "^0.4.18",
"js-base64": "^3.7.7",
"jsdom": "^26.0.0",
"jsdom": "^26.1.0",
"json-schema-to-typescript": "^15.0.4",
"micromatch": "^4.0.8",
"path-browserify": "^1.0.1",

View File

@@ -1,28 +1,25 @@
import { MockedD3 } from './tests/MockedD3.js';
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
import type { D3Element } from './types.js';
import { addSVGa11yTitleDescription, setA11yDiagramInfo } from './accessibility.js';
import { ensureNodeFromSelector, jsdomIt } from './tests/util.js';
import { expect } from 'vitest';
describe('accessibility', () => {
const fauxSvgNode: MockedD3 = new MockedD3();
describe('setA11yDiagramInfo', () => {
it('should set svg element role to "graphics-document document"', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document');
jsdomIt('should set svg element role to "graphics-document document"', ({ svg }) => {
setA11yDiagramInfo(svg, 'flowchart');
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('role')).toBe('graphics-document document');
});
it('should set aria-roledescription to the diagram type', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart');
jsdomIt('should set aria-roledescription to the diagram type', ({ svg }) => {
setA11yDiagramInfo(svg, 'flowchart');
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-roledescription')).toBe('flowchart');
});
it('should not set aria-roledescription if the diagram type is empty', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
setA11yDiagramInfo(fauxSvgNode, '');
expect(svgAttrSpy).toHaveBeenCalledTimes(1);
expect(svgAttrSpy).toHaveBeenCalledWith('role', expect.anything()); // only called to set the role
jsdomIt('should not set aria-roledescription if the diagram type is empty', ({ svg }) => {
setA11yDiagramInfo(svg, '');
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-roledescription')).toBeNull();
});
});
@@ -39,115 +36,78 @@ describe('accessibility', () => {
expect(noInsertAttrSpy).not.toHaveBeenCalled();
});
// convenience functions to DRY up the spec
function expectAriaLabelledByItTitleId(
svgD3Node: D3Element,
title: string | undefined,
desc: string | undefined,
givenId: string
): void {
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
}
function expectAriaDescribedByItDescId(
svgD3Node: D3Element,
title: string | undefined,
desc: string | undefined,
givenId: string
): void {
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
}
function a11yTitleTagInserted(
svgD3Node: D3Element,
title: string | undefined,
desc: string | undefined,
givenId: string,
callNumber: number
): void {
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
}
function a11yDescTagInserted(
svgD3Node: D3Element,
title: string | undefined,
desc: string | undefined,
givenId: string,
callNumber: number
): void {
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
}
function a11yTagInserted(
_svgD3Node: D3Element,
title: string | undefined,
desc: string | undefined,
givenId: string,
callNumber: number,
expectedPrefix: string,
expectedText: string | undefined
): void {
const fauxInsertedD3: MockedD3 = new MockedD3();
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
expect(svginsertpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText);
}
describe('with a11y title', () => {
const a11yTitle = 'a11y title';
describe('with a11y description', () => {
const a11yDesc = 'a11y description';
it('should set aria-labelledby to the title id inserted as a child', () => {
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
jsdomIt('should set aria-labelledby to the title id inserted as a child', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-labelledby')).toBe(`chart-title-${givenId}`);
});
it('should set aria-describedby to the description id inserted as a child', () => {
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
jsdomIt(
'should set aria-describedby to the description id inserted as a child',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-describedby')).toBe(`chart-desc-${givenId}`);
}
);
it('should insert title tag as the first child with the text set to the accTitle given', () => {
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
});
jsdomIt(
'should insert title tag as the first child with the text set to the accTitle given',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const titleNode = ensureNodeFromSelector('title', svgNode);
expect(titleNode?.innerHTML).toBe(a11yTitle);
}
);
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
jsdomIt(
'should insert desc tag as the 2nd child with the text set to accDescription given',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const descNode = ensureNodeFromSelector('desc', svgNode);
expect(descNode?.innerHTML).toBe(a11yDesc);
}
);
});
describe(`without a11y description`, () => {
describe(`without a11y description`, {}, () => {
const a11yDesc = undefined;
it('should set aria-labelledby to the title id inserted as a child', () => {
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
jsdomIt('should set aria-labelledby to the title id inserted as a child', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-labelledby')).toBe(`chart-title-${givenId}`);
});
it('should not set aria-describedby', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
jsdomIt('should not set aria-describedby', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-describedby')).toBeNull();
});
it('should insert title tag as the first child with the text set to the accTitle given', () => {
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
jsdomIt(
'should insert title tag as the first child with the text set to the accTitle given',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const titleNode = ensureNodeFromSelector('title', svgNode);
expect(titleNode?.innerHTML).toBe(a11yTitle);
}
);
it('should not insert description tag', () => {
const fauxTitle: MockedD3 = new MockedD3();
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
jsdomIt('should not insert description tag', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const descNode = svgNode.querySelector('desc');
expect(descNode).toBeNull();
});
});
});
@@ -158,55 +118,66 @@ describe('accessibility', () => {
describe('with a11y description', () => {
const a11yDesc = 'a11y description';
it('should not set aria-labelledby', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
jsdomIt('should not set aria-labelledby', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-labelledby')).toBeNull();
});
it('should not insert title tag', () => {
const fauxTitle: MockedD3 = new MockedD3();
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
jsdomIt('should not insert title tag', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const titleNode = svgNode.querySelector('title');
expect(titleNode).toBeNull();
});
it('should set aria-describedby to the description id inserted as a child', () => {
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
jsdomIt(
'should set aria-describedby to the description id inserted as a child',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-describedby')).toBe(`chart-desc-${givenId}`);
}
);
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
jsdomIt(
'should insert desc tag as the 2nd child with the text set to accDescription given',
({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const descNode = ensureNodeFromSelector('desc', svgNode);
expect(descNode?.innerHTML).toBe(a11yDesc);
}
);
});
describe('without a11y description', () => {
const a11yDesc = undefined;
it('should not set aria-labelledby', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
jsdomIt('should not set aria-labelledby', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-labelledby')).toBeNull();
});
it('should not set aria-describedby', () => {
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
jsdomIt('should not set aria-describedby', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
expect(svgNode.getAttribute('aria-describedby')).toBeNull();
});
it('should not insert title tag', () => {
const fauxTitle: MockedD3 = new MockedD3();
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
jsdomIt('should not insert title tag', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const titleNode = svgNode.querySelector('title');
expect(titleNode).toBeNull();
});
it('should not insert description tag', () => {
const fauxDesc: MockedD3 = new MockedD3();
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
jsdomIt('should not insert description tag', ({ svg }) => {
addSVGa11yTitleDescription(svg, a11yTitle, a11yDesc, givenId);
const svgNode = ensureNodeFromSelector('svg');
const descNode = svgNode.querySelector('desc');
expect(descNode).toBeNull();
});
});
});

View File

@@ -262,6 +262,18 @@ const config: RequiredDeep<MermaidConfig> = {
radar: {
...defaultConfigJson.radar,
},
treemap: {
useMaxWidth: true,
padding: 10,
diagramPadding: 8,
showValues: true,
nodeWidth: 100,
nodeHeight: 40,
borderWidth: 1,
valueFontSize: 12,
labelFontSize: 14,
valueFormat: ',',
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -27,6 +27,7 @@ import block from '../diagrams/block/blockDetector.js';
import architecture from '../diagrams/architecture/architectureDetector.js';
import { registerLazyLoadedDiagrams } from './detectType.js';
import { registerDiagram } from './diagramAPI.js';
import { treemap } from '../diagrams/treemap/detector.js';
import '../type.d.ts';
let hasLoadedDiagrams = false;
@@ -99,6 +100,7 @@ export const addDiagrams = () => {
packet,
xychart,
block,
radar
radar,
treemap
);
};

View File

@@ -7,20 +7,21 @@ export const loadRegisteredDiagrams = async () => {
// Load all lazy loaded diagrams in parallel
const results = await Promise.allSettled(
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
if (loader) {
if (!loader) {
return;
}
try {
getDiagram(key);
} catch {
try {
getDiagram(key);
} catch {
try {
// Register diagram if it is not already registered
const { diagram, id } = await loader();
registerDiagram(id, diagram, detector);
} catch (err) {
// Remove failed diagram from detectors
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
delete detectors[key];
throw err;
}
// Register diagram if it is not already registered
const { diagram, id } = await loader();
registerDiagram(id, diagram, detector);
} catch (err) {
// Remove failed diagram from detectors
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
delete detectors[key];
throw err;
}
}
})

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type * as d3 from 'd3';
import type { SetRequired } from 'type-fest';
import type { SetOptional, SetRequired } from 'type-fest';
import type { Diagram } from '../Diagram.js';
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
@@ -91,17 +91,13 @@ export interface DiagramDefinition {
) => void;
}
export interface DetectorRecord {
detector: DiagramDetector;
loader?: DiagramLoader;
}
export interface ExternalDiagramDefinition {
id: string;
detector: DiagramDetector;
loader: DiagramLoader;
}
export type DetectorRecord = SetOptional<Omit<ExternalDiagramDefinition, 'id'>, 'loader'>;
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DiagramLoader = () => Promise<{ id: string; diagram: DiagramDefinition }>;

View File

@@ -379,6 +379,15 @@ function layoutArchitecture(
},
},
],
layout: {
name: 'grid',
boundingBox: {
x1: 0,
x2: 100,
y1: 0,
y2: 100,
},
},
});
// Remove element after layout
renderEl.remove();

View File

@@ -287,7 +287,7 @@ const setBlock = (block: Block) => {
blockDatabase.set(block.id, block);
};
const getLogger = () => console;
const getLogger = () => log;
/**
* Return all of the style classes

View File

@@ -1,4 +1,4 @@
import { rejects } from 'assert';
import { log } from '../../logger.js';
import { db } from './gitGraphAst.js';
import { parser } from './gitGraphParser.js';
@@ -1319,4 +1319,42 @@ describe('when parsing a gitGraph', function () {
}
});
});
it('should log a warning when two commits have the same ID', async () => {
const str = `gitGraph
commit id:"initial commit"
commit id:"work on first release"
commit id:"design freeze from here"
branch v1-rc
checkout v1-rc
commit id:"bugfix 1"
commit id:"bigfix 2" tag:"v1.0.1"
branch FORK-v1.0-MDR
checkout FORK-v1.0-MDR
commit id:"working on MDR"
checkout v1-rc
commit id:"minor design changes for MDR" tag:"v1.0.2"
checkout FORK-v1.0-MDR
merge v1-rc
checkout main
commit id:"new feature for v1.1…"
checkout FORK-v1.0-MDR
commit id:"working on MDR"
commit id:"finishing MDR"
branch v1.0-MDR
checkout v1.0-MDR
commit id:"brush up release" tag:"v1.0.2-MDR"
checkout v1-rc
commit id:"bugfix without MDR"
checkout main
commit id:"work on v1.1"
`;
const logWarnSpy = vi.spyOn(log, 'warn').mockImplementation(() => undefined);
await parser.parse(str);
expect(logWarnSpy).toHaveBeenCalledWith('Commit ID working on MDR already exists');
logWarnSpy.mockRestore();
});
});

View File

@@ -125,6 +125,9 @@ export const commit = function (commitDB: CommitDB) {
};
state.records.head = newCommit;
log.info('main branch', config.mainBranchName);
if (state.records.commits.has(newCommit.id)) {
log.warn(`Commit ID ${newCommit.id} already exists`);
}
state.records.commits.set(newCommit.id, newCommit);
state.records.branches.set(state.records.currBranch, newCommit.id);
log.debug('in pushCommit ' + newCommit.id);

View File

@@ -7,7 +7,7 @@ import type {
const id = 'packet';
const detector: DiagramDetector = (txt) => {
return /^\s*packet-beta/.test(txt);
return /^\s*packet(-beta)?/.test(txt);
};
const loader: DiagramLoader = async () => {

View File

@@ -15,8 +15,14 @@ describe('packet diagrams', () => {
expect(getPacket()).toMatchInlineSnapshot('[]');
});
it('should handle a packet definition', async () => {
const str = `packet`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getPacket()).toMatchInlineSnapshot('[]');
});
it('should handle diagram with data and title', async () => {
const str = `packet-beta
const str = `packet
title Packet diagram
accTitle: Packet accTitle
accDescr: Packet accDescription
@@ -41,7 +47,7 @@ describe('packet diagrams', () => {
});
it('should handle single bits', async () => {
const str = `packet-beta
const str = `packet
0-10: "test"
11: "single"
`;
@@ -67,7 +73,7 @@ describe('packet diagrams', () => {
});
it('should handle bit counts', async () => {
const str = `packet-beta
const str = `packet
+8: "byte"
+16: "word"
`;
@@ -93,7 +99,7 @@ describe('packet diagrams', () => {
});
it('should handle bit counts with bit or bits', async () => {
const str = `packet-beta
const str = `packet
+8: "byte"
+16: "word"
`;
@@ -119,7 +125,7 @@ describe('packet diagrams', () => {
});
it('should split into multiple rows', async () => {
const str = `packet-beta
const str = `packet
0-10: "test"
11-90: "multiple"
`;
@@ -161,7 +167,7 @@ describe('packet diagrams', () => {
});
it('should split into multiple rows when cut at exact length', async () => {
const str = `packet-beta
const str = `packet
0-16: "test"
17-63: "multiple"
`;
@@ -195,7 +201,7 @@ describe('packet diagrams', () => {
});
it('should throw error if numbers are not continuous', async () => {
const str = `packet-beta
const str = `packet
0-16: "test"
18-20: "error"
`;
@@ -205,7 +211,7 @@ describe('packet diagrams', () => {
});
it('should throw error if numbers are not continuous with bit counts', async () => {
const str = `packet-beta
const str = `packet
+16: "test"
18-20: "error"
`;
@@ -215,7 +221,7 @@ describe('packet diagrams', () => {
});
it('should throw error if numbers are not continuous for single packets', async () => {
const str = `packet-beta
const str = `packet
0-16: "test"
18: "error"
`;
@@ -225,7 +231,7 @@ describe('packet diagrams', () => {
});
it('should throw error if numbers are not continuous for single packets with bit counts', async () => {
const str = `packet-beta
const str = `packet
+16: "test"
18: "error"
`;
@@ -235,7 +241,7 @@ describe('packet diagrams', () => {
});
it('should throw error if numbers are not continuous for single packets - 2', async () => {
const str = `packet-beta
const str = `packet
0-16: "test"
17: "good"
19: "error"
@@ -246,7 +252,7 @@ describe('packet diagrams', () => {
});
it('should throw error if end is less than start', async () => {
const str = `packet-beta
const str = `packet
0-16: "test"
25-20: "error"
`;
@@ -256,7 +262,7 @@ describe('packet diagrams', () => {
});
it('should throw error if bit count is 0', async () => {
const str = `packet-beta
const str = `packet
+0: "test"
`;
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(

View File

@@ -524,7 +524,7 @@ export const drawBox = function (elem, box, conf) {
box.name,
g,
box.x,
box.y + (box.textMaxHeight || 0) / 2,
box.y + conf.boxTextMargin + (box.textMaxHeight || 0) / 2,
box.width,
0,
{ class: 'text' },

View File

@@ -28,8 +28,7 @@ interface TimelineTask {
export const draw = function (text: string, id: string, version: string, diagObj: Diagram) {
//1. Fetch the configuration
const conf = getConfig();
// @ts-expect-error - wrong config?
const LEFT_MARGIN = conf.leftMargin ?? 50;
const LEFT_MARGIN = conf.timeline?.leftMargin ?? 50;
log.debug('timeline', diagObj.db);

View File

@@ -0,0 +1,112 @@
import { getConfig as commonGetConfig } from '../../config.js';
import DEFAULT_CONFIG from '../../defaultConfig.js';
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
import { isLabelStyle } from '../../rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js';
import { cleanAndMerge } from '../../utils.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import {
clear as commonClear,
getAccDescription,
getAccTitle,
getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import type { TreemapDB, TreemapData, TreemapDiagramConfig, TreemapNode } from './types.js';
const defaultTreemapData: TreemapData = {
nodes: [],
levels: new Map(),
outerNodes: [],
classes: new Map(),
};
const state = new ImperativeState<TreemapData>(() => structuredClone(defaultTreemapData));
const getConfig = (): Required<TreemapDiagramConfig> => {
// Use type assertion with unknown as intermediate step
const defaultConfig = DEFAULT_CONFIG as unknown as { treemap: Required<TreemapDiagramConfig> };
const userConfig = commonGetConfig() as unknown as { treemap?: Partial<TreemapDiagramConfig> };
return cleanAndMerge({
...defaultConfig.treemap,
...(userConfig.treemap ?? {}),
}) as Required<TreemapDiagramConfig>;
};
const getNodes = (): TreemapNode[] => state.records.nodes;
const addNode = (node: TreemapNode, level: number) => {
const data = state.records;
data.nodes.push(node);
data.levels.set(node, level);
if (level === 0) {
data.outerNodes.push(node);
}
// Set the root node if this is a level 0 node and we don't have a root yet
if (level === 0 && !data.root) {
data.root = node;
}
};
const getRoot = (): TreemapNode | undefined => ({ name: '', children: state.records.outerNodes });
const addClass = (id: string, _style: string) => {
const classes = state.records.classes;
const styleClass = classes.get(id) ?? { id, styles: [], textStyles: [] };
classes.set(id, styleClass);
const styles = _style.replace(/\\,/g, '§§§').replace(/,/g, ';').replace(/§§§/g, ',').split(';');
if (styles) {
styles.forEach((s) => {
if (isLabelStyle(s)) {
if (styleClass?.textStyles) {
styleClass.textStyles.push(s);
} else {
styleClass.textStyles = [s];
}
}
if (styleClass?.styles) {
styleClass.styles.push(s);
} else {
styleClass.styles = [s];
}
});
}
classes.set(id, styleClass);
};
const getClasses = (): Map<string, DiagramStyleClassDef> => {
return state.records.classes;
};
const getStylesForClass = (classSelector: string): string[] => {
return state.records.classes.get(classSelector)?.styles ?? [];
};
const clear = () => {
commonClear();
state.reset();
};
export const db: TreemapDB = {
getNodes,
addNode,
getRoot,
getConfig,
clear,
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
addClass,
getClasses,
getStylesForClass,
};

View File

@@ -0,0 +1,22 @@
import type {
DiagramDetector,
DiagramLoader,
ExternalDiagramDefinition,
} from '../../diagram-api/types.js';
const id = 'treemap';
const detector: DiagramDetector = (txt) => {
return /^\s*treemap/.test(txt);
};
const loader: DiagramLoader = async () => {
const { diagram } = await import('./diagram.js');
return { id, diagram };
};
export const treemap: ExternalDiagramDefinition = {
id,
detector,
loader,
};

View File

@@ -0,0 +1,12 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
import { db } from './db.js';
import { parser } from './parser.js';
import { renderer } from './renderer.js';
import styles from './styles.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles,
};

View File

@@ -0,0 +1,100 @@
import { parse } from '@mermaid-js/parser';
import type { ParserDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { populateCommonDb } from '../common/populateCommonDb.js';
import { db } from './db.js';
import type { TreemapNode, TreemapAst } from './types.js';
import { buildHierarchy } from './utils.js';
/**
* Populates the database with data from the Treemap AST
* @param ast - The Treemap AST
*/
const populate = (ast: TreemapAst) => {
// We need to bypass the type checking for populateCommonDb
// eslint-disable-next-line @typescript-eslint/no-explicit-any
populateCommonDb(ast as any, db);
const items: {
level: number;
name: string;
type: string;
value?: number;
classSelector?: string;
cssCompiledStyles?: string;
}[] = [];
// Extract classes and styles from the treemap
for (const row of ast.TreemapRows ?? []) {
if (row.$type === 'ClassDefStatement') {
db.addClass(row.className ?? '', row.styleText ?? '');
}
}
// Extract data from each row in the treemap
for (const row of ast.TreemapRows ?? []) {
const item = row.item;
if (!item) {
continue;
}
const level = row.indent ? parseInt(row.indent) : 0;
const name = getItemName(item);
// Get styles as a string if they exist
const styles = item.classSelector ? db.getStylesForClass(item.classSelector) : [];
const cssCompiledStyles = styles.length > 0 ? styles.join(';') : undefined;
const itemData = {
level,
name,
type: item.$type,
value: item.value,
classSelector: item.classSelector,
cssCompiledStyles,
};
items.push(itemData);
}
// Convert flat structure to hierarchical
const hierarchyNodes = buildHierarchy(items);
// Add all nodes to the database
const addNodesRecursively = (nodes: TreemapNode[], level: number) => {
for (const node of nodes) {
db.addNode(node, level);
if (node.children && node.children.length > 0) {
addNodesRecursively(node.children, level + 1);
}
}
};
addNodesRecursively(hierarchyNodes, 0);
};
/**
* Gets the name of a treemap item
* @param item - The treemap item
* @returns The name of the item
*/
const getItemName = (item: { name?: string | number }): string => {
return item.name ? String(item.name) : '';
};
export const parser: ParserDefinition = {
parse: async (text: string): Promise<void> => {
try {
// Use a generic parse that accepts any diagram type
const parseFunc = parse as (diagramType: string, text: string) => Promise<TreemapAst>;
const ast = await parseFunc('treemap', text);
log.debug('Treemap AST:', ast);
populate(ast);
} catch (error) {
log.error('Error parsing treemap:', error);
throw error;
}
},
};

View File

@@ -0,0 +1,526 @@
import type { Diagram } from '../../Diagram.js';
import type {
DiagramRenderer,
DiagramStyleClassDef,
DrawDefinition,
} from '../../diagram-api/types.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { TreemapDB, TreemapNode } from './types.js';
import { scaleOrdinal, treemap, hierarchy, format, select } from 'd3';
import { styles2String } from '../../rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js';
import { getConfig } from '../../config.js';
import { log } from '../../logger.js';
import type { Node } from '../../rendering-util/types.js';
const DEFAULT_INNER_PADDING = 10; // Default for inner padding between cells/sections
const SECTION_INNER_PADDING = 10; // Default for inner padding between cells/sections
const SECTION_HEADER_HEIGHT = 25;
/**
* Draws the treemap diagram
*/
const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
const treemapDb = diagram.db as TreemapDB;
const config = treemapDb.getConfig();
const treemapInnerPadding = config.padding ?? DEFAULT_INNER_PADDING;
const title = treemapDb.getDiagramTitle();
const root = treemapDb.getRoot();
const { themeVariables } = getConfig();
if (!root) {
return;
}
// Define dimensions
const titleHeight = title ? 30 : 0;
const svg = selectSvgElement(id);
// Use config dimensions or defaults
const width = config.nodeWidth ? config.nodeWidth * SECTION_INNER_PADDING : 960;
const height = config.nodeHeight ? config.nodeHeight * SECTION_INNER_PADDING : 500;
const svgWidth = width;
const svgHeight = height + titleHeight;
// Set the SVG size
svg.attr('viewBox', `0 0 ${svgWidth} ${svgHeight}`);
configureSvgSize(svg, svgHeight, svgWidth, config.useMaxWidth);
// Format for displaying values
let valueFormat;
try {
// Handle special format patterns
const formatStr = config.valueFormat || ',';
// Handle special cases that aren't directly supported by D3 format
if (formatStr === '$0,0') {
// Currency with thousands separator
valueFormat = (value: number) => '$' + format(',')(value);
} else if (formatStr.startsWith('$') && formatStr.includes(',')) {
// Other dollar formats with commas
const precision = /\.\d+/.exec(formatStr);
const precisionStr = precision ? precision[0] : '';
valueFormat = (value: number) => '$' + format(',' + precisionStr)(value);
} else if (formatStr.startsWith('$')) {
// Simple dollar sign prefix
const restOfFormat = formatStr.substring(1);
valueFormat = (value: number) => '$' + format(restOfFormat || '')(value);
} else {
// Standard D3 format
valueFormat = format(formatStr);
}
} catch (error) {
log.error('Error creating format function:', error);
// Fallback to default format
valueFormat = format(',');
}
// Create color scale
const colorScale = scaleOrdinal<string>().range([
'transparent',
themeVariables.cScale0,
themeVariables.cScale1,
themeVariables.cScale2,
themeVariables.cScale3,
themeVariables.cScale4,
themeVariables.cScale5,
themeVariables.cScale6,
themeVariables.cScale7,
themeVariables.cScale8,
themeVariables.cScale9,
themeVariables.cScale10,
themeVariables.cScale11,
]);
const colorScalePeer = scaleOrdinal<string>().range([
'transparent',
themeVariables.cScalePeer0,
themeVariables.cScalePeer1,
themeVariables.cScalePeer2,
themeVariables.cScalePeer3,
themeVariables.cScalePeer4,
themeVariables.cScalePeer5,
themeVariables.cScalePeer6,
themeVariables.cScalePeer7,
themeVariables.cScalePeer8,
themeVariables.cScalePeer9,
themeVariables.cScalePeer10,
themeVariables.cScalePeer11,
]);
const colorScaleLabel = scaleOrdinal<string>().range([
themeVariables.cScaleLabel0,
themeVariables.cScaleLabel1,
themeVariables.cScaleLabel2,
themeVariables.cScaleLabel3,
themeVariables.cScaleLabel4,
themeVariables.cScaleLabel5,
themeVariables.cScaleLabel6,
themeVariables.cScaleLabel7,
themeVariables.cScaleLabel8,
themeVariables.cScaleLabel9,
themeVariables.cScaleLabel10,
themeVariables.cScaleLabel11,
]);
// Draw the title if it exists
if (title) {
svg
.append('text')
.attr('x', svgWidth / 2)
.attr('y', titleHeight / 2)
.attr('class', 'treemapTitle')
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.text(title);
}
// Create a main container for the treemap, translated below the title
const g = svg
.append('g')
.attr('transform', `translate(0, ${titleHeight})`)
.attr('class', 'treemapContainer');
// Create the hierarchical structure
const hierarchyRoot = hierarchy<TreemapNode>(root)
.sum((d) => d.value ?? 0)
.sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
// Create treemap layout
const treemapLayout = treemap<TreemapNode>()
.size([width, height])
.paddingTop((d) =>
d.children && d.children.length > 0 ? SECTION_HEADER_HEIGHT + SECTION_INNER_PADDING : 0
)
.paddingInner(treemapInnerPadding)
.paddingLeft((d) => (d.children && d.children.length > 0 ? SECTION_INNER_PADDING : 0))
.paddingRight((d) => (d.children && d.children.length > 0 ? SECTION_INNER_PADDING : 0))
.paddingBottom((d) => (d.children && d.children.length > 0 ? SECTION_INNER_PADDING : 0))
.round(true);
// Apply the treemap layout to the hierarchy
const treemapData = treemapLayout(hierarchyRoot);
// Draw section nodes (branches - nodes with children)
const branchNodes = treemapData.descendants().filter((d) => d.children && d.children.length > 0);
const sections = g
.selectAll('.treemapSection')
.data(branchNodes)
.enter()
.append('g')
.attr('class', 'treemapSection')
.attr('transform', (d) => `translate(${d.x0},${d.y0})`);
// Add section header background
sections
.append('rect')
.attr('width', (d) => d.x1 - d.x0)
.attr('height', SECTION_HEADER_HEIGHT)
.attr('class', 'treemapSectionHeader')
.attr('fill', 'none')
.attr('fill-opacity', 0.6)
.attr('stroke-width', 0.6)
.attr('style', (d) => {
// Hide the label for the root section
if (d.depth === 0) {
return 'display: none;';
}
return '';
});
// Add clip paths for section headers to prevent text overflow
sections
.append('clipPath')
.attr('id', (_d, i) => `clip-section-${id}-${i}`)
.append('rect')
.attr('width', (d) => Math.max(0, d.x1 - d.x0 - 12)) // 6px padding on each side
.attr('height', SECTION_HEADER_HEIGHT);
sections
.append('rect')
.attr('width', (d) => d.x1 - d.x0)
.attr('height', (d) => d.y1 - d.y0)
.attr('class', (_d, i) => {
return `treemapSection section${i}`;
})
.attr('fill', (d) => colorScale(d.data.name))
.attr('fill-opacity', 0.6)
.attr('stroke', (d) => colorScalePeer(d.data.name))
.attr('stroke-width', 2.0)
.attr('stroke-opacity', 0.4)
.attr('style', (d) => {
// Hide the label for the root section
if (d.depth === 0) {
return 'display: none;';
}
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return styles.nodeStyles + ';' + styles.borderStyles.join(';');
});
// Add section labels
sections
.append('text')
.attr('class', 'treemapSectionLabel')
.attr('x', 6) // Keep original left padding
.attr('y', SECTION_HEADER_HEIGHT / 2)
.attr('dominant-baseline', 'middle')
.text((d) => (d.depth === 0 ? '' : d.data.name)) // Skip label for root section
.attr('font-weight', 'bold')
.attr('style', (d) => {
// Hide the label for the root section
if (d.depth === 0) {
return 'display: none;';
}
const labelStyles =
'dominant-baseline: middle; font-size: 12px; fill:' +
colorScaleLabel(d.data.name) +
'; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return labelStyles + styles.labelStyles.replace('color:', 'fill:');
})
.each(function (d) {
// Skip processing for root section
if (d.depth === 0) {
return;
}
const self = select(this);
const originalText = d.data.name;
self.text(originalText);
const totalHeaderWidth = d.x1 - d.x0;
const labelXPosition = 6;
let spaceForTextContent;
if (config.showValues !== false && d.value) {
const valueEndsAtXRelative = totalHeaderWidth - 10;
const estimatedValueTextActualWidth = 30;
const gapBetweenLabelAndValue = 10;
const labelMustEndBeforeX =
valueEndsAtXRelative - estimatedValueTextActualWidth - gapBetweenLabelAndValue;
spaceForTextContent = labelMustEndBeforeX - labelXPosition;
} else {
const labelOwnRightPadding = 6;
spaceForTextContent = totalHeaderWidth - labelXPosition - labelOwnRightPadding;
}
const minimumWidthToDisplay = 15;
const actualAvailableWidth = Math.max(minimumWidthToDisplay, spaceForTextContent);
const textNode = self.node()!;
const currentTextContentLength = textNode.getComputedTextLength();
if (currentTextContentLength > actualAvailableWidth) {
const ellipsis = '...';
let currentTruncatedText = originalText;
while (currentTruncatedText.length > 0) {
currentTruncatedText = originalText.substring(0, currentTruncatedText.length - 1);
if (currentTruncatedText.length === 0) {
self.text(ellipsis);
if (textNode.getComputedTextLength() > actualAvailableWidth) {
self.text('');
}
break;
}
self.text(currentTruncatedText + ellipsis);
if (textNode.getComputedTextLength() <= actualAvailableWidth) {
break;
}
}
}
});
// Add section values if enabled
if (config.showValues !== false) {
sections
.append('text')
.attr('class', 'treemapSectionValue')
.attr('x', (d) => d.x1 - d.x0 - 10)
.attr('y', SECTION_HEADER_HEIGHT / 2)
.attr('text-anchor', 'end')
.attr('dominant-baseline', 'middle')
.text((d) => (d.value ? valueFormat(d.value) : ''))
.attr('font-style', 'italic')
.attr('style', (d) => {
// Hide the value for the root section
if (d.depth === 0) {
return 'display: none;';
}
const labelStyles =
'text-anchor: end; dominant-baseline: middle; font-size: 10px; fill:' +
colorScaleLabel(d.data.name) +
'; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return labelStyles + styles.labelStyles.replace('color:', 'fill:');
});
}
// Draw the leaf nodes
const leafNodes = treemapData.leaves();
const cell = g
.selectAll('.treemapLeafGroup')
.data(leafNodes)
.enter()
.append('g')
.attr('class', (d, i) => {
return `treemapNode treemapLeafGroup leaf${i}${d.data.classSelector ? ` ${d.data.classSelector}` : ''}x`;
})
.attr('transform', (d) => `translate(${d.x0},${d.y0})`);
// Add rectangle for each leaf node
cell
.append('rect')
.attr('width', (d) => d.x1 - d.x0)
.attr('height', (d) => d.y1 - d.y0)
.attr('class', 'treemapLeaf')
.attr('fill', (d) => {
// Leaves inherit color from their immediate parent section's name.
// If a leaf is the root itself (no parent), it uses its own name.
return d.parent ? colorScale(d.parent.data.name) : colorScale(d.data.name);
})
.attr('style', (d) => {
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return styles.nodeStyles;
})
.attr('fill-opacity', 0.3)
.attr('stroke', (d) => {
// Leaves inherit color from their immediate parent section's name.
// If a leaf is the root itself (no parent), it uses its own name.
return d.parent ? colorScale(d.parent.data.name) : colorScale(d.data.name);
})
.attr('stroke-width', 3.0);
// Add clip paths to prevent text from extending outside nodes
cell
.append('clipPath')
.attr('id', (_d, i) => `clip-${id}-${i}`)
.append('rect')
.attr('width', (d) => Math.max(0, d.x1 - d.x0 - 4))
.attr('height', (d) => Math.max(0, d.y1 - d.y0 - 4));
// Add node labels with clipping
const leafLabels = cell
.append('text')
.attr('class', 'treemapLabel')
.attr('x', (d) => (d.x1 - d.x0) / 2)
.attr('y', (d) => (d.y1 - d.y0) / 2)
// .style('fill', (d) => colorScaleLabel(d.data.name))
.attr('style', (d) => {
const labelStyles =
'text-anchor: middle; dominant-baseline: middle; font-size: 38px;fill:' +
colorScaleLabel(d.data.name) +
';';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return labelStyles + styles.labelStyles.replace('color:', 'fill:');
})
.attr('clip-path', (_d, i) => `url(#clip-${id}-${i})`)
.text((d) => d.data.name);
leafLabels.each(function (d) {
const self = select(this);
const nodeWidth = d.x1 - d.x0;
const nodeHeight = d.y1 - d.y0;
const textNode = self.node()!;
const padding = 4;
const availableWidth = nodeWidth - 2 * padding;
const availableHeight = nodeHeight - 2 * padding;
if (availableWidth < 10 || availableHeight < 10) {
self.style('display', 'none');
return;
}
let currentLabelFontSize = parseInt(self.style('font-size'), 10);
const minLabelFontSize = 8;
const originalValueRelFontSize = 28; // Original font size of value, for max cap
const valueScaleFactor = 0.6; // Value font size as a factor of label font size
const minValueFontSize = 6;
const spacingBetweenLabelAndValue = 2;
// 1. Adjust label font size to fit width
while (
textNode.getComputedTextLength() > availableWidth &&
currentLabelFontSize > minLabelFontSize
) {
currentLabelFontSize--;
self.style('font-size', `${currentLabelFontSize}px`);
}
// 2. Adjust both label and prospective value font size to fit combined height
let prospectiveValueFontSize = Math.max(
minValueFontSize,
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
);
let combinedHeight =
currentLabelFontSize + spacingBetweenLabelAndValue + prospectiveValueFontSize;
while (combinedHeight > availableHeight && currentLabelFontSize > minLabelFontSize) {
currentLabelFontSize--;
prospectiveValueFontSize = Math.max(
minValueFontSize,
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
);
if (
prospectiveValueFontSize < minValueFontSize &&
currentLabelFontSize === minLabelFontSize
) {
break;
} // Avoid shrinking label if value is already at min
self.style('font-size', `${currentLabelFontSize}px`);
combinedHeight =
currentLabelFontSize + spacingBetweenLabelAndValue + prospectiveValueFontSize;
if (prospectiveValueFontSize <= minValueFontSize && combinedHeight > availableHeight) {
// If value is at min and still doesn't fit, label might need to shrink more alone
// This might lead to label being too small for its own text, checked next
}
}
// Update label font size based on height adjustment
self.style('font-size', `${currentLabelFontSize}px`);
// 3. Final visibility check for the label
if (
textNode.getComputedTextLength() > availableWidth ||
currentLabelFontSize < minLabelFontSize ||
availableHeight < currentLabelFontSize
) {
self.style('display', 'none');
// If label is hidden, value will be hidden by its own .each() loop
}
});
// Add node values with clipping
if (config.showValues !== false) {
const leafValues = cell
.append('text')
.attr('class', 'treemapValue')
.attr('x', (d) => (d.x1 - d.x0) / 2)
.attr('y', function (d) {
// Y position calculated dynamically in leafValues.each based on final label metrics
return (d.y1 - d.y0) / 2; // Placeholder, will be overwritten
})
.attr('style', (d) => {
const labelStyles =
'text-anchor: middle; dominant-baseline: hanging; font-size: 28px;fill:' +
colorScaleLabel(d.data.name) +
';';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
return labelStyles + styles.labelStyles.replace('color:', 'fill:');
})
.attr('clip-path', (_d, i) => `url(#clip-${id}-${i})`)
.text((d) => (d.value ? valueFormat(d.value) : ''));
leafValues.each(function (d) {
const valueTextElement = select(this);
const parentCellNode = this.parentNode as SVGGElement | null;
if (!parentCellNode) {
valueTextElement.style('display', 'none');
return;
}
const labelElement = select(parentCellNode).select<SVGTextElement>('.treemapLabel');
if (labelElement.empty() || labelElement.style('display') === 'none') {
valueTextElement.style('display', 'none');
return;
}
const finalLabelFontSize = parseFloat(labelElement.style('font-size'));
const originalValueFontSize = 28; // From initial style setting
const valueScaleFactor = 0.6;
const minValueFontSize = 6;
const spacingBetweenLabelAndValue = 2;
const actualValueFontSize = Math.max(
minValueFontSize,
Math.min(originalValueFontSize, Math.round(finalLabelFontSize * valueScaleFactor))
);
valueTextElement.style('font-size', `${actualValueFontSize}px`);
const labelCenterY = (d.y1 - d.y0) / 2;
const valueTopActualY = labelCenterY + finalLabelFontSize / 2 + spacingBetweenLabelAndValue;
valueTextElement.attr('y', valueTopActualY);
const nodeWidth = d.x1 - d.x0;
const nodeTotalHeight = d.y1 - d.y0;
const cellBottomPadding = 4;
const maxValueBottomY = nodeTotalHeight - cellBottomPadding;
const availableWidthForValue = nodeWidth - 2 * 4; // padding for value text
if (
valueTextElement.node()!.getComputedTextLength() > availableWidthForValue ||
valueTopActualY + actualValueFontSize > maxValueBottomY ||
actualValueFontSize < minValueFontSize
) {
valueTextElement.style('display', 'none');
} else {
valueTextElement.style('display', null);
}
});
}
const diagramPadding = config.diagramPadding ?? 8;
setupViewPortForSVG(svg, diagramPadding, 'flowchart', config?.useMaxWidth || false);
};
const getClasses = function (
_text: string,
diagramObj: Pick<Diagram, 'db'>
): Map<string, DiagramStyleClassDef> {
return (diagramObj.db as TreemapDB).getClasses();
};
export const renderer: DiagramRenderer = { draw, getClasses };

View File

@@ -0,0 +1,51 @@
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
import { cleanAndMerge } from '../../utils.js';
import type { TreemapStyleOptions } from './types.js';
const defaultTreemapStyleOptions: TreemapStyleOptions = {
sectionStrokeColor: 'black',
sectionStrokeWidth: '1',
sectionFillColor: '#efefef',
leafStrokeColor: 'black',
leafStrokeWidth: '1',
leafFillColor: '#efefef',
labelColor: 'black',
labelFontSize: '12px',
valueFontSize: '10px',
valueColor: 'black',
titleColor: 'black',
titleFontSize: '14px',
};
export const getStyles: DiagramStylesProvider = ({
treemap,
}: { treemap?: TreemapStyleOptions } = {}) => {
const options = cleanAndMerge(defaultTreemapStyleOptions, treemap);
return `
.treemapNode.section {
stroke: ${options.sectionStrokeColor};
stroke-width: ${options.sectionStrokeWidth};
fill: ${options.sectionFillColor};
}
.treemapNode.leaf {
stroke: ${options.leafStrokeColor};
stroke-width: ${options.leafStrokeWidth};
fill: ${options.leafFillColor};
}
.treemapLabel {
fill: ${options.labelColor};
font-size: ${options.labelFontSize};
}
.treemapValue {
fill: ${options.valueColor};
font-size: ${options.valueFontSize};
}
.treemapTitle {
fill: ${options.titleColor};
font-size: ${options.titleFontSize};
}
`;
};
export default getStyles;

View File

@@ -0,0 +1,80 @@
import type { DiagramDBBase, DiagramStyleClassDef } from '../../diagram-api/types.js';
import type { BaseDiagramConfig } from '../../config.type.js';
export interface TreemapNode {
name: string;
children?: TreemapNode[];
value?: number;
parent?: TreemapNode;
classSelector?: string;
cssCompiledStyles?: string[];
}
export interface TreemapDB extends DiagramDBBase<TreemapDiagramConfig> {
getNodes: () => TreemapNode[];
addNode: (node: TreemapNode, level: number) => void;
getRoot: () => TreemapNode | undefined;
getClasses: () => Map<string, DiagramStyleClassDef>;
addClass: (className: string, style: string) => void;
getStylesForClass: (classSelector: string) => string[];
}
export interface TreemapStyleOptions {
sectionStrokeColor?: string;
sectionStrokeWidth?: string;
sectionFillColor?: string;
leafStrokeColor?: string;
leafStrokeWidth?: string;
leafFillColor?: string;
labelColor?: string;
labelFontSize?: string;
valueFontSize?: string;
valueColor?: string;
titleColor?: string;
titleFontSize?: string;
}
export interface TreemapData {
nodes: TreemapNode[];
levels: Map<TreemapNode, number>;
root?: TreemapNode;
outerNodes: TreemapNode[];
classes: Map<string, DiagramStyleClassDef>;
}
export interface TreemapItem {
$type: string;
name: string;
value?: number;
classSelector?: string;
}
export interface TreemapRow {
$type: string;
indent?: string;
item?: TreemapItem;
className?: string;
styleText?: string;
}
export interface TreemapAst {
TreemapRows?: TreemapRow[];
title?: string;
description?: string;
accDescription?: string;
accTitle?: string;
diagramTitle?: string;
}
// Define the TreemapDiagramConfig interface
export interface TreemapDiagramConfig extends BaseDiagramConfig {
padding?: number;
diagramPadding?: number;
showValues?: boolean;
nodeWidth?: number;
nodeHeight?: number;
borderWidth?: number;
valueFontSize?: number;
labelFontSize?: number;
valueFormat?: string;
}

View File

@@ -0,0 +1,100 @@
import { describe, it, expect } from 'vitest';
import { buildHierarchy } from './utils.js';
import type { TreemapNode } from './types.js';
describe('treemap utilities', () => {
describe('buildHierarchy', () => {
it('should convert a flat array into a hierarchical structure', () => {
// Input flat structure
const flatItems = [
{ level: 0, name: 'Root', type: 'Section' },
{ level: 4, name: 'Branch 1', type: 'Section' },
{ level: 8, name: 'Leaf 1.1', type: 'Leaf', value: 10 },
{ level: 8, name: 'Leaf 1.2', type: 'Leaf', value: 15 },
{ level: 4, name: 'Branch 2', type: 'Section' },
{ level: 8, name: 'Leaf 2.1', type: 'Leaf', value: 20 },
{ level: 8, name: 'Leaf 2.2', type: 'Leaf', value: 25 },
{ level: 8, name: 'Leaf 2.3', type: 'Leaf', value: 30 },
];
// Expected hierarchical structure
const expectedHierarchy: TreemapNode[] = [
{
name: 'Root',
children: [
{
name: 'Branch 1',
children: [
{ name: 'Leaf 1.1', value: 10 },
{ name: 'Leaf 1.2', value: 15 },
],
},
{
name: 'Branch 2',
children: [
{ name: 'Leaf 2.1', value: 20 },
{ name: 'Leaf 2.2', value: 25 },
{ name: 'Leaf 2.3', value: 30 },
],
},
],
},
];
const result = buildHierarchy(flatItems);
expect(result).toEqual(expectedHierarchy);
});
it('should handle empty input', () => {
expect(buildHierarchy([])).toEqual([]);
});
it('should handle only root nodes', () => {
const flatItems = [
{ level: 0, name: 'Root 1', type: 'Section' },
{ level: 0, name: 'Root 2', type: 'Section' },
];
const expected = [
{ name: 'Root 1', children: [] },
{ name: 'Root 2', children: [] },
];
expect(buildHierarchy(flatItems)).toEqual(expected);
});
it('should handle complex nesting levels', () => {
const flatItems = [
{ level: 0, name: 'Root', type: 'Section' },
{ level: 2, name: 'Level 1', type: 'Section' },
{ level: 4, name: 'Level 2', type: 'Section' },
{ level: 6, name: 'Leaf 1', type: 'Leaf', value: 10 },
{ level: 4, name: 'Level 2 again', type: 'Section' },
{ level: 6, name: 'Leaf 2', type: 'Leaf', value: 20 },
];
const expected = [
{
name: 'Root',
children: [
{
name: 'Level 1',
children: [
{
name: 'Level 2',
children: [{ name: 'Leaf 1', value: 10 }],
},
{
name: 'Level 2 again',
children: [{ name: 'Leaf 2', value: 20 }],
},
],
},
],
},
];
expect(buildHierarchy(flatItems)).toEqual(expected);
});
});
});

View File

@@ -0,0 +1,64 @@
import type { TreemapNode } from './types.js';
/**
* Converts a flat array of treemap items into a hierarchical structure
* @param items - Array of flat treemap items with level, name, type, and optional value
* @returns A hierarchical tree structure
*/
export function buildHierarchy(
items: {
level: number;
name: string;
type: string;
value?: number;
classSelector?: string;
cssCompiledStyles?: string;
}[]
): TreemapNode[] {
if (!items.length) {
return [];
}
const root: TreemapNode[] = [];
const stack: { node: TreemapNode; level: number }[] = [];
items.forEach((item) => {
const node: TreemapNode = {
name: item.name,
children: item.type === 'Leaf' ? undefined : [],
};
node.classSelector = item?.classSelector;
if (item?.cssCompiledStyles) {
node.cssCompiledStyles = [item.cssCompiledStyles];
}
if (item.type === 'Leaf' && item.value !== undefined) {
node.value = item.value;
}
// Find the right parent for this node
while (stack.length > 0 && stack[stack.length - 1].level >= item.level) {
stack.pop();
}
if (stack.length === 0) {
// This is a root node
root.push(node);
} else {
// Add as child to the parent
const parent = stack[stack.length - 1].node;
if (parent.children) {
parent.children.push(node);
} else {
parent.children = [node];
}
}
// Only add to stack if it can have children
if (item.type !== 'Leaf') {
stack.push({ node, level: item.level });
}
});
return root;
}

View File

@@ -141,7 +141,6 @@ function sidebarAll() {
],
},
...sidebarSyntax(),
...sidebarAlgorithms(),
...sidebarEcosystem(),
...sidebarConfig(),
...sidebarCommunity(),
@@ -180,6 +179,7 @@ function sidebarSyntax() {
{ text: 'Kanban 🔥', link: '/syntax/kanban' },
{ text: 'Architecture 🔥', link: '/syntax/architecture' },
{ text: 'Radar 🔥', link: '/syntax/radar' },
{ text: 'Treemap 🔥', link: '/syntax/treemap' },
{ text: 'Other Examples', link: '/syntax/examples' },
],
},
@@ -223,27 +223,6 @@ function sidebarEcosystem() {
];
}
function sidebarAlgorithms() {
return [
{
text: '🧠 Diagram Algorithms',
collapsed: false,
items: [
{ text: 'Introduction', link: '/layouts/introduction' },
{ text: 'Layout Algorithm Development', link: '/layouts/development' },
{
text: 'IPSep-Cola',
collapsed: false,
items: [
{ text: 'Overview', link: '/layouts/ipsepcola/overview' },
{ text: 'Implementation', link: '/layouts/ipsepcola/implementation' },
],
},
],
},
];
}
function sidebarCommunity() {
return [
{

View File

@@ -302,7 +302,7 @@ If you are adding a feature, you will definitely need to add tests. Depending on
Unit tests are tests that test a single function or module. They are the easiest to write and the fastest to run.
Unit tests are mandatory for all code except the renderers. (The renderers are tested with integration tests.)
Unit tests are mandatory for all code except the layout tests. (The layouts are tested with integration tests.)
We use [Vitest](https://vitest.dev) to run unit tests.
@@ -328,6 +328,30 @@ When using Docker prepend your command with `./run`:
./run pnpm test
```
##### Testing the DOM
One can use `jsdomIt` to test any part of Mermaid that interacts with the DOM, as long as it is not related to the layout.
The function `jsdomIt` ([developed in utils.ts](../../tests/util.ts)) overrides `it` from `vitest`, and creates a pseudo-browser environment that works almost like the real deal for the duration of the test. It uses JSDOM to create a DOM, and adds objects `window` and `document` to `global` to mock the browser environment.
> [!NOTE]
> The layout cannot work in `jsdomIt` tests because JSDOM has no rendering engine, hence the pseudo-browser environment.
Example :
```typescript
import { ensureNodeFromSelector, jsdomIt } from './tests/util.js';
jsdomIt('should add element "thing" in the SVG', ({ svg }) => {
// Code in this block runs in a pseudo-browser environment
addThing(svg); // The svg item is the D3 selection of the SVG node
const svgNode = ensureNodeFromSelector('svg'); // Retrieve the DOM node using the DOM API
expect(svgNode.querySelector('thing')).not.toBeNull(); // Test the structure of the SVG
});
```
They can be used to test any method that interacts with the DOM, including for testing renderers. For renderers, additional integration testing is necessary to test the layout though.
#### Integration / End-to-End (E2E) Tests
These test the rendering and visual appearance of the diagrams.

View File

@@ -106,3 +106,13 @@ const themes = {
```
The actual options and values for the colors are defined in **src/theme/theme-[xyz].js**. If you provide the options your diagram needs in the existing theme files then the theming will work smoothly without hiccups.
## Examples
The `@mermaid-js/examples` package contains a collection of examples that are used by tools like mermaid.live to help users get started with the new diagram.
You can duplicate an existing diagram example file, eg: `packages/examples/src/examples/flowchart.ts`, and modify it with details specific to your diagram.
Then you can import the example in the `packages/examples/src/index.ts` file and add it to the `examples` array.
Each diagram should have at least one example, and that should be marked as default. It is good to add more examples to showcase different features of the diagram.

Some files were not shown because too many files have changed in this diff Show More