Compare commits

...

333 Commits

Author SHA1 Message Date
Sidharth Vinod
d63dc319c7 Merge pull request #5866 from mermaid-js/changeset-release/master
Version Packages
2024-09-16 22:10:27 +05:30
github-actions[bot]
c5539e0aa0 Version Packages 2024-09-16 16:39:08 +00:00
Sidharth Vinod
14601b7871 Merge pull request #5865 from mermaid-js/develop
Pre-Release
2024-09-16 22:06:35 +05:30
Sidharth Vinod
7a86fd7b44 Merge branch 'master' into develop
* master:
  Version Packages
  add product hunt badge to homepage
2024-09-16 22:05:12 +05:30
Sidharth Vinod
0b3522894a Update firefox changeset to patch 2024-09-16 22:03:33 +05:30
Sidharth Vinod
d2de9702c5 Merge pull request #5840 from mermaid-js/renovate/eslint
chore(deps): update eslint (minor)
2024-09-16 12:37:19 +05:30
renovate[bot]
92a8783d95 chore(deps): update eslint 2024-09-16 06:46:29 +00:00
Sidharth Vinod
4bd1e5c524 Merge pull request #5853 from jayaprabhakar/bug/5716_seq-actor-timeline-start
Sequence Diagram: Start participant timeline from the bottom of the participant
2024-09-16 05:47:51 +00:00
Sidharth Vinod
10826055f3 Merge pull request #5863 from mermaid-js/renovate/patch-eslint
chore(deps): update dependency eslint-plugin-jsdoc to v50.2.3
2024-09-16 05:46:32 +00:00
renovate[bot]
6cf050e6e5 chore(deps): update dependency eslint-plugin-jsdoc to v50.2.3 2024-09-16 01:58:46 +00:00
Ashish Jain
17c6ed6303 Merge pull request #5856 from mermaid-js/5787_firefox_label_width
#5787 Fix for issue with labels in firefox
2024-09-12 11:31:20 +02:00
Knut Sveidqvist
bfd8c63daa #5787 Fix for issue with labels in firefox 2024-09-12 11:05:32 +02:00
Knut Sveidqvist
e44eb5258b Merge pull request #5826 from mermaid-js/add-product-hunt-badge
DOCS: add Product Hunt badge to homepage
2024-09-11 08:52:35 +02:00
jayaprabhakar
e564c395ba Sequence Diagram: Start participant timeline from the bottom of the participant 2024-09-10 13:46:35 -07:00
Sidharth Vinod
9c41708d9e Merge pull request #5848 from mermaid-js/changeset-release/master
Version Packages
2024-09-09 19:29:39 +05:30
github-actions[bot]
a51d3aa2ad Version Packages 2024-09-09 13:59:11 +00:00
Sidharth Vinod
797d72b716 Merge pull request #5847 from mermaid-js/develop
changesets
2024-09-09 19:27:04 +05:30
Sidharth Vinod
dd0304387e changesets 2024-09-09 19:26:27 +05:30
Sidharth Vinod
80903ec281 Merge pull request #5846 from mermaid-js/changeset-release/master
Version Packages
2024-09-09 18:51:04 +05:30
github-actions[bot]
1f373c8430 Version Packages 2024-09-09 13:09:57 +00:00
Sidharth Vinod
02d3bec856 Merge pull request #5845 from mermaid-js/develop
Pre-release
2024-09-09 18:37:57 +05:30
Sidharth Vinod
ddb51cdcbc Merge pull request #5831 from mermaid-js/sidv/returnConfig
feat: Return parsed config from `mermaid.parse`
2024-09-09 12:17:02 +00:00
autofix-ci[bot]
d053284536 [autofix.ci] apply automated fixes 2024-09-09 12:11:29 +00:00
Sidharth Vinod
acc1e500d3 Merge branch 'develop' into sidv/returnConfig
* develop:
  [autofix.ci] apply automated fixes
  Create red-beans-cross.md
  remove tsconfig compiler option paths
  linting
  replace mermaid/dist with relative paths
  more , linting
  add test for out-of-tree tsc project
  replace  with mermaid/dist
2024-09-09 17:31:07 +05:30
Sidharth Vinod
c0187101e9 fix: Return type, make config non optional
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-09-09 17:30:22 +05:30
Sidharth Vinod
303f1f4545 Merge branch 'develop' into sidv/returnConfig
* develop:
  chore(deps): update dependency eslint-plugin-jsdoc to v50
  chore: add git command and set safe dir
  Version Packages
  Adding changeset
  Cleanup of test-file
  Updated cypress test for self-loops
  Fix for issues with self loops
  rebuild docs
  add another maybe-undefined operator elk
  pass linter
  fix import
  make rendering-util/types a real ts file
2024-09-09 17:19:28 +05:30
Sidharth Vinod
750f58d7db Merge pull request #5838 from bollwyvl/gh-5747-relative-paths
fix 5747 replace $root with relative paths
2024-09-09 11:47:58 +00:00
autofix-ci[bot]
1951a0cdc8 [autofix.ci] apply automated fixes 2024-09-09 04:12:51 +00:00
Sidharth Vinod
cc61629a8c Merge pull request #5842 from mermaid-js/renovate/major-eslint
chore(deps): update dependency eslint-plugin-jsdoc to v50
2024-09-09 04:09:33 +00:00
Sidharth Vinod
5e75320d49 Create red-beans-cross.md 2024-09-09 09:38:02 +05:30
renovate[bot]
b830e6151b chore(deps): update dependency eslint-plugin-jsdoc to v50 2024-09-09 00:15:23 +00:00
Nicholas Bollweg
fd3c3fc022 remove tsconfig compiler option paths 2024-09-08 13:57:56 -05:00
Nicholas Bollweg
94deacb1b5 linting 2024-09-08 12:48:41 -05:00
Nicholas Bollweg
571dfda629 replace mermaid/dist with relative paths 2024-09-08 12:47:19 -05:00
Nicholas Bollweg
6a3f1d194a Merge branch 'develop' into gh-5747-no-dollar-root 2024-09-08 13:03:25 -04:00
Sidharth Vinod
e5aebf3df6 Merge pull request #5798 from bollwyvl/gh-5747-rendering-util-types
fix 5747 rendering util types
2024-09-08 14:36:38 +00:00
Sidharth Vinod
c95a0c7de4 Merge pull request #5834 from kairi003/bug/5833_add_git_for_changeset
chore: add `git` to dev image for `changeset`
2024-09-08 14:26:09 +00:00
Sidharth Vinod
bf9842000f Merge branch 'master' into develop
* master:
  Version Packages
  Adding changeset
  Cleanup of test-file
  Updated cypress test for self-loops
  Fix for issues with self loops
2024-09-08 20:01:33 +05:30
Nicholas Bollweg
35c08985ee Merge branch 'develop' into gh-5747-rendering-util-types 2024-09-08 09:25:02 -04:00
Nicholas Bollweg
ba95f394a3 Merge branch 'develop' into gh-5747-no-dollar-root 2024-09-08 09:24:53 -04:00
kairi003
2879f3775b chore: add git command and set safe dir 2024-09-08 06:04:01 +09:00
Sidharth Vinod
f7be983199 Merge branch 'develop' into sidv/returnConfig
* develop:
  chore: Fix check
  feat: Preview release
2024-09-06 23:26:10 +05:30
Sidharth Vinod
58524aceaa chore: Fix check 2024-09-06 23:25:23 +05:30
Sidharth Vinod
ab25e9ea7d Merge pull request #5832 from mermaid-js/sidv/previewRelease
feat: Preview releases
2024-09-06 23:24:41 +05:30
Sidharth Vinod
57d2d905ef feat: Preview release 2024-09-06 23:18:27 +05:30
Sidharth Vinod
64abf29ea8 feat: Return parsed config from mermaid.parse 2024-09-06 23:05:30 +05:30
Ashish Jain
73147196b1 Merge pull request #5830 from mermaid-js/changeset-release/master
Version Packages
2024-09-06 19:18:44 +02:00
github-actions[bot]
c895109497 Version Packages 2024-09-06 17:17:58 +00:00
Ashish Jain
145acd0ee4 Merge pull request #5828 from mermaid-js/hotfix/5827-self-loops
Hotfix/5827 self loops
2024-09-06 19:16:08 +02:00
Knut Sveidqvist
4c43d21196 Adding changeset 2024-09-06 18:47:39 +02:00
Knut Sveidqvist
b4941aa4ce Cleanup of test-file 2024-09-06 18:43:22 +02:00
Knut Sveidqvist
aa259e03f2 Updated cypress test for self-loops 2024-09-06 18:33:34 +02:00
Knut Sveidqvist
0d7aaae0ff Fix for issues with self loops 2024-09-06 18:33:13 +02:00
Steph
0f8cc48d03 add product hunt badge to homepage 2024-09-06 08:08:41 -07:00
Nicholas Bollweg
826faad37b Merge branch 'develop' into gh-5747-no-dollar-root 2024-09-04 10:06:44 -04:00
Nicholas Bollweg
c078840c63 Merge branch 'develop' into gh-5747-rendering-util-types 2024-09-04 10:06:35 -04:00
Sidharth Vinod
bfc4abeae2 Merge branch 'master' into develop
* master:
  add blog post - architecture diagrams
2024-09-03 23:59:19 +05:30
Sidharth Vinod
cd67fdf89d docs: Fix SMW link 2024-09-03 23:58:55 +05:30
Sidharth Vinod
958fb6da2b Merge pull request #5815 from mermaid-js/add-blog-posts
DOCS: add blog post - Architecture diagrams
2024-09-03 22:02:20 +05:30
Sidharth Vinod
a6dbf0a9ee Merge branch 'master' of https://github.com/mermaid-js/mermaid into add-blog-posts
* 'master' of https://github.com/mermaid-js/mermaid: (197 commits)
  chore: Update changelogs
  Version Packages
  chore: Update repo name
  Revert "Version Packages"
  chore: Update permissions
  Version Packages
  Update nice-flowers-yawn.md
  Remove icons from docs links
  test: Add second icon pack
  feat: Lazy load icons
  test: Wrap in describe block
  chore: Update cypress
  [autofix.ci] apply automated fixes
  chore: Disable mermaid in SSR
  chore: update pnpm, remove dependency
  test: External diagram rendering test
  docs: Add external icons in demos
  [autofix.ci] apply automated fixes
  docs: Add iconify docs
  docs: Add iconify docs
  ...
2024-09-03 22:01:09 +05:30
Steph
31b4ec3e10 add blog post - architecture diagrams 2024-09-03 08:05:19 -07:00
Nicholas Bollweg
b56fe796d6 more , linting 2024-09-02 16:07:28 -05:00
Nicholas Bollweg
7156cf4c71 merge upstream 2024-09-02 15:57:57 -05:00
Nicholas Bollweg
426d311faa Merge branch 'develop' into gh-5747-rendering-util-types 2024-09-02 16:41:55 -04:00
Sidharth Vinod
55aff88e9f chore: Update changelogs 2024-09-02 21:04:34 +05:30
Knut Sveidqvist
32710c1d99 Merge pull request #5812 from mermaid-js/changeset-release/master
Version Packages
2024-09-02 17:17:19 +02:00
github-actions[bot]
9bcf614a9a Version Packages 2024-09-02 15:16:23 +00:00
Sidharth Vinod
0520329fe0 chore: Update repo name 2024-09-02 20:44:29 +05:30
Sidharth Vinod
10795b1d89 Revert "Version Packages"
This reverts commit 517c47b780.
2024-09-02 20:43:32 +05:30
Sidharth Vinod
6bdeb7ac01 chore: Update permissions 2024-09-02 20:42:55 +05:30
Knut Sveidqvist
3f699ede9e Merge pull request #5811 from mermaid-js/changeset-release/master
Version Packages
2024-09-02 17:09:50 +02:00
github-actions[bot]
517c47b780 Version Packages 2024-09-02 15:05:24 +00:00
Knut Sveidqvist
e1c40bcf05 Update nice-flowers-yawn.md
Removed invalid package
2024-09-02 17:03:30 +02:00
Knut Sveidqvist
20c321076a Merge pull request #5810 from mermaid-js/develop
Release
2024-09-02 16:59:08 +02:00
Sidharth Vinod
87fa9e5289 Remove icons from docs links 2024-09-02 20:22:28 +05:30
Knut Sveidqvist
9fa9bd9e93 Merge pull request #5793 from mermaid-js/sidv/iconify
feat: Support Iconify Icons
2024-09-02 16:43:43 +02:00
Sidharth Vinod
e44cdbd79d test: Add second icon pack 2024-09-02 19:55:04 +05:30
Sidharth Vinod
0edfab1048 feat: Lazy load icons
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-09-02 19:51:14 +05:30
Sidharth Vinod
c68ae309e5 test: Wrap in describe block 2024-09-02 18:34:58 +05:30
Sidharth Vinod
815e4b5748 Merge branch 'develop' into sidv/iconify 2024-09-02 18:19:27 +05:30
Alois Klink
6acbd9789a Merge pull request #5809 from mermaid-js/sidv/updatePnpm
chore: update pnpm, remove dependency
2024-09-02 12:40:51 +00:00
Sidharth Vinod
f4decdee49 chore: Update cypress 2024-09-02 18:01:35 +05:30
autofix-ci[bot]
0b127eecd9 [autofix.ci] apply automated fixes 2024-09-02 10:46:53 +00:00
Sidharth Vinod
69c9a564f2 chore: Disable mermaid in SSR 2024-09-02 16:11:33 +05:30
Sidharth Vinod
8ef24e5a18 chore: update pnpm, remove dependency 2024-09-02 14:47:46 +05:30
Sidharth Vinod
f5cc0dcaea test: External diagram rendering test 2024-09-02 14:05:37 +05:30
Sidharth Vinod
007da4588b Merge branch 'sidv/iconify' of https://github.com/mermaid-js/mermaid into sidv/iconify
* 'sidv/iconify' of https://github.com/mermaid-js/mermaid:
  [autofix.ci] apply automated fixes
2024-09-02 14:00:09 +05:30
Sidharth Vinod
7056c20cca docs: Add external icons in demos 2024-09-02 14:00:03 +05:30
autofix-ci[bot]
7cda494bf4 [autofix.ci] apply automated fixes 2024-09-02 08:25:52 +00:00
Sidharth Vinod
2d8342261b docs: Add iconify docs 2024-09-02 13:50:50 +05:30
Sidharth Vinod
ef26fc921c docs: Add iconify docs 2024-09-02 13:20:34 +05:30
Nicholas Bollweg
0b672e2636 add test for out-of-tree tsc project 2024-09-01 16:40:12 -05:00
Nicholas Bollweg
1b7433b637 replace with mermaid/dist 2024-08-31 09:12:59 -05:00
Nicholas Bollweg
73f8d70b86 rebuild docs 2024-08-30 09:21:11 -05:00
Nicholas Bollweg
6a97f80cc3 add another maybe-undefined operator elk 2024-08-30 09:16:38 -05:00
Nicholas Bollweg
3e922c83f0 pass linter 2024-08-30 09:10:53 -05:00
Nicholas Bollweg
a45588ce7c Merge remote-tracking branch 'upstream/develop' into gh-5747-rendering-util-types 2024-08-30 08:54:05 -05:00
Nicholas Bollweg
a176d64389 fix import 2024-08-30 08:51:40 -05:00
Nicholas Bollweg
e519686f01 Merge remote-tracking branch 'upstream/master' 2024-08-30 08:38:58 -05:00
Nicholas Bollweg
59d6f04e4b make rendering-util/types a real ts file 2024-08-30 08:38:41 -05:00
autofix-ci[bot]
249e18314a [autofix.ci] apply automated fixes 2024-08-29 09:36:26 +00:00
Sidharth Vinod
115cb071b0 Merge pull request #5791 from robsonpiere/patch-1
Added Microsoft Loop to Integrations docs
2024-08-29 14:58:13 +05:30
Sidharth Vinod
49323aa05b Merge branch 'develop' into sidv/iconify
* develop:
  [autofix.ci] apply automated fixes
  test: Skip architecture diagram
  [autofix.ci] apply automated fixes
  fix: Update diagram keyword
2024-08-29 14:57:16 +05:30
Sidharth Vinod
d279f4e905 Merge pull request #5789 from mermaid-js/sidv/architecture
chore: Remove public APIs for icons
2024-08-29 14:56:47 +05:30
autofix-ci[bot]
eaae85c6f8 [autofix.ci] apply automated fixes 2024-08-29 08:38:25 +00:00
Sidharth Vinod
62feced97a Merge branch 'develop' into patch-1 2024-08-29 14:05:12 +05:30
Sidharth Vinod
d51ea942e1 Merge branch 'develop' into sidv/architecture
* develop:
  test: Skip architecture diagram
  [autofix.ci] apply automated fixes
  fix: Update diagram keyword
2024-08-29 14:03:20 +05:30
Sidharth Vinod
4ac7c5edbb Merge pull request #5792 from mermaid-js/sidv/fixArchitectureTest
fix: Update diagram keyword
2024-08-29 14:01:56 +05:30
Sidharth Vinod
7d8143b917 docs: Changeset 2024-08-29 14:01:26 +05:30
Sidharth Vinod
790f71bb1a feat: Move architecture icons into iconify format 2024-08-29 14:00:21 +05:30
Sidharth Vinod
a4b7e494db feat: Support - in icon names 2024-08-29 13:56:43 +05:30
Sidharth Vinod
6ecdf7be68 docs: Changeset 2024-08-29 13:56:16 +05:30
Sidharth Vinod
e0f7ea56e1 fix: Unknown icon size 2024-08-29 13:55:20 +05:30
Sidharth Vinod
0ea88df662 feat: Add iconify support 2024-08-29 13:25:45 +05:30
Sidharth Vinod
1b69e121dc Merge branch 'sidv/fixArchitectureTest' of https://github.com/mermaid-js/mermaid into sidv/fixArchitectureTest
* 'sidv/fixArchitectureTest' of https://github.com/mermaid-js/mermaid:
  [autofix.ci] apply automated fixes
2024-08-29 13:03:11 +05:30
Sidharth Vinod
45e2366b5b test: Skip architecture diagram 2024-08-29 13:03:02 +05:30
autofix-ci[bot]
5309d21588 [autofix.ci] apply automated fixes 2024-08-29 07:13:36 +00:00
Sidharth Vinod
c17f9be10e fix: Update diagram keyword 2024-08-29 12:38:33 +05:30
autofix-ci[bot]
a568f51024 [autofix.ci] apply automated fixes 2024-08-28 18:15:48 +00:00
Robson Piere
8af76c2608 Adding Microsoft Loop 2024-08-28 15:04:48 -03:00
Sidharth Vinod
dccd6121b1 chore: Remove aws icons from demo 2024-08-28 21:37:54 +05:30
autofix-ci[bot]
0ecdbf4374 [autofix.ci] apply automated fixes 2024-08-28 15:55:38 +00:00
Sidharth Vinod
10bdc8dde6 docs: Remove icons docs 2024-08-28 21:18:52 +05:30
Sidharth Vinod
16faef4613 docs: Changeset 2024-08-28 21:14:30 +05:30
Sidharth Vinod
bed6c5dd0c chore: Move icons to architecture
We are planning to release an icons library separately. Till we figure out the APIs for those, all external surface for icons are removed, to avoid making a breaking change when the new library comes.
2024-08-28 21:04:21 +05:30
Sidharth Vinod
08d59d3d2b chore: Align export syntax 2024-08-28 19:06:47 +05:30
Ashish Jain
30d4632a0b Prettier lint fixes 2024-08-28 14:50:31 +02:00
Knut Sveidqvist
904410f7f7 Updated changeset for parser package from the new architecture diagram 2024-08-28 14:26:11 +02:00
Knut Sveidqvist
fda092bbe9 Fix for merge realted error in pnpm-lock and langium-config 2024-08-28 14:24:00 +02:00
Knut Sveidqvist
75e8119c6a Merge pull request #5452 from NicolasNewman/5367/architecture-diagram
New Diagram: Architecture
2024-08-28 14:18:34 +02:00
Knut Sveidqvist
80c4b24803 Merge branch 'develop' into 5367/architecture-diagram 2024-08-28 14:14:49 +02:00
Knut Sveidqvist
dd00575d57 Merge branch 'develop' of github.com:mermaid-js/mermaid into develop 2024-08-28 14:11:05 +02:00
Knut Sveidqvist
33a809f09a Change set for PR yesterday 2024-08-28 14:10:36 +02:00
Ashish Jain
28bd07fdeb added changeset for recent PR 2024-08-28 14:07:29 +02:00
Sidharth Vinod
5293b63f30 Merge branch 'master' of https://github.com/mermaid-js/mermaid into develop
* 'master' of https://github.com/mermaid-js/mermaid:
  docs: Add argos in intro
  docs: Add argos in readme
2024-08-28 17:23:44 +05:30
Knut Sveidqvist
256a148bbf Adding changeset for acrhitecture diagrams 2024-08-28 13:52:51 +02:00
Knut Sveidqvist
eee1be474a Merge branch 'develop' into 5367/architecture-diagram 2024-08-28 13:50:05 +02:00
Ashish Jain
0bd00764c4 Merge pull request #5786 from mermaid-js/5782_supporting_defaultRenderer
#5782 fix: adding backwards compatability for defaultRenderer directive
2024-08-28 13:40:56 +02:00
Knut Sveidqvist
011c036350 #5782 fix: adding backwards compatability for defaultRenderer directive 2024-08-28 13:07:11 +02:00
Sidharth Vinod
8c8ed571d5 Merge pull request #5778 from mermaid-js/renovate/all-minor
chore(deps): update all minor dependencies (minor)
2024-08-28 06:04:11 +00:00
renovate[bot]
b0f7abb3a9 chore(deps): update all minor dependencies 2024-08-28 04:56:58 +00:00
Ashish Jain
2d481c4b73 Merge pull request #5780 from mermaid-js/knsv/elk-tweaks
Adjustments of the elk configuration
2024-08-27 08:37:56 +02:00
autofix-ci[bot]
5744c8614a [autofix.ci] apply automated fixes 2024-08-26 14:02:56 +00:00
Knut Sveidqvist
9cf562476a Tweaking the elk config and exposing elk.layered.cycleBreaking.strategy to mermaid configuration 2024-08-26 15:34:14 +02:00
Knut Sveidqvist
3b3b599c38 #4866, #5255 fix: Fixes bug for links to self for nodes inside a cluster 2024-08-26 14:39:38 +02:00
Sidharth Vinod
10e05f352e Merge pull request #5776 from mermaid-js/renovate/patch-all-patch
chore(deps): update all patch dependencies (patch)
2024-08-26 16:46:59 +05:30
renovate[bot]
43b0e808d0 chore(deps): update all patch dependencies 2024-08-26 08:40:14 +00:00
Sidharth Vinod
286a3474d9 Merge pull request #5772 from adjerbetian/patch-1
Fix tutorials.md with url safe encoding
2024-08-26 05:12:02 +00:00
Sidharth Vinod
5aa8490e9e Merge pull request #5777 from mermaid-js/renovate/patch-eslint
chore(deps): update eslint (patch)
2024-08-26 05:09:31 +00:00
autofix-ci[bot]
49a77dda3a [autofix.ci] apply automated fixes 2024-08-26 05:00:41 +00:00
Sidharth Vinod
0049372b2e chore: Add urlsafe to cspell 2024-08-26 10:25:56 +05:30
Sidharth Vinod
293411b005 Merge pull request #5773 from mmorel-35/openssf/pinned-dependencies
chore: pin dependencies
2024-08-26 04:53:49 +00:00
renovate[bot]
efc1a0adc4 chore(deps): update eslint 2024-08-26 04:39:18 +00:00
Sidharth Vinod
e3b2a6949c Merge pull request #5711 from NicolasNewman/multi-line-er-label
feat(er): allow multi-line relationship labels
2024-08-26 04:22:49 +00:00
NicolasNewman
dbd4658028 Merge branch 'multi-line-er-label' of https://github.com/NicolasNewman/mermaid into multi-line-er-label 2024-08-25 12:53:28 -05:00
NicolasNewman
940146fadd tests(er): added test for line breaks 2024-08-25 12:52:29 -05:00
Matthieu MOREL
efcafd99db chore: pin dependencies 2024-08-25 09:01:32 +00:00
Alexandre Djerbetian
6585380a4c Fix tutorials.md with url safe encoding
See https://github.com/jihchi/mermaid.ink/issues/396 for the bug that this PR fixes.
2024-08-25 11:18:27 +03:00
Sidharth Vinod
f653510d1b Merge pull request #5767 from mermaid-js/sidv/huskyUpdate
chore: Fix husky deprecation notice
2024-08-25 04:08:48 +00:00
Sidharth Vinod
541cf251b2 Merge pull request #5770 from mermaid-js/sidv/argos
Add argos to website
2024-08-25 09:38:14 +05:30
Sidharth Vinod
755394ac9a docs: Add argos in intro 2024-08-25 09:36:50 +05:30
Matthieu MOREL
2f1cfe0a31 Merge pull request #5763 from mmorel-35/dockerfile
chore: use corepack to install pnpm
2024-08-24 21:45:04 +00:00
Sidharth Vinod
e0253b7768 Merge pull request #5769 from mermaid-js/sidv/argos
docs: Add argos in readme
2024-08-25 01:48:51 +05:30
Sidharth Vinod
5d1b50cb65 docs: Add argos in readme 2024-08-25 01:47:48 +05:30
autofix-ci[bot]
ed352debd5 [autofix.ci] apply automated fixes 2024-08-24 16:08:16 +02:00
Matthieu MOREL
221aedc5f5 chore: use corepack to install pnpm 2024-08-24 16:08:16 +02:00
Sidharth Vinod
4b195d17ed Merge branch 'master' into develop
* master:
  feat: Add verification script for MERMAID_RELEASE_VERSION
  fix: Revert accidental change of MERMAID_RELEASE_VERSION
2024-08-24 18:17:26 +05:30
Sidharth Vinod
8e640da543 docs: Add changeset 2024-08-24 18:03:45 +05:30
Sidharth Vinod
559be33ff7 docs: Fix <MERMAID_RELEASE_VERSION> 2024-08-24 18:01:34 +05:30
Sidharth Vinod
0bfb42c68f feat: Add verification script for MERMAID_RELEASE_VERSION 2024-08-24 17:57:36 +05:30
Sidharth Vinod
c95e997f8f fix: Revert accidental change of MERMAID_RELEASE_VERSION 2024-08-24 17:31:37 +05:30
Sidharth Vinod
baafaf09df Merge branch 'develop' into multi-line-er-label 2024-08-24 17:24:16 +05:30
Sidharth Vinod
db1b055c7f chore: Update husky 2024-08-24 17:21:21 +05:30
Sidharth Vinod
5ba5e30a47 Merge pull request #5766 from mermaid-js/fix/scorecard
fix: correct target branch for scorecard
2024-08-24 17:11:16 +05:30
Matthieu MOREL
7e9946eea8 fix: correct target branch for scorecard 2024-08-24 13:31:03 +02:00
Sidharth Vinod
cac60db775 Merge pull request #5764 from mmorel-35/scorecard
chore: define scorecard workflow
2024-08-24 10:24:57 +00:00
Matthieu MOREL
83ee06e9e7 chore: define scorecard workflow 2024-08-24 10:10:06 +00:00
Sidharth Vinod
534d3dd038 Merge branches 'develop' and 'develop' of https://github.com/mermaid-js/mermaid into develop
* 'develop' of https://github.com/mermaid-js/mermaid:
  chore: Merge checks into lint
  chore: Remove unnecessary workflows
  chore: Add concurrency for actions
  chore: Skip running E2E on Push to most branches

* 'develop' of https://github.com/mermaid-js/mermaid:
  chore: Merge checks into lint
  chore: Remove unnecessary workflows
  chore: Add concurrency for actions
  chore: Skip running E2E on Push to most branches
2024-08-24 15:37:27 +05:30
Sidharth Vinod
f6333b4a5e chore: Add provenance flag 2024-08-24 15:34:53 +05:30
Matthieu MOREL
ac58785f50 Merge pull request #5765 from mermaid-js/sidv/reduceDuplicateCI
chore: reduce redundant CI runs
2024-08-24 09:53:03 +00:00
Sidharth Vinod
d08ef70e0f chore: Merge checks into lint 2024-08-24 15:06:08 +05:30
Sidharth Vinod
ab4ba4cf2f chore: Remove unnecessary workflows 2024-08-24 15:05:28 +05:30
Sidharth Vinod
1a1f668745 chore: Add concurrency for actions 2024-08-24 15:04:14 +05:30
Sidharth Vinod
5c59505589 chore: Skip running E2E on Push to most branches 2024-08-24 14:51:23 +05:30
Sidharth Vinod
95274278f3 Merge pull request #5762 from mermaid-js/changeset-release/master
Version Packages
2024-08-24 12:48:56 +05:30
github-actions[bot]
1532721f09 Version Packages 2024-08-24 07:17:31 +00:00
Sidharth Vinod
5db856cfb0 Merge pull request #5761 from mermaid-js/sidv/fixElkTypes
fix: Type file path for layout-elk
2024-08-24 12:41:56 +05:30
Sidharth Vinod
b34dfe8f45 fix: Type file path for layout-elk 2024-08-24 12:41:18 +05:30
Sidharth Vinod
158f9925ce Merge pull request #5760 from mermaid-js/changeset-release/master
Version Packages
2024-08-24 12:33:37 +05:30
github-actions[bot]
dab8f08a9e Version Packages 2024-08-24 07:03:01 +00:00
Sidharth Vinod
313fefe704 Merge pull request #5759 from mermaid-js/develop
Release v11.0.1
2024-08-24 12:31:04 +05:30
Sidharth Vinod
17c0af1cf6 chore: Update flowchart widths 2024-08-24 12:18:19 +05:30
Sidharth Vinod
1bb09cc9a8 Merge branch 'master' into develop
* master:
  Correct casing for hand drawn
  Updates to information about the new syntax
  Updating syntax reference with code showing how to select layout and look.
  [autofix.ci] apply automated fixes
  Version Packages (#5749)
  Bump mermaid minor
  Revert mermaid version
  add latest blog posts
  Version Packages (#5748)
  Test changeset (#5746)
  Version Packages
  Release parser, v0.1.0, test changeset
2024-08-24 11:55:07 +05:30
Sidharth Vinod
1e35341136 Merge pull request #5758 from mermaid-js/sidv/fixTypes
fix: layout-elk types
2024-08-24 11:53:47 +05:30
Sidharth Vinod
501a55d8f2 chore: Add changeset 2024-08-24 11:51:33 +05:30
Sidharth Vinod
ca57235e05 fix: Type export for layout-elk 2024-08-24 11:51:00 +05:30
Knut Sveidqvist
a4e1479759 Correct casing for hand drawn 2024-08-23 17:49:52 +02:00
Knut Sveidqvist
e499f700ce Updates to information about the new syntax 2024-08-23 17:41:24 +02:00
Knut Sveidqvist
d559fcc90b Updating syntax reference with code showing how to select layout and look. 2024-08-23 17:35:44 +02:00
Sidharth Vinod
35b98f2b89 Merge pull request #5750 from mermaid-js/add-blog-posts
DOCS: add latest blog posts
2024-08-23 20:40:16 +05:30
autofix-ci[bot]
e391966de8 [autofix.ci] apply automated fixes 2024-08-23 15:08:07 +00:00
Steph
f04fa4d565 add latest blog posts 2024-08-23 07:58:53 -07:00
Sidharth Vinod
a10f46932f Merge pull request #5664 from Austin-Fulbright/feature/4401_creating_langium_parser_gitGraph
Feature/4401 creating langium parser git graph
2024-08-23 19:11:51 +05:30
Sidharth Vinod
5deaef456e chore: add changeset 2024-08-23 19:11:04 +05:30
Sidharth Vinod
a87f77339e Merge branch 'develop' into pr/Austin-Fulbright/5664
* develop: (34 commits)
  Mermaid version v11.0.0
  Run release drafter in release branch
  [autofix.ci] apply automated fixes
  Updating syntax reference with code showing how to select layout and look.
  Fix for selecting the correct diagramPadding for flowcharts
  Fix for turned arrow heads and missing edge sections
  Basic fix for handdrawn subgraph styling
  Fix for text alignment issue
  #5237 Fix for weird line intersection
  Fix config
  docs: Update release version
  docs: Update release version
  chore: Revert version changes
  chore: Revert version changes
  Remove release drafter
  Add changeset to PR template
  Remove changesets
  Update changelog format
  verify Docs version before publish
  chore: Remove prePublishOnly
  ...
2024-08-23 19:08:46 +05:30
Sidharth Vinod
6c89763ef9 Merge branch 'develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-23 17:40:08 +05:30
Austin-Fulbright
52f5d95c81 Merge branch 'develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-21 12:33:18 -04:00
autofix-ci[bot]
792a62438f [autofix.ci] apply automated fixes 2024-08-21 15:35:41 +00:00
NicolasNewman
73e16d0857 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 5367/architecture-diagram 2024-08-21 10:28:57 -05:00
Austin Fulbright
24ba5b73da added gitgraph in imperative state 2024-08-20 14:53:01 -04:00
Austin-Fulbright
8ef30a2642 Merge branch 'develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-20 14:34:50 -04:00
Austin Fulbright
3ac242978d fixed merge 2024-08-20 07:04:05 -04:00
Austin Fulbright
94ee076aad fixed config for user configs 2024-08-20 06:46:33 -04:00
Austin Fulbright
7a7b41557d implemented transfer objects from parser to db 2024-08-20 06:37:49 -04:00
Austin Fulbright
d9d9cc9ddc added objects to be transfered from parser to db 2024-08-20 06:06:19 -04:00
Austin-Fulbright
4ec0dcfe1f Merge branch 'develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-20 06:03:37 -04:00
Austin Fulbright
b93691be0e fixed small error 2024-08-20 01:00:13 -04:00
Austin Fulbright
66e53df04b added most suggested changes 2024-08-20 00:30:01 -04:00
NicolasNewman
95c483934d feat(arch): all non-generic icon packs are now lazy loaded 2024-08-19 17:40:12 -05:00
Austin Fulbright
53798beb96 fixed gitgraphconfig problem 2024-08-15 16:10:20 -04:00
NicolasNewman
d36522648f fix(arch): async/await fixes for drawText changes 2024-08-14 10:32:56 -05:00
NicolasNewman
1df90b4a05 build(arch): pnpm-lock update 2024-08-14 10:11:56 -05:00
NicolasNewman
8f970cddf9 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 5367/architecture-diagram 2024-08-14 10:08:30 -05:00
Austin Fulbright
299e559aa5 added config as global 2024-08-14 10:51:45 -04:00
autofix-ci[bot]
3539a35578 [autofix.ci] apply automated fixes 2024-08-13 21:56:26 +00:00
Austin-Fulbright
d50150cbfa Merge branch 'mermaid-js:develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-13 17:51:21 -04:00
NicolasNewman
731b330e40 docs(er): added min version req to multi-line labels 2024-08-12 16:00:44 -05:00
NicolasNewman
261aea3089 feat(er): allow multi-line relationship labels 2024-08-12 15:51:03 -05:00
Austin-Fulbright
73aae9e86e Merge branch 'develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-12 09:13:42 -04:00
Austin Fulbright
d73a090875 fixed BT with parallel commits and added more unit tests 2024-08-10 23:33:23 -04:00
Austin Fulbright
aba306b685 fixed highlight color 2024-08-10 12:32:25 -04:00
Austin Fulbright
c49a1bf60c fixed custom type REVERSE for merge, fixed branch spacing for TB 2024-08-10 12:28:24 -04:00
Austin Fulbright
d684e0d924 added more unit tests to gitGraphParser.ts and gitGraphRenderer.ts 2024-08-10 11:22:22 -04:00
Austin Fulbright
269284c6d7 added parser unit tests and organized config in gitGraphAst.ts 2024-08-10 10:37:24 -04:00
Austin Fulbright
62950c31a4 finished gitGraphRenderer.ts 2024-08-10 07:46:10 -04:00
Austin-Fulbright
e0d0cdcf1f Merge branch 'mermaid-js:develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-10 06:15:10 -04:00
Austin Fulbright
a93b8324ad made draw commit more readable, included more helper functions and interfaces, added in-source test suite to renderer 2024-08-10 06:08:57 -04:00
Austin Fulbright
2218929416 fixed types in gitGraphTypes 2024-08-08 19:49:09 -04:00
Austin Fulbright
00603e7bac fix ts-ignore errors refactored large functions to use helpers 2024-08-08 18:47:34 -04:00
Austin Fulbright
8fe0ed1d03 added parser test and combined the two gitGraph tests 2024-08-06 19:26:13 -04:00
Austin Fulbright
38e048b94e fixed hash error loc & line properties 2024-08-05 14:08:42 -04:00
Austin Fulbright
2a38d46fd9 fixed the rest of the concerns, refactored portions of the gitGraphParser test to handle async actions 2024-08-05 13:53:51 -04:00
Austin-Fulbright
9ed38ccf3a Merge branch 'mermaid-js:develop' into feature/4401_creating_langium_parser_gitGraph 2024-08-05 11:07:32 -04:00
Austin-Fulbright
f30085c47e Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:06:43 -04:00
Austin-Fulbright
ef25160b8e Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:06:09 -04:00
Austin-Fulbright
6c1e5aae92 Delete packages/mermaid/src/diagrams/git/parser/gitGraph.jison 2024-07-27 04:05:34 -04:00
Austin-Fulbright
a0207f9195 Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:04:23 -04:00
Austin-Fulbright
346efdd384 Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:04:16 -04:00
Austin-Fulbright
6e5e5f9c61 Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:03:25 -04:00
Austin-Fulbright
871f0478c6 Update packages/parser/src/language/gitGraph/module.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:03:19 -04:00
Austin-Fulbright
5dfc94e6f5 Update packages/mermaid/src/diagrams/git/gitGraphParser.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:03:14 -04:00
Austin-Fulbright
ec2d9c9a08 Update packages/mermaid/src/diagrams/git/gitGraphParser.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:03:08 -04:00
Austin-Fulbright
281064f714 Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:03:02 -04:00
Austin-Fulbright
62757c529f Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:02:56 -04:00
Austin-Fulbright
e57fee1f37 Update packages/mermaid/src/diagrams/git/gitGraphAst.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-07-27 04:02:49 -04:00
Austin Fulbright
0d4c3e5f72 added unit tests for gitGraph parser 2024-07-27 03:51:28 -04:00
Austin Fulbright
275dbe9b2e fixed all rendering differences 2024-07-27 02:02:12 -04:00
Austin-Fulbright
9f6a7b79ac allows for custom merge type 2024-07-27 00:49:07 -04:00
Austin Fulbright
3168084cf5 fixed rendering 2024-07-27 00:03:59 -04:00
Austin Fulbright
887e5803d8 fixed some features and added propper default direction 2024-07-26 23:28:07 -04:00
Austin Fulbright
a386bd0b74 fixed tags for gitGraph 2024-07-26 22:55:40 -04:00
Austin Fulbright
1af90946bc fixed options e2e test 2024-07-25 06:22:37 -04:00
Austin Fulbright
d0eadebb99 added parser 2024-07-25 05:25:19 -04:00
Austin Fulbright
1a95d48852 added Imperative state 2024-07-24 13:53:00 -04:00
Austin Fulbright
0b67cffdfa fixed cherrypicking tests 2024-07-24 10:37:21 -04:00
Austin Fulbright
d2e2017907 fixed undefined for type errors 2024-07-24 09:48:46 -04:00
Austin Fulbright
5460bc0a0c fixed checkout branch with no commits 2024-07-24 07:13:10 -04:00
Austin-Fulbright
ebd4da95cd Merge branch 'mermaid-js:develop' into feature/4401_creating_langium_parser_gitGraph 2024-07-24 05:37:54 -04:00
NicolasNewman
880c4d4ed6 feat(arch): updated syntax & demos/docs to reflect changes 2024-07-23 15:31:11 -05:00
NicolasNewman
2ae2686e34 chore(arch): merged with develop 2024-07-22 11:17:52 -05:00
NicolasNewman
25609d69c4 chore(arch): changed syntax keyword from architecture -> architecture-beta 2024-07-22 09:34:36 -05:00
Austin Fulbright
6f7c291512 Added gitGraphAst as typescript added gitGraphTypes and gitGraphParser 2024-07-22 04:10:36 -04:00
Austin Fulbright
1d0e98dd62 Added the langium module for gitGraph 2024-07-21 19:50:59 -04:00
Sidharth Vinod
33da2b4057 Merge branch 'develop' into 5367/architecture-diagram 2024-07-18 15:47:30 +05:30
autofix-ci[bot]
8fe2d7c2e6 [autofix.ci] apply automated fixes 2024-07-18 10:03:35 +00:00
Sidharth Vinod
e099e7d220 Merge branch 'develop' into pr/NicolasNewman/5452
* develop:
  [autofix.ci] apply automated fixes
  [autofix.ci] apply automated fixes
  docs: Test autofix.ci
  chore: Remove update step from lint.yml
  Add autofix.ci
  fix: double space in wrapped sequence diagram messages
  chore: update browsers list
  chore(deps): update eslint
  chore: move abs below return check
  fix: Handle negative numbers in `formatBytes`
  chore: Use single quotes
  fix: emphasis => em
  fix: Handle spaces after newline
  test: Change emphasis to em
  chore: Fix emphasis type
  feat: Use marked instead of mdast-util-from-markdown
  Add Madness to integrations-community.md
2024-07-18 15:29:14 +05:30
NicolasNewman
7c1cb236a6 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 5367/architecture-diagram 2024-07-13 22:12:52 -05:00
NicolasNewman
d9b16953f0 refactor(arch): linting/doc strings for icon related code 2024-07-09 15:59:21 -05:00
NicolasNewman
78dfffa1a7 docs(arch): icon documentation 2024-07-09 15:51:03 -05:00
NicolasNewman
ce2f834683 fix(arch): fixed broken lockfile 2024-07-06 17:36:23 -05:00
NicolasNewman
4d6b92e5bf fix(arch): correctly utilize calculated style 2024-07-06 17:32:07 -05:00
NicolasNewman
1b128cae77 fix(arch): fixed error from eslint refactor 2024-07-06 16:46:49 -05:00
NicolasNewman
a94fe811dd docs(arch): included junction nodes in docs 2024-07-06 16:34:03 -05:00
NicolasNewman
bde35b23f7 chore(arch): merge with develop 2024-07-06 16:24:08 -05:00
NicolasNewman
3f039562b9 chore(arch): merge with develop 2024-05-22 10:08:12 -05:00
NicolasNewman
a4cf503071 chore(arch): fixed typos in demo 2024-05-22 09:26:54 -05:00
NicolasNewman
2315d3c90a test(arch): added test cases for architecture diagrams 2024-05-22 09:25:50 -05:00
NicolasNewman
e9ca618780 style(arch): arenamed icon files to match convention 2024-05-20 11:17:08 -05:00
NicolasNewman
7c9e0e51da feat(arch): added github, digital ocean icons 2024-05-20 11:15:09 -05:00
NicolasNewman
769d5660f5 feat(arch): added aws icons 2024-05-16 11:26:58 -05:00
NicolasNewman
b09dc5db67 feat(arch): implemented junction nodes 2024-05-10 10:10:19 -05:00
NicolasNewman
0049127bb7 chore(arch): synced with develop 2024-05-09 14:40:57 -05:00
NicolasNewman
5b6c95cea3 feat(arch): extended parser to control how edges pass through groups 2024-05-09 14:33:44 -05:00
NicolasNewman
48e6901936 style(arch): linting 2024-05-06 10:04:17 -05:00
NicolasNewman
734bde3877 feat(arch): implemented node labels 2024-05-06 09:59:33 -05:00
NicolasNewman
cabb7b65e9 fix(arch): Y axis labels rotated to opposite side 2024-04-29 10:05:36 -05:00
NicolasNewman
b21fc9fe22 feat(arch): added theme variables for dark and default 2024-04-22 10:49:36 -05:00
NicolasNewman
634b00b379 feat(arch): changed how groups are stored in the db 2024-04-22 10:49:06 -05:00
NicolasNewman
b28f9f8136 docs(arch): started writing diagram documentation 2024-04-22 10:48:01 -05:00
NicolasNewman
ac7891c14b feat(arch): improved handling of styles 2024-04-19 12:30:55 -05:00
NicolasNewman
2dfadca14c style(arch): disabled no-console for archDB, removed jison 2024-04-18 10:05:45 -05:00
NicolasNewman
84f1d82aac feat(arch): implemented group icons 2024-04-18 10:03:20 -05:00
NicolasNewman
1d27fac4d9 feat(arch): added cloud svg icon 2024-04-17 15:12:05 -05:00
NicolasNewman
a5d3164ea4 feat(arch): edge labels implemented 2024-04-17 12:27:53 -05:00
NicolasNewman
cb302a08b8 feat(arch): converted parser from jison to langium 2024-04-15 15:42:05 -05:00
NicolasNewman
497712a3fa feat(arch): exposed createIcon function 2024-04-11 12:22:34 -05:00
NicolasNewman
073175e5f7 style(arch): resolved es-lint errors 2024-04-11 12:21:44 -05:00
NicolasNewman
6bd1da219a refactor(arch): refactored code to be consistent with other diagrams 2024-04-07 17:27:01 -05:00
NicolasNewman
0a14f2cffc fix(arch): changed how cytoscape namespace extension is handled to prevent conflicts 2024-04-06 22:50:25 -05:00
NicolasNewman
dcb1b4871f merge(arch): : Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 5367/architecture-diagram 2024-04-06 22:03:46 -05:00
NicolasNewman
baa6c9e4b9 test(arch): added edge arrow demo 2024-04-06 21:55:06 -05:00
NicolasNewman
251e808a29 build(arch): expose SVG library types 2024-04-06 21:51:35 -05:00
NicolasNewman
22c0090979 refactor(arch): reorganized code and added more documentation 2024-04-06 21:46:37 -05:00
NicolasNewman
aef991bc49 feat(arch): XY edges now have a 90deg bend 2024-04-06 21:12:30 -05:00
NicolasNewman
6d5791a63a feat(arch): arrows now rendered on diagram 2024-04-04 13:47:01 -05:00
NicolasNewman
2709c2d2e1 style(arch): prettier formatting & code cleanup 2024-04-04 09:58:58 -05:00
NicolasNewman
92d819ede5 feat(arch): disconnected graph handling for positioning 2024-04-03 15:32:43 -05:00
NicolasNewman
36f52be3bf feat(arch): improved positioning system to better handle edge cases 2024-04-03 13:45:41 -05:00
NicolasNewman
f47bbee24a feat(arch): added more demos 2024-03-31 13:54:06 -05:00
NicolasNewman
361e25ba34 fix(arch): fixed compound nodes overlapping 2024-03-31 13:53:34 -05:00
NicolasNewman
46a37a6eea feat(arch): added fallback icon 2024-03-31 13:52:23 -05:00
NicolasNewman
b911bd3e42 feat(arch): improved error handling 2024-03-31 13:51:40 -05:00
NicolasNewman
10682ef31f feat(arch): added demo 2024-03-27 09:31:06 -05:00
NicolasNewman
0ab7a3d8ec feat(arch): added 4 default icons, added config field for icons 2024-03-27 09:30:44 -05:00
NicolasNewman
a493e2fbb3 feat(arch): dynamic node sizing 2024-03-25 14:17:05 -05:00
NicolasNewman
5e214877a4 style(arch): prettier formatting 2024-03-19 13:57:37 -05:00
NicolasNewman
84bd20b04b feat(arch): improved group rendering 2024-03-17 15:24:17 -05:00
NicolasNewman
6c6ce28f7d feat(arch): implemented basic rendering for diagram 2024-03-13 09:25:20 -05:00
NicolasNewman
e01acec12b feat(arch): implemented icon registration 2024-03-13 09:24:04 -05:00
NicolasNewman
cc22e13e71 feat: added architecture DB & types 2024-03-11 15:14:21 -05:00
NicolasNewman
346ae22108 feat: registered architecture diagram 2024-03-11 15:13:05 -05:00
NicolasNewman
5a4d4972e2 feat: added architecture diagram parser 2024-03-11 13:41:43 -05:00
NicolasNewman
03bbfa0b94 build: added cytoscape-fcose 2024-03-11 13:37:47 -05:00
175 changed files with 10306 additions and 4768 deletions

View File

@@ -25,6 +25,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
'sankey',
'block',
'packet',
'architecture',
] as const;
/**

View File

@@ -55,6 +55,7 @@ GENERICTYPE
getBoundarys
grammr
graphtype
halign
iife
interp
introdcued
@@ -66,6 +67,7 @@ Kaufmann
keyify
LABELPOS
LABELTYPE
layoutstop
lcov
LEFTOF
Lexa
@@ -138,6 +140,7 @@ tsdoc
typeof
typestr
unshift
urlsafe
verifymethod
VERIFYMTHD
WARN_DOCSDIR_DOESNT_MATCH

View File

@@ -24,11 +24,13 @@ Doctave
DokuWiki
dompurify
elkjs
fcose
fontawesome
Foswiki
Gitea
graphlib
Grav
icones
iconify
Inkdrop
jiti

View File

@@ -1,5 +1,6 @@
Adamiecki
arrowend
Bendpoints
bmatrix
braintree
catmull

View File

@@ -4,3 +4,4 @@ handDrawn
KOEPF
neato
newbranch
validify

View File

@@ -7,17 +7,19 @@ on:
permissions:
contents: read
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -40,4 +42,4 @@ jobs:
working-directory: ./packages/mermaid
run: pnpm run docs:build
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c # main

View File

@@ -8,6 +8,8 @@ on:
pull_request:
merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
@@ -16,12 +18,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -1,49 +0,0 @@
name: Build
on:
push: {}
merge_group:
pull_request:
types:
- opened
- synchronize
- ready_for_review
permissions:
contents: read
jobs:
build-mermaid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: |
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Build
run: pnpm run build
- name: Upload Mermaid Build as Artifact
uses: actions/upload-artifact@v4
with:
name: mermaid-build
path: packages/mermaid/dist
- name: Upload Mermaid Mindmap Build as Artifact
uses: actions/upload-artifact@v4
with:
name: mermaid-mindmap-build
path: packages/mermaid-mindmap/dist

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Check for difference in README.md and docs/README.md
run: |

View File

@@ -1,26 +0,0 @@
on:
push:
merge_group:
pull_request:
types:
- opened
- synchronize
- ready_for_review
name: Static analysis on Test files
jobs:
check-tests:
runs-on: ubuntu-latest
name: check tests
if: github.repository_owner == 'mermaid-js'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: testomatio/check-tests@stable
with:
framework: cypress
tests: './cypress/e2e/**/**.spec.js'
token: ${{ secrets.GITHUB_TOKEN }}
has-tests-label: true

View File

@@ -11,6 +11,9 @@ on:
- synchronize
- ready_for_review
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
analyze:
name: Analyze
@@ -29,11 +32,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
with:
config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
@@ -45,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -59,4 +62,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5

View File

@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4

View File

@@ -11,6 +11,8 @@ on:
default: master
description: 'Parent branch to use for PRs'
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
@@ -30,13 +32,13 @@ jobs:
run: |
echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.node-version'
@@ -52,7 +54,7 @@ jobs:
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
- name: Cypress run
uses: cypress-io/github-action@v4
uses: cypress-io/github-action@d79d2d530a66e641eb4a5f227e13bc985c60b964 # v4.2.2
id: cypress
with:
start: pnpm run dev

View File

@@ -2,11 +2,15 @@ name: E2E
on:
push:
branches-ignore:
- 'gh-readonly-queue/**'
branches:
- develop
- master
- release/**
pull_request:
merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
@@ -32,15 +36,15 @@ jobs:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.node-version'
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache@v4
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
save-always: true
path: ./cypress/snapshots
@@ -49,13 +53,13 @@ jobs:
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
- name: Switch to base branch
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
ref: ${{ env.targetHash }}
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
with:
# just perform install
runTests: false
@@ -78,26 +82,26 @@ jobs:
matrix:
containers: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version-file: '.node-version'
# These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache/restore@v4
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
with:
runTests: false
@@ -113,7 +117,7 @@ jobs:
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
# Otherwise (e.g. if running from fork), we run on a single container only
@@ -137,7 +141,7 @@ jobs:
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
# Run step only pushes to develop and pull_requests
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
with:

View File

@@ -4,11 +4,17 @@ on:
issues:
types: [opened]
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
triage:
permissions:
issues: write # for andymckay/labeler to label issues
pull-requests: write # for andymckay/labeler to label PRs
runs-on: ubuntu-latest
steps:
- uses: andymckay/labeler@1.0.4
- uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
add-labels: 'Status: Triage'

View File

@@ -19,6 +19,9 @@ on:
# * is a special character in YAML so you have to quote this string
- cron: '30 8 * * *'
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
link-checker:
runs-on: ubuntu-latest
@@ -26,17 +29,17 @@ jobs:
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Restore lychee cache
uses: actions/cache@v4
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
- name: Link Checker
uses: lycheeverse/lychee-action@v1.9.3
uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a # v1.9.3
with:
args: >-
--config .github/lychee.toml

View File

@@ -4,26 +4,32 @@ on:
push:
merge_group:
pull_request:
types:
- opened
- synchronize
- ready_for_review
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
docker-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
with:
verbose: true
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -82,3 +88,10 @@ jobs:
working-directory: ./packages/mermaid
continue-on-error: ${{ github.event_name == 'push' }}
run: pnpm run docs:verify
- uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable
with:
framework: cypress
tests: './cypress/e2e/**/**.spec.js'
token: ${{ secrets.GITHUB_TOKEN }}
has-tests-label: true

View File

@@ -22,7 +22,7 @@ jobs:
pull-requests: write # write permission is required to label PRs
steps:
- name: Label PR
uses: release-drafter/release-drafter@v6
uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v6.0.0
with:
config-name: pr-labeler.yml
disable-autolabeler: false

View File

@@ -23,12 +23,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -37,13 +37,13 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
- name: Run Build
run: pnpm --filter mermaid run docs:build:vitepress
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:
path: packages/mermaid/src/vitepress/.vitepress/dist
@@ -56,4 +56,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5

View File

@@ -9,14 +9,14 @@ jobs:
publish-preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -28,7 +28,7 @@ jobs:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Install Json
run: npm i json --global
run: npm i json@11.0.0 --global
- name: Publish
working-directory: ./packages/mermaid

42
.github/workflows/release-preview.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Preview release
on:
pull_request:
branches: [develop]
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
contents: read
actions: write
jobs:
preview:
if: ${{ github.repository_owner == 'mermaid-js' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
name: Publish preview release
timeout-minutes: 5
steps:
- name: Checkout Repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: pnpm install --frozen-lockfile
- name: Publish packages
run: pnpx pkg-pr-new publish --pnpm './packages/*'

View File

@@ -1,47 +0,0 @@
name: Publish release
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: fregante/setup-git-user@v2
- uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: |
pnpm install --frozen-lockfile
npm i json --global
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Prepare release
run: |
VERSION=${GITHUB_REF:10}
echo "Preparing release $VERSION"
git checkout -t origin/release/$VERSION
npm version --no-git-tag-version --allow-same-version $VERSION
git add package.json
git commit -nm "Bump version $VERSION"
git checkout -t origin/master
git merge -m "Release $VERSION" --no-ff release/$VERSION
git push --no-verify
- name: Publish
run: |
npm set //registry.npmjs.org/:_authToken $NPM_TOKEN
npm publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -7,18 +7,26 @@ on:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
release:
if: github.repository == 'mermaid-js/mermaid'
permissions:
contents: write # to create release (changesets/action)
id-token: write # OpenID Connect token needed for provenance
pull-requests: write # to create pull request (changesets/action)
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -28,10 +36,11 @@ jobs:
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1
uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7
with:
version: pnpm changeset:version
publish: pnpm changeset:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

37
.github/workflows/scorecard.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Scorecard supply-chain security
on:
branch_protection_rule:
push:
branches:
- develop
schedule:
- cron: 29 15 * * 0
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
permissions:
id-token: write
security-events: write
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
with:
sarif_file: results.sarif

View File

@@ -9,13 +9,13 @@ jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
cache: pnpm
node-version-file: '.node-version'
@@ -38,8 +38,12 @@ jobs:
run: |
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
- name: Verify out-of-tree build with TypeScript
run: |
pnpm test:check:tsc
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
# Run step only pushes to develop and pull_requests
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
with:

View File

@@ -8,6 +8,6 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: Dunning-Kruger/unlock-issues@v1
- uses: Dunning-Kruger/unlock-issues@b06b7f7e5c3f2eaa1c6d5d89f40930e4d6d9699e # v1
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -8,18 +8,18 @@ jobs:
update-browser-list:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- run: npx update-browserslist-db@latest
- name: Commit changes
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
author_name: ${{ github.actor }}
author_email: ${{ github.actor }}@users.noreply.github.com
message: 'chore: update browsers list'
push: false
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
with:
branch: update-browserslist
title: Update Browserslist

2
.hadolint.yaml Normal file
View File

@@ -0,0 +1,2 @@
ignored:
- DL3002 # TODO: Last USER should not be root

View File

@@ -1,4 +1,2 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#!/usr/bin/env sh
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit

View File

@@ -1,2 +1,13 @@
FROM node:20.12.2-alpine3.19 AS base
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
FROM node:20.12.2-alpine3.19@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2
USER 0:0
RUN corepack enable \
&& corepack enable pnpm
RUN apk add --no-cache git~=2.43.4 \
&& git config --add --system safe.directory /mermaid
ENV NODE_OPTIONS="--max_old_space_size=8192"
EXPOSE 9000 3333

View File

@@ -35,7 +35,8 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com)
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mermaid-js/mermaid/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mermaid-js/mermaid)
<img src="./img/header.png" alt="" />
@@ -82,6 +83,10 @@ You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](https://mermaid.js.org/intro/getting-started.html), [Usage](https://mermaid.js.org/config/usage.html) and [Tutorials](https://mermaid.js.org/ecosystem/tutorials.html).
Our PR Visual Regression Testing is powered by [Argos](https://argos-ci.com/?utm_source=mermaid&utm_campaign=oss) with their generous Open Source plan. It makes the process of reviewing PRs with visual changes a breeze.
[![Covered by Argos Visual Testing](https://argos-ci.com/badge-large.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
<a href="https://applitools.com/">

View File

@@ -73,7 +73,7 @@ export const imgSnapshotTest = (
export const urlSnapshotTest = (
url: string,
options: CypressMermaidConfig,
options: CypressMermaidConfig = {},
_api = false,
validation?: any
): void => {

View File

@@ -0,0 +1,180 @@
import { imgSnapshotTest, urlSnapshotTest } from '../../helpers/util.ts';
describe.skip('architecture diagram', () => {
it('should render a simple architecture diagram with groups', () => {
imgSnapshotTest(
`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
service gateway(internet)[Gateway]
db L--R server
disk1 T--B server
disk2 T--B db
server T--B gateway
`
);
});
it('should render an architecture diagram with groups within groups', () => {
imgSnapshotTest(
`architecture-beta
group api[API]
group public[Public API] in api
group private[Private API] in api
service serv1(server)[Server] in public
service serv2(server)[Server] in private
service db(database)[Database] in private
service gateway(internet)[Gateway] in api
serv1 B--T serv2
serv2 L--R db
serv1 L--R gateway
`
);
});
it('should render an architecture diagram with the fallback icon', () => {
imgSnapshotTest(
`architecture-beta
service unknown(iconnamedoesntexist)[Unknown Icon]
`
);
});
it('should render an architecture diagram with split directioning', () => {
imgSnapshotTest(
`architecture-beta
service db(database)[Database]
service s3(disk)[Storage]
service serv1(server)[Server 1]
service serv2(server)[Server 2]
service disk(disk)[Disk]
db L--R s3
serv1 L--T s3
serv2 L--B s3
serv1 T--B disk
`
);
});
it('should render an architecture diagram with directional arrows', () => {
imgSnapshotTest(
`architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC (L--R) servL
servC (R--L) servR
servC (T--B) servT
servC (B--T) servB
servL (T--L) servT
servL (B--L) servB
servR (T--R) servT
servR (B--R) servB
`
);
});
it('should render an architecture diagram with group edges', () => {
imgSnapshotTest(
`architecture-beta
group left_group(cloud)[Left]
group right_group(cloud)[Right]
group top_group(cloud)[Top]
group bottom_group(cloud)[Bottom]
group center_group(cloud)[Center]
service left_disk(disk)[Disk] in left_group
service right_disk(disk)[Disk] in right_group
service top_disk(disk)[Disk] in top_group
service bottom_disk(disk)[Disk] in bottom_group
service center_disk(disk)[Disk] in center_group
left_disk{group} (R--L) center_disk{group}
right_disk{group} (L--R) center_disk{group}
top_disk{group} (B--T) center_disk{group}
bottom_disk{group} (T--B) center_disk{group}
`
);
});
it('should render an architecture diagram with edge labels', () => {
imgSnapshotTest(
`architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC L-[Label]-R servL
servC R-[Label]-L servR
servC T-[Label]-B servT
servC B-[Label]-T servB
servL T-[Label]-L servT
servL B-[Label]-L servB
servR T-[Label]-R servT
servR B-[Label]-R servB
`
);
});
it('should render an architecture diagram with simple junction edges', () => {
imgSnapshotTest(
`architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction juncC
junction juncR
left_disk R--L juncC
top_disk B--T juncC
bottom_disk T--B juncC
juncC R--L juncR
top_gateway B--T juncR
bottom_gateway T--B juncR
`
);
});
it('should render an architecture diagram with complex junction edges', () => {
imgSnapshotTest(
`architecture-beta
group left
group right
service left_disk(disk)[Disk] in left
service top_disk(disk)[Disk] in left
service bottom_disk(disk)[Disk] in left
service top_gateway(internet)[Gateway] in right
service bottom_gateway(internet)[Gateway] in right
junction juncC in left
junction juncR in right
left_disk R--L juncC
top_disk B--T juncC
bottom_disk T--B juncC
top_gateway (B--T juncR
bottom_gateway (T--B juncR
juncC{group} R--L) juncR{group}
`
);
});
});
describe('architecture - external', () => {
it('should allow adding external icons', () => {
urlSnapshotTest('http://localhost:9000/architecture-external.html');
});
});

View File

@@ -321,4 +321,37 @@ ORDER ||--|{ LINE-ITEM : contains
{ logLevel: 1 }
);
});
it('should render relationship labels with line breaks', () => {
imgSnapshotTest(
`
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
b["Customer Account Secondary"] {
string email
}
c["Customer Account Tertiary"] {
string email
}
d["Customer Account Nth"] {
string email
}
p ||--o| a : "has<br />one"
p ||--o| b : "has<br />one<br />two"
p ||--o| c : "has<br />one<br/>two<br />three"
p ||--o| d : "has<br />one<br />two<br/>three<br />...<br/>Nth"
`,
{ logLevel: 1 }
);
});
});

View File

@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(446 * 0.95 - 1, 446 * 1.05);
expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05);
});
});
it('8: should render a flowchart when useMaxWidth is false', () => {
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±5%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
expect(width).to.be.within(446 * 0.95 - 1, 446 * 1.05);
expect(width).to.be.within(417 * 0.95, 417 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
@@ -786,7 +786,7 @@ A ~~~ B
`---
title: Subgraph nodeSpacing and rankSpacing example
config:
flowchart:
flowchart:
nodeSpacing: 250
rankSpacing: 250
---
@@ -1052,5 +1052,28 @@ end
}
);
});
it('Should render self-loops', () => {
imgSnapshotTest(
`flowchart
A --> A
subgraph B
B1 --> B1
end
subgraph C
subgraph C1
C2 --> C2
subgraph D
D1 --> D1
end
D --> D
end
C1 --> C1
end
`,
{
flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } },
}
);
});
});
});

View File

@@ -0,0 +1,52 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Architecture Mermaid Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h2>External Icons Demo</h2>
<pre class="mermaid">
architecture-beta
service s3(logos:aws-s3)[Cloud Store]
service ec2(logos:aws-ec2)[Server]
service api(logos:aws-api-gateway)[Api Gateway]
service fa(fa:image)[Font Awesome Icon]
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
startOnLoad: false,
logLevel: 0,
});
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () =>
fetch('https://unpkg.com/@iconify-json/fa6-regular/icons.json').then((res) =>
res.json()
),
},
]);
await mermaid.run();
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

View File

@@ -73,7 +73,9 @@
font-family: monospace;
font-size: 72px;
}
pre {
width: 100%;
}
/* tspan {
font-size: 6px !important;
} */
@@ -81,90 +83,166 @@
</head>
<body>
<div class="flex">
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid">
---
title: hello2
config:
look: handDrawn
layout: elk
elk:
nodePlacementStrategy: BRANDES_KOEPF
<!-- nodePlacementStrategy: INTERACTIVE -->
<!-- mergeEdges: true -->
---
flowchart LR
A[Start] --Some text--> B(Continue)
B --> C{Evaluate}
C -- One --> D[Option 1]
C -- Two --> E[Option 2]
C -- Three --> F[fa:fa-car Option 3]
stateDiagram-v2
direction LR
accTitle: An idealized Open Source supply-chain graph
%%
state "🟦 Importer" as author_importer
state "🟥 Supplier, Owner" as author_owner
state "🟨🟥 Maintainer, Author\n🟨 Custodian" as author
state "🟩 Distributor" as repository_distributor
state "🟦 Importer" as language_importer
state "🟦🟨 Packager" as language_packager
state "🟦🟨 OSS Steward" as language_steward
state "🟨 Curator" as language_curator
state "🟩 Distributor" as language_distributor
state "🟦 Contributor" as contributor
state "🟦 Importer" as package_importer
state "🟨 Patcher" as package_patcher
state "🟨🟦 Builder\n🟨🟦 Packager\n🟨🟦 Containerizer" as package_packager
state "🟨 Curator" as package_curator
state "🟩 Distributor" as package_distributor
state "🟦 Importer" as integrator_importer
state "🟥 Supplier, Manufacturer, Owner" as integrator_owner
state "🟦🟨🟥 Integrator, Developer" as integrator_developer
state "🟩🟨 SBOM Redactor\n🟩 Publisher" as integrator_publisher
state "🟦🟨 Builder" as integrator_builder
state "🟨 Deployer" as deployer
state "🟦 Vuln. Checker" as integrator_checker
state "🟩🟨 SBOM Redactor" as redactor
state "🟦 Consumer\n🟦 User" as consumer
state "🟦 Auditor" as auditor_internal
state "🟦 Auditor" as auditor_external
%%
classDef createsSBOM stroke:red,stroke-width:3px;
classDef updatesSBOM stroke:yellow,stroke-width:3px;
classDef assemblesSBOM stroke:yellow,stroke-width:3px;
classDef distributesSBOM stroke:green,stroke-width:3px;
classDef verifiesSBOM stroke:#07f,stroke-width:3px;
%%
class author_importer verifiesSBOM
class author_owner createsSBOM
class manufacturer_owner createsSBOM
class author assemblesSBOM
class package_importer verifiesSBOM
class package_patcher updatesSBOM
class package_packager assemblesSBOM
class package_curator distributesSBOM
class package_distributor distributesSBOM
class language_importer verifiesSBOM
class language_packager assemblesSBOM
class language_steward updatesSBOM
class language_curator distributesSBOM
class language_distributor distributesSBOM
class repository_distributor distributesSBOM
class integrator_importer verifiesSBOM
class integrator_owner createsSBOM
class integrator_developer assemblesSBOM
class integrator_publisher distributesSBOM
class integrator_builder assemblesSBOM
class integrator_checker verifiesSBOM
class deployer assemblesSBOM
class redactor distributesSBOM
class auditor_internal verifiesSBOM
class auditor_external verifiesSBOM
state "Maintainer Environment" as environment_maintainer {
[*] --> author_importer
[*] --> author
author_importer --> author
author_owner --> author
author --> language_packager
}
[*] --> environment_maintainer
state "Language Ecosystem" as ecosystem_lang {
[*] --> language_importer
[*] --> language_steward
[*] --> language_curator
[*] --> language_distributor
language_importer --> language_distributor
language_importer --> language_curator
language_steward --> language_curator
language_curator --> language_distributor
}
language_packager --> ecosystem_lang
ecosystem_lang --> ecosystem_lang
state "Public Collaboration Ecosystem" as ecosystem_repo {
[*] --> repository_distributor
}
author --> ecosystem_repo
ecosystem_repo --> author
repository_distributor --> contributor
contributor --> repository_distributor
state "Package Ecosystem" as ecosystem_package {
[*] --> package_importer
[*] --> package_packager
[*] --> package_patcher
package_importer --> package_patcher
package_importer --> package_packager
package_patcher --> package_packager
package_packager --> package_curator
package_packager --> package_distributor
package_curator --> package_distributor
}
repository_distributor --> ecosystem_package
language_distributor --> ecosystem_package
ecosystem_package --> ecosystem_package
state "Integrator Environment" as environment_integrator {
[*] --> integrator_developer
[*] --> integrator_importer
integrator_importer --> integrator_developer
integrator_owner --> integrator_developer
integrator_builder --> integrator_publisher
integrator_developer --> integrator_checker
integrator_checker --> integrator_developer
auditor_internal --> integrator_developer
integrator_developer --> integrator_builder
integrator_developer --> auditor_internal
}
repository_distributor --> environment_integrator
language_distributor --> environment_integrator
package_distributor --> environment_integrator
state "Production Environment" as environment_prod {
[*] --> deployer
deployer --> redactor
}
integrator_publisher --> [*]
integrator_developer --> environment_prod
integrator_builder --> environment_prod
integrator_publisher --> environment_prod
deployer --> auditor_external
deployer --> consumer
redactor --> consumer
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
look: handdrawn
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? handdrawn - true]
</pre
>
</div>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: false
---
flowchart
A[I am a long text, where do I go??? classic - false]
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? classic - true]
</pre
>
</div>
<pre id="diagram2" class="mermaid2">
flowchart LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
</pre>
<pre id="diagram3" class="mermaid2">
flowchart LR
A:::foo & B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef ash color:red
class C ash
style C stroke:#00f, fill:black
</pre>
<pre id="diagram4" class="mermaid2">
stateDiagram
A:::foo
B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
style C stroke:#00f, fill:black, color:white
</pre>
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
@@ -195,7 +273,7 @@ flowchart LR
messageFontFamily: 'courier',
},
fontSize: 12,
logLevel: 0,
logLevel: 3,
securityLevel: 'loose',
});
function callback() {

256
demos/architecture.html Normal file
View File

@@ -0,0 +1,256 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Architecture Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h1>Architecture diagram demo</h1>
<h2>Simple diagram with groups</h2>
<pre class="mermaid">
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
service gateway(internet)[Gateway]
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
server:T -- B:gateway
</pre>
<hr />
<h2>Groups within groups</h2>
<pre class="mermaid">
architecture-beta
group api[API]
group public[Public API] in api
group private[Private API] in api
service serv1(server)[Server] in public
service serv2(server)[Server] in private
service db(database)[Database] in private
service gateway(internet)[Gateway] in api
serv1:B -- T:serv2
serv2:L -- R:db
serv1:L -- R:gateway
</pre>
<hr />
<h2>Default icon (?) from unknown icon name</h2>
<pre class="mermaid">
architecture-beta
service unknown(iconnamedoesntexist)[Unknown Icon]
</pre>
<hr />
<h2>Split Direction</h2>
<pre class="mermaid">
architecture-beta
service db(database)[Database]
service s3(disk)[Storage]
service serv1(server)[Server 1]
service serv2(server)[Server 2]
service disk(disk)[Disk]
db:L -- R:s3
serv1:L -- T:s3
serv2:L -- B:s3
serv1:T -- B:disk
</pre>
<hr />
<h2>Arrow Tests</h2>
<pre class="mermaid">
architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC:L <--> R:servL
servC:R <--> L:servR
servC:T <--> B:servT
servC:B <--> T:servB
servL:T <--> L:servT
servL:B <--> L:servB
servR:T <--> R:servT
servR:B <--> R:servB
</pre>
<pre class="mermaid">
architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC:L <--> R:servL
servC:R <--> L:servR
servC:T <--> B:servT
servC:B <--> T:servB
servT:L <--> T:servL
servB:L <--> B:servL
servT:R <--> T:servR
servB:R <--> B:servR
</pre>
<hr />
<h2>Group Edges</h2>
<pre class="mermaid">
architecture-beta
group left_group(cloud)[Left]
group right_group(cloud)[Right]
group top_group(cloud)[Top]
group bottom_group(cloud)[Bottom]
group center_group(cloud)[Center]
service left_disk(disk)[Disk] in left_group
service right_disk(disk)[Disk] in right_group
service top_disk(disk)[Disk] in top_group
service bottom_disk(disk)[Disk] in bottom_group
service center_disk(disk)[Disk] in center_group
left_disk{group}:R <--> L:center_disk{group}
right_disk{group}:L <--> R:center_disk{group}
top_disk{group}:B <--> T:center_disk{group}
bottom_disk{group}:T <--> B:center_disk{group}
</pre
>
<hr />
<h2>Edge Label Test</h2>
<pre class="mermaid">
architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC:L -[Label]- R:servL
servC:R -[Label]- L:servR
servC:T -[Label]- B:servT
servC:B -[Label]- T:servB
servL:T -[Label]- L:servT
servL:B -[Label]- L:servB
servR:T -[Label]- R:servT
servR:B -[Label]- R:servB
</pre>
<pre class="mermaid">
architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC:L -[Label that is Long]- R:servL
servC:R -[Label that is Long]- L:servR
servC:T -[Label that is Long]- B:servT
servC:B -[Label that is Long]- T:servB
servL:T -[Label that is Long]- L:servT
servL:B -[Label that is Long]- L:servB
servR:T -[Label that is Long]- R:servT
servR:B -[Label that is Long]- R:servB
</pre>
<hr />
<h2>Junction Demo</h2>
<pre class="mermaid">
architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction juncC
junction juncR
left_disk:R -- L:juncC
top_disk:B -- T:juncC
bottom_disk:T -- B:juncC
juncC:R -- L:juncR
top_gateway:B -- T:juncR
bottom_gateway:T -- B:juncR
</pre>
<hr />
<h2>Junction Demo Groups</h2>
<pre class="mermaid">
architecture-beta
group left
group right
service left_disk(disk)[Disk] in left
service top_disk(disk)[Disk] in left
service bottom_disk(disk)[Disk] in left
service top_gateway(internet)[Gateway] in right
service bottom_gateway(internet)[Gateway] in right
junction juncC in left
junction juncR in right
left_disk:R -- L:juncC
top_disk:B -- T:juncC
bottom_disk:T -- B:juncC
top_gateway:B <-- T:juncR
bottom_gateway:T <-- B:juncR
juncC{group}:R --> L:juncR{group}
</pre>
<hr />
<h2>External Icons Demo</h2>
<pre class="mermaid">
architecture-beta
service s3(logos:aws-s3)[Cloud Store]
service ec2(logos:aws-ec2)[Server]
service api(logos:aws-api-gateway)[Api Gateway]
service fa(fa:image)[Font Awesome Icon]
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () =>
fetch('https://unpkg.com/@iconify-json/fa6-regular/icons.json').then((res) =>
res.json()
),
},
]);
</script>
</body>
</html>

View File

@@ -125,6 +125,35 @@
</pre>
<hr />
<pre class="mermaid">
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
b["Customer Account Secondary"] {
string email
}
c["Customer Account Tertiary"] {
string email
}
d["Customer Account Nth"] {
string email
}
p ||--o| a : "has<br />one"
p ||--o| b : "has<br />one<br />two"
p ||--o| c : "has<br />one<br />two<br />three"
p ||--o| d : "has<br />one<br />two<br />three<br />...<br />Nth"
</pre>
<hr />
<pre class="mermaid">
erDiagram
_customer_order {

View File

@@ -88,6 +88,9 @@
<li>
<h2><a href="./block.html">Layered Blocks</a></h2>
</li>
<li>
<h2><a href="./architecture.html">Architecture</a></h2>
</li>
</ul>
</body>
</html>

View File

@@ -7,9 +7,6 @@ services:
tty: true
working_dir: /mermaid
mem_limit: '8G'
entrypoint: ./docker-entrypoint.sh
environment:
- NODE_OPTIONS=--max_old_space_size=8192
volumes:
- ./:/mermaid
- root_cache:/root/.cache

View File

@@ -1,3 +0,0 @@
#!/bin/sh
source /root/.shrc
exec "$@"

View File

@@ -370,9 +370,9 @@ If the users have no way to know that things have changed, then you haven't real
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
The documentation has to be updated for users to know that things have been changed and added!
If you are adding a new feature, add `(v10.8.0+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
eg: `# Feature Name (v10.8.0+)`
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
We know it can sometimes be hard to code _and_ write user documentation.

View File

@@ -16,11 +16,11 @@
### config
**config**: `MermaidConfig`
**config**: [`MermaidConfig`](mermaid.MermaidConfig.md)
#### Defined in
[packages/mermaid/src/rendering-util/types.d.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L118)
[packages/mermaid/src/rendering-util/types.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L117)
---
@@ -30,7 +30,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.d.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L117)
[packages/mermaid/src/rendering-util/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L116)
---
@@ -40,4 +40,4 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.d.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L116)
[packages/mermaid/src/rendering-util/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L115)

View File

@@ -28,7 +28,7 @@ page.
#### Defined in
[packages/mermaid/src/mermaid.ts:435](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L435)
[packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
---
@@ -59,7 +59,7 @@ A graph definition key
#### Defined in
[packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
[packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
---
@@ -89,7 +89,7 @@ Use [initialize](mermaid.Mermaid.md#initialize) and [run](mermaid.Mermaid.md#run
#### Defined in
[packages/mermaid/src/mermaid.ts:430](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L430)
[packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
---
@@ -116,7 +116,7 @@ This function should be called before the run function.
#### Defined in
[packages/mermaid/src/mermaid.ts:434](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L434)
[packages/mermaid/src/mermaid.ts:435](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L435)
---
@@ -130,7 +130,7 @@ Use [parse](mermaid.Mermaid.md#parse) and [render](mermaid.Mermaid.md#render) in
#### Defined in
[packages/mermaid/src/mermaid.ts:424](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L424)
[packages/mermaid/src/mermaid.ts:425](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L425)
---
@@ -180,7 +180,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
#### Defined in
[packages/mermaid/src/mermaid.ts:425](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L425)
[packages/mermaid/src/mermaid.ts:426](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L426)
---
@@ -190,7 +190,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
#### Defined in
[packages/mermaid/src/mermaid.ts:419](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L419)
[packages/mermaid/src/mermaid.ts:420](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L420)
---
@@ -218,7 +218,31 @@ Used to register external diagram types.
#### Defined in
[packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
[packages/mermaid/src/mermaid.ts:434](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L434)
---
### registerIconPacks
**registerIconPacks**: (`iconLoaders`: `IconLoader`\[]) => `void`
#### Type declaration
▸ (`iconLoaders`): `void`
##### Parameters
| Name | Type |
| :------------ | :-------------- |
| `iconLoaders` | `IconLoader`\[] |
##### Returns
`void`
#### Defined in
[packages/mermaid/src/mermaid.ts:439](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L439)
---
@@ -242,7 +266,7 @@ Used to register external diagram types.
#### Defined in
[packages/mermaid/src/mermaid.ts:432](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L432)
[packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
---
@@ -268,7 +292,7 @@ Used to register external diagram types.
#### Defined in
[packages/mermaid/src/mermaid.ts:426](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L426)
[packages/mermaid/src/mermaid.ts:427](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L427)
---
@@ -316,7 +340,7 @@ Renders the mermaid diagrams
#### Defined in
[packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
[packages/mermaid/src/mermaid.ts:432](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L432)
---
@@ -351,7 +375,7 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
#### Defined in
[packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
[packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
---
@@ -361,4 +385,4 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
#### Defined in
[packages/mermaid/src/mermaid.ts:418](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L418)
[packages/mermaid/src/mermaid.ts:419](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L419)

View File

@@ -16,7 +16,17 @@
#### Defined in
[packages/mermaid/src/config.type.ts:112](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L112)
[packages/mermaid/src/config.type.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L122)
---
### architecture
`Optional` **architecture**: `ArchitectureDiagramConfig`
#### Defined in
[packages/mermaid/src/config.type.ts:194](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L194)
---
@@ -29,7 +39,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
[packages/mermaid/src/config.type.ts:141](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L141)
---
@@ -39,7 +49,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
[packages/mermaid/src/config.type.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L200)
---
@@ -49,7 +59,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
[packages/mermaid/src/config.type.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L197)
---
@@ -59,7 +69,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:177](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L177)
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
---
@@ -69,7 +79,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L103)
[packages/mermaid/src/config.type.ts:113](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L113)
---
@@ -83,7 +93,7 @@ You can set this attribute to base the seed on a static string.
#### Defined in
[packages/mermaid/src/config.type.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L171)
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
---
@@ -101,7 +111,7 @@ should not change unless content is changed.
#### Defined in
[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164)
[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
---
@@ -111,7 +121,7 @@ should not change unless content is changed.
#### Defined in
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201)
---
@@ -121,10 +131,11 @@ should not change unless content is changed.
#### Type declaration
| Name | Type | Description |
| :----------------------- | :---------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
| Name | Type | Description |
| :----------------------- | :-------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cycleBreakingStrategy?` | `"GREEDY"` \| `"DEPTH_FIRST"` \| `"INTERACTIVE"` \| `"MODEL_ORDER"` \| `"GREEDY_MODEL_ORDER"` | This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops. |
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
#### Defined in
@@ -138,7 +149,7 @@ should not change unless content is changed.
#### Defined in
[packages/mermaid/src/config.type.ts:179](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L179)
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
---
@@ -148,7 +159,7 @@ should not change unless content is changed.
#### Defined in
[packages/mermaid/src/config.type.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L172)
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
---
@@ -162,7 +173,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
#### Defined in
[packages/mermaid/src/config.type.ts:111](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L111)
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
---
@@ -172,7 +183,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
#### Defined in
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203)
---
@@ -186,7 +197,7 @@ If set to true, ignores legacyMathML.
#### Defined in
[packages/mermaid/src/config.type.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L153)
[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163)
---
@@ -196,7 +207,7 @@ If set to true, ignores legacyMathML.
#### Defined in
[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
---
@@ -206,7 +217,7 @@ If set to true, ignores legacyMathML.
#### Defined in
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
[packages/mermaid/src/config.type.ts:196](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L196)
---
@@ -228,7 +239,7 @@ Defines the seed to be used when using handDrawn look. This is important for the
#### Defined in
[packages/mermaid/src/config.type.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L104)
[packages/mermaid/src/config.type.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L114)
---
@@ -238,7 +249,7 @@ Defines the seed to be used when using handDrawn look. This is important for the
#### Defined in
[packages/mermaid/src/config.type.ts:175](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L175)
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
---
@@ -265,7 +276,7 @@ fall back to legacy rendering for KaTeX.
#### Defined in
[packages/mermaid/src/config.type.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L146)
[packages/mermaid/src/config.type.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L156)
---
@@ -277,7 +288,7 @@ This option decides the amount of logging to be used by mermaid.
#### Defined in
[packages/mermaid/src/config.type.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L117)
[packages/mermaid/src/config.type.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L127)
---
@@ -299,7 +310,7 @@ Defines which main look to use for the diagram.
#### Defined in
[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204)
---
@@ -333,7 +344,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
[packages/mermaid/src/config.type.ts:195](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L195)
---
@@ -343,7 +354,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
---
@@ -353,7 +364,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:180](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L180)
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
---
@@ -363,7 +374,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
---
@@ -373,7 +384,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
---
@@ -383,7 +394,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
[packages/mermaid/src/config.type.ts:198](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L198)
---
@@ -397,7 +408,7 @@ This prevents malicious graph directives from overriding a site's default securi
#### Defined in
[packages/mermaid/src/config.type.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L138)
[packages/mermaid/src/config.type.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L148)
---
@@ -409,7 +420,7 @@ Level of trust for parsed diagram
#### Defined in
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
---
@@ -419,7 +430,7 @@ Level of trust for parsed diagram
#### Defined in
[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173)
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
---
@@ -431,7 +442,7 @@ Dictates whether mermaid starts on Page load
#### Defined in
[packages/mermaid/src/config.type.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L125)
[packages/mermaid/src/config.type.ts:135](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L135)
---
@@ -441,7 +452,7 @@ Dictates whether mermaid starts on Page load
#### Defined in
[packages/mermaid/src/config.type.ts:178](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L178)
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
---
@@ -454,7 +465,7 @@ This is useful when you want to control how to handle syntax errors in your appl
#### Defined in
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
[packages/mermaid/src/config.type.ts:210](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L210)
---
@@ -497,7 +508,7 @@ You may also use `themeCSS` to override this value.
#### Defined in
[packages/mermaid/src/config.type.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L176)
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
---
@@ -507,7 +518,7 @@ You may also use `themeCSS` to override this value.
#### Defined in
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202)
---
@@ -517,4 +528,4 @@ You may also use `themeCSS` to override this value.
#### Defined in
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)

View File

@@ -19,4 +19,4 @@ The `parseError` function will not be called.
#### Defined in
[packages/mermaid/src/types.ts:43](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L43)
[packages/mermaid/src/types.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L45)

View File

@@ -10,6 +10,18 @@
## Properties
### config
**config**: [`MermaidConfig`](mermaid.MermaidConfig.md)
The config passed as YAML frontmatter or directives
#### Defined in
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)
---
### diagramType
**diagramType**: `string`
@@ -18,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:50](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L50)
[packages/mermaid/src/types.ts:52](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L52)

View File

@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[packages/mermaid/src/types.ts:73](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L73)
[packages/mermaid/src/types.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L79)
---
@@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L63)
[packages/mermaid/src/types.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L69)
---
@@ -63,4 +63,4 @@ The svg code for the rendered graph.
#### Defined in
[packages/mermaid/src/types.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L59)
[packages/mermaid/src/types.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L65)

View File

@@ -18,7 +18,7 @@ The nodes to render. If this is set, `querySelector` will be ignored.
#### Defined in
[packages/mermaid/src/mermaid.ts:48](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L48)
[packages/mermaid/src/mermaid.ts:49](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L49)
---
@@ -44,7 +44,7 @@ A callback to call after each diagram is rendered.
#### Defined in
[packages/mermaid/src/mermaid.ts:52](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L52)
[packages/mermaid/src/mermaid.ts:53](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L53)
---
@@ -56,7 +56,7 @@ The query selector to use when finding elements to render. Default: `".mermaid"`
#### Defined in
[packages/mermaid/src/mermaid.ts:44](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L44)
[packages/mermaid/src/mermaid.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L45)
---
@@ -68,4 +68,4 @@ If `true`, errors will be logged to the console, but not thrown. Default: `false
#### Defined in
[packages/mermaid/src/mermaid.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L56)
[packages/mermaid/src/mermaid.ts:57](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L57)

View File

@@ -87,4 +87,4 @@
#### Defined in
[packages/mermaid/src/mermaid.ts:440](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L440)
[packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)

View File

@@ -74,6 +74,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
- [Microsoft Loop](https://loop.cloud.microsoft) ✅
### LLM integrations
@@ -144,7 +145,7 @@ Communication tools and platforms
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
- [PmWiki](https://www.pmwiki.org)
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
- [Semantic Media Wiki](https://semantic-mediawiki.org)
- [Semantic Media Wiki](https://www.semantic-mediawiki.org)
- [Mermaid Plugin](https://github.com/SemanticMediaWiki/Mermaid)
- [TiddlyWiki](https://tiddlywiki.com/)
- [mermaid-tw5: wrapper for Mermaid Live](https://github.com/efurlanm/mermaid-tw5)

View File

@@ -63,7 +63,7 @@ import matplotlib.pyplot as plt
def mm(graph):
graphbytes = graph.encode("utf8")
base64_bytes = base64.b64encode(graphbytes)
base64_bytes = base64.urlsafe_b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string))

View File

@@ -55,6 +55,10 @@ For a more detailed introduction to Mermaid and some of its more basic uses, loo
**Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project 🙏**
Our PR Visual Regression Testing is powered by [Argos](https://argos-ci.com/?utm_source=mermaid&utm_campaign=oss) with their generous Open Source plan. It makes the process of reviewing PRs with visual changes a breeze.
[![Covered by Argos Visual Testing](https://argos-ci.com/badge-large.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
<a href="https://applitools.com/">

View File

@@ -83,3 +83,139 @@ Allows for the limited reconfiguration of a diagram just before it is rendered.
### [Theme Manipulation](../config/theming.md)
An application of using Directives to change [Themes](../config/theming.md). `Theme` is a value within Mermaid's configuration that dictates the color scheme for diagrams.
### Layout and look
We've restructured how Mermaid renders diagrams, enabling new features like selecting layout and look. **Currently, this is supported for flowcharts and state diagrams**, with plans to extend support to all diagram types.
### Selecting Diagram Looks
Mermaid offers a variety of styles or “looks” for your diagrams, allowing you to tailor the visual appearance to match your specific needs or preferences. Whether you prefer a hand-drawn or classic style, you can easily customize your diagrams.
**Available Looks:**
```
• Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
• Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
```
**How to Select a Look:**
You can select a look by adding the look parameter in the metadata section of your Mermaid diagram code. Heres an example:
```mermaid-example
---
config:
look: handDrawn
theme: neutral
---
flowchart LR
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
```mermaid
---
config:
look: handDrawn
theme: neutral
---
flowchart LR
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
#### Selecting Layout Algorithms
In addition to customizing the look of your diagrams, Mermaid Chart now allows you to choose different layout algorithms to better organize and present your diagrams, especially when dealing with more complex structures. The layout algorithm dictates how nodes and edges are arranged on the page.
#### Supported Layout Algorithms:
```
• Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
• ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
```
#### How to Select a Layout Algorithm:
You can specify the layout algorithm directly in the metadata section of your Mermaid diagram code. Heres an example:
```mermaid-example
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TB
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
```mermaid
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TB
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
In this example, the `layout: elk` line configures the diagram to use the ELK layout algorithm, along with the hand drawn look and forest theme.
#### Customizing ELK Layout:
When using the ELK layout, you can further refine the diagrams configuration, such as how nodes are placed and whether parallel edges should be combined:
- To combine parallel edges, use mergeEdges: true | false.
- To configure node placement, use nodePlacementStrategy with the following options:
- SIMPLE
- NETWORK_SIMPLEX
- LINEAR_SEGMENTS
- BRANDES_KOEPF (default)
**Example configuration:**
```
---
config:
layout: elk
elk:
mergeEdges: true
nodePlacementStrategy: LINEAR_SEGMENTS
---
flowchart LR
A[Start] --> B{Choose Path}
B -->|Option 1| C[Path 1]
B -->|Option 2| D[Path 2]
#### Using Dagre Layout with Classic Look:
```
Another example:
```
---
config:
layout: dagre
look: classic
theme: default
---
flowchart LR
A[Start] --> B{Choose Path}
B -->|Option 1| C[Path 1]
B -->|Option 2| D[Path 2]
```
These options give you the flexibility to create diagrams that not only look great but are also arranged to best suit your datas structure and flow.
When integrating Mermaid, you can include look and layout configuration with the initialize call. This is also where you add the loading of elk.

View File

@@ -6,6 +6,30 @@
# Blog
## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/)
2 September 2024 · 2 mins
Discover the fresh new and unique Neo and Hand-Drawn looks for Mermaid Diagrams, while still offering the classic look you love.
## [Mermaid v11 is out!](https://www.mermaidchart.com/blog/posts/mermaid-v11/)
23 August 2024 · 2 mins
Mermaid v11 introduces advanced layout options, new diagram types, and enhanced customization features, thanks to the incredible contributions from our community.
## [Mermaid Innovation - Introducing New Looks for Mermaid Diagrams](https://www.mermaidchart.com/blog/posts/mermaid-innovation-introducing-new-looks-for-mermaid-diagrams/)
6 August 2024 ·3 mins
Discover the fresh new and unique Neo and Hand-Drawn looks for Mermaid Diagrams, while still offering the classic look you love.
## [The Mermaid Chart Plugin for Jira: A How-To User Guide](https://www.mermaidchart.com/blog/posts/the-mermaid-chart-plugin-for-jira-a-how-to-user-guide/)
31 July 2024 · 5 mins
The Mermaid Chart plugin for Jira has arrived!
## [Mermaid AI Is Here to Change the Game For Diagram Creation](https://www.mermaidchart.com/blog/posts/mermaid-ai-is-here-to-change-the-game-for-diagram-creation/)
22 July 2024 · 5 mins

275
docs/syntax/architecture.md Normal file
View File

@@ -0,0 +1,275 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/architecture.md](../../packages/mermaid/src/docs/syntax/architecture.md).
# Architecture Diagrams Documentation (v11.1.0+)
> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.
## Example
```mermaid-example
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
```
```mermaid
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
```
## Syntax
The building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
To begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.
### Groups
The syntax for declaring a group is:
```
group {group id}({icon name})[{title}] (in {parent id})?
```
Put together:
```
group public_api(cloud)[Public API]
```
creates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.
Additionally, groups can be placed within a group using the optional `in` keyword
```
group private_api(cloud)[Private API] in public_api
```
### Services
The syntax for declaring a service is:
```
service {service id}({icon name})[{title}] (in {parent id})?
```
Put together:
```
service database(db)[Database]
```
creates the service identified as `database`, using the icon `db`, with the label `Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database(db)[Database] in private_api
```
### Edges
The syntax for declaring an edge is:
```
{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?
```
#### Edge Direction
The side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`
For example:
```
db:R -- L:server
```
creates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.
```
db:T -- L:server
```
creates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.
#### Arrows
Arrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.
For example:
```
subnet:R --> L:gateway
```
creates an edge with the arrow going into the `gateway` service
#### Edges out of Groups
To have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.
For example:
```
service server[Server] in groupOne
service subnet[Subnet] in groupTwo
server{group}:B --> T:subnet{group}
```
creates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.
It's important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.
### Junctions
Junctions are a special type of node which acts as a potential 4-way split between edges.
The syntax for declaring a junction is:
```
junction {junction id} (in {parent id})?
```
```mermaid-example
architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction junctionCenter
junction junctionRight
left_disk:R -- L:junctionCenter
top_disk:B -- T:junctionCenter
bottom_disk:T -- B:junctionCenter
junctionCenter:R -- L:junctionRight
top_gateway:B -- T:junctionRight
bottom_gateway:T -- B:junctionRight
```
```mermaid
architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction junctionCenter
junction junctionRight
left_disk:R -- L:junctionCenter
top_disk:B -- T:junctionCenter
bottom_disk:T -- B:junctionCenter
junctionCenter:R -- L:junctionRight
top_gateway:B -- T:junctionRight
bottom_gateway:T -- B:junctionRight
```
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.
```mermaid-example
architecture-beta
group api(logos:aws-lambda)[API]
service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```
```mermaid
architecture-beta
group api(logos:aws-lambda)[API]
service db(logos:aws-aurora)[Database] in api
service disk1(logos:aws-glacier)[Storage] in api
service disk2(logos:aws-s3)[Storage] in api
service server(logos:aws-ec2)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
```

View File

@@ -286,6 +286,7 @@ erDiagram
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
- (v11.1.0+) If you want a multi-line label on a relationship, use `<br />` between the two lines (`"first line<br />second line"`)
## Styling

View File

@@ -23,6 +23,7 @@ export default tseslint.config(
'**/generated/',
'**/coverage/',
'packages/mermaid/src/config.type.ts',
'packages/mermaid/src/docs/.vitepress/components.d.ts',
],
},
{

View File

@@ -4,7 +4,7 @@
"version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247",
"packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1",
"keywords": [
"diagram",
"markdown",
@@ -42,7 +42,8 @@
"test": "pnpm lint && vitest run",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage",
"prepare": "husky install && pnpm build",
"test:check:tsc": "tsx scripts/tsc-check.ts",
"prepare": "husky && pnpm build",
"pre-commit": "lint-staged"
},
"repository": {
@@ -88,7 +89,7 @@
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
"cspell": "^8.6.0",
"cypress": "^13.11.0",
"cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.21.5",
"eslint": "^9.4.0",
@@ -96,7 +97,7 @@
"eslint-plugin-cypress": "^3.3.0",
"eslint-plugin-html": "^8.1.1",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-jsdoc": "^48.2.9",
"eslint-plugin-jsdoc": "^50.0.0",
"eslint-plugin-json": "^4.0.0",
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-markdown": "^5.0.0",
@@ -116,7 +117,6 @@
"markdown-table": "^3.0.3",
"nyc": "^15.1.0",
"path-browserify": "^1.0.1",
"pnpm": "^8.15.5",
"prettier": "^3.2.5",
"prettier-plugin-jsdoc": "^1.3.0",
"rimraf": "^5.0.5",

View File

@@ -0,0 +1,31 @@
# @mermaid-js/layout-elk
## 0.1.4
### Patch Changes
- [#5847](https://github.com/mermaid-js/mermaid/pull/5847) [`dd03043`](https://github.com/mermaid-js/mermaid/commit/dd0304387e85fc57a9ebb666f89ef788c012c2c5) Thanks [@sidharthv96](https://github.com/sidharthv96)! - chore: fix render types
## 0.1.3
### Patch Changes
- [#5810](https://github.com/mermaid-js/mermaid/pull/5810) [`33a809f`](https://github.com/mermaid-js/mermaid/commit/33a809f09a9aa1f84ba06201ab550bad81c3ff65) Thanks [@knsv](https://github.com/knsv)! - fix: Updates to the default elk configuration
feat: exposing cycleBreakingStrategy to the configuration so that it can be modified suing the configuration.
- Updated dependencies [[`6ecdf7b`](https://github.com/mermaid-js/mermaid/commit/6ecdf7be688efdc53c52fea3ba891327242bc890), [`28bd07f`](https://github.com/mermaid-js/mermaid/commit/28bd07fdeb4fc981107d21317ec6160b31f80116), [`8e640da`](https://github.com/mermaid-js/mermaid/commit/8e640da5436e8ae013b11b1c1821a9afcc15d0d3), [`256a148`](https://github.com/mermaid-js/mermaid/commit/256a148bbf484fc7db6c19f94dd69d5d268ee048), [`16faef4`](https://github.com/mermaid-js/mermaid/commit/16faef4613b91a7d3a98a1563c25b57f9238acc7)]:
- mermaid@11.1.0
## 0.1.2
### Patch Changes
- [#5761](https://github.com/mermaid-js/mermaid/pull/5761) [`b34dfe8`](https://github.com/mermaid-js/mermaid/commit/b34dfe8f45eded31da10965ced7ea40fde1ca76c) Thanks [@sidharthv96](https://github.com/sidharthv96)! - Fix type file path
## 0.1.1
### Patch Changes
- [#5758](https://github.com/mermaid-js/mermaid/pull/5758) [`501a55d`](https://github.com/mermaid-js/mermaid/commit/501a55d8f225901ba345c498dec4298490a0196e) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: Types path
- Updated dependencies [[`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff)]:
- mermaid@11.0.2

View File

@@ -1,14 +1,14 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.1.0",
"version": "0.1.4",
"description": "ELK layout engine for mermaid",
"module": "dist/mermaid-layout-elk.core.mjs",
"types": "dist/packages/mermaid-layout-elk/src/index.d.ts",
"types": "dist/layouts.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-layout-elk.core.mjs",
"types": "./dist/packages/mermaid-layout-elk/src/index.d.ts"
"types": "./dist/layouts.d.ts"
},
"./*": "./*"
},

View File

@@ -224,7 +224,7 @@ export const render = async (
* Add edges to graph based on parsed graph definition
*/
const addEdges = async function (
dataForLayout: { edges: any; direction: string },
dataForLayout: { edges: any; direction?: string },
graph: {
id?: string;
layoutOptions?: {
@@ -749,17 +749,37 @@ export const render = async (
layoutOptions: {
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
'elk.algorithm': algorithm,
'nodePlacement.strategy': data4Layout.config.elk.nodePlacementStrategy,
'elk.layered.mergeEdges': data4Layout.config.elk.mergeEdges,
'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,
'elk.direction': 'DOWN',
'spacing.baseValue': 30,
// 'spacing.nodeNode': 40,
// 'spacing.nodeNodeBetweenLayers': 45,
// 'spacing.edgeNode': 40,
// 'spacing.edgeNodeBetweenLayers': 30,
// 'spacing.edgeEdge': 30,
// 'spacing.edgeEdgeBetweenLayers': 40,
// 'spacing.nodeSelfLoop': 50,
'spacing.baseValue': 35,
'elk.layered.unnecessaryBendpoints': true,
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk?.cycleBreakingStrategy,
// 'spacing.nodeNode': 20,
// 'spacing.nodeNodeBetweenLayers': 25,
// 'spacing.edgeNode': 20,
// 'spacing.edgeNodeBetweenLayers': 10,
// 'spacing.edgeEdge': 10,
// 'spacing.edgeEdgeBetweenLayers': 20,
// 'spacing.nodeSelfLoop': 20,
// Tweaking options
// 'elk.layered.nodePlacement.favorStraightEdges': true,
// 'nodePlacement.feedbackEdges': true,
// 'elk.layered.wrapping.multiEdge.improveCuts': true,
// 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
// 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
// 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
// 'elk.layered.mergeHierarchyEdges': true,
// 'elk.layered.feedbackEdges': true,
// 'elk.layered.crossingMinimization.semiInteractive': true,
// 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
// 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
// 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
// 'elk.insideSelfLoops.activate': true,
// 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',
// 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
},
children: [],
edges: [],
@@ -817,8 +837,8 @@ export const render = async (
...node.layoutOptions,
'elk.algorithm': algorithm,
'elk.direction': dir2ElkDirection(node.dir),
'nodePlacement.strategy': data4Layout.config['elk.nodePlacement.strategy'],
'elk.layered.mergeEdges': data4Layout.config['elk.mergeEdges'],
'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,
'elk.hierarchyHandling': 'SEPARATE_CHILDREN',
};
}

View File

@@ -1,5 +1,58 @@
# mermaid
## 11.2.1
### Patch Changes
- [#5856](https://github.com/mermaid-js/mermaid/pull/5856) [`bfd8c63`](https://github.com/mermaid-js/mermaid/commit/bfd8c63daaa8420e57da9953922b9f0c94123064) Thanks [@knsv](https://github.com/knsv)! - Fix for issue with calculation of label width when using in firefox
## 11.2.0
### Minor Changes
- [#5831](https://github.com/mermaid-js/mermaid/pull/5831) [`64abf29`](https://github.com/mermaid-js/mermaid/commit/64abf29ea870eaa47148197f95ce714f85bd7eea) Thanks [@sidharthv96](https://github.com/sidharthv96)! - feat: Return parsed config from mermaid.parse
### Patch Changes
- [#5838](https://github.com/mermaid-js/mermaid/pull/5838) [`5e75320`](https://github.com/mermaid-js/mermaid/commit/5e75320d49eab65aca630dcc3c04c8d620a8bbf7) Thanks [@bollwyvl](https://github.com/bollwyvl)! - fix: Replace $root with relative paths
## 11.1.1
### Patch Changes
- [#5828](https://github.com/mermaid-js/mermaid/pull/5828) [`4c43d21`](https://github.com/mermaid-js/mermaid/commit/4c43d21196f784b6f483ae635fc462329f3d176f) Thanks [@knsv](https://github.com/knsv)! - fix: Fix for issue where self-loops in the root of diagrams break the rendering
## 11.1.0
### Minor Changes
- [#5793](https://github.com/mermaid-js/mermaid/pull/5793) [`6ecdf7b`](https://github.com/mermaid-js/mermaid/commit/6ecdf7be688efdc53c52fea3ba891327242bc890) Thanks [@sidharthv96](https://github.com/sidharthv96)! - feat: Add support for iconify icons
- [#5711](https://github.com/mermaid-js/mermaid/pull/5711) [`8e640da`](https://github.com/mermaid-js/mermaid/commit/8e640da5436e8ae013b11b1c1821a9afcc15d0d3) Thanks [@NicolasNewman](https://github.com/NicolasNewman)! - feat(er): allow multi-line relationship labels
- [#5452](https://github.com/mermaid-js/mermaid/pull/5452) [`256a148`](https://github.com/mermaid-js/mermaid/commit/256a148bbf484fc7db6c19f94dd69d5d268ee048) Thanks [@NicolasNewman](https://github.com/NicolasNewman)! - New Diagram: Architecture
Adds architecture diagrams which allows users to show relations between services.
### Patch Changes
- [#5810](https://github.com/mermaid-js/mermaid/pull/5810) [`28bd07f`](https://github.com/mermaid-js/mermaid/commit/28bd07fdeb4fc981107d21317ec6160b31f80116) Thanks [@knsv](https://github.com/knsv)! - Fix for self loops in cluster
Supporting legacy defaultRenderer directive
- [#5789](https://github.com/mermaid-js/mermaid/pull/5789) [`16faef4`](https://github.com/mermaid-js/mermaid/commit/16faef4613b91a7d3a98a1563c25b57f9238acc7) Thanks [@sidharthv96](https://github.com/sidharthv96)! - chore: Move icons to architecture, remove full icon sets to reduce bundle size
- Updated dependencies [[`256a148`](https://github.com/mermaid-js/mermaid/commit/256a148bbf484fc7db6c19f94dd69d5d268ee048), [`7d8143b`](https://github.com/mermaid-js/mermaid/commit/7d8143b917ee3562149a0e0a821ed2d6f29cc05d)]:
- @mermaid-js/parser@0.3.0
## 11.0.2
### Patch Changes
- [#5664](https://github.com/mermaid-js/mermaid/pull/5664) [`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff) Thanks [@Austin-Fulbright](https://github.com/Austin-Fulbright)! - chore: Migrate git graph to langium, use typescript for internals
- Updated dependencies [[`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff)]:
- @mermaid-js/parser@0.2.0
## 11.0.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.0.1",
"version": "11.2.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",
@@ -68,9 +68,11 @@
},
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "workspace:^",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",
@@ -87,7 +89,9 @@
},
"devDependencies": {
"@adobe/jsonschema2md": "^8.0.0",
"@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.4",
"@types/cytoscape-fcose": "^2.2.4",
"@types/d3": "^7.4.3",
"@types/d3-sankey": "^0.12.4",
"@types/d3-scale": "^4.0.8",

View File

@@ -5,23 +5,34 @@
* So contributors adding new features will only have to add the placeholder and not worry about updating the version number.
*
*/
import { readFile, writeFile } from 'fs/promises';
import { posix } from 'path';
import {
getGlobs,
getFilesFromGlobs,
SOURCE_DOCS_DIR,
readSyncedUTF8file,
getGlobs,
MERMAID_RELEASE_VERSION,
readSyncedUTF8file,
SOURCE_DOCS_DIR,
} from './docs.mjs';
import { writeFile } from 'fs/promises';
const verifyOnly: boolean = process.argv.includes('--verify');
const versionPlaceholder = '<MERMAID_RELEASE_VERSION>';
const verifyDocumentation = async () => {
const fileContent = await readFile('./src/docs/community/contributing.md', 'utf-8');
if (!fileContent.includes(versionPlaceholder)) {
console.error(
`The placeholder ${versionPlaceholder} is not present in the contributing.md file.`
);
process.exit(1);
}
};
const main = async () => {
await verifyDocumentation();
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
mdFileGlobs.push('!**/community/development.md', '!**/community/code.md');
mdFileGlobs.push('!**/community/contributing.md');
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
mdFiles.sort();
const mdFilesWithPlaceholder: string[] = [];

View File

@@ -99,6 +99,16 @@ export interface MermaidConfig {
*
*/
nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
/**
* This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
*
*/
cycleBreakingStrategy?:
| 'GREEDY'
| 'DEPTH_FIRST'
| 'INTERACTIVE'
| 'MODEL_ORDER'
| 'GREEDY_MODEL_ORDER';
};
darkMode?: boolean;
htmlLabels?: boolean;
@@ -181,6 +191,7 @@ export interface MermaidConfig {
quadrantChart?: QuadrantChartConfig;
xyChart?: XYChartConfig;
requirement?: RequirementDiagramConfig;
architecture?: ArchitectureDiagramConfig;
mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig;
@@ -991,6 +1002,17 @@ export interface RequirementDiagramConfig extends BaseDiagramConfig {
rect_padding?: number;
line_height?: number;
}
/**
* The object containing configurations specific for architecture diagrams
*
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "ArchitectureDiagramConfig".
*/
export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
padding?: number;
iconSize?: number;
fontSize?: number;
}
/**
* The object containing configurations specific for mindmap diagrams
*

View File

@@ -22,6 +22,7 @@ import mindmap from '../diagrams/mindmap/detector.js';
import sankey from '../diagrams/sankey/sankeyDetector.js';
import { packet } from '../diagrams/packet/detector.js';
import block from '../diagrams/block/blockDetector.js';
import architecture from '../diagrams/architecture/architectureDetector.js';
import { registerLazyLoadedDiagrams } from './detectType.js';
import { registerDiagram } from './diagramAPI.js';
@@ -90,6 +91,7 @@ export const addDiagrams = () => {
sankey,
packet,
xychart,
block
block,
architecture
);
};

View File

@@ -0,0 +1,333 @@
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import DEFAULT_CONFIG from '../../defaultConfig.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { D3Element } from '../../types.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import {
clear as commonClear,
getAccDescription,
getAccTitle,
getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import type {
ArchitectureDB,
ArchitectureDirectionPair,
ArchitectureDirectionPairMap,
ArchitectureEdge,
ArchitectureGroup,
ArchitectureJunction,
ArchitectureNode,
ArchitectureService,
ArchitectureSpatialMap,
ArchitectureState,
} from './architectureTypes.js';
import {
getArchitectureDirectionPair,
isArchitectureDirection,
isArchitectureJunction,
isArchitectureService,
shiftPositionByArchitectureDirectionPair,
} from './architectureTypes.js';
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
DEFAULT_CONFIG.architecture;
const state = new ImperativeState<ArchitectureState>(() => ({
nodes: {},
groups: {},
edges: [],
registeredIds: {},
config: DEFAULT_ARCHITECTURE_CONFIG,
dataStructures: undefined,
elements: {},
}));
const clear = (): void => {
state.reset();
commonClear();
};
const addService = function ({
id,
icon,
in: parent,
title,
iconText,
}: Omit<ArchitectureService, 'edges'>) {
if (state.records.registeredIds[id] !== undefined) {
throw new Error(
`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`
);
}
if (parent !== undefined) {
if (id === parent) {
throw new Error(`The service [${id}] cannot be placed within itself`);
}
if (state.records.registeredIds[parent] === undefined) {
throw new Error(
`The service [${id}]'s parent does not exist. Please make sure the parent is created before this service`
);
}
if (state.records.registeredIds[parent] === 'node') {
throw new Error(`The service [${id}]'s parent is not a group`);
}
}
state.records.registeredIds[id] = 'node';
state.records.nodes[id] = {
id,
type: 'service',
icon,
iconText,
title,
edges: [],
in: parent,
};
};
const getServices = (): ArchitectureService[] =>
Object.values(state.records.nodes).filter<ArchitectureService>(isArchitectureService);
const addJunction = function ({ id, in: parent }: Omit<ArchitectureJunction, 'edges'>) {
state.records.registeredIds[id] = 'node';
state.records.nodes[id] = {
id,
type: 'junction',
edges: [],
in: parent,
};
};
const getJunctions = (): ArchitectureJunction[] =>
Object.values(state.records.nodes).filter<ArchitectureJunction>(isArchitectureJunction);
const getNodes = (): ArchitectureNode[] => Object.values(state.records.nodes);
const getNode = (id: string): ArchitectureNode | null => state.records.nodes[id];
const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) {
if (state.records.registeredIds[id] !== undefined) {
throw new Error(
`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`
);
}
if (parent !== undefined) {
if (id === parent) {
throw new Error(`The group [${id}] cannot be placed within itself`);
}
if (state.records.registeredIds[parent] === undefined) {
throw new Error(
`The group [${id}]'s parent does not exist. Please make sure the parent is created before this group`
);
}
if (state.records.registeredIds[parent] === 'node') {
throw new Error(`The group [${id}]'s parent is not a group`);
}
}
state.records.registeredIds[id] = 'group';
state.records.groups[id] = {
id,
icon,
title,
in: parent,
};
};
const getGroups = (): ArchitectureGroup[] => {
return Object.values(state.records.groups);
};
const addEdge = function ({
lhsId,
rhsId,
lhsDir,
rhsDir,
lhsInto,
rhsInto,
lhsGroup,
rhsGroup,
title,
}: ArchitectureEdge<string>) {
if (!isArchitectureDirection(lhsDir)) {
throw new Error(
`Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}`
);
}
if (!isArchitectureDirection(rhsDir)) {
throw new Error(
`Invalid direction given for right hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${rhsDir}`
);
}
if (state.records.nodes[lhsId] === undefined && state.records.groups[lhsId] === undefined) {
throw new Error(
`The left-hand id [${lhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
);
}
if (state.records.nodes[rhsId] === undefined && state.records.groups[lhsId] === undefined) {
throw new Error(
`The right-hand id [${rhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
);
}
const lhsGroupId = state.records.nodes[lhsId].in;
const rhsGroupId = state.records.nodes[rhsId].in;
if (lhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
throw new Error(
`The left-hand id [${lhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
);
}
if (rhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
throw new Error(
`The right-hand id [${rhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
);
}
const edge = {
lhsId,
lhsDir,
lhsInto,
lhsGroup,
rhsId,
rhsDir,
rhsInto,
rhsGroup,
title,
};
state.records.edges.push(edge);
if (state.records.nodes[lhsId] && state.records.nodes[rhsId]) {
state.records.nodes[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
state.records.nodes[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
}
};
const getEdges = (): ArchitectureEdge[] => state.records.edges;
/**
* Returns the current diagram's adjacency list & spatial map.
* If they have not been created, run the algorithms to generate them.
* @returns
*/
const getDataStructures = () => {
if (state.records.dataStructures === undefined) {
// Create an adjacency list of the diagram to perform BFS on
// Outer reduce applied on all services
// Inner reduce applied on the edges for a service
const adjList = Object.entries(state.records.nodes).reduce<
Record<string, ArchitectureDirectionPairMap>
>((prevOuter, [id, service]) => {
prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
if (edge.lhsId === id) {
// source is LHS
const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
if (pair) {
prevInner[pair] = edge.rhsId;
}
} else {
// source is RHS
const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
if (pair) {
prevInner[pair] = edge.lhsId;
}
}
return prevInner;
}, {});
return prevOuter;
}, {});
// Configuration for the initial pass of BFS
const firstId = Object.keys(adjList)[0];
const visited = { [firstId]: 1 };
const notVisited = Object.keys(adjList).reduce(
(prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
{} as Record<string, number>
);
// Perform BFS on the adjacency list
const BFS = (startingId: string): ArchitectureSpatialMap => {
const spatialMap = { [startingId]: [0, 0] };
const queue = [startingId];
while (queue.length > 0) {
const id = queue.shift();
if (id) {
visited[id] = 1;
delete notVisited[id];
const adj = adjList[id];
const [posX, posY] = spatialMap[id];
Object.entries(adj).forEach(([dir, rhsId]) => {
if (!visited[rhsId]) {
spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
[posX, posY],
dir as ArchitectureDirectionPair
);
queue.push(rhsId);
}
});
}
}
return spatialMap;
};
const spatialMaps = [BFS(firstId)];
// If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
while (Object.keys(notVisited).length > 0) {
spatialMaps.push(BFS(Object.keys(notVisited)[0]));
}
state.records.dataStructures = {
adjList,
spatialMaps,
};
}
return state.records.dataStructures;
};
const setElementForId = (id: string, element: D3Element) => {
state.records.elements[id] = element;
};
const getElementById = (id: string) => state.records.elements[id];
export const db: ArchitectureDB = {
clear,
setDiagramTitle,
getDiagramTitle,
setAccTitle,
getAccTitle,
setAccDescription,
getAccDescription,
addService,
getServices,
addJunction,
getJunctions,
getNodes,
getNode,
addGroup,
getGroups,
addEdge,
getEdges,
setElementForId,
getElementById,
getDataStructures,
};
/**
* Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
* @param field - the config field to access
* @returns
*/
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
field: T
): Required<ArchitectureDiagramConfig>[T] {
const arch = getConfig().architecture;
if (arch?.[field]) {
return arch[field] as Required<ArchitectureDiagramConfig>[T];
}
return DEFAULT_ARCHITECTURE_CONFIG[field];
}

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
import { unknownIcon } from '../../rendering-util/icons.js';
import type { IconifyJSON } from '@iconify/types';
const wrapIcon = (icon: string) => {
return `<g><rect width="80" height="80" style="fill: #087ebf; stroke-width: 0px;"/>${icon}</g>`;
};
export const architectureIcons: IconifyJSON = {
prefix: 'mermaid-architecture',
height: 80,
width: 80,
icons: {
database: {
body: wrapIcon(
'<path id="b" data-name="4" d="m20,57.86c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><path id="c" data-name="3" d="m20,45.95c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><path id="d" data-name="2" d="m20,34.05c0,3.94,8.95,7.14,20,7.14s20-3.2,20-7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse id="e" data-name="1" cx="40" cy="22.14" rx="20" ry="7.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="20" y1="57.86" x2="20" y2="22.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="60" y1="57.86" x2="60" y2="22.14" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>'
),
},
server: {
body: wrapIcon(
'<rect x="17.5" y="17.5" width="45" height="45" rx="2" ry="2" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="17.5" y1="32.5" x2="62.5" y2="32.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="17.5" y1="47.5" x2="62.5" y2="47.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><g><path d="m56.25,25c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/><path d="m56.25,25c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/></g><g><path d="m56.25,40c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/><path d="m56.25,40c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/></g><g><path d="m56.25,55c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: #fff; stroke-width: 0px;"/><path d="m56.25,55c0,.27-.45.5-1,.5h-10.5c-.55,0-1-.23-1-.5s.45-.5,1-.5h10.5c.55,0,1,.23,1,.5Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10;"/></g><g><circle cx="32.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="27.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="22.5" cy="25" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/></g><g><circle cx="32.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="27.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="22.5" cy="40" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/></g><g><circle cx="32.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="27.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/><circle cx="22.5" cy="55" r=".75" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10;"/></g>'
),
},
disk: {
body: wrapIcon(
'<rect x="20" y="15" width="40" height="50" rx="1" ry="1" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="24" cy="19.17" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="56" cy="19.17" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="24" cy="60.83" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="56" cy="60.83" rx=".8" ry=".83" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="40" cy="33.75" rx="14" ry="14.58" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><ellipse cx="40" cy="33.75" rx="4" ry="4.17" style="fill: #fff; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><path d="m37.51,42.52l-4.83,13.22c-.26.71-1.1,1.02-1.76.64l-4.18-2.42c-.66-.38-.81-1.26-.33-1.84l9.01-10.8c.88-1.05,2.56-.08,2.09,1.2Z" style="fill: #fff; stroke-width: 0px;"/>'
),
},
internet: {
body: wrapIcon(
'<circle cx="40" cy="40" r="22.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="40" y1="17.5" x2="40" y2="62.5" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="17.5" y1="40" x2="62.5" y2="40" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><path d="m39.99,17.51c-15.28,11.1-15.28,33.88,0,44.98" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><path d="m40.01,17.51c15.28,11.1,15.28,33.88,0,44.98" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="19.75" y1="30.1" x2="60.25" y2="30.1" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/><line x1="19.75" y1="49.9" x2="60.25" y2="49.9" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>'
),
},
cloud: {
body: wrapIcon(
'<path d="m65,47.5c0,2.76-2.24,5-5,5H20c-2.76,0-5-2.24-5-5,0-1.87,1.03-3.51,2.56-4.36-.04-.21-.06-.42-.06-.64,0-2.6,2.48-4.74,5.65-4.97,1.65-4.51,6.34-7.76,11.85-7.76.86,0,1.69.08,2.5.23,2.09-1.57,4.69-2.5,7.5-2.5,6.1,0,11.19,4.38,12.28,10.17,2.14.56,3.72,2.51,3.72,4.83,0,.03,0,.07-.01.1,2.29.46,4.01,2.48,4.01,4.9Z" style="fill: none; stroke: #fff; stroke-miterlimit: 10; stroke-width: 2px;"/>'
),
},
unknown: unknownIcon,
blank: {
body: wrapIcon(''),
},
},
};

View File

@@ -0,0 +1,24 @@
import type { Architecture } from '@mermaid-js/parser';
import { parse } from '@mermaid-js/parser';
import { log } from '../../logger.js';
import type { ParserDefinition } from '../../diagram-api/types.js';
import { populateCommonDb } from '../common/populateCommonDb.js';
import type { ArchitectureDB } from './architectureTypes.js';
import { db } from './architectureDb.js';
const populateDb = (ast: Architecture, db: ArchitectureDB) => {
populateCommonDb(ast, db);
ast.groups.map(db.addGroup);
ast.services.map((service) => db.addService({ ...service, type: 'service' }));
ast.junctions.map((service) => db.addJunction({ ...service, type: 'junction' }));
// @ts-ignore TODO our parser guarantees the type is L/R/T/B and not string. How to change to union type?
ast.edges.map(db.addEdge);
};
export const parser: ParserDefinition = {
parse: async (input: string): Promise<void> => {
const ast: Architecture = await parse('architecture', input);
log.debug(ast);
populateDb(ast, db);
},
};

View File

@@ -0,0 +1,466 @@
import { registerIconPacks } from '../../rendering-util/icons.js';
import type { Position } from 'cytoscape';
import cytoscape from 'cytoscape';
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
import fcose from 'cytoscape-fcose';
import { select } from 'd3';
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
import type { Diagram } from '../../Diagram.js';
import { log } from '../../logger.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import { getConfigField } from './architectureDb.js';
import { architectureIcons } from './architectureIcons.js';
import type {
ArchitectureDataStructures,
ArchitectureJunction,
ArchitectureSpatialMap,
EdgeSingular,
EdgeSingularData,
NodeSingularData,
} from './architectureTypes.js';
import {
type ArchitectureDB,
type ArchitectureDirection,
type ArchitectureEdge,
type ArchitectureGroup,
type ArchitectureService,
ArchitectureDirectionName,
edgeData,
getOppositeArchitectureDirection,
isArchitectureDirectionXY,
isArchitectureDirectionY,
nodeData,
} from './architectureTypes.js';
import { drawEdges, drawGroups, drawJunctions, drawServices } from './svgDraw.js';
registerIconPacks([
{
name: architectureIcons.prefix,
icons: architectureIcons,
},
]);
cytoscape.use(fcose);
function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
services.forEach((service) => {
cy.add({
group: 'nodes',
data: {
type: 'service',
id: service.id,
icon: service.icon,
label: service.title,
parent: service.in,
width: getConfigField('iconSize'),
height: getConfigField('iconSize'),
} as NodeSingularData,
classes: 'node-service',
});
});
}
function addJunctions(junctions: ArchitectureJunction[], cy: cytoscape.Core) {
junctions.forEach((junction) => {
cy.add({
group: 'nodes',
data: {
type: 'junction',
id: junction.id,
parent: junction.in,
width: getConfigField('iconSize'),
height: getConfigField('iconSize'),
} as NodeSingularData,
classes: 'node-junction',
});
});
}
function positionNodes(db: ArchitectureDB, cy: cytoscape.Core) {
cy.nodes().map((node) => {
const data = nodeData(node);
if (data.type === 'group') {
return;
}
data.x = node.position().x;
data.y = node.position().y;
const nodeElem = db.getElementById(data.id);
nodeElem.attr('transform', 'translate(' + (data.x || 0) + ',' + (data.y || 0) + ')');
});
}
function addGroups(groups: ArchitectureGroup[], cy: cytoscape.Core) {
groups.forEach((group) => {
cy.add({
group: 'nodes',
data: {
type: 'group',
id: group.id,
icon: group.icon,
label: group.title,
parent: group.in,
} as NodeSingularData,
classes: 'node-group',
});
});
}
function addEdges(edges: ArchitectureEdge[], cy: cytoscape.Core) {
edges.forEach((parsedEdge) => {
const { lhsId, rhsId, lhsInto, lhsGroup, rhsInto, lhsDir, rhsDir, rhsGroup, title } =
parsedEdge;
const edgeType = isArchitectureDirectionXY(parsedEdge.lhsDir, parsedEdge.rhsDir)
? 'segments'
: 'straight';
const edge: EdgeSingularData = {
id: `${lhsId}-${rhsId}`,
label: title,
source: lhsId,
sourceDir: lhsDir,
sourceArrow: lhsInto,
sourceGroup: lhsGroup,
sourceEndpoint:
lhsDir === 'L'
? '0 50%'
: lhsDir === 'R'
? '100% 50%'
: lhsDir === 'T'
? '50% 0'
: '50% 100%',
target: rhsId,
targetDir: rhsDir,
targetArrow: rhsInto,
targetGroup: rhsGroup,
targetEndpoint:
rhsDir === 'L'
? '0 50%'
: rhsDir === 'R'
? '100% 50%'
: rhsDir === 'T'
? '50% 0'
: '50% 100%',
};
cy.add({
group: 'edges',
data: edge,
classes: edgeType,
});
});
}
function getAlignments(spatialMaps: ArchitectureSpatialMap[]): fcose.FcoseAlignmentConstraint {
const alignments = spatialMaps.map((spatialMap) => {
const horizontalAlignments: Record<number, string[]> = {};
const verticalAlignments: Record<number, string[]> = {};
// Group service ids in an object with their x and y coordinate as the key
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
if (!horizontalAlignments[y]) {
horizontalAlignments[y] = [];
}
if (!verticalAlignments[x]) {
verticalAlignments[x] = [];
}
horizontalAlignments[y].push(id);
verticalAlignments[x].push(id);
});
// Merge the values of each object into a list if the inner list has at least 2 elements
return {
horiz: Object.values(horizontalAlignments).filter((arr) => arr.length > 1),
vert: Object.values(verticalAlignments).filter((arr) => arr.length > 1),
};
});
// Merge the alignment lists for each spatial map into one 2d array per axis
const [horizontal, vertical] = alignments.reduce(
([prevHoriz, prevVert], { horiz, vert }) => {
return [
[...prevHoriz, ...horiz],
[...prevVert, ...vert],
];
},
[[] as string[][], [] as string[][]]
);
return {
horizontal,
vertical,
};
}
function getRelativeConstraints(
spatialMaps: ArchitectureSpatialMap[]
): fcose.FcoseRelativePlacementConstraint[] {
const relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
spatialMaps.forEach((spatialMap) => {
const invSpatialMap = Object.fromEntries(
Object.entries(spatialMap).map(([id, pos]) => [posToStr(pos), id])
);
// perform BFS
const queue = [posToStr([0, 0])];
const visited: Record<string, number> = {};
const directions: Record<ArchitectureDirection, number[]> = {
L: [-1, 0],
R: [1, 0],
T: [0, 1],
B: [0, -1],
};
while (queue.length > 0) {
const curr = queue.shift();
if (curr) {
visited[curr] = 1;
const currId = invSpatialMap[curr];
if (currId) {
const currPos = strToPos(curr);
Object.entries(directions).forEach(([dir, shift]) => {
const newPos = posToStr([currPos[0] + shift[0], currPos[1] + shift[1]]);
const newId = invSpatialMap[newPos];
// If there is an adjacent service to the current one and it has not yet been visited
if (newId && !visited[newPos]) {
queue.push(newPos);
// @ts-ignore cannot determine if left/right or top/bottom are paired together
relativeConstraints.push({
[ArchitectureDirectionName[dir as ArchitectureDirection]]: newId,
[ArchitectureDirectionName[
getOppositeArchitectureDirection(dir as ArchitectureDirection)
]]: currId,
gap: 1.5 * getConfigField('iconSize'),
});
}
});
}
}
}
});
return relativeConstraints;
}
function layoutArchitecture(
services: ArchitectureService[],
junctions: ArchitectureJunction[],
groups: ArchitectureGroup[],
edges: ArchitectureEdge[],
{ spatialMaps }: ArchitectureDataStructures
): Promise<cytoscape.Core> {
return new Promise((resolve) => {
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
const cy = cytoscape({
container: document.getElementById('cy'),
style: [
{
selector: 'edge',
style: {
'curve-style': 'straight',
label: 'data(label)',
'source-endpoint': 'data(sourceEndpoint)',
'target-endpoint': 'data(targetEndpoint)',
},
},
{
selector: 'edge.segments',
style: {
'curve-style': 'segments',
'segment-weights': '0',
'segment-distances': [0.5],
// @ts-ignore Incorrect library types
'edge-distances': 'endpoints',
'source-endpoint': 'data(sourceEndpoint)',
'target-endpoint': 'data(targetEndpoint)',
},
},
{
selector: 'node',
style: {
// @ts-ignore Incorrect library types
'compound-sizing-wrt-labels': 'include',
},
},
{
selector: 'node[label]',
style: {
'text-valign': 'bottom',
'text-halign': 'center',
'font-size': `${getConfigField('fontSize')}px`,
},
},
{
selector: '.node-service',
style: {
label: 'data(label)',
width: 'data(width)',
height: 'data(height)',
},
},
{
selector: '.node-junction',
style: {
width: 'data(width)',
height: 'data(height)',
},
},
{
selector: '.node-group',
style: {
// @ts-ignore Incorrect library types
padding: `${getConfigField('padding')}px`,
},
},
],
});
// Remove element after layout
renderEl.remove();
addGroups(groups, cy);
addServices(services, cy);
addJunctions(junctions, cy);
addEdges(edges, cy);
// Use the spatial map to create alignment arrays for fcose
const alignmentConstraint = getAlignments(spatialMaps);
// Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
const layout = cy.layout({
name: 'fcose',
quality: 'proof',
styleEnabled: false,
animate: false,
nodeDimensionsIncludeLabels: false,
// Adjust the edge parameters if it passes through the border of a group
// Hacky fix for: https://github.com/iVis-at-Bilkent/cytoscape.js-fcose/issues/67
idealEdgeLength(edge: EdgeSingular) {
const [nodeA, nodeB] = edge.connectedNodes();
const { parent: parentA } = nodeData(nodeA);
const { parent: parentB } = nodeData(nodeB);
const elasticity =
parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize');
return elasticity;
},
edgeElasticity(edge: EdgeSingular) {
const [nodeA, nodeB] = edge.connectedNodes();
const { parent: parentA } = nodeData(nodeA);
const { parent: parentB } = nodeData(nodeB);
const elasticity = parentA === parentB ? 0.45 : 0.001;
return elasticity;
},
alignmentConstraint,
relativePlacementConstraint,
} as FcoseLayoutOptions);
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
layout.one('layoutstop', () => {
function getSegmentWeights(
source: Position,
target: Position,
pointX: number,
pointY: number
) {
let W, D;
const { x: sX, y: sY } = source;
const { x: tX, y: tY } = target;
D =
(pointY - sY + ((sX - pointX) * (sY - tY)) / (sX - tX)) /
Math.sqrt(1 + Math.pow((sY - tY) / (sX - tX), 2));
W = Math.sqrt(Math.pow(pointY - sY, 2) + Math.pow(pointX - sX, 2) - Math.pow(D, 2));
const distAB = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2));
W = W / distAB;
//check whether the point (pointX, pointY) is on right or left of the line src to tgt. for instance : a point C(X, Y) and line (AB). d=(xB-xA)(yC-yA)-(yB-yA)(xC-xA). if d>0, then C is on left of the line. if d<0, it is on right. if d=0, it is on the line.
let delta1 = (tX - sX) * (pointY - sY) - (tY - sY) * (pointX - sX);
switch (true) {
case delta1 >= 0:
delta1 = 1;
break;
case delta1 < 0:
delta1 = -1;
break;
}
//check whether the point (pointX, pointY) is "behind" the line src to tgt
let delta2 = (tX - sX) * (pointX - sX) + (tY - sY) * (pointY - sY);
switch (true) {
case delta2 >= 0:
delta2 = 1;
break;
case delta2 < 0:
delta2 = -1;
break;
}
D = Math.abs(D) * delta1; //ensure that sign of D is same as sign of delta1. Hence we need to take absolute value of D and multiply by delta1
W = W * delta2;
return {
distances: D,
weights: W,
};
}
cy.startBatch();
for (const edge of Object.values(cy.edges())) {
if (edge.data?.()) {
const { x: sX, y: sY } = edge.source().position();
const { x: tX, y: tY } = edge.target().position();
if (sX !== tX && sY !== tY) {
const sEP = edge.sourceEndpoint();
const tEP = edge.targetEndpoint();
const { sourceDir } = edgeData(edge);
const [pointX, pointY] = isArchitectureDirectionY(sourceDir)
? [sEP.x, tEP.y]
: [tEP.x, sEP.y];
const { weights, distances } = getSegmentWeights(sEP, tEP, pointX, pointY);
edge.style('segment-distances', distances);
edge.style('segment-weights', weights);
}
}
}
cy.endBatch();
layout.run();
});
layout.run();
cy.ready((e) => {
log.info('Ready', e);
resolve(cy);
});
});
}
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
const db = diagObj.db as ArchitectureDB;
const services = db.getServices();
const junctions = db.getJunctions();
const groups = db.getGroups();
const edges = db.getEdges();
const ds = db.getDataStructures();
const svg: SVG = selectSvgElement(id);
const edgesElem = svg.append('g');
edgesElem.attr('class', 'architecture-edges');
const servicesElem = svg.append('g');
servicesElem.attr('class', 'architecture-services');
const groupElem = svg.append('g');
groupElem.attr('class', 'architecture-groups');
await drawServices(db, servicesElem, services);
drawJunctions(db, servicesElem, junctions);
const cy = await layoutArchitecture(services, junctions, groups, edges, ds);
await drawEdges(edgesElem, cy);
await drawGroups(groupElem, cy);
positionNodes(db, cy);
setupGraphViewbox(undefined, svg, getConfigField('padding'), getConfigField('useMaxWidth'));
};
export const renderer = { draw };

View File

@@ -0,0 +1,38 @@
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
import type { ArchitectureStyleOptions } from './architectureTypes.js';
const getStyles: DiagramStylesProvider = (options: ArchitectureStyleOptions) =>
`
.edge {
stroke-width: ${options.archEdgeWidth};
stroke: ${options.archEdgeColor};
fill: none;
}
.arrow {
fill: ${options.archEdgeArrowColor};
}
.node-bkg {
fill: none;
stroke: ${options.archGroupBorderColor};
stroke-width: ${options.archGroupBorderWidth};
stroke-dasharray: 8;
}
.node-icon-text {
display: flex;
align-items: center;
}
.node-icon-text > div {
color: #fff;
margin: 1px;
height: fit-content;
text-align: center;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
}
`;
export default getStyles;

View File

@@ -0,0 +1,351 @@
import type { DiagramDB } from '../../diagram-api/types.js';
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import type { D3Element } from '../../types.js';
import type cytoscape from 'cytoscape';
/*=======================================*\
| Architecture Diagram Types |
\*=======================================*/
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
export type ArchitectureDirectionX = Extract<ArchitectureDirection, 'L' | 'R'>;
export type ArchitectureDirectionY = Extract<ArchitectureDirection, 'T' | 'B'>;
/**
* Contains LL, RR, TT, BB which are impossible connections
*/
export type InvalidArchitectureDirectionPair = `${ArchitectureDirection}${ArchitectureDirection}`;
export type ArchitectureDirectionPair = Exclude<
InvalidArchitectureDirectionPair,
'LL' | 'RR' | 'TT' | 'BB'
>;
export type ArchitectureDirectionPairXY = Exclude<
InvalidArchitectureDirectionPair,
'LL' | 'RR' | 'TT' | 'BB' | 'LR' | 'RL' | 'TB' | 'BT'
>;
export const ArchitectureDirectionName = {
L: 'left',
R: 'right',
T: 'top',
B: 'bottom',
} as const;
export const ArchitectureDirectionArrow = {
L: (scale: number) => `${scale},${scale / 2} 0,${scale} 0,0`,
R: (scale: number) => `0,${scale / 2} ${scale},0 ${scale},${scale}`,
T: (scale: number) => `0,0 ${scale},0 ${scale / 2},${scale}`,
B: (scale: number) => `${scale / 2},0 ${scale},${scale} 0,${scale}`,
} as const;
export const ArchitectureDirectionArrowShift = {
L: (orig: number, arrowSize: number) => orig - arrowSize + 2,
R: (orig: number, _arrowSize: number) => orig - 2,
T: (orig: number, arrowSize: number) => orig - arrowSize + 2,
B: (orig: number, _arrowSize: number) => orig - 2,
} as const;
export const getOppositeArchitectureDirection = function (
x: ArchitectureDirection
): ArchitectureDirection {
if (isArchitectureDirectionX(x)) {
return x === 'L' ? 'R' : 'L';
} else {
return x === 'T' ? 'B' : 'T';
}
};
export const isArchitectureDirection = function (x: unknown): x is ArchitectureDirection {
const temp = x as ArchitectureDirection;
return temp === 'L' || temp === 'R' || temp === 'T' || temp === 'B';
};
export const isArchitectureDirectionX = function (
x: ArchitectureDirection
): x is ArchitectureDirectionX {
const temp = x as ArchitectureDirectionX;
return temp === 'L' || temp === 'R';
};
export const isArchitectureDirectionY = function (
x: ArchitectureDirection
): x is ArchitectureDirectionY {
const temp = x as ArchitectureDirectionY;
return temp === 'T' || temp === 'B';
};
export const isArchitectureDirectionXY = function (
a: ArchitectureDirection,
b: ArchitectureDirection
) {
const aX_bY = isArchitectureDirectionX(a) && isArchitectureDirectionY(b);
const aY_bX = isArchitectureDirectionY(a) && isArchitectureDirectionX(b);
return aX_bY || aY_bX;
};
export const isArchitecturePairXY = function (
pair: ArchitectureDirectionPair
): pair is ArchitectureDirectionPairXY {
const lhs = pair[0] as ArchitectureDirection;
const rhs = pair[1] as ArchitectureDirection;
const aX_bY = isArchitectureDirectionX(lhs) && isArchitectureDirectionY(rhs);
const aY_bX = isArchitectureDirectionY(lhs) && isArchitectureDirectionX(rhs);
return aX_bY || aY_bX;
};
/**
* Verifies that the architecture direction pair does not contain an invalid match (LL, RR, TT, BB)
* @param x - architecture direction pair which could potentially be invalid
* @returns true if the pair is not LL, RR, TT, or BB
*/
export const isValidArchitectureDirectionPair = function (
x: InvalidArchitectureDirectionPair
): x is ArchitectureDirectionPair {
return x !== 'LL' && x !== 'RR' && x !== 'TT' && x !== 'BB';
};
export type ArchitectureDirectionPairMap = {
[key in ArchitectureDirectionPair]?: string;
};
/**
* Creates a pair of the directions of each side of an edge. This function should be used instead of manually creating it to ensure that the source is always the first character.
*
* Note: Undefined is returned when sourceDir and targetDir are the same. In theory this should never happen since the diagram parser throws an error if a user defines it as such.
* @param sourceDir - source direction
* @param targetDir - target direction
* @returns
*/
export const getArchitectureDirectionPair = function (
sourceDir: ArchitectureDirection,
targetDir: ArchitectureDirection
): ArchitectureDirectionPair | undefined {
const pair: `${ArchitectureDirection}${ArchitectureDirection}` = `${sourceDir}${targetDir}`;
return isValidArchitectureDirectionPair(pair) ? pair : undefined;
};
/**
* Given an x,y position for an arrow and the direction of the edge it belongs to, return a factor for slightly shifting the edge
* @param param0 - [x, y] coordinate pair
* @param pair - architecture direction pair
* @returns a new [x, y] coordinate pair
*/
export const shiftPositionByArchitectureDirectionPair = function (
[x, y]: number[],
pair: ArchitectureDirectionPair
): number[] {
const lhs = pair[0] as ArchitectureDirection;
const rhs = pair[1] as ArchitectureDirection;
if (isArchitectureDirectionX(lhs)) {
if (isArchitectureDirectionY(rhs)) {
return [x + (lhs === 'L' ? -1 : 1), y + (rhs === 'T' ? 1 : -1)];
} else {
return [x + (lhs === 'L' ? -1 : 1), y];
}
} else {
if (isArchitectureDirectionX(rhs)) {
return [x + (rhs === 'L' ? 1 : -1), y + (lhs === 'T' ? 1 : -1)];
} else {
return [x, y + (lhs === 'T' ? 1 : -1)];
}
}
};
/**
* Given the directional pair of an XY edge, get the scale factors necessary to shift the coordinates inwards towards the edge
* @param pair - XY pair of an edge
* @returns - number[] containing [+/- 1, +/- 1]
*/
export const getArchitectureDirectionXYFactors = function (
pair: ArchitectureDirectionPairXY
): number[] {
if (pair === 'LT' || pair === 'TL') {
return [1, 1];
} else if (pair === 'BL' || pair === 'LB') {
return [1, -1];
} else if (pair === 'BR' || pair === 'RB') {
return [-1, -1];
} else {
return [-1, 1];
}
};
export interface ArchitectureStyleOptions {
archEdgeColor: string;
archEdgeArrowColor: string;
archEdgeWidth: string;
archGroupBorderColor: string;
archGroupBorderWidth: string;
}
export interface ArchitectureService {
id: string;
type: 'service';
edges: ArchitectureEdge[];
icon?: string;
iconText?: string;
title?: string;
in?: string;
width?: number;
height?: number;
}
export interface ArchitectureJunction {
id: string;
type: 'junction';
edges: ArchitectureEdge[];
in?: string;
width?: number;
height?: number;
}
export type ArchitectureNode = ArchitectureService | ArchitectureJunction;
export const isArchitectureService = function (x: ArchitectureNode): x is ArchitectureService {
const temp = x as ArchitectureService;
return temp.type === 'service';
};
export const isArchitectureJunction = function (x: ArchitectureNode): x is ArchitectureJunction {
const temp = x as ArchitectureJunction;
return temp.type === 'junction';
};
export interface ArchitectureGroup {
id: string;
icon?: string;
title?: string;
in?: string;
}
export interface ArchitectureEdge<DT = ArchitectureDirection> {
lhsId: string;
lhsDir: DT;
lhsInto?: boolean;
lhsGroup?: boolean;
rhsId: string;
rhsDir: DT;
rhsInto?: boolean;
rhsGroup?: boolean;
title?: string;
}
export interface ArchitectureDB extends DiagramDB {
clear: () => void;
addService: (service: Omit<ArchitectureService, 'edges'>) => void;
getServices: () => ArchitectureService[];
addJunction: (service: Omit<ArchitectureJunction, 'edges'>) => void;
getJunctions: () => ArchitectureJunction[];
getNodes: () => ArchitectureNode[];
getNode: (id: string) => ArchitectureNode | null;
addGroup: (group: ArchitectureGroup) => void;
getGroups: () => ArchitectureGroup[];
addEdge: (edge: ArchitectureEdge) => void;
getEdges: () => ArchitectureEdge[];
setElementForId: (id: string, element: D3Element) => void;
getElementById: (id: string) => D3Element;
getDataStructures: () => ArchitectureDataStructures;
}
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
export type ArchitectureSpatialMap = Record<string, number[]>;
export interface ArchitectureDataStructures {
adjList: ArchitectureAdjacencyList;
spatialMaps: ArchitectureSpatialMap[];
}
export interface ArchitectureState extends Record<string, unknown> {
nodes: Record<string, ArchitectureNode>;
groups: Record<string, ArchitectureGroup>;
edges: ArchitectureEdge[];
registeredIds: Record<string, 'node' | 'group'>;
dataStructures?: ArchitectureDataStructures;
elements: Record<string, D3Element>;
config: ArchitectureDiagramConfig;
}
/*=======================================*\
| Cytoscape Override Types |
\*=======================================*/
export interface EdgeSingularData {
id: string;
label?: string;
source: string;
sourceDir: ArchitectureDirection;
sourceArrow?: boolean;
sourceGroup?: boolean;
target: string;
targetDir: ArchitectureDirection;
targetArrow?: boolean;
targetGroup?: boolean;
[key: string]: any;
}
export const edgeData = (edge: cytoscape.EdgeSingular) => {
return edge.data() as EdgeSingularData;
};
export interface EdgeSingular extends cytoscape.EdgeSingular {
_private: {
bodyBounds: unknown;
rscratch: {
startX: number;
startY: number;
midX: number;
midY: number;
endX: number;
endY: number;
};
};
data(): EdgeSingularData;
data<T extends keyof EdgeSingularData>(key: T): EdgeSingularData[T];
}
export type NodeSingularData =
| {
type: 'service';
id: string;
icon?: string;
label?: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| {
type: 'junction';
id: string;
parent?: string;
width: number;
height: number;
[key: string]: any;
}
| {
type: 'group';
id: string;
icon?: string;
label?: string;
parent?: string;
[key: string]: any;
};
export const nodeData = (node: cytoscape.NodeSingular) => {
return node.data() as NodeSingularData;
};
export interface NodeSingular extends cytoscape.NodeSingular {
_private: {
bodyBounds: {
h: number;
w: number;
x1: number;
x2: number;
y1: number;
y2: number;
};
children: cytoscape.NodeSingular[];
};
data(): NodeSingularData;
data<T extends keyof NodeSingularData>(key: T): NodeSingularData[T];
}

View File

@@ -0,0 +1,370 @@
import { getIconSVG } from '../../rendering-util/icons.js';
import type cytoscape from 'cytoscape';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { createText } from '../../rendering-util/createText.js';
import type { D3Element } from '../../types.js';
import { db, getConfigField } from './architectureDb.js';
import { architectureIcons } from './architectureIcons.js';
import {
ArchitectureDirectionArrow,
ArchitectureDirectionArrowShift,
edgeData,
getArchitectureDirectionPair,
getArchitectureDirectionXYFactors,
isArchitectureDirectionX,
isArchitectureDirectionXY,
isArchitectureDirectionY,
isArchitecturePairXY,
nodeData,
type ArchitectureDB,
type ArchitectureJunction,
type ArchitectureService,
} from './architectureTypes.js';
export const drawEdges = async function (edgesEl: D3Element, cy: cytoscape.Core) {
const padding = getConfigField('padding');
const iconSize = getConfigField('iconSize');
const halfIconSize = iconSize / 2;
const arrowSize = iconSize / 6;
const halfArrowSize = arrowSize / 2;
await Promise.all(
cy.edges().map(async (edge) => {
const {
source,
sourceDir,
sourceArrow,
sourceGroup,
target,
targetDir,
targetArrow,
targetGroup,
label,
} = edgeData(edge);
let { x: startX, y: startY } = edge[0].sourceEndpoint();
const { x: midX, y: midY } = edge[0].midpoint();
let { x: endX, y: endY } = edge[0].targetEndpoint();
// Adjust the edge distance if it has the {group} modifier
const groupEdgeShift = padding + 4;
// +18 comes from the service label height that extends the padding on the bottom side of each group
if (sourceGroup) {
if (isArchitectureDirectionX(sourceDir)) {
startX += sourceDir === 'L' ? -groupEdgeShift : groupEdgeShift;
} else {
startY += sourceDir === 'T' ? -groupEdgeShift : groupEdgeShift + 18;
}
}
if (targetGroup) {
if (isArchitectureDirectionX(targetDir)) {
endX += targetDir === 'L' ? -groupEdgeShift : groupEdgeShift;
} else {
endY += targetDir === 'T' ? -groupEdgeShift : groupEdgeShift + 18;
}
}
// Adjust the edge distance if it doesn't have the {group} modifier and the endpoint is a junction node
if (!sourceGroup && db.getNode(source)?.type === 'junction') {
if (isArchitectureDirectionX(sourceDir)) {
startX += sourceDir === 'L' ? halfIconSize : -halfIconSize;
} else {
startY += sourceDir === 'T' ? halfIconSize : -halfIconSize;
}
}
if (!targetGroup && db.getNode(target)?.type === 'junction') {
if (isArchitectureDirectionX(targetDir)) {
endX += targetDir === 'L' ? halfIconSize : -halfIconSize;
} else {
endY += targetDir === 'T' ? halfIconSize : -halfIconSize;
}
}
if (edge[0]._private.rscratch) {
// const bounds = edge[0]._private.rscratch;
const g = edgesEl.insert('g');
g.insert('path')
.attr('d', `M ${startX},${startY} L ${midX},${midY} L${endX},${endY} `)
.attr('class', 'edge');
if (sourceArrow) {
const xShift = isArchitectureDirectionX(sourceDir)
? ArchitectureDirectionArrowShift[sourceDir](startX, arrowSize)
: startX - halfArrowSize;
const yShift = isArchitectureDirectionY(sourceDir)
? ArchitectureDirectionArrowShift[sourceDir](startY, arrowSize)
: startY - halfArrowSize;
g.insert('polygon')
.attr('points', ArchitectureDirectionArrow[sourceDir](arrowSize))
.attr('transform', `translate(${xShift},${yShift})`)
.attr('class', 'arrow');
}
if (targetArrow) {
const xShift = isArchitectureDirectionX(targetDir)
? ArchitectureDirectionArrowShift[targetDir](endX, arrowSize)
: endX - halfArrowSize;
const yShift = isArchitectureDirectionY(targetDir)
? ArchitectureDirectionArrowShift[targetDir](endY, arrowSize)
: endY - halfArrowSize;
g.insert('polygon')
.attr('points', ArchitectureDirectionArrow[targetDir](arrowSize))
.attr('transform', `translate(${xShift},${yShift})`)
.attr('class', 'arrow');
}
if (label) {
const axis = !isArchitectureDirectionXY(sourceDir, targetDir)
? isArchitectureDirectionX(sourceDir)
? 'X'
: 'Y'
: 'XY';
let width = 0;
if (axis === 'X') {
width = Math.abs(startX - endX);
} else if (axis === 'Y') {
// Reduce width by a factor of 1.5 to avoid overlapping service labels
width = Math.abs(startY - endY) / 1.5;
} else {
width = Math.abs(startX - endX) / 2;
}
const textElem = g.append('g');
await createText(
textElem,
label,
{
useHtmlLabels: false,
width,
classes: 'architecture-service-label',
},
getConfig()
);
textElem
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
if (axis === 'X') {
textElem.attr('transform', 'translate(' + midX + ', ' + midY + ')');
} else if (axis === 'Y') {
textElem.attr('transform', 'translate(' + midX + ', ' + midY + ') rotate(-90)');
} else if (axis === 'XY') {
const pair = getArchitectureDirectionPair(sourceDir, targetDir);
if (pair && isArchitecturePairXY(pair)) {
const bboxOrig = textElem.node().getBoundingClientRect();
const [x, y] = getArchitectureDirectionXYFactors(pair);
textElem
.attr('dominant-baseline', 'auto')
.attr('transform', `rotate(${-1 * x * y * 45})`);
// Calculate the new width/height with the rotation applied, and transform to the proper position
const bboxNew = textElem.node().getBoundingClientRect();
textElem.attr(
'transform',
`
translate(${midX}, ${midY - bboxOrig.height / 2})
translate(${(x * bboxNew.width) / 2}, ${(y * bboxNew.height) / 2})
rotate(${-1 * x * y * 45}, 0, ${bboxOrig.height / 2})
`
);
}
}
}
}
})
);
};
export const drawGroups = async function (groupsEl: D3Element, cy: cytoscape.Core) {
const padding = getConfigField('padding');
const groupIconSize = padding * 0.75;
const fontSize = getConfigField('fontSize');
const iconSize = getConfigField('iconSize');
const halfIconSize = iconSize / 2;
await Promise.all(
cy.nodes().map(async (node) => {
const data = nodeData(node);
if (data.type === 'group') {
const { h, w, x1, y1 } = node.boundingBox();
groupsEl
.append('rect')
.attr('x', x1 + halfIconSize)
.attr('y', y1 + halfIconSize)
.attr('width', w)
.attr('height', h)
.attr('class', 'node-bkg');
const groupLabelContainer = groupsEl.append('g');
let shiftedX1 = x1;
let shiftedY1 = y1;
if (data.icon) {
const bkgElem = groupLabelContainer.append('g');
bkgElem.html(
`<g>${await getIconSVG(data.icon, { height: groupIconSize, width: groupIconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
);
bkgElem.attr(
'transform',
'translate(' +
(shiftedX1 + halfIconSize + 1) +
', ' +
(shiftedY1 + halfIconSize + 1) +
')'
);
shiftedX1 += groupIconSize;
// TODO: test with more values
// - 1 - 2 comes from the Y axis transform of the icon and label
shiftedY1 += fontSize / 2 - 1 - 2;
}
if (data.label) {
const textElem = groupLabelContainer.append('g');
await createText(
textElem,
data.label,
{
useHtmlLabels: false,
width: w,
classes: 'architecture-service-label',
},
getConfig()
);
textElem
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'start')
.attr('text-anchor', 'start');
textElem.attr(
'transform',
'translate(' +
(shiftedX1 + halfIconSize + 4) +
', ' +
(shiftedY1 + halfIconSize + 2) +
')'
);
}
}
})
);
};
export const drawServices = async function (
db: ArchitectureDB,
elem: D3Element,
services: ArchitectureService[]
): Promise<number> {
for (const service of services) {
const serviceElem = elem.append('g');
const iconSize = getConfigField('iconSize');
if (service.title) {
const textElem = serviceElem.append('g');
await createText(
textElem,
service.title,
{
useHtmlLabels: false,
width: iconSize * 1.5,
classes: 'architecture-service-label',
},
getConfig()
);
textElem
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
textElem.attr('transform', 'translate(' + iconSize / 2 + ', ' + iconSize + ')');
}
const bkgElem = serviceElem.append('g');
if (service.icon) {
// TODO: should a warning be given to end-users saying which icon names are available?
// if (!isIconNameInUse(service.icon)) {
// throw new Error(`Invalid SVG Icon name: "${service.icon}"`);
// }
bkgElem.html(
`<g>${await getIconSVG(service.icon, { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
);
} else if (service.iconText) {
bkgElem.html(
`<g>${await getIconSVG('blank', { height: iconSize, width: iconSize, fallbackPrefix: architectureIcons.prefix })}</g>`
);
const textElemContainer = bkgElem.append('g');
const fo = textElemContainer
.append('foreignObject')
.attr('width', iconSize)
.attr('height', iconSize);
const divElem = fo
.append('div')
.attr('class', 'node-icon-text')
.attr('style', `height: ${iconSize}px;`)
.append('div')
.html(service.iconText);
const fontSize =
parseInt(
window
.getComputedStyle(divElem.node(), null)
.getPropertyValue('font-size')
.replace(/\D/g, '')
) ?? 16;
divElem.attr('style', `-webkit-line-clamp: ${Math.floor((iconSize - 2) / fontSize)};`);
} else {
bkgElem
.append('path')
.attr('class', 'node-bkg')
.attr('id', 'node-' + service.id)
.attr(
'd',
`M0 ${iconSize} v${-iconSize} q0,-5 5,-5 h${iconSize} q5,0 5,5 v${iconSize} H0 Z`
);
}
serviceElem.attr('class', 'architecture-service');
const { width, height } = serviceElem._groups[0][0].getBBox();
service.width = width;
service.height = height;
db.setElementForId(service.id, serviceElem);
}
return 0;
};
export const drawJunctions = function (
db: ArchitectureDB,
elem: D3Element,
junctions: ArchitectureJunction[]
) {
junctions.forEach((junction) => {
const junctionElem = elem.append('g');
const iconSize = getConfigField('iconSize');
const bkgElem = junctionElem.append('g');
bkgElem
.append('rect')
.attr('id', 'node-' + junction.id)
.attr('fill-opacity', '0')
.attr('width', iconSize)
.attr('height', iconSize);
junctionElem.attr('class', 'architecture-junction');
const { width, height } = junctionElem._groups[0][0].getBBox();
junctionElem.width = width;
junctionElem.height = height;
db.setElementForId(junction.id, junctionElem);
});
};

View File

@@ -519,6 +519,8 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
// Append a text node containing the label
const labelId = 'rel' + relCnt;
const labelText = rel.roleA.split(/<br ?\/>/g);
const labelNode = svg
.append('text')
.classed('er relationshipLabel', true)
@@ -528,8 +530,20 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
.style('text-anchor', 'middle')
.style('dominant-baseline', 'middle')
.style('font-family', getConfig().fontFamily)
.style('font-size', conf.fontSize + 'px')
.text(rel.roleA);
.style('font-size', conf.fontSize + 'px');
if (labelText.length == 1) {
labelNode.text(rel.roleA);
} else {
const firstShift = -(labelText.length - 1) * 0.5;
labelText.forEach((txt, i) => {
labelNode
.append('tspan')
.attr('x', labelPoint.x)
.attr('dy', `${i === 0 ? firstShift : 1}em`)
.text(txt);
});
}
// Figure out how big the opaque 'container' rectangle needs to be
const labelBBox = labelNode.node().getBBox();

View File

@@ -7,13 +7,14 @@ import type {
const id = 'flowchart-v2';
const detector: DiagramDetector = (txt, config) => {
if (
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
config?.flowchart?.defaultRenderer === 'elk'
) {
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
return false;
}
if (config?.flowchart?.defaultRenderer === 'elk') {
config.layout = 'elk';
}
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
if (/^\s*graph/.test(txt) && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
return true;

File diff suppressed because it is too large Load Diff

View File

@@ -1,535 +0,0 @@
import { log } from '../../logger.js';
import { random } from '../../utils.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
let { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
let commits = new Map();
let head = null;
let branchesConfig = new Map();
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
let branches = new Map();
branches.set(mainBranchName, head);
let curBranch = mainBranchName;
let direction = 'LR';
let seq = 0;
/**
*
*/
function getId() {
return random({ length: 7 });
}
// /**
// * @param currentCommit
// * @param otherCommit
// */
// function isFastForwardable(currentCommit, otherCommit) {
// log.debug('Entering isFastForwardable:', currentCommit.id, otherCommit.id);
// let cnt = 0;
// while (currentCommit.seq <= otherCommit.seq && currentCommit !== otherCommit && cnt < 1000) {
// cnt++;
// // only if other branch has more commits
// if (otherCommit.parent == null) break;
// if (Array.isArray(otherCommit.parent)) {
// log.debug('In merge commit:', otherCommit.parent);
// return (
// isFastForwardable(currentCommit, commits.get(otherCommit.parent[0])) ||
// isFastForwardable(currentCommit, commits.get(otherCommit.parent[1]))
// );
// } else {
// otherCommit = commits.get(otherCommit.parent);
// }
// }
// log.debug(currentCommit.id, otherCommit.id);
// return currentCommit.id === otherCommit.id;
// }
/**
* @param currentCommit
* @param otherCommit
*/
// function isReachableFrom(currentCommit, otherCommit) {
// const currentSeq = currentCommit.seq;
// const otherSeq = otherCommit.seq;
// if (currentSeq > otherSeq) return isFastForwardable(otherCommit, currentCommit);
// return false;
// }
/**
* @param list
* @param fn
*/
function uniqBy(list, fn) {
const recordMap = Object.create(null);
return list.reduce((out, item) => {
const key = fn(item);
if (!recordMap[key]) {
recordMap[key] = true;
out.push(item);
}
return out;
}, []);
}
export const setDirection = function (dir) {
direction = dir;
};
let options = {};
export const setOptions = function (rawOptString) {
log.debug('options str', rawOptString);
rawOptString = rawOptString?.trim();
rawOptString = rawOptString || '{}';
try {
options = JSON.parse(rawOptString);
} catch (e) {
log.error('error while parsing gitGraph options', e.message);
}
};
export const getOptions = function () {
return options;
};
export const commit = function (msg, id, type, tags) {
log.debug('Entering commit:', msg, id, type, tags);
const config = getConfig();
id = common.sanitizeText(id, config);
msg = common.sanitizeText(msg, config);
tags = tags?.map((tag) => common.sanitizeText(tag, config));
const commit = {
id: id ? id : seq + '-' + getId(),
message: msg,
seq: seq++,
type: type ? type : commitType.NORMAL,
tags: tags ?? [],
parents: head == null ? [] : [head.id],
branch: curBranch,
};
head = commit;
commits.set(commit.id, commit);
branches.set(curBranch, commit.id);
log.debug('in pushCommit ' + commit.id);
};
export const branch = function (name, order) {
name = common.sanitizeText(name, getConfig());
if (!branches.has(name)) {
branches.set(name, head != null ? head.id : null);
branchesConfig.set(name, { name, order: order ? parseInt(order, 10) : null });
checkout(name);
log.debug('in createBranch');
} else {
let error = new Error(
'Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ' +
name +
'")'
);
error.hash = {
text: 'branch ' + name,
token: 'branch ' + name,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"checkout ' + name + '"'],
};
throw error;
}
};
export const merge = function (otherBranch, custom_id, override_type, custom_tags) {
const config = getConfig();
otherBranch = common.sanitizeText(otherBranch, config);
custom_id = common.sanitizeText(custom_id, config);
const currentCommit = commits.get(branches.get(curBranch));
const otherCommit = commits.get(branches.get(otherBranch));
if (curBranch === otherBranch) {
let error = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
error.hash = {
text: 'merge ' + otherBranch,
token: 'merge ' + otherBranch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['branch abc'],
};
throw error;
} else if (currentCommit === undefined || !currentCommit) {
let error = new Error(
'Incorrect usage of "merge". Current branch (' + curBranch + ')has no commits'
);
error.hash = {
text: 'merge ' + otherBranch,
token: 'merge ' + otherBranch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['commit'],
};
throw error;
} else if (!branches.has(otherBranch)) {
let error = new Error(
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
);
error.hash = {
text: 'merge ' + otherBranch,
token: 'merge ' + otherBranch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['branch ' + otherBranch],
};
throw error;
} else if (otherCommit === undefined || !otherCommit) {
let error = new Error(
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
);
error.hash = {
text: 'merge ' + otherBranch,
token: 'merge ' + otherBranch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"commit"'],
};
throw error;
} else if (currentCommit === otherCommit) {
let error = new Error('Incorrect usage of "merge". Both branches have same head');
error.hash = {
text: 'merge ' + otherBranch,
token: 'merge ' + otherBranch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['branch abc'],
};
throw error;
} else if (custom_id && commits.has(custom_id)) {
let error = new Error(
'Incorrect usage of "merge". Commit with id:' +
custom_id +
' already exists, use different custom Id'
);
error.hash = {
text: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
token: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: [
`merge ${otherBranch} ${custom_id}_UNIQUE ${override_type} ${custom_tags?.join(',')}`,
],
};
throw error;
}
// if (isReachableFrom(currentCommit, otherCommit)) {
// log.debug('Already merged');
// return;
// }
// if (isFastForwardable(currentCommit, otherCommit)) {
// branches.set(curBranch, branches.get(otherBranch));
// head = commits.get(branches.get(curBranch));
// } else {
// create merge commit
const commit = {
id: custom_id ? custom_id : seq + '-' + getId(),
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
seq: seq++,
parents: [head == null ? null : head.id, branches.get(otherBranch)],
branch: curBranch,
type: commitType.MERGE,
customType: override_type,
customId: custom_id ? true : false,
tags: custom_tags ? custom_tags : [],
};
head = commit;
commits.set(commit.id, commit);
branches.set(curBranch, commit.id);
// }
log.debug(branches);
log.debug('in mergeBranch');
};
export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
log.debug('Entering cherryPick:', sourceId, targetId, tags);
const config = getConfig();
sourceId = common.sanitizeText(sourceId, config);
targetId = common.sanitizeText(targetId, config);
tags = tags?.map((tag) => common.sanitizeText(tag, config));
parentCommitId = common.sanitizeText(parentCommitId, config);
if (!sourceId || !commits.has(sourceId)) {
let error = new Error(
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
);
error.hash = {
text: 'cherryPick ' + sourceId + ' ' + targetId,
token: 'cherryPick ' + sourceId + ' ' + targetId,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['cherry-pick abc'],
};
throw error;
}
let sourceCommit = commits.get(sourceId);
let sourceCommitBranch = sourceCommit.branch;
if (
parentCommitId &&
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
) {
let error = new Error(
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
);
throw error;
}
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
let error = new Error(
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
);
throw error;
}
if (!targetId || !commits.has(targetId)) {
// cherry-pick source commit to current branch
if (sourceCommitBranch === curBranch) {
let error = new Error(
'Incorrect usage of "cherryPick". Source commit is already on current branch'
);
error.hash = {
text: 'cherryPick ' + sourceId + ' ' + targetId,
token: 'cherryPick ' + sourceId + ' ' + targetId,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['cherry-pick abc'],
};
throw error;
}
const currentCommit = commits.get(branches.get(curBranch));
if (currentCommit === undefined || !currentCommit) {
let error = new Error(
'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits'
);
error.hash = {
text: 'cherryPick ' + sourceId + ' ' + targetId,
token: 'cherryPick ' + sourceId + ' ' + targetId,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['cherry-pick abc'],
};
throw error;
}
const commit = {
id: seq + '-' + getId(),
message: 'cherry-picked ' + sourceCommit + ' into ' + curBranch,
seq: seq++,
parents: [head == null ? null : head.id, sourceCommit.id],
branch: curBranch,
type: commitType.CHERRY_PICK,
tags: tags
? tags.filter(Boolean)
: [
`cherry-pick:${sourceCommit.id}${
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
}`,
],
};
head = commit;
commits.set(commit.id, commit);
branches.set(curBranch, commit.id);
log.debug(branches);
log.debug('in cherryPick');
}
};
export const checkout = function (branch) {
branch = common.sanitizeText(branch, getConfig());
if (!branches.has(branch)) {
let error = new Error(
'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")'
);
error.hash = {
text: 'checkout ' + branch,
token: 'checkout ' + branch,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['"branch ' + branch + '"'],
};
throw error;
} else {
curBranch = branch;
const id = branches.get(curBranch);
head = commits.get(id);
}
};
// export const reset = function (commitRef) {
// log.debug('in reset', commitRef);
// const ref = commitRef.split(':')[0];
// let parentCount = parseInt(commitRef.split(':')[1]);
// let commit = ref === 'HEAD' ? head : commits.get(branches.get(ref));
// log.debug(commit, parentCount);
// while (parentCount > 0) {
// commit = commits.get(commit.parent);
// parentCount--;
// if (!commit) {
// const err = 'Critical error - unique parent commit not found during reset';
// log.error(err);
// throw err;
// }
// }
// head = commit;
// branches[curBranch] = commit.id;
// };
/**
* @param arr
* @param key
* @param newVal
*/
function upsert(arr, key, newVal) {
const index = arr.indexOf(key);
if (index === -1) {
arr.push(newVal);
} else {
arr.splice(index, 1, newVal);
}
}
/** @param commitArr */
function prettyPrintCommitHistory(commitArr) {
const commit = commitArr.reduce((out, commit) => {
if (out.seq > commit.seq) {
return out;
}
return commit;
}, commitArr[0]);
let line = '';
commitArr.forEach(function (c) {
if (c === commit) {
line += '\t*';
} else {
line += '\t|';
}
});
const label = [line, commit.id, commit.seq];
for (let branch in branches) {
if (branches.get(branch) === commit.id) {
label.push(branch);
}
}
log.debug(label.join(' '));
if (commit.parents && commit.parents.length == 2) {
const newCommit = commits.get(commit.parents[0]);
upsert(commitArr, commit, newCommit);
commitArr.push(commits.get(commit.parents[1]));
} else if (commit.parents.length == 0) {
return;
} else {
const nextCommit = commits.get(commit.parents);
upsert(commitArr, commit, nextCommit);
}
commitArr = uniqBy(commitArr, (c) => c.id);
prettyPrintCommitHistory(commitArr);
}
export const prettyPrint = function () {
log.debug(commits);
const node = getCommitsArray()[0];
prettyPrintCommitHistory([node]);
};
export const clear = function () {
commits = new Map();
head = null;
const { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
branches = new Map();
branches.set(mainBranchName, null);
branchesConfig = new Map();
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
curBranch = mainBranchName;
seq = 0;
commonClear();
};
export const getBranchesAsObjArray = function () {
const branchesArray = [...branchesConfig.values()]
.map((branchConfig, i) => {
if (branchConfig.order !== null) {
return branchConfig;
}
return {
...branchConfig,
order: parseFloat(`0.${i}`, 10),
};
})
.sort((a, b) => a.order - b.order)
.map(({ name }) => ({ name }));
return branchesArray;
};
export const getBranches = function () {
return branches;
};
export const getCommits = function () {
return commits;
};
export const getCommitsArray = function () {
const commitArr = [...commits.values()];
commitArr.forEach(function (o) {
log.debug(o.id);
});
commitArr.sort((a, b) => a.seq - b.seq);
return commitArr;
};
export const getCurrentBranch = function () {
return curBranch;
};
export const getDirection = function () {
return direction;
};
export const getHead = function () {
return head;
};
export const commitType = {
NORMAL: 0,
REVERSE: 1,
HIGHLIGHT: 2,
MERGE: 3,
CHERRY_PICK: 4,
};
export default {
getConfig: () => getConfig().gitGraph,
setDirection,
setOptions,
getOptions,
commit,
branch,
merge,
cherryPick,
checkout,
//reset,
prettyPrint,
clear,
getBranchesAsObjArray,
getBranches,
getCommits,
getCommitsArray,
getCurrentBranch,
getDirection,
getHead,
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
setDiagramTitle,
getDiagramTitle,
commitType,
};

View File

@@ -0,0 +1,522 @@
import { log } from '../../logger.js';
import { cleanAndMerge, random } from '../../utils.js';
import { getConfig as commonGetConfig } from '../../config.js';
import common from '../common/common.js';
import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import type {
DiagramOrientation,
Commit,
GitGraphDB,
CommitDB,
MergeDB,
BranchDB,
CherryPickDB,
} from './gitGraphTypes.js';
import { commitType } from './gitGraphTypes.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import DEFAULT_CONFIG from '../../defaultConfig.js';
import type { GitGraphDiagramConfig } from '../../config.type.js';
interface GitGraphState {
commits: Map<string, Commit>;
head: Commit | null;
branchConfig: Map<string, { name: string; order: number | undefined }>;
branches: Map<string, string | null>;
currBranch: string;
direction: DiagramOrientation;
seq: number;
options: any;
}
const DEFAULT_GITGRAPH_CONFIG: Required<GitGraphDiagramConfig> = DEFAULT_CONFIG.gitGraph;
const getConfig = (): Required<GitGraphDiagramConfig> => {
const config = cleanAndMerge({
...DEFAULT_GITGRAPH_CONFIG,
...commonGetConfig().gitGraph,
});
return config;
};
const state = new ImperativeState<GitGraphState>(() => {
const config = getConfig();
const mainBranchName = config.mainBranchName;
const mainBranchOrder = config.mainBranchOrder;
return {
mainBranchName,
commits: new Map(),
head: null,
branchConfig: new Map([[mainBranchName, { name: mainBranchName, order: mainBranchOrder }]]),
branches: new Map([[mainBranchName, null]]),
currBranch: mainBranchName,
direction: 'LR',
seq: 0,
options: {},
};
});
function getID() {
return random({ length: 7 });
}
/**
* @param list - list of items
* @param fn - function to get the key
*/
function uniqBy(list: any[], fn: (item: any) => any) {
const recordMap = Object.create(null);
return list.reduce((out, item) => {
const key = fn(item);
if (!recordMap[key]) {
recordMap[key] = true;
out.push(item);
}
return out;
}, []);
}
export const setDirection = function (dir: DiagramOrientation) {
state.records.direction = dir;
};
export const setOptions = function (rawOptString: string) {
log.debug('options str', rawOptString);
rawOptString = rawOptString?.trim();
rawOptString = rawOptString || '{}';
try {
state.records.options = JSON.parse(rawOptString);
} catch (e: any) {
log.error('error while parsing gitGraph options', e.message);
}
};
export const getOptions = function () {
return state.records.options;
};
export const commit = function (commitDB: CommitDB) {
let msg = commitDB.msg;
let id = commitDB.id;
const type = commitDB.type;
let tags = commitDB.tags;
log.info('commit', msg, id, type, tags);
log.debug('Entering commit:', msg, id, type, tags);
const config = getConfig();
id = common.sanitizeText(id, config);
msg = common.sanitizeText(msg, config);
tags = tags?.map((tag) => common.sanitizeText(tag, config));
const newCommit: Commit = {
id: id ? id : state.records.seq + '-' + getID(),
message: msg,
seq: state.records.seq++,
type: type ?? commitType.NORMAL,
tags: tags ?? [],
parents: state.records.head == null ? [] : [state.records.head.id],
branch: state.records.currBranch,
};
state.records.head = newCommit;
log.info('main branch', config.mainBranchName);
state.records.commits.set(newCommit.id, newCommit);
state.records.branches.set(state.records.currBranch, newCommit.id);
log.debug('in pushCommit ' + newCommit.id);
};
export const branch = function (branchDB: BranchDB) {
let name = branchDB.name;
const order = branchDB.order;
name = common.sanitizeText(name, getConfig());
if (state.records.branches.has(name)) {
throw new Error(
`Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ${name}")`
);
}
state.records.branches.set(name, state.records.head != null ? state.records.head.id : null);
state.records.branchConfig.set(name, { name, order });
checkout(name);
log.debug('in createBranch');
};
export const merge = (mergeDB: MergeDB): void => {
let otherBranch = mergeDB.branch;
let customId = mergeDB.id;
const overrideType = mergeDB.type;
const customTags = mergeDB.tags;
const config = getConfig();
otherBranch = common.sanitizeText(otherBranch, config);
if (customId) {
customId = common.sanitizeText(customId, config);
}
const currentBranchCheck = state.records.branches.get(state.records.currBranch);
const otherBranchCheck = state.records.branches.get(otherBranch);
const currentCommit = currentBranchCheck
? state.records.commits.get(currentBranchCheck)
: undefined;
const otherCommit: Commit | undefined = otherBranchCheck
? state.records.commits.get(otherBranchCheck)
: undefined;
if (currentCommit && otherCommit && currentCommit.branch === otherBranch) {
throw new Error(`Cannot merge branch '${otherBranch}' into itself.`);
}
if (state.records.currBranch === otherBranch) {
const error: any = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: ['branch abc'],
};
throw error;
}
if (currentCommit === undefined || !currentCommit) {
const error: any = new Error(
`Incorrect usage of "merge". Current branch (${state.records.currBranch})has no commits`
);
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: ['commit'],
};
throw error;
}
if (!state.records.branches.has(otherBranch)) {
const error: any = new Error(
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
);
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: [`branch ${otherBranch}`],
};
throw error;
}
if (otherCommit === undefined || !otherCommit) {
const error: any = new Error(
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
);
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: ['"commit"'],
};
throw error;
}
if (currentCommit === otherCommit) {
const error: any = new Error('Incorrect usage of "merge". Both branches have same head');
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: ['branch abc'],
};
throw error;
}
if (customId && state.records.commits.has(customId)) {
const error: any = new Error(
'Incorrect usage of "merge". Commit with id:' +
customId +
' already exists, use different custom Id'
);
error.hash = {
text: `merge ${otherBranch} ${customId} ${overrideType} ${customTags?.join(' ')}`,
token: `merge ${otherBranch} ${customId} ${overrideType} ${customTags?.join(' ')}`,
expected: [
`merge ${otherBranch} ${customId}_UNIQUE ${overrideType} ${customTags?.join(' ')}`,
],
};
throw error;
}
const verifiedBranch: string = otherBranchCheck ? otherBranchCheck : ''; //figure out a cleaner way to do this
const commit = {
id: customId || `${state.records.seq}-${getID()}`,
message: `merged branch ${otherBranch} into ${state.records.currBranch}`,
seq: state.records.seq++,
parents: state.records.head == null ? [] : [state.records.head.id, verifiedBranch],
branch: state.records.currBranch,
type: commitType.MERGE,
customType: overrideType,
customId: customId ? true : false,
tags: customTags ?? [],
} satisfies Commit;
state.records.head = commit;
state.records.commits.set(commit.id, commit);
state.records.branches.set(state.records.currBranch, commit.id);
log.debug(state.records.branches);
log.debug('in mergeBranch');
};
export const cherryPick = function (cherryPickDB: CherryPickDB) {
let sourceId = cherryPickDB.id;
let targetId = cherryPickDB.targetId;
let tags = cherryPickDB.tags;
let parentCommitId = cherryPickDB.parent;
log.debug('Entering cherryPick:', sourceId, targetId, tags);
const config = getConfig();
sourceId = common.sanitizeText(sourceId, config);
targetId = common.sanitizeText(targetId, config);
tags = tags?.map((tag) => common.sanitizeText(tag, config));
parentCommitId = common.sanitizeText(parentCommitId, config);
if (!sourceId || !state.records.commits.has(sourceId)) {
const error: any = new Error(
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
);
error.hash = {
text: `cherryPick ${sourceId} ${targetId}`,
token: `cherryPick ${sourceId} ${targetId}`,
expected: ['cherry-pick abc'],
};
throw error;
}
const sourceCommit = state.records.commits.get(sourceId);
if (sourceCommit === undefined || !sourceCommit) {
throw new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');
}
if (
parentCommitId &&
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
) {
const error = new Error(
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
);
throw error;
}
const sourceCommitBranch = sourceCommit.branch;
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
const error = new Error(
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
);
throw error;
}
if (!targetId || !state.records.commits.has(targetId)) {
// cherry-pick source commit to current branch
if (sourceCommitBranch === state.records.currBranch) {
const error: any = new Error(
'Incorrect usage of "cherryPick". Source commit is already on current branch'
);
error.hash = {
text: `cherryPick ${sourceId} ${targetId}`,
token: `cherryPick ${sourceId} ${targetId}`,
expected: ['cherry-pick abc'],
};
throw error;
}
const currentCommitId = state.records.branches.get(state.records.currBranch);
if (currentCommitId === undefined || !currentCommitId) {
const error: any = new Error(
`Incorrect usage of "cherry-pick". Current branch (${state.records.currBranch})has no commits`
);
error.hash = {
text: `cherryPick ${sourceId} ${targetId}`,
token: `cherryPick ${sourceId} ${targetId}`,
expected: ['cherry-pick abc'],
};
throw error;
}
const currentCommit = state.records.commits.get(currentCommitId);
if (currentCommit === undefined || !currentCommit) {
const error: any = new Error(
`Incorrect usage of "cherry-pick". Current branch (${state.records.currBranch})has no commits`
);
error.hash = {
text: `cherryPick ${sourceId} ${targetId}`,
token: `cherryPick ${sourceId} ${targetId}`,
expected: ['cherry-pick abc'],
};
throw error;
}
const commit = {
id: state.records.seq + '-' + getID(),
message: `cherry-picked ${sourceCommit?.message} into ${state.records.currBranch}`,
seq: state.records.seq++,
parents: state.records.head == null ? [] : [state.records.head.id, sourceCommit.id],
branch: state.records.currBranch,
type: commitType.CHERRY_PICK,
tags: tags
? tags.filter(Boolean)
: [
`cherry-pick:${sourceCommit.id}${
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
}`,
],
};
state.records.head = commit;
state.records.commits.set(commit.id, commit);
state.records.branches.set(state.records.currBranch, commit.id);
log.debug(state.records.branches);
log.debug('in cherryPick');
}
};
export const checkout = function (branch: string) {
branch = common.sanitizeText(branch, getConfig());
if (!state.records.branches.has(branch)) {
const error: any = new Error(
`Trying to checkout branch which is not yet created. (Help try using "branch ${branch}")`
);
error.hash = {
text: `checkout ${branch}`,
token: `checkout ${branch}`,
expected: [`branch ${branch}`],
};
throw error;
} else {
state.records.currBranch = branch;
const id = state.records.branches.get(state.records.currBranch);
if (id === undefined || !id) {
state.records.head = null;
} else {
state.records.head = state.records.commits.get(id) ?? null;
}
}
};
/**
* @param arr - array
* @param key - key
* @param newVal - new value
*/
function upsert(arr: any[], key: any, newVal: any) {
const index = arr.indexOf(key);
if (index === -1) {
arr.push(newVal);
} else {
arr.splice(index, 1, newVal);
}
}
function prettyPrintCommitHistory(commitArr: Commit[]) {
const commit = commitArr.reduce((out, commit) => {
if (out.seq > commit.seq) {
return out;
}
return commit;
}, commitArr[0]);
let line = '';
commitArr.forEach(function (c) {
if (c === commit) {
line += '\t*';
} else {
line += '\t|';
}
});
const label = [line, commit.id, commit.seq];
for (const branch in state.records.branches) {
if (state.records.branches.get(branch) === commit.id) {
label.push(branch);
}
}
log.debug(label.join(' '));
if (commit.parents && commit.parents.length == 2 && commit.parents[0] && commit.parents[1]) {
const newCommit = state.records.commits.get(commit.parents[0]);
upsert(commitArr, commit, newCommit);
if (commit.parents[1]) {
commitArr.push(state.records.commits.get(commit.parents[1])!);
}
} else if (commit.parents.length == 0) {
return;
} else {
if (commit.parents[0]) {
const newCommit = state.records.commits.get(commit.parents[0]);
upsert(commitArr, commit, newCommit);
}
}
commitArr = uniqBy(commitArr, (c) => c.id);
prettyPrintCommitHistory(commitArr);
}
export const prettyPrint = function () {
log.debug(state.records.commits);
const node = getCommitsArray()[0];
prettyPrintCommitHistory([node]);
};
export const clear = function () {
state.reset();
commonClear();
};
export const getBranchesAsObjArray = function () {
const branchesArray = [...state.records.branchConfig.values()]
.map((branchConfig, i) => {
if (branchConfig.order !== null && branchConfig.order !== undefined) {
return branchConfig;
}
return {
...branchConfig,
order: parseFloat(`0.${i}`),
};
})
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map(({ name }) => ({ name }));
return branchesArray;
};
export const getBranches = function () {
return state.records.branches;
};
export const getCommits = function () {
return state.records.commits;
};
export const getCommitsArray = function () {
const commitArr = [...state.records.commits.values()];
commitArr.forEach(function (o) {
log.debug(o.id);
});
commitArr.sort((a, b) => a.seq - b.seq);
return commitArr;
};
export const getCurrentBranch = function () {
return state.records.currBranch;
};
export const getDirection = function () {
return state.records.direction;
};
export const getHead = function () {
return state.records.head;
};
export const db: GitGraphDB = {
commitType,
getConfig,
setDirection,
setOptions,
getOptions,
commit,
branch,
merge,
cherryPick,
checkout,
//reset,
prettyPrint,
clear,
getBranchesAsObjArray,
getBranches,
getCommits,
getCommitsArray,
getCurrentBranch,
getDirection,
getHead,
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
setDiagramTitle,
getDiagramTitle,
};

View File

@@ -1,13 +1,13 @@
// @ts-ignore: JISON doesn't support types
import gitGraphParser from './parser/gitGraph.jison';
import gitGraphDb from './gitGraphAst.js';
import { parser } from './gitGraphParser.js';
import { db } from './gitGraphAst.js';
import gitGraphRenderer from './gitGraphRenderer.js';
import gitGraphStyles from './styles.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram: DiagramDefinition = {
parser: gitGraphParser,
db: gitGraphDb,
parser,
db,
renderer: gitGraphRenderer,
styles: gitGraphStyles,
};

View File

@@ -1,272 +0,0 @@
import gitGraphAst from './gitGraphAst.js';
import { parser } from './parser/gitGraph.jison';
describe('when parsing a gitGraph', function () {
beforeEach(function () {
parser.yy = gitGraphAst;
parser.yy.clear();
});
it('should handle a gitGraph definition', function () {
const str = 'gitGraph:\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should handle a gitGraph definition with empty options', function () {
const str = 'gitGraph:\n' + 'options\n' + ' end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(parser.yy.getOptions()).toEqual({});
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should handle a gitGraph definition with valid options', function () {
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(parser.yy.getOptions().key).toBe('value');
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should not fail on a gitGraph with malformed json', function () {
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should handle set direction top to bottom', function () {
const str = 'gitGraph TB:\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('TB');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should handle set direction bottom to top', function () {
const str = 'gitGraph BT:\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(1);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('BT');
expect(parser.yy.getBranches().size).toBe(1);
});
it('should checkout a branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(0);
expect(parser.yy.getCurrentBranch()).toBe('new');
});
it('should switch a branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'switch new\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(0);
expect(parser.yy.getCurrentBranch()).toBe('new');
});
it('should add commits to checked out branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(2);
expect(parser.yy.getCurrentBranch()).toBe('new');
const branchCommit = parser.yy.getBranches().get('new');
expect(branchCommit).not.toBeNull();
expect(commits.get(branchCommit).parent).not.toBeNull();
});
it('should handle commit with args', function () {
const str = 'gitGraph:\n' + 'commit "a commit"\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(1);
const key = commits.keys().next().value;
expect(commits.get(key).message).toBe('a commit');
expect(parser.yy.getCurrentBranch()).toBe('main');
});
// Reset has been commented out in JISON
it.skip('should reset a branch', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'reset main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(3);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
});
it.skip('reset can take an argument', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'reset main^\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(3);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
const main = commits.get(parser.yy.getBranches().get('main'));
expect(parser.yy.getHead().id).toEqual(main.parent);
});
it.skip('should handle fast forwardable merges', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
'checkout main\n' +
'merge newbranch\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(4);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
});
it('should handle cases when merge is a noop', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
'merge main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(4);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
expect(parser.yy.getBranches().get('newbranch')).not.toEqual(
parser.yy.getBranches().get('main')
);
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
});
it('should handle merge with 2 parents', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
'checkout main\n' +
'commit\n' +
'merge newbranch\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(5);
expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getBranches().get('newbranch')).not.toEqual(
parser.yy.getBranches().get('main')
);
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('main'));
});
it.skip('should handle ff merge when history walk has two parents (merge commit)', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
'checkout main\n' +
'commit\n' +
'merge newbranch\n' +
'commit\n' +
'checkout newbranch\n' +
'merge main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(commits.size).toBe(7);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('main'));
parser.yy.prettyPrint();
});
it('should generate an array of known branches', function () {
const str =
'gitGraph:\n' +
'commit\n' +
'branch b1\n' +
'checkout b1\n' +
'commit\n' +
'commit\n' +
'branch b2\n';
parser.parse(str);
const branches = gitGraphAst.getBranchesAsObjArray();
expect(branches).toHaveLength(3);
expect(branches[0]).toHaveProperty('name', 'main');
expect(branches[1]).toHaveProperty('name', 'b1');
expect(branches[2]).toHaveProperty('name', 'b2');
});
});

View File

@@ -0,0 +1,243 @@
import type { GitGraph } from '@mermaid-js/parser';
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 './gitGraphAst.js';
import { commitType } from './gitGraphTypes.js';
import type {
CheckoutAst,
CherryPickingAst,
MergeAst,
CommitAst,
BranchAst,
GitGraphDBParseProvider,
CommitDB,
BranchDB,
MergeDB,
CherryPickDB,
} from './gitGraphTypes.js';
const populate = (ast: GitGraph, db: GitGraphDBParseProvider) => {
populateCommonDb(ast, db);
// @ts-ignore: this wont exist if the direction is not specified
if (ast.dir) {
// @ts-ignore: this wont exist if the direction is not specified
db.setDirection(ast.dir);
}
for (const statement of ast.statements) {
parseStatement(statement, db);
}
};
const parseStatement = (statement: any, db: GitGraphDBParseProvider) => {
const parsers: Record<string, (stmt: any) => void> = {
Commit: (stmt) => db.commit(parseCommit(stmt)),
Branch: (stmt) => db.branch(parseBranch(stmt)),
Merge: (stmt) => db.merge(parseMerge(stmt)),
Checkout: (stmt) => db.checkout(parseCheckout(stmt)),
CherryPicking: (stmt) => db.cherryPick(parseCherryPicking(stmt)),
};
const parser = parsers[statement.$type];
if (parser) {
parser(statement);
} else {
log.error(`Unknown statement type: ${statement.$type}`);
}
};
const parseCommit = (commit: CommitAst): CommitDB => {
const commitDB: CommitDB = {
id: commit.id,
msg: commit.message ?? '',
type: commit.type !== undefined ? commitType[commit.type] : commitType.NORMAL,
tags: commit.tags ?? undefined,
};
return commitDB;
};
const parseBranch = (branch: BranchAst): BranchDB => {
const branchDB: BranchDB = {
name: branch.name,
order: branch.order ?? 0,
};
return branchDB;
};
const parseMerge = (merge: MergeAst): MergeDB => {
const mergeDB: MergeDB = {
branch: merge.branch,
id: merge.id ?? '',
type: merge.type !== undefined ? commitType[merge.type] : undefined,
tags: merge.tags ?? undefined,
};
return mergeDB;
};
const parseCheckout = (checkout: CheckoutAst): string => {
const branch = checkout.branch;
return branch;
};
const parseCherryPicking = (cherryPicking: CherryPickingAst): CherryPickDB => {
const cherryPickDB: CherryPickDB = {
id: cherryPicking.id,
targetId: '',
tags: cherryPicking.tags?.length === 0 ? undefined : cherryPicking.tags,
parent: cherryPicking.parent,
};
return cherryPickDB;
};
export const parser: ParserDefinition = {
parse: async (input: string): Promise<void> => {
const ast: GitGraph = await parse('gitGraph', input);
log.debug(ast);
populate(ast, db);
},
};
if (import.meta.vitest) {
const { it, expect, describe } = import.meta.vitest;
const mockDB: GitGraphDBParseProvider = {
commitType: commitType,
setDirection: vi.fn(),
commit: vi.fn(),
branch: vi.fn(),
merge: vi.fn(),
cherryPick: vi.fn(),
checkout: vi.fn(),
};
describe('GitGraph Parser', () => {
it('should parse a commit statement', () => {
const commit = {
$type: 'Commit',
id: '1',
message: 'test',
tags: ['tag1', 'tag2'],
type: 'NORMAL',
};
parseStatement(commit, mockDB);
expect(mockDB.commit).toHaveBeenCalledWith({
id: '1',
msg: 'test',
tags: ['tag1', 'tag2'],
type: 0,
});
});
it('should parse a branch statement', () => {
const branch = {
$type: 'Branch',
name: 'newBranch',
order: 1,
};
parseStatement(branch, mockDB);
expect(mockDB.branch).toHaveBeenCalledWith({ name: 'newBranch', order: 1 });
});
it('should parse a checkout statement', () => {
const checkout = {
$type: 'Checkout',
branch: 'newBranch',
};
parseStatement(checkout, mockDB);
expect(mockDB.checkout).toHaveBeenCalledWith('newBranch');
});
it('should parse a merge statement', () => {
const merge = {
$type: 'Merge',
branch: 'newBranch',
id: '1',
tags: ['tag1', 'tag2'],
type: 'NORMAL',
};
parseStatement(merge, mockDB);
expect(mockDB.merge).toHaveBeenCalledWith({
branch: 'newBranch',
id: '1',
tags: ['tag1', 'tag2'],
type: 0,
});
});
it('should parse a cherry picking statement', () => {
const cherryPick = {
$type: 'CherryPicking',
id: '1',
tags: ['tag1', 'tag2'],
parent: '2',
};
parseStatement(cherryPick, mockDB);
expect(mockDB.cherryPick).toHaveBeenCalledWith({
id: '1',
targetId: '',
parent: '2',
tags: ['tag1', 'tag2'],
});
});
it('should parse a langium generated gitGraph ast', () => {
const dummy: GitGraph = {
$type: 'GitGraph',
statements: [],
};
const gitGraphAst: GitGraph = {
$type: 'GitGraph',
statements: [
{
$container: dummy,
$type: 'Commit',
id: '1',
message: 'test',
tags: ['tag1', 'tag2'],
type: 'NORMAL',
},
{
$container: dummy,
$type: 'Branch',
name: 'newBranch',
order: 1,
},
{
$container: dummy,
$type: 'Merge',
branch: 'newBranch',
id: '1',
tags: ['tag1', 'tag2'],
type: 'NORMAL',
},
{
$container: dummy,
$type: 'Checkout',
branch: 'newBranch',
},
{
$container: dummy,
$type: 'CherryPicking',
id: '1',
tags: ['tag1', 'tag2'],
parent: '2',
},
],
};
populate(gitGraphAst, mockDB);
expect(mockDB.commit).toHaveBeenCalledWith({
id: '1',
msg: 'test',
tags: ['tag1', 'tag2'],
type: 0,
});
expect(mockDB.branch).toHaveBeenCalledWith({ name: 'newBranch', order: 1 });
expect(mockDB.merge).toHaveBeenCalledWith({
branch: 'newBranch',
id: '1',
tags: ['tag1', 'tag2'],
type: 0,
});
expect(mockDB.checkout).toHaveBeenCalledWith('newBranch');
});
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,893 +0,0 @@
import { select } from 'd3';
import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import utils from '../../utils.js';
/**
* @typedef {Map<string, { id: string, message: string, seq: number, type: number, tag: string, parents: string[], branch: string }>} CommitMap
*/
/** @type {CommitMap} */
let allCommitsDict = new Map();
const commitType = {
NORMAL: 0,
REVERSE: 1,
HIGHLIGHT: 2,
MERGE: 3,
CHERRY_PICK: 4,
};
const THEME_COLOR_LIMIT = 8;
let branchPos = {};
let commitPos = {};
let lanes = [];
let maxPos = 0;
let dir = 'LR';
let defaultPos = 30;
const clear = () => {
branchPos = new Map();
commitPos = new Map();
allCommitsDict = new Map();
maxPos = 0;
lanes = [];
dir = 'LR';
};
/**
* Draws a text, used for labels of the branches
*
* @param {string} txt The text
* @returns {SVGElement}
*/
const drawText = (txt) => {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
let rows = [];
// Handling of new lines in the label
if (typeof txt === 'string') {
rows = txt.split(/\\n|\n|<br\s*\/?>/gi);
} else if (Array.isArray(txt)) {
rows = txt;
} else {
rows = [];
}
for (const row of rows) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '0');
tspan.setAttribute('class', 'row');
tspan.textContent = row.trim();
svgLabel.appendChild(tspan);
}
/**
* @param svg
* @param selector
*/
return svgLabel;
};
/**
* Searches for the closest parent from the parents list passed as argument.
* The parents list comes from an individual commit. The closest parent is actually
* the one farther down the graph, since that means it is closer to its child.
*
* @param {string[]} parents
* @returns {string | undefined}
*/
const findClosestParent = (parents) => {
let closestParent = '';
let maxPosition = 0;
parents.forEach((parent) => {
const parentPosition =
dir === 'TB' || dir === 'BT' ? commitPos.get(parent).y : commitPos.get(parent).x;
if (parentPosition >= maxPosition) {
closestParent = parent;
maxPosition = parentPosition;
}
});
return closestParent || undefined;
};
/**
* Searches for the closest parent from the parents list passed as argument for Bottom-to-Top orientation.
* The parents list comes from an individual commit. The closest parent is actually
* the one farther down the graph, since that means it is closer to its child.
*
* @param {string[]} parents
* @returns {string | undefined}
*/
const findClosestParentBT = (parents) => {
let closestParent = '';
let maxPosition = Infinity;
parents.forEach((parent) => {
const parentPosition = commitPos.get(parent).y;
if (parentPosition <= maxPosition) {
closestParent = parent;
maxPosition = parentPosition;
}
});
return closestParent || undefined;
};
/**
* Sets the position of the commit elements when the orientation is set to BT-Parallel.
* This is needed to render the chart in Bottom-to-Top mode while keeping the parallel
* commits in the correct position. First, it finds the correct position of the root commit
* using the findClosestParent method. Then, it uses the findClosestParentBT to set the position
* of the remaining commits.
*
* @param {any} sortedKeys
* @param {CommitMap} commits
* @param {any} defaultPos
* @param {any} commitStep
* @param {any} layoutOffset
*/
const setParallelBTPos = (sortedKeys, commits, defaultPos, commitStep, layoutOffset) => {
let curPos = defaultPos;
let maxPosition = defaultPos;
let roots = [];
sortedKeys.forEach((key) => {
const commit = commits.get(key);
if (commit.parents.length) {
const closestParent = findClosestParent(commit.parents);
curPos = commitPos.get(closestParent).y + commitStep;
if (curPos >= maxPosition) {
maxPosition = curPos;
}
} else {
roots.push(commit);
}
const x = branchPos.get(commit.branch).pos;
const y = curPos + layoutOffset;
commitPos.set(commit.id, { x: x, y: y });
});
curPos = maxPosition;
roots.forEach((commit) => {
const posWithOffset = curPos + defaultPos;
const y = posWithOffset;
const x = branchPos.get(commit.branch).pos;
commitPos.set(commit.id, { x: x, y: y });
});
sortedKeys.forEach((key) => {
const commit = commits.get(key);
if (commit.parents.length) {
const closestParent = findClosestParentBT(commit.parents);
curPos = commitPos.get(closestParent).y - commitStep;
if (curPos <= maxPosition) {
maxPosition = curPos;
}
const x = branchPos.get(commit.branch).pos;
const y = curPos - layoutOffset;
commitPos.set(commit.id, { x: x, y: y });
}
});
};
/**
* Draws the commits with its symbol and labels. The function has two modes, one which only
* calculates the positions and one that does the actual drawing. This for a simple way getting the
* vertical layering correct in the graph.
*
* @param {any} svg
* @param {CommitMap} commits
* @param {any} modifyGraph
*/
const drawCommits = (svg, commits, modifyGraph) => {
const gitGraphConfig = getConfig().gitGraph;
const gBullets = svg.append('g').attr('class', 'commit-bullets');
const gLabels = svg.append('g').attr('class', 'commit-labels');
let pos = 0;
if (dir === 'TB' || dir === 'BT') {
pos = defaultPos;
}
const keys = [...commits.keys()];
const isParallelCommits = gitGraphConfig.parallelCommits;
const layoutOffset = 10;
const commitStep = 40;
let sortedKeys =
dir !== 'BT' || (dir === 'BT' && isParallelCommits)
? keys.sort((a, b) => {
return commits.get(a).seq - commits.get(b).seq;
})
: keys
.sort((a, b) => {
return commits.get(a).seq - commits.get(b).seq;
})
.reverse();
if (dir === 'BT' && isParallelCommits) {
setParallelBTPos(sortedKeys, commits, pos, commitStep, layoutOffset);
sortedKeys = sortedKeys.reverse();
}
sortedKeys.forEach((key) => {
const commit = commits.get(key);
if (isParallelCommits) {
if (commit.parents.length) {
const closestParent =
dir === 'BT' ? findClosestParentBT(commit.parents) : findClosestParent(commit.parents);
if (dir === 'TB') {
pos = commitPos.get(closestParent).y + commitStep;
} else if (dir === 'BT') {
pos = commitPos.get(key).y - commitStep;
} else {
pos = commitPos.get(closestParent).x + commitStep;
}
} else {
if (dir === 'TB') {
pos = defaultPos;
} else if (dir === 'BT') {
pos = commitPos.get(key).y - commitStep;
} else {
pos = 0;
}
}
}
const posWithOffset = dir === 'BT' && isParallelCommits ? pos : pos + layoutOffset;
const y = dir === 'TB' || dir === 'BT' ? posWithOffset : branchPos.get(commit.branch).pos;
const x = dir === 'TB' || dir === 'BT' ? branchPos.get(commit.branch).pos : posWithOffset;
// Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
if (modifyGraph) {
let typeClass;
let commitSymbolType =
commit.customType !== undefined && commit.customType !== ''
? commit.customType
: commit.type;
switch (commitSymbolType) {
case commitType.NORMAL:
typeClass = 'commit-normal';
break;
case commitType.REVERSE:
typeClass = 'commit-reverse';
break;
case commitType.HIGHLIGHT:
typeClass = 'commit-highlight';
break;
case commitType.MERGE:
typeClass = 'commit-merge';
break;
case commitType.CHERRY_PICK:
typeClass = 'commit-cherry-pick';
break;
default:
typeClass = 'commit-normal';
}
if (commitSymbolType === commitType.HIGHLIGHT) {
const circle = gBullets.append('rect');
circle.attr('x', x - 10);
circle.attr('y', y - 10);
circle.attr('height', 20);
circle.attr('width', 20);
circle.attr(
'class',
`commit ${commit.id} commit-highlight${
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
} ${typeClass}-outer`
);
gBullets
.append('rect')
.attr('x', x - 6)
.attr('y', y - 6)
.attr('height', 12)
.attr('width', 12)
.attr(
'class',
`commit ${commit.id} commit${
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
} ${typeClass}-inner`
);
} else if (commitSymbolType === commitType.CHERRY_PICK) {
gBullets
.append('circle')
.attr('cx', x)
.attr('cy', y)
.attr('r', 10)
.attr('class', `commit ${commit.id} ${typeClass}`);
gBullets
.append('circle')
.attr('cx', x - 3)
.attr('cy', y + 2)
.attr('r', 2.75)
.attr('fill', '#fff')
.attr('class', `commit ${commit.id} ${typeClass}`);
gBullets
.append('circle')
.attr('cx', x + 3)
.attr('cy', y + 2)
.attr('r', 2.75)
.attr('fill', '#fff')
.attr('class', `commit ${commit.id} ${typeClass}`);
gBullets
.append('line')
.attr('x1', x + 3)
.attr('y1', y + 1)
.attr('x2', x)
.attr('y2', y - 5)
.attr('stroke', '#fff')
.attr('class', `commit ${commit.id} ${typeClass}`);
gBullets
.append('line')
.attr('x1', x - 3)
.attr('y1', y + 1)
.attr('x2', x)
.attr('y2', y - 5)
.attr('stroke', '#fff')
.attr('class', `commit ${commit.id} ${typeClass}`);
} else {
const circle = gBullets.append('circle');
circle.attr('cx', x);
circle.attr('cy', y);
circle.attr('r', commit.type === commitType.MERGE ? 9 : 10);
circle.attr(
'class',
`commit ${commit.id} commit${branchPos.get(commit.branch).index % THEME_COLOR_LIMIT}`
);
if (commitSymbolType === commitType.MERGE) {
const circle2 = gBullets.append('circle');
circle2.attr('cx', x);
circle2.attr('cy', y);
circle2.attr('r', 6);
circle2.attr(
'class',
`commit ${typeClass} ${commit.id} commit${
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
}`
);
}
if (commitSymbolType === commitType.REVERSE) {
const cross = gBullets.append('path');
cross
.attr('d', `M ${x - 5},${y - 5}L${x + 5},${y + 5}M${x - 5},${y + 5}L${x + 5},${y - 5}`)
.attr(
'class',
`commit ${typeClass} ${commit.id} commit${
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
}`
);
}
}
}
if (dir === 'TB' || dir === 'BT') {
commitPos.set(commit.id, { x: x, y: posWithOffset });
} else {
commitPos.set(commit.id, { x: posWithOffset, y: y });
}
// The first iteration over the commits are for positioning purposes, this
// is required for drawing the lines. The circles and labels is drawn after the labels
// placing them on top of the lines.
if (modifyGraph) {
const px = 4;
const py = 2;
// Draw the commit label
if (
commit.type !== commitType.CHERRY_PICK &&
((commit.customId && commit.type === commitType.MERGE) ||
commit.type !== commitType.MERGE) &&
gitGraphConfig.showCommitLabel
) {
const wrapper = gLabels.append('g');
const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg');
const text = wrapper
.append('text')
.attr('x', pos)
.attr('y', y + 25)
.attr('class', 'commit-label')
.text(commit.id);
let bbox = text.node().getBBox();
// Now we have the label, lets position the background
labelBkg
.attr('x', posWithOffset - bbox.width / 2 - py)
.attr('y', y + 13.5)
.attr('width', bbox.width + 2 * py)
.attr('height', bbox.height + 2 * py);
if (dir === 'TB' || dir === 'BT') {
labelBkg.attr('x', x - (bbox.width + 4 * px + 5)).attr('y', y - 12);
text.attr('x', x - (bbox.width + 4 * px)).attr('y', y + bbox.height - 12);
} else {
text.attr('x', posWithOffset - bbox.width / 2);
}
if (gitGraphConfig.rotateCommitLabel) {
if (dir === 'TB' || dir === 'BT') {
text.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
labelBkg.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
} else {
let r_x = -7.5 - ((bbox.width + 10) / 25) * 9.5;
let r_y = 10 + (bbox.width / 25) * 8.5;
wrapper.attr(
'transform',
'translate(' + r_x + ', ' + r_y + ') rotate(' + -45 + ', ' + pos + ', ' + y + ')'
);
}
}
}
if (commit.tags.length > 0) {
let yOffset = 0;
let maxTagBboxWidth = 0;
let maxTagBboxHeight = 0;
const tagElements = [];
for (const tagValue of commit.tags.reverse()) {
const rect = gLabels.insert('polygon');
const hole = gLabels.append('circle');
const tag = gLabels
.append('text')
// Note that we are delaying setting the x position until we know the width of the text
.attr('y', y - 16 - yOffset)
.attr('class', 'tag-label')
.text(tagValue);
let tagBbox = tag.node().getBBox();
maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width);
maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height);
// We don't use the max over here to center the text within the tags
tag.attr('x', posWithOffset - tagBbox.width / 2);
tagElements.push({
tag,
hole,
rect,
yOffset,
});
yOffset += 20;
}
for (const { tag, hole, rect, yOffset } of tagElements) {
const h2 = maxTagBboxHeight / 2;
const ly = y - 19.2 - yOffset;
rect.attr('class', 'tag-label-bkg').attr(
'points',
`
${pos - maxTagBboxWidth / 2 - px / 2},${ly + py}
${pos - maxTagBboxWidth / 2 - px / 2},${ly - py}
${posWithOffset - maxTagBboxWidth / 2 - px},${ly - h2 - py}
${posWithOffset + maxTagBboxWidth / 2 + px},${ly - h2 - py}
${posWithOffset + maxTagBboxWidth / 2 + px},${ly + h2 + py}
${posWithOffset - maxTagBboxWidth / 2 - px},${ly + h2 + py}`
);
hole
.attr('cy', ly)
.attr('cx', pos - maxTagBboxWidth / 2 + px / 2)
.attr('r', 1.5)
.attr('class', 'tag-hole');
if (dir === 'TB' || dir === 'BT') {
const yOrigin = pos + yOffset;
rect
.attr('class', 'tag-label-bkg')
.attr(
'points',
`
${x},${yOrigin + py}
${x},${yOrigin - py}
${x + layoutOffset},${yOrigin - h2 - py}
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin - h2 - py}
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin + h2 + py}
${x + layoutOffset},${yOrigin + h2 + py}`
)
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
hole
.attr('cx', x + px / 2)
.attr('cy', yOrigin)
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
tag
.attr('x', x + 5)
.attr('y', yOrigin + 3)
.attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')');
}
}
}
}
pos = dir === 'BT' && isParallelCommits ? pos + commitStep : pos + commitStep + layoutOffset;
if (pos > maxPos) {
maxPos = pos;
}
});
};
/**
* Detect if there are commits
* between commitA's x-position
* and commitB's x-position on the
* same branch as commitA, where
* commitA isn't main
*
* @param {any} commitA
* @param {any} commitB
* @param p1
* @param p2
* @param {CommitMap} allCommits
* @returns {boolean}
* If there are commits between
* commitA's x-position
* and commitB's x-position
* on the source branch, where
* source branch is not main
* return true
*/
const shouldRerouteArrow = (commitA, commitB, p1, p2, allCommits) => {
const commitBIsFurthest = dir === 'TB' || dir === 'BT' ? p1.x < p2.x : p1.y < p2.y;
const branchToGetCurve = commitBIsFurthest ? commitB.branch : commitA.branch;
const isOnBranchToGetCurve = (x) => x.branch === branchToGetCurve;
const isBetweenCommits = (x) => x.seq > commitA.seq && x.seq < commitB.seq;
return [...allCommits.values()].some((commitX) => {
return isBetweenCommits(commitX) && isOnBranchToGetCurve(commitX);
});
};
/**
* This function find a lane in the y-axis that is not overlapping with any other lanes. This is
* used for drawing the lines between commits.
*
* @param {any} y1
* @param {any} y2
* @param {any} depth
* @returns {number} Y value between y1 and y2
*/
const findLane = (y1, y2, depth = 0) => {
const candidate = y1 + Math.abs(y1 - y2) / 2;
if (depth > 5) {
return candidate;
}
let ok = lanes.every((lane) => Math.abs(lane - candidate) >= 10);
if (ok) {
lanes.push(candidate);
return candidate;
}
const diff = Math.abs(y1 - y2);
return findLane(y1, y2 - diff / 5, depth + 1);
};
/**
* Draw the lines between the commits. They were arrows initially.
*
* @param {any} svg
* @param {any} commitA
* @param {any} commitB
* @param {CommitMap} allCommits
*/
const drawArrow = (svg, commitA, commitB, allCommits) => {
const p1 = commitPos.get(commitA.id); // arrowStart
const p2 = commitPos.get(commitB.id); // arrowEnd
const arrowNeedsRerouting = shouldRerouteArrow(commitA, commitB, p1, p2, allCommits);
// log.debug('drawArrow', p1, p2, arrowNeedsRerouting, commitA.id, commitB.id);
// Lower-right quadrant logic; top-left is 0,0
let arc = '';
let arc2 = '';
let radius = 0;
let offset = 0;
let colorClassNum = branchPos.get(commitB.branch).index;
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
colorClassNum = branchPos.get(commitA.branch).index;
}
let lineDef;
if (arrowNeedsRerouting) {
arc = 'A 10 10, 0, 0, 0,';
arc2 = 'A 10 10, 0, 0, 1,';
radius = 10;
offset = 10;
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
const lineX = p1.x < p2.x ? findLane(p1.x, p2.x) : findLane(p2.x, p1.x);
if (dir === 'TB') {
if (p1.x < p2.x) {
// Source commit is on branch position left of destination commit
// so render arrow rightward with colour of destination branch
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${
p1.y + offset
} L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
} else {
// Source commit is on branch position right of destination commit
// so render arrow leftward with colour of source branch
colorClassNum = branchPos.get(commitA.branch).index;
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc} ${lineX} ${
p1.y + offset
} L ${lineX} ${p2.y - radius} ${arc2} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
}
} else if (dir === 'BT') {
if (p1.x < p2.x) {
// Source commit is on branch position left of destination commit
// so render arrow rightward with colour of destination branch
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc} ${lineX} ${
p1.y - offset
} L ${lineX} ${p2.y + radius} ${arc2} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
} else {
// Source commit is on branch position right of destination commit
// so render arrow leftward with colour of source branch
colorClassNum = branchPos.get(commitA.branch).index;
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc2} ${lineX} ${
p1.y - offset
} L ${lineX} ${p2.y + radius} ${arc} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
}
} else {
if (p1.y < p2.y) {
// Source commit is on branch positioned above destination commit
// so render arrow downward with colour of destination branch
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${
p1.x + offset
} ${lineY} L ${p2.x - radius} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`;
} else {
// Source commit is on branch positioned below destination commit
// so render arrow upward with colour of source branch
colorClassNum = branchPos.get(commitA.branch).index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY + radius} ${arc2} ${
p1.x + offset
} ${lineY} L ${p2.x - radius} ${lineY} ${arc} ${p2.x} ${lineY - offset} L ${p2.x} ${p2.y}`;
}
}
} else {
arc = 'A 20 20, 0, 0, 0,';
arc2 = 'A 20 20, 0, 0, 1,';
radius = 20;
offset = 20;
if (dir === 'TB') {
if (p1.x < p2.x) {
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
p1.y + offset
} L ${p2.x} ${p2.y}`;
}
}
if (p1.x > p2.x) {
arc = 'A 20 20, 0, 0, 0,';
arc2 = 'A 20 20, 0, 0, 1,';
radius = 20;
offset = 20;
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x + radius} ${p1.y} ${arc} ${p2.x} ${
p1.y + offset
} L ${p2.x} ${p2.y}`;
}
}
if (p1.x === p2.x) {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
}
} else if (dir === 'BT') {
if (p1.x < p2.x) {
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc2} ${p1.x + offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
p1.y - offset
} L ${p2.x} ${p2.y}`;
}
}
if (p1.x > p2.x) {
arc = 'A 20 20, 0, 0, 0,';
arc2 = 'A 20 20, 0, 0, 1,';
radius = 20;
offset = 20;
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc} ${p1.x - offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
p1.y - offset
} L ${p2.x} ${p2.y}`;
}
}
if (p1.x === p2.x) {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
}
} else {
if (p1.y < p2.y) {
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
p1.y + offset
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
}
}
if (p1.y > p2.y) {
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
p1.y - offset
} L ${p2.x} ${p2.y}`;
} else {
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc2} ${p1.x + offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
}
}
if (p1.y === p2.y) {
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
}
}
}
svg
.append('path')
.attr('d', lineDef)
.attr('class', 'arrow arrow' + (colorClassNum % THEME_COLOR_LIMIT));
};
/**
* @param {*} svg
* @param {CommitMap} commits
*/
const drawArrows = (svg, commits) => {
const gArrows = svg.append('g').attr('class', 'commit-arrows');
[...commits.keys()].forEach((key) => {
const commit = commits.get(key);
if (commit.parents && commit.parents.length > 0) {
commit.parents.forEach((parent) => {
drawArrow(gArrows, commits.get(parent), commit, commits);
});
}
});
};
/**
* Adds the branches and the branches' labels to the svg.
*
* @param svg
* @param branches
*/
const drawBranches = (svg, branches) => {
const gitGraphConfig = getConfig().gitGraph;
const g = svg.append('g');
branches.forEach((branch, index) => {
const adjustIndexForTheme = index % THEME_COLOR_LIMIT;
const pos = branchPos.get(branch.name).pos;
const line = g.append('line');
line.attr('x1', 0);
line.attr('y1', pos);
line.attr('x2', maxPos);
line.attr('y2', pos);
line.attr('class', 'branch branch' + adjustIndexForTheme);
if (dir === 'TB') {
line.attr('y1', defaultPos);
line.attr('x1', pos);
line.attr('y2', maxPos);
line.attr('x2', pos);
} else if (dir === 'BT') {
line.attr('y1', maxPos);
line.attr('x1', pos);
line.attr('y2', defaultPos);
line.attr('x2', pos);
}
lanes.push(pos);
let name = branch.name;
// Create the actual text element
const labelElement = drawText(name);
// Create outer g, edgeLabel, this will be positioned after graph layout
const bkg = g.insert('rect');
const branchLabel = g.insert('g').attr('class', 'branchLabel');
// Create inner g, label, this will be positioned now for centering the text
const label = branchLabel.insert('g').attr('class', 'label branch-label' + adjustIndexForTheme);
label.node().appendChild(labelElement);
let bbox = labelElement.getBBox();
bkg
.attr('class', 'branchLabelBkg label' + adjustIndexForTheme)
.attr('rx', 4)
.attr('ry', 4)
.attr('x', -bbox.width - 4 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0))
.attr('y', -bbox.height / 2 + 8)
.attr('width', bbox.width + 18)
.attr('height', bbox.height + 4);
label.attr(
'transform',
'translate(' +
(-bbox.width - 14 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0)) +
', ' +
(pos - bbox.height / 2 - 1) +
')'
);
if (dir === 'TB') {
bkg.attr('x', pos - bbox.width / 2 - 10).attr('y', 0);
label.attr('transform', 'translate(' + (pos - bbox.width / 2 - 5) + ', ' + 0 + ')');
} else if (dir === 'BT') {
bkg.attr('x', pos - bbox.width / 2 - 10).attr('y', maxPos);
label.attr('transform', 'translate(' + (pos - bbox.width / 2 - 5) + ', ' + maxPos + ')');
} else {
bkg.attr('transform', 'translate(' + -19 + ', ' + (pos - bbox.height / 2) + ')');
}
});
};
/**
* @param txt
* @param id
* @param ver
* @param diagObj
*/
export const draw = function (txt, id, ver, diagObj) {
clear();
const conf = getConfig();
const gitGraphConfig = conf.gitGraph;
// try {
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
allCommitsDict = diagObj.db.getCommits();
const branches = diagObj.db.getBranchesAsObjArray();
dir = diagObj.db.getDirection();
const diagram = select(`[id="${id}"]`);
// Position branches
let pos = 0;
branches.forEach((branch, index) => {
const labelElement = drawText(branch.name);
const g = diagram.append('g');
const branchLabel = g.insert('g').attr('class', 'branchLabel');
const label = branchLabel.insert('g').attr('class', 'label branch-label');
label.node().appendChild(labelElement);
let bbox = labelElement.getBBox();
branchPos.set(branch.name, { pos, index });
pos +=
50 +
(gitGraphConfig.rotateCommitLabel ? 40 : 0) +
(dir === 'TB' || dir === 'BT' ? bbox.width / 2 : 0);
label.remove();
branchLabel.remove();
g.remove();
});
drawCommits(diagram, allCommitsDict, false);
if (gitGraphConfig.showBranches) {
drawBranches(diagram, branches);
}
drawArrows(diagram, allCommitsDict);
drawCommits(diagram, allCommitsDict, true);
utils.insertTitle(
diagram,
'gitTitleText',
gitGraphConfig.titleTopMargin,
diagObj.db.getDiagramTitle()
);
// Setup the view box and size of the svg element
setupGraphViewbox(
undefined,
diagram,
gitGraphConfig.diagramPadding,
gitGraphConfig.useMaxWidth ?? conf.useMaxWidth
);
};
export default {
draw,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
import type { GitGraphDiagramConfig } from '../../config.type.js';
import type { DiagramDBBase } from '../../diagram-api/types.js';
export const commitType = {
NORMAL: 0,
REVERSE: 1,
HIGHLIGHT: 2,
MERGE: 3,
CHERRY_PICK: 4,
} as const;
export interface CommitDB {
msg: string;
id: string;
type: number;
tags?: string[];
}
export interface BranchDB {
name: string;
order: number;
}
export interface MergeDB {
branch: string;
id: string;
type?: number;
tags?: string[];
}
export interface CherryPickDB {
id: string;
targetId: string;
parent: string;
tags?: string[];
}
export interface Commit {
id: string;
message: string;
seq: number;
type: number;
tags: string[];
parents: string[];
branch: string;
customType?: number;
customId?: boolean;
}
export interface GitGraph {
statements: Statement[];
}
export type Statement = CommitAst | BranchAst | MergeAst | CheckoutAst | CherryPickingAst;
export interface CommitAst {
$type: 'Commit';
id: string;
message?: string;
tags?: string[];
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
}
export interface BranchAst {
$type: 'Branch';
name: string;
order?: number;
}
export interface MergeAst {
$type: 'Merge';
branch: string;
id?: string;
tags?: string[];
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
}
export interface CheckoutAst {
$type: 'Checkout';
branch: string;
}
export interface CherryPickingAst {
$type: 'CherryPicking';
id: string;
parent: string;
tags?: string[];
}
export interface GitGraphDB extends DiagramDBBase<GitGraphDiagramConfig> {
commitType: typeof commitType;
setDirection: (dir: DiagramOrientation) => void;
setOptions: (rawOptString: string) => void;
getOptions: () => any;
commit: (commitDB: CommitDB) => void;
branch: (branchDB: BranchDB) => void;
merge: (mergeDB: MergeDB) => void;
cherryPick: (cherryPickDB: CherryPickDB) => void;
checkout: (branch: string) => void;
prettyPrint: () => void;
clear: () => void;
getBranchesAsObjArray: () => { name: string }[];
getBranches: () => Map<string, string | null>;
getCommits: () => Map<string, Commit>;
getCommitsArray: () => Commit[];
getCurrentBranch: () => string;
getDirection: () => DiagramOrientation;
getHead: () => Commit | null;
}
export interface GitGraphDBParseProvider extends Partial<GitGraphDB> {
commitType: typeof commitType;
setDirection: (dir: DiagramOrientation) => void;
commit: (commitDB: CommitDB) => void;
branch: (branchDB: BranchDB) => void;
merge: (mergeDB: MergeDB) => void;
cherryPick: (cherryPickDB: CherryPickDB) => void;
checkout: (branch: string) => void;
}
export interface GitGraphDBRenderProvider extends Partial<GitGraphDB> {
prettyPrint: () => void;
clear: () => void;
getBranchesAsObjArray: () => { name: string }[];
getBranches: () => Map<string, string | null>;
getCommits: () => Map<string, Commit>;
getCommitsArray: () => Commit[];
getCurrentBranch: () => string;
getDirection: () => DiagramOrientation;
getHead: () => Commit | null;
getDiagramTitle: () => string;
}
export type DiagramOrientation = 'LR' | 'TB' | 'BT';

View File

@@ -1,248 +0,0 @@
/*
* Parse following
* gitGraph:
* commit
* commit
* branch
*/
%lex
%x string
%x options
%x acc_title
%x acc_descr
%x acc_descr_multiline
%options case-insensitive
%%
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
(\r?\n)+ /*{console.log('New line');return 'NL';}*/ return 'NL';
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"gitGraph" return 'GG';
commit(?=\s|$) return 'COMMIT';
"id:" return 'COMMIT_ID';
"type:" return 'COMMIT_TYPE';
"msg:" return 'COMMIT_MSG';
"NORMAL" return 'NORMAL';
"REVERSE" return 'REVERSE';
"HIGHLIGHT" return 'HIGHLIGHT';
"tag:" return 'COMMIT_TAG';
branch(?=\s|$) return 'BRANCH';
"order:" return 'ORDER';
merge(?=\s|$) return 'MERGE';
cherry\-pick(?=\s|$) return 'CHERRY_PICK';
"parent:" return 'PARENT_COMMIT'
// "reset" return 'RESET';
\b(checkout|switch)(?=\s|$) return 'CHECKOUT';
"LR" return 'DIR';
"TB" return 'DIR';
"BT" return 'DIR';
":" return ':';
"^" return 'CARET'
"options"\r?\n this.begin("options"); //
<options>[ \r\n\t]+"end" this.popState(); // not used anymore in the renderer, fixed for backward compatibility
<options>[\s\S]+(?=[ \r\n\t]+"end") return 'OPT'; //
["]["] return 'EMPTYSTR';
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return 'STR';
[0-9]+(?=\s|$) return 'NUM';
\w([-\./\w]*[-\w])? return 'ID'; // only a subset of https://git-scm.com/docs/git-check-ref-format
<<EOF>> return 'EOF';
\s+ /* skip all whitespace */ // lowest priority so we can use lookaheads in earlier regex
/lex
%left '^'
%start start
%% /* language grammar */
start
: eol start
| GG document EOF{ return $3; }
| GG ':' document EOF{ return $3; }
| GG DIR ':' document EOF {yy.setDirection($2); return $4;}
;
document
: /*empty*/
| options body { yy.setOptions($1); $$ = $2}
;
options
: options OPT {$1 +=$2; $$=$1}
| NL
;
body
: /*empty*/ {$$ = []}
| body line {$1.push($2); $$=$1;}
;
line
: statement eol {$$ =$1}
| NL
;
statement
: commitStatement
| mergeStatement
| cherryPickStatement
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| branchStatement
| CHECKOUT ref {yy.checkout($2)}
// | RESET reset_arg {yy.reset($2)}
;
branchStatement
: BRANCH ref {yy.branch($2)}
| BRANCH ref ORDER NUM {yy.branch($2, $4)}
;
cherryPickStatement
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
| CHERRY_PICK COMMIT_ID STR commitTags {yy.cherryPick($3, '', $4)}
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR commitTags {yy.cherryPick($3, '', $6,$5)}
| CHERRY_PICK COMMIT_ID STR commitTags PARENT_COMMIT STR {yy.cherryPick($3, '', $4,$6)}
| CHERRY_PICK commitTags COMMIT_ID STR {yy.cherryPick($4, '', $2)}
| CHERRY_PICK commitTags COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($4, '', $2,$6)}
;
mergeStatement
: MERGE ref {yy.merge($2,'','', undefined)}
| MERGE ref COMMIT_ID STR {yy.merge($2, $4,'', undefined)}
| MERGE ref COMMIT_TYPE commitType {yy.merge($2,'', $4, undefined)}
| MERGE ref commitTags {yy.merge($2, '','',$3)}
| MERGE ref commitTags COMMIT_ID STR {yy.merge($2, $5,'', $3)}
| MERGE ref commitTags COMMIT_TYPE commitType {yy.merge($2, '',$5, $3)}
| MERGE ref COMMIT_TYPE commitType commitTags {yy.merge($2, '',$4, $5)}
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $4, $6, undefined)}
| MERGE ref COMMIT_ID STR commitTags {yy.merge($2, $4, '', $5)}
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $6,$4, undefined)}
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.merge($2, $4, $6, $7)}
| MERGE ref COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.merge($2, $7, $4, $5)}
| MERGE ref COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.merge($2, $4, $7, $5)}
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.merge($2, $6, $4, $7)}
| MERGE ref commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $7, $5, $3)}
| MERGE ref commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $5, $7, $3)}
;
commitStatement
: COMMIT commit_arg {yy.commit($2)}
| COMMIT commitTags {yy.commit('','',yy.commitType.NORMAL,$2)}
| COMMIT COMMIT_TYPE commitType {yy.commit('','',$3, undefined)}
| COMMIT commitTags COMMIT_TYPE commitType {yy.commit('','',$4,$2)}
| COMMIT COMMIT_TYPE commitType commitTags {yy.commit('','',$3,$4)}
| COMMIT COMMIT_ID STR {yy.commit('',$3,yy.commitType.NORMAL, undefined)}
| COMMIT COMMIT_ID STR commitTags {yy.commit('',$3,yy.commitType.NORMAL,$4)}
| COMMIT commitTags COMMIT_ID STR {yy.commit('',$4,yy.commitType.NORMAL,$2)}
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$3,$5, undefined)}
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$5,$3, undefined)}
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit('',$3,$5,$6)}
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit('',$3,$6,$4)}
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit('',$5,$3,$6)}
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit('',$6,$3,$4)}
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$6,$4,$2)}
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$4,$6,$2)}
| COMMIT COMMIT_MSG STR {yy.commit($3,'',yy.commitType.NORMAL, undefined)}
| COMMIT commitTags COMMIT_MSG STR {yy.commit($4,'',yy.commitType.NORMAL,$2)}
| COMMIT COMMIT_MSG STR commitTags {yy.commit($3,'',yy.commitType.NORMAL,$4)}
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($3,'',$5, undefined)}
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($5,'',$3, undefined)}
| COMMIT COMMIT_ID STR COMMIT_MSG STR {yy.commit($5,$3,yy.commitType.NORMAL, undefined)}
| COMMIT COMMIT_MSG STR COMMIT_ID STR {yy.commit($3,$5,yy.commitType.NORMAL, undefined)}
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($3,'',$5,$6)}
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($3,'',$6,$4)}
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($5,'',$3,$6)}
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($6,'',$3,$4)}
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($6,'',$4,$2)}
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($4,'',$6,$2)}
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$7,$5, undefined)}
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$5,$7, undefined)}
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($5,$7,$3, undefined)}
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($7,$5,$3, undefined)}
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($7,$3,$5, undefined)}
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($5,$3,$7, undefined)}
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($3,$6,yy.commitType.NORMAL,$4)}
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($3,$5,yy.commitType.NORMAL,$6)}
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($4,$6,yy.commitType.NORMAL,$2)}
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($6,$4,yy.commitType.NORMAL,$2)}
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($6,$3,yy.commitType.NORMAL,$4)}
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($5,$3,yy.commitType.NORMAL,$6)}
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit($3,$5,$7,$8)}
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit($3,$5,$8,$6)}
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit($3,$7,$5,$8)}
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit($3,$8,$5,$6)}
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$6,$8,$4)}
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$8,$6,$4)}
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($5,$3,$7,$8)}
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($5,$3,$8,$6)}
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($7,$3,$5,$8)}
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($8,$3,$5,$6)}
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$3,$8,$4)}
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$3,$6,$4)}
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$4,$6,$2)}
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$4,$8,$2)}
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$4,$2)}
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$4,$2)}
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($4,$6,$8,$2)}
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($4,$8,$6,$2)}
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($7,$5,$3,$8)}
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($8,$5,$3,$6)}
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$3,$4)}
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$3,$4)}
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($5,$7,$3,$8)}
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($5,$8,$3,$6)}
;
commit_arg
: /* empty */ {$$ = ""}
| STR {$$=$1}
;
commitType
: NORMAL { $$=yy.commitType.NORMAL;}
| REVERSE { $$=yy.commitType.REVERSE;}
| HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;}
;
commitTags
: COMMIT_TAG STR {$$=[$2]}
| COMMIT_TAG EMPTYSTR {$$=['']}
| commitTags COMMIT_TAG STR {$commitTags.push($3); $$=$commitTags;}
| commitTags COMMIT_TAG EMPTYSTR {$commitTags.push(''); $$=$commitTags;}
;
ref
: ID
| STR
;
eol
: NL
| ';'
| EOF
;
// reset_arg
// : 'HEAD' reset_parents{$$ = $1+ ":" + $2 }
// | ID reset_parents{$$ = $1+ ":" + yy.count; yy.count = 0}
// ;
// reset_parents
// : /* empty */ {yy.count = 0}
// | CARET reset_parents { yy.count += 1 }
// ;

View File

@@ -329,7 +329,7 @@ export const fixLifeLineHeights = (diagram, actors, actorKeys, conf) => {
const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 5;
const centerY = actorY + actor.height;
const boxplusLineGroup = elem.append('g').lower();
var g = boxplusLineGroup;

View File

@@ -1,11 +1,11 @@
<template>
<div class="-mt-6 mb-8">
<a
href="https://www.producthunt.com/posts/mermaid-ai-2?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;ai&#0045;2"
href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart"
target="_blank"
><img
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=465568&theme=light"
alt="Mermaid&#0032;AI - Maximize&#0032;your&#0032;diagramming&#0032;efficiency&#0032;with&#0032;Mermaid&#0032;AI | Product Hunt"
src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light"
alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt"
style="width: 250px; height: 54px"
width="250"
height="54"

View File

@@ -157,6 +157,7 @@ function sidebarSyntax() {
{ text: 'XY Chart 🔥', link: '/syntax/xyChart' },
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
{ text: 'Packet 🔥', link: '/syntax/packet' },
{ text: 'Architecture 🔥', link: '/syntax/architecture' },
{ text: 'Other Examples', link: '/syntax/examples' },
],
},

View File

@@ -9,6 +9,8 @@ import Contributors from '../components/Contributors.vue';
import HomePage from '../components/HomePage.vue';
// @ts-ignore Type not available
import TopBar from '../components/TopBar.vue';
// @ts-ignore Type not available
import ProductHuntBadge from '../components/ProductHuntBadge.vue';
import { getRedirect } from './redirect.js';
// @ts-ignore Type not available
import 'uno.css';
@@ -23,6 +25,7 @@ export default {
return h(Theme.Layout, null, {
// Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar.
'home-hero-before': () => h(TopBar),
'home-hero-info-before': () => h(ProductHuntBadge),
'home-features-after': () => h(HomePage),
'doc-before': () => h(TopBar),
});

View File

@@ -2,6 +2,13 @@ import mermaid, { type MermaidConfig } from 'mermaid';
import zenuml from '../../../../../mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
const init = mermaid.registerExternalDiagrams([zenuml]);
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
export const render = async (id: string, code: string, config: MermaidConfig): Promise<string> => {
await init;

View File

@@ -371,9 +371,9 @@ If the users have no way to know that things have changed, then you haven't real
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
The documentation has to be updated for users to know that things have been changed and added!
If you are adding a new feature, add `(v10.8.0+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
eg: `# Feature Name (v10.8.0+)`
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
We know it can sometimes be hard to code _and_ write user documentation.

View File

@@ -69,6 +69,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
- [Microsoft Loop](https://loop.cloud.microsoft) ✅
### LLM integrations
@@ -139,7 +140,7 @@ Communication tools and platforms
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
- [PmWiki](https://www.pmwiki.org)
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
- [Semantic Media Wiki](https://semantic-mediawiki.org)
- [Semantic Media Wiki](https://www.semantic-mediawiki.org)
- [Mermaid Plugin](https://github.com/SemanticMediaWiki/Mermaid)
- [TiddlyWiki](https://tiddlywiki.com/)
- [mermaid-tw5: wrapper for Mermaid Live](https://github.com/efurlanm/mermaid-tw5)

View File

@@ -57,7 +57,7 @@ import matplotlib.pyplot as plt
def mm(graph):
graphbytes = graph.encode("utf8")
base64_bytes = base64.b64encode(graphbytes)
base64_bytes = base64.urlsafe_b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string))

View File

@@ -50,6 +50,10 @@ For a more detailed introduction to Mermaid and some of its more basic uses, loo
**Thanks to all involved, people committing pull requests, people answering questions and special thanks to Tyler Long who is helping me maintain the project 🙏**
Our PR Visual Regression Testing is powered by [Argos](https://argos-ci.com/?utm_source=mermaid&utm_campaign=oss) with their generous Open Source plan. It makes the process of reviewing PRs with visual changes a breeze.
[![Covered by Argos Visual Testing](https://argos-ci.com/badge-large.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
<a href="https://applitools.com/">

View File

@@ -65,3 +65,110 @@ Allows for the limited reconfiguration of a diagram just before it is rendered.
### [Theme Manipulation](../config/theming.md)
An application of using Directives to change [Themes](../config/theming.md). `Theme` is a value within Mermaid's configuration that dictates the color scheme for diagrams.
### Layout and look
We've restructured how Mermaid renders diagrams, enabling new features like selecting layout and look. **Currently, this is supported for flowcharts and state diagrams**, with plans to extend support to all diagram types.
### Selecting Diagram Looks
Mermaid offers a variety of styles or “looks” for your diagrams, allowing you to tailor the visual appearance to match your specific needs or preferences. Whether you prefer a hand-drawn or classic style, you can easily customize your diagrams.
**Available Looks:**
• Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
• Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
**How to Select a Look:**
You can select a look by adding the look parameter in the metadata section of your Mermaid diagram code. Heres an example:
```mermaid
---
config:
look: handDrawn
theme: neutral
---
flowchart LR
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
#### Selecting Layout Algorithms
In addition to customizing the look of your diagrams, Mermaid Chart now allows you to choose different layout algorithms to better organize and present your diagrams, especially when dealing with more complex structures. The layout algorithm dictates how nodes and edges are arranged on the page.
#### Supported Layout Algorithms:
• Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
• ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
#### How to Select a Layout Algorithm:
You can specify the layout algorithm directly in the metadata section of your Mermaid diagram code. Heres an example:
```mermaid
---
config:
layout: elk
look: handDrawn
theme: dark
---
flowchart TB
A[Start] --> B{Decision}
B -->|Yes| C[Continue]
B -->|No| D[Stop]
```
In this example, the `layout: elk` line configures the diagram to use the ELK layout algorithm, along with the hand drawn look and forest theme.
#### Customizing ELK Layout:
When using the ELK layout, you can further refine the diagrams configuration, such as how nodes are placed and whether parallel edges should be combined:
- To combine parallel edges, use mergeEdges: true | false.
- To configure node placement, use nodePlacementStrategy with the following options:
- SIMPLE
- NETWORK_SIMPLEX
- LINEAR_SEGMENTS
- BRANDES_KOEPF (default)
**Example configuration:**
```
---
config:
layout: elk
elk:
mergeEdges: true
nodePlacementStrategy: LINEAR_SEGMENTS
---
flowchart LR
A[Start] --> B{Choose Path}
B -->|Option 1| C[Path 1]
B -->|Option 2| D[Path 2]
#### Using Dagre Layout with Classic Look:
```
Another example:
```
---
config:
layout: dagre
look: classic
theme: default
---
flowchart LR
A[Start] --> B{Choose Path}
B -->|Option 1| C[Path 1]
B -->|Option 2| D[Path 2]
```
These options give you the flexibility to create diagrams that not only look great but are also arranged to best suit your datas structure and flow.
When integrating Mermaid, you can include look and layout configuration with the initialize call. This is also where you add the loading of elk.

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