Compare commits

...

257 Commits

Author SHA1 Message Date
Knut Sveidqvist
75f9de3d4a UPdated version to 10.2.3 2023-06-08 16:24:25 +02:00
Knut Sveidqvist
81e86bc87c Merge branch '10.2.2' into develop 2023-06-08 15:07:31 +02:00
Sidharth Vinod
f464212dc0 Merge pull request #4416 from MikeJeffers/bug/4408
Fix #4408: Handle wrapping long words
2023-06-08 11:46:09 +05:30
Sidharth Vinod
0ad084182a Remove old changelog 2023-06-08 11:42:14 +05:30
Sidharth Vinod
e2e9d0f1f3 Remove old changelog 2023-06-08 11:36:47 +05:30
Sidharth Vinod
ac488dd800 Merge branch 'develop' into pr/MikeJeffers/4416
* develop:
  Update docs
2023-06-08 11:13:51 +05:30
sidharthv96
d00d20fc37 Update docs 2023-06-07 18:49:51 +00:00
Sidharth Vinod
88b94dea66 Merge branch 'develop' into pr/MikeJeffers/4416
* develop: (45 commits)
  Add nextra to cSpell
  Update docs: Added Nextra to Blogs category on integrations page
  Render empty lines correctly
  Restore classes on edges for elk
  Update diagram proposal
  Update docs
  Added CKEditor and GitHub Writer to available integrations.
  Fix exceptions for empty lines
  chore(deps): update all patch dependencies
  build(deps): fix broken pnpm-lock.yaml file
  Mermaid version 10.2.0
  Mermaid Version 10.2.0-rc.4
  Label background fix
  Test commit
  Fix for regression error in sequenceDiagrams
  Update all minor dependencies
  Update all patch dependencies
  Update docs
  Add contributors profile url
  ignore ZenUML types
  ...
2023-06-08 00:16:49 +05:30
Sidharth Vinod
9896402be6 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into develop
* 'develop' of https://github.com/mermaid-js/mermaid:
  Update docs: Added Nextra to Blogs category on integrations page
2023-06-08 00:16:16 +05:30
Sidharth Vinod
e9b76bba9b Add nextra to cSpell 2023-06-08 00:16:12 +05:30
Sidharth Vinod
ad09d63f65 only split if string is over length. 2023-06-08 00:07:33 +05:30
Sidharth Vinod
af7531d08a Merge pull request #4463 from try-to-fly/add-nextra-to-blogs-integrations
Update docs: Added Nextra to Blogs category on integrations page
2023-06-07 20:28:40 +05:30
微笑
eef6745272 Update docs: Added Nextra to Blogs category on integrations page 2023-06-07 16:06:52 +08:00
mikejeffers
0cb6df1ef8 refactor text width computing func 2023-06-06 23:11:07 -04:00
Sidharth Vinod
f20ff4d580 Merge pull request #4436 from slab/zh-mermaid-empty-text
Fix exceptions for empty lines
2023-06-05 11:43:43 +05:30
Zihua Li
2a6603b33a Render empty lines correctly 2023-06-04 16:24:41 +08:00
Sidharth Vinod
6115f6976a Merge pull request #4452 from yoavst/feature/restore-classes-on-edges-for-elk
Restore classes on edges for elk
2023-06-04 12:02:54 +05:30
Yoav Sternberg
da1beb36bf Restore classes on edges for elk 2023-06-02 22:10:39 +03:00
Knut Sveidqvist
d2409dc163 Setting version to 10.2.2 2023-06-02 08:50:59 +02:00
Knut Sveidqvist
1b40f552b2 Merge pull request #4334 from mermaid-js/sidv/zenuml
Add `@mermaid-js/mermaid-zenuml` package for zenuml Integration
2023-06-02 08:41:49 +02:00
Knut Sveidqvist
5b696a72f8 Merge pull request #4448 from mermaid-js/sidv/diagramProposal
Update diagram proposal
2023-06-02 08:40:35 +02:00
Sidharth Vinod
d148a65967 Merge branch 'develop' into sidv/zenuml
* develop: (22 commits)
  Update docs
  Added CKEditor and GitHub Writer to available integrations.
  chore(deps): update all patch dependencies
  build(deps): fix broken pnpm-lock.yaml file
  Mermaid version 10.2.0
  Mermaid Version 10.2.0-rc.4
  Label background fix
  Test commit
  Fix for regression error in sequenceDiagrams
  Fix visibility issue for fields
  fix parsing issue with class diagrams
  fix: Use unicode arrows in quadrant chart axis
  fix: Use unicode arrows in quadrant chart axis
  fix lint command
  Bump version
  Back to JS with jsdoc types
  Add unsupported text
  Reduce changes in test
  Fix deps
  Fix lockfile
  ...
2023-06-02 11:59:12 +05:30
Sidharth Vinod
74ccf6410d Update diagram proposal 2023-06-02 11:50:49 +05:30
Knut Sveidqvist
7cdb15c526 Merge pull request #4447 from mermaid-js/4446-handling-of-arrowMarker-urls
#4446 Updating the cleanup criteria
2023-06-01 17:55:45 +02:00
Knut Sveidqvist
55092f532f #4446 Improved regular expression 2023-06-01 17:39:54 +02:00
Knut Sveidqvist
7b6cb4f974 #4446 Updating the cleanup criteria 2023-06-01 16:30:57 +02:00
Knut Sveidqvist
0cd1c5de97 Merge branch 'release/10.2.1' 2023-06-01 13:12:20 +02:00
Knut Sveidqvist
48e5d743b3 Merge pull request #4445 from mermaid-js/4438-revering-to-non-esm-use-of-dayjs
#4438 Reverted to the changes from #4285
2023-06-01 13:03:14 +02:00
Knut Sveidqvist
98e75959cc #4438 Reverted to the changes from #4285 2023-06-01 12:53:21 +02:00
Sidharth Vinod
0cec854f3b Fix download 2023-05-31 19:33:30 +05:30
Sidharth Vinod
abcae14fa7 Fix compile error in docs. 2023-05-31 19:10:42 +05:30
Sidharth Vinod
641098e602 Fix Contributor link in homepage 2023-05-31 19:06:36 +05:30
sidharthv96
c2c8024718 Update docs 2023-05-31 11:11:04 +00:00
Sidharth Vinod
5338d90077 Merge pull request #4440 from AnnaTomanek/patch-1
Add CKEditor and GitHub Writer to available integrations
2023-05-31 16:36:30 +05:30
Anna Tomanek
180c695f2f Added CKEditor and GitHub Writer to available integrations. 2023-05-31 12:15:40 +02:00
Zihua Li
ce9bdcc53f Fix exceptions for empty lines 2023-05-29 21:00:37 +08:00
renovate[bot]
38d9ebc5da chore(deps): update all patch dependencies 2023-05-29 05:19:50 +00:00
Sidharth Vinod
5fd5de10b5 Merge pull request #4400 from mermaid-js/sidv/quadrantArrows
Quadrant chart unicode arrows
2023-05-26 17:21:26 +05:30
Sidharth Vinod
ca5d78098e Merge pull request #4426 from aloisklink/chore/fix-broken-lockfile
Merge PR #4425 to `master` to fix uploading v10.2.0 docs to `mermaid.js.org` website
2023-05-25 07:13:58 +05:30
Alois Klink
cb2bc2a587 Merge pull request #4425 from aloisklink/chore/fix-broken-lockfile
Fix broken `pnpm-lock.yaml` file to fix CI
2023-05-24 23:03:54 +01:00
Alois Klink
6093383d45 build(deps): fix broken pnpm-lock.yaml file
Looks like a bad merge conflict resolution broke this file,
and for some reason, the `packages/mermaid/src/vitepress` bit got
removed when releasing v10.2.0-rc.4.

Fixes: bd1343648e
Fixes: 9c12c42a26
2023-05-24 22:19:49 +01:00
Knut Sveidqvist
bd1343648e Merge branch 'master' into develop 2023-05-24 19:20:47 +02:00
Knut Sveidqvist
3517314390 Merge branch 'release/10.2.0' 2023-05-24 19:19:41 +02:00
Knut Sveidqvist
8d43b87200 Mermaid version 10.2.0 2023-05-24 19:03:22 +02:00
Knut Sveidqvist
9c12c42a26 Mermaid Version 10.2.0-rc.4 2023-05-24 18:18:54 +02:00
Knut Sveidqvist
5bfe32524d Label background fix 2023-05-24 12:53:28 +02:00
Knut Sveidqvist
bd616e0ef5 Test commit 2023-05-24 12:27:30 +02:00
Knut Sveidqvist
9fb25310e8 Fix for regression error in sequenceDiagrams 2023-05-24 11:27:17 +02:00
Sidharth Vinod
102b9bc8c3 Merge branch 'develop' into sidv/zenuml
* develop:
  Update all minor dependencies
  Update all patch dependencies
2023-05-23 10:36:02 +05:30
Sidharth Vinod
9cec5ae1c4 Merge branch 'sidv/zenuml' of https://github.com/mermaid-js/mermaid into sidv/zenuml
* 'sidv/zenuml' of https://github.com/mermaid-js/mermaid:
  Update docs
  Add contributors profile url
  ignore ZenUML types
  fixed typo and update peerDependencies version
  update pnpm-lock.yaml
  Address mermaid-zenuml PR comments
  Update all minor dependencies
  Update all patch dependencies
  Fix vitepress build
2023-05-23 10:34:08 +05:30
renovate[bot]
ac23787084 Update all minor dependencies 2023-05-22 07:53:27 +00:00
renovate[bot]
ad61f4c6b1 Update all patch dependencies 2023-05-22 06:06:37 +00:00
mikejeffers
496585b543 handle trailing whitespace on lines 2023-05-20 16:04:58 -04:00
mikejeffers
9e6168111f add e2e test 2023-05-20 15:20:28 -04:00
mikejeffers
f535640c1e Fixes #4408: solve infinite loop on words that exceed width constraint with word break 2023-05-20 14:59:04 -04:00
Alois Klink
acc19db5bb Merge branch 'develop' into sidv/zenuml
Conflicts:
	pnpm-lock.yaml
2023-05-17 19:59:58 +01:00
aloisklink
6cce1b20fb Update docs 2023-05-17 18:46:58 +00:00
dontry
a00616863d Add contributors profile url 2023-05-17 19:26:43 +01:00
dontry
49c97dad4c ignore ZenUML types 2023-05-17 19:26:43 +01:00
Alois Klink
e984b34af4 Merge branch 'develop' into sidv/zenuml
* develop: (66 commits)
  Update docs
  Update docs
  Improve the wording of security level values
  Added quadrantChart to the side bar
  Added e2e test cases with some fixes
  Added documentation for the quadrantChart
  Restructured the build function and addressed more review comment
  Fixed some parser issue and added test cases for the parser
  Fixed review comment
  Update docs
  Converted files to typescript and added proper types
  Fix blog linting
  Debug fetch-contributors
  Fix lockfile
  Revert "Replace esno & ts-node with tsx"
  [draft] Added support for quadrant chart
  Fix lockfile
  Fix build
  Update pnpm-lock
  Replace esno & ts-node with tsx
  ...

Merge conflicts:
  - pnpm-lock.yaml
2023-05-17 19:25:29 +01:00
Sidharth Vinod
c255d95ae2 Merge pull request #4405 from ZenUml/dongc/zenuml
Address mermaid-zenuml PR comments
2023-05-17 19:10:25 +01:00
Dong Cai
27e59e5adc fixed typo and update peerDependencies version 2023-05-17 19:10:19 +01:00
Dong Cai
507e24cc72 update pnpm-lock.yaml 2023-05-17 19:10:03 +01:00
Dong Cai
bb400d53b9 Address mermaid-zenuml PR comments 2023-05-17 19:09:48 +01:00
renovate[bot]
b1590c2211 Update all minor dependencies 2023-05-17 13:32:09 +00:00
renovate[bot]
9eeeb97e15 Update all patch dependencies 2023-05-17 09:17:43 +00:00
Sidharth Vinod
0ef115f69a Fix vitepress build 2023-05-17 11:28:17 +05:30
Sidharth Vinod
6a89ef94f7 Cleanup 2023-05-17 11:21:03 +05:30
Sidharth Vinod
97be4983f0 Merge branch 'develop' into sidv/zenuml
* develop: (66 commits)
  Update docs
  Update docs
  Improve the wording of security level values
  Added quadrantChart to the side bar
  Added e2e test cases with some fixes
  Added documentation for the quadrantChart
  Restructured the build function and addressed more review comment
  Fixed some parser issue and added test cases for the parser
  Fixed review comment
  Update docs
  Converted files to typescript and added proper types
  Fix blog linting
  Debug fetch-contributors
  Fix lockfile
  Revert "Replace esno & ts-node with tsx"
  [draft] Added support for quadrant chart
  Fix lockfile
  Fix build
  Update pnpm-lock
  Replace esno & ts-node with tsx
  ...
2023-05-16 23:26:35 +05:30
Sidharth Vinod
c7affbe33b Merge pull request #4405 from ZenUml/dongc/zenuml
Address mermaid-zenuml PR comments
2023-05-16 23:25:29 +05:30
Dong Cai
82eccb70fa fixed typo and update peerDependencies version 2023-05-16 23:41:53 +10:00
Justin Greywolf
78efac2c51 Fix visibility issue for fields 2023-05-16 06:13:37 -07:00
Justin Greywolf
58ec2686da Merge branch 'release/10.2.0' of https://github.com/mermaid-js/mermaid into release/10.2.0 2023-05-16 04:12:27 -07:00
Justin Greywolf
3a5c4a149f fix parsing issue with class diagrams 2023-05-16 04:12:03 -07:00
Dong Cai
c889ef0c30 update pnpm-lock.yaml 2023-05-16 21:08:25 +10:00
Dong Cai
cce5505662 Address mermaid-zenuml PR comments 2023-05-16 20:30:25 +10:00
Sidharth Vinod
b925849893 fix: Use unicode arrows in quadrant chart axis 2023-05-15 13:36:35 +05:30
Sidharth Vinod
d86d1e7887 fix: Use unicode arrows in quadrant chart axis 2023-05-15 13:32:59 +05:30
Knut Sveidqvist
132ce2fb4b Merge branch 'develop' into release/10.2.0 2023-05-15 09:50:02 +02:00
Knut Sveidqvist
458ac84749 Merge pull request #4383 from amsubhash/feature/4282_add_quadrant_chart
Added support for quadrant chart
2023-05-15 09:49:23 +02:00
Knut Sveidqvist
fa3e800802 Merge remote-tracking branch 'origin/release/10.2.0' into release/10.2.0 2023-05-15 08:52:13 +02:00
Knut Sveidqvist
f616663f2f Merge branch 'develop' into release/10.2.0 2023-05-15 08:51:47 +02:00
knsv
b563ddc19d Update docs 2023-05-15 06:49:56 +00:00
Knut Sveidqvist
c755e45961 Merge pull request #4349 from danielcompton/patch-1
Indent subgraph sections
2023-05-15 08:46:38 +02:00
knsv
de34a06718 Update docs 2023-05-15 06:22:10 +00:00
Knut Sveidqvist
040827d95d Merge pull request #4395 from Gusted/docs-sec
Improve the wording of security level values
2023-05-15 08:18:45 +02:00
Gusted
a991c32732 Improve the wording of security level values
- The current wording can be misinterpreted because, for some people
'tags' might not be associated with HTML tags.
- Improve the wording to make it clear that HTML tags are intended.
2023-05-13 20:25:04 +02:00
Subhash Halder
c4aebe6d57 Added quadrantChart to the side bar 2023-05-13 20:28:49 +05:30
Subhash Halder
0f354ff974 Added e2e test cases with some fixes 2023-05-13 19:45:45 +05:30
Subhash Halder
b2a5ddcd85 Added documentation for the quadrantChart 2023-05-13 17:33:31 +05:30
Subhash Halder
816800adcd Restructured the build function and addressed more review comment 2023-05-12 20:16:26 +05:30
Subhash Halder
fe9c109837 Fixed some parser issue and added test cases for the parser 2023-05-12 17:09:14 +05:30
Subhash Halder
1253733962 Fixed review comment 2023-05-11 12:19:59 +05:30
amsubhash
4cbcfa054e Update docs 2023-05-09 14:50:54 +00:00
Subhash Halder
9a7dbab5ed Converted files to typescript and added proper types 2023-05-09 20:17:04 +05:30
Sidharth Vinod
6eef09661d Merge branch 'develop' into release/10.2.0
* develop:
  Fix blog linting
2023-05-09 10:25:27 +05:30
Sidharth Vinod
10a66030b9 Fix blog linting 2023-05-09 10:25:16 +05:30
Sidharth Vinod
93370a8e11 Merge branch 'develop' into release/10.2.0
* develop:
  Updated url
  Updated url
  Updated url
  Updated url
  add blog post to blog page
  add the latest blog post
2023-05-09 10:24:38 +05:30
Sidharth Vinod
4e3c2adffc Merge branch 'master' into develop
* master:
  Updated url
  Updated url
  Updated url
  Updated url
  add blog post to blog page
  add the latest blog post
2023-05-09 10:24:14 +05:30
Sidharth Vinod
fbd35ae95a fix lint command 2023-05-09 10:24:05 +05:30
Sidharth Vinod
75e73a4f03 Bump version 2023-05-09 10:20:33 +05:30
Sidharth Vinod
17c798a1b3 Merge branch 'develop' into release/10.2.0
* develop: (49 commits)
  Debug fetch-contributors
  Fix lockfile
  Revert "Replace esno & ts-node with tsx"
  Fix lockfile
  Fix build
  Update pnpm-lock
  Replace esno & ts-node with tsx
  chore(deps): update all minor dependencies
  chore(deps): update all patch dependencies
  Fix missing `await` in usage document
  Add integrations
  test: fix classDiagramGrammer unit test
  restore path calculation for grammar test
  Allow overlapping notes
  remove spellcheck warnings
  Fix path in test
  fix import issue
  requested update
  Reject ridiculous years in Gantt charts.
  Remove unnecessary dependencies
  ...
2023-05-09 10:13:15 +05:30
Sidharth Vinod
c0b7ba305f Merge pull request #4356 from mermaid-js/sidv/contributors
Show all contributors in homepage
2023-05-09 10:02:08 +05:30
Sidharth Vinod
bd53e9d449 Debug fetch-contributors 2023-05-09 09:56:40 +05:30
Sidharth Vinod
45139470a3 Fix lockfile 2023-05-08 22:08:47 +05:30
Sidharth Vinod
3677d44a58 Revert "Replace esno & ts-node with tsx"
This reverts commit 75337287ea.
2023-05-08 22:08:11 +05:30
Subhash Halder
272ccded93 [draft] Added support for quadrant chart 2023-05-08 19:23:58 +05:30
Sidharth Vinod
b27d018c09 Fix lockfile 2023-05-08 19:04:21 +05:30
Sidharth Vinod
a1fe7222ac Fix build 2023-05-08 18:52:03 +05:30
Sidharth Vinod
1b48c19897 Merge branch 'develop' into sidv/contributors
* develop:
  chore(deps): update all minor dependencies
  requested update
  Update packages/mermaid/src/docs/syntax/flowchart.md
  Update docs
  Docs: Flowchart - minor verbiage update
2023-05-08 18:51:10 +05:30
Sidharth Vinod
2dc780a766 Merge pull request #4315 from mermaid-js/huynhicode-docs-flowchart
Docs: Flowchart - minor verbiage update
2023-05-08 18:49:47 +05:30
Sidharth Vinod
0b73d40ca1 Merge pull request #4380 from mermaid-js/renovate/all-minor
chore(deps): update all minor dependencies (minor)
2023-05-08 18:47:54 +05:30
Sidharth Vinod
500efda8d6 Update pnpm-lock 2023-05-08 18:47:22 +05:30
Sidharth Vinod
8e9d1bfc35 Merge branch 'develop' into sidv/contributors
* develop:
  chore(deps): update all patch dependencies
2023-05-08 18:46:39 +05:30
Sidharth Vinod
75337287ea Replace esno & ts-node with tsx 2023-05-08 18:46:14 +05:30
renovate[bot]
b04155178a chore(deps): update all minor dependencies 2023-05-08 10:09:26 +00:00
renovate[bot]
d7e140a829 chore(deps): update all patch dependencies 2023-05-08 10:08:10 +00:00
Knut Sveidqvist
25a7164df2 Merge branch 'develop' into sidv/contributors 2023-05-08 09:17:49 +02:00
Knut Sveidqvist
29c8f8c867 Merge pull request #4350 from mermaid-js/sidv/removeSimpleMarkdown
remove SimpleMarkdown
2023-05-08 09:16:28 +02:00
Knut Sveidqvist
a226b073a0 Merge pull request #4374 from remcohaszing/add-integrations
Add integrations
2023-05-08 09:13:16 +02:00
Knut Sveidqvist
0c18f26dc0 Merge pull request #4370 from mermaid-js/Overlap-notes-messages
Allow overlapping notes
2023-05-08 09:11:33 +02:00
Knut Sveidqvist
4d0447574a Merge pull request #4366 from mermaid-js/update-latest-news
Latest News section: update content
2023-05-08 09:11:11 +02:00
Knut Sveidqvist
40176f9bef Updated url 2023-05-08 09:10:45 +02:00
Knut Sveidqvist
97a34a0cfc Updated url 2023-05-08 09:10:21 +02:00
Knut Sveidqvist
d1690a03db Updated url 2023-05-08 09:09:32 +02:00
Knut Sveidqvist
d8c67cd2c9 Updated url 2023-05-08 09:08:18 +02:00
Knut Sveidqvist
ccc87be462 Merge branch 'develop' into Overlap-notes-messages 2023-05-08 09:04:19 +02:00
Knut Sveidqvist
f8b029de0e Merge pull request #4376 from rhysd/doc/missing_await
Fix missing `await` in usage document
2023-05-08 09:01:22 +02:00
Knut Sveidqvist
8573db5114 Merge pull request #4378 from aloisklink/fix/fix-broken-unit-tests
test: fix classDiagramGrammer unit test
2023-05-08 09:00:49 +02:00
rhysd
c6c3b07c33 Fix missing await in usage document 2023-05-08 13:44:40 +09:00
Remco Haszing
7df21c01ec Add integrations
This adds the projects `mermaid-isomorphic`, `rehype-mermaidjs`, and
`gatsby-remark-mermaid` to the integrations page.

This also removes `remark-mermaid`. This project is outdated and
unmaintained.
2023-05-07 21:00:17 +02:00
Alois Klink
a1e64c2102 test: fix classDiagramGrammer unit test
The classDiagramGrammer.spec.ts unit test had some bad filepath
manipulation that fails on UNIX platforms.

Instead, we can use the recommended method from the Node.JS
documentation, see https://nodejs.org/api/esm.html#importmetaurl.

Fixes: 221640aa25
2023-05-07 18:05:43 +01:00
Knut Sveidqvist
7f9ebcf2c6 Merge pull request #4361 from tcbuzor/feature-4146-eraggregation
add master detail relationship support
2023-05-07 08:04:07 +02:00
Justin Greywolf
7e240dac22 restore path calculation for grammar test 2023-05-04 09:15:21 -07:00
Justin Greywolf
06fdb95389 Allow overlapping notes 2023-05-03 22:15:59 -07:00
Justin Greywolf
2216bbad25 remove spellcheck warnings 2023-05-03 17:02:44 -07:00
Justin Greywolf
43819f1bd8 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into develop 2023-05-03 17:01:43 -07:00
Justin Greywolf
221640aa25 Fix path in test 2023-05-03 17:01:27 -07:00
Justin Greywolf
5f1a507820 Merge pull request #4206 from ksilverwall/feature/class-namespace
Implement `package` on class diagram
2023-05-03 16:58:08 -07:00
Justin Greywolf
7fd4814abc Merge pull request #4259 from mermaid-js/svgDrawRefactor
Refactor to consolidate shared svgDraw components
2023-05-03 16:57:09 -07:00
Justin Greywolf
61e31b3fe6 fix import issue 2023-05-03 16:29:57 -07:00
Justin Greywolf
e903f06f75 Merge branch 'develop' into svgDrawRefactor 2023-05-03 16:18:17 -07:00
Steph
35051f1946 add blog post to blog page 2023-05-02 23:50:14 -07:00
Sidharth Vinod
bf37956528 Merge pull request #4367 from toolness/bug/4353_reject_ridiculous_dates
Reject ridiculous years in Gantt charts.
2023-05-03 00:37:05 +05:30
Atul Varma
725b80831e Reject ridiculous years in Gantt charts. 2023-05-02 09:46:59 -04:00
Sidharth Vinod
cb3982de7e Remove unnecessary dependencies
Co-authored-by: Alois Klink <alois.klink@gmail.com>
2023-05-02 14:16:35 +05:30
Steph
4c9e35e269 add the latest blog post 2023-05-01 22:48:29 -07:00
eopaant
18571052ad added style to reflect true aggregation 2023-05-01 22:34:36 -05:00
Kazuki Tsunemi
0d373f3a6a Modify jsdoc of addClasses 2023-05-01 19:12:39 +09:00
Kazuki Tsunemi
70a52da393 namespace rect is not filled 2023-05-01 19:06:46 +09:00
eopaant
3549ffc4ac add master detail relationship support 2023-04-30 13:19:35 -05:00
Sidharth Vinod
c79be5d829 Merge pull request #4357 from agentraghav/code-improvment
updating es6 rules in flowchart diagram
2023-04-30 22:14:59 +05:30
raghvendra
38efaf93c8 updating es6 rules in flowchart diagram 2023-04-29 16:06:17 +05:30
Sidharth Vinod
dfa65c5fc8 remove nr 2023-04-29 11:30:11 +05:30
Sidharth Vinod
32928224e5 Remove contributor-names from git 2023-04-29 10:55:24 +05:30
Sidharth Vinod
4456c2a267 Fix user-avatar copying 2023-04-29 10:55:06 +05:30
Sidharth Vinod
9e0410e0d3 Simplify contributors 2023-04-29 10:51:03 +05:30
Sidharth Vinod
cdc68d99fe Cleanup 2023-04-29 10:50:30 +05:30
Sidharth Vinod
110cd61c64 Fix lockfile 2023-04-29 10:17:51 +05:30
Sidharth Vinod
baf5e8f6b2 Fix docs, mmd -> mermaid 2023-04-29 10:13:00 +05:30
Sidharth Vinod
99abd4c31b Fix avatar fetching 2023-04-29 10:12:47 +05:30
Sidharth Vinod
9238deaf00 Merge branch 'develop' into sidv/contributors
* develop: (237 commits)
  submit built docs
  update Font Awesome Version
  Clarify FontAwesome support
  Update version
  Fix classParser
  Check for conflict when linting jison
  Update class grammar test
  fix Class diagram grammar
  Skip sourcemap
  Bump version
  Update deps
  Fix unit tests
  Update vite
  Fix applitools cypress
  Update packages/mermaid/package.json
  chore(deps): update dependency typescript to v5
  fix typedoc
  fix(deps): update all minor dependencies
  chore(deps): update pnpm to v8
  chore(deps): update fregante/setup-git-user action to v2
  ...
2023-04-29 09:32:07 +05:30
Sidharth Vinod
a01343bf1b Fix lockfile 2023-04-27 10:37:35 +05:30
Sidharth Vinod
88f9233aa8 Merge branch 'develop' into sidv/zenuml
* develop:
  submit built docs
  update Font Awesome Version
  Clarify FontAwesome support
2023-04-27 10:36:16 +05:30
Sidharth Vinod
33c81d557e Merge pull request #4347 from josh-bouganim-avant/patch-1
Clarify FontAwesome support
2023-04-26 11:07:17 +05:30
Sidharth Vinod
91f1ef9c42 Back to JS with jsdoc types 2023-04-26 11:00:35 +05:30
Sidharth Vinod
eba3a7bdcc Add unsupported text 2023-04-26 10:55:37 +05:30
Sidharth Vinod
5bbce45c56 Reduce changes in test 2023-04-26 10:51:54 +05:30
Sidharth Vinod
395a794758 Fix deps 2023-04-26 10:37:03 +05:30
Sidharth Vinod
1f2af83a0c Fix lockfile 2023-04-26 10:35:24 +05:30
Sidharth Vinod
4d3e5e42e1 Use mdast-util-from-markdown instead of simple-markdown 2023-04-26 10:30:13 +05:30
Sidharth Vinod
9b912c4a59 handleMarkdown JS -> TS 2023-04-26 10:11:19 +05:30
Daniel Compton
c6fbabc70e Indent subgraph sections
The two subgraph paragraphs below "Subgraphs" should be
indented below it.
2023-04-26 09:39:17 +12:00
Josh Bouganim
41969c434f submit built docs 2023-04-25 20:35:45 +00:00
Sidharth Vinod
8cd48bf405 Add docs 2023-04-26 00:31:00 +05:30
Sidharth Vinod
8b18fd93f4 Add test 2023-04-26 00:20:00 +05:30
Sidharth Vinod
ec62c2bf5b Merge branch 'develop' into sidv/zenuml
* develop: (33 commits)
  Update version
  Fix classParser
  Check for conflict when linting jison
  Update class grammar test
  fix Class diagram grammar
  Skip sourcemap
  Bump version
  Update deps
  Fix unit tests
  Update vite
  Fix applitools cypress
  Update packages/mermaid/package.json
  chore(deps): update dependency typescript to v5
  fix typedoc
  fix(deps): update all minor dependencies
  chore(deps): update pnpm to v8
  chore(deps): update fregante/setup-git-user action to v2
  fix(deps): update all minor dependencies
  chore(deps): update dependency start-server-and-test to v2
  chore(deps): update dependency rimraf to v5
  ...
2023-04-26 00:15:52 +05:30
Sidharth Vinod
b9e7ba023f Update ZenUML 2023-04-26 00:12:39 +05:30
Josh Bouganim
284d032d26 update Font Awesome Version 2023-04-25 11:06:43 -07:00
Josh Bouganim
9859491381 Clarify FontAwesome support 2023-04-25 11:01:09 -07:00
Kazuki Tsunemi
95d8e3a5df Merge branch 'develop' into feature/class-namespace 2023-04-25 16:40:42 +09:00
Sidharth Vinod
56ec1ab52c Merge pull request #4338 from mermaid-js/sidv/fixClassGrammar
fix ClassGrammar
2023-04-24 20:43:48 +05:30
Sidharth Vinod
597a162249 Update version 2023-04-24 00:35:50 +05:30
Sidharth Vinod
24f5a15f20 Fix classParser 2023-04-24 00:34:41 +05:30
Sidharth Vinod
d125d22488 Check for conflict when linting jison 2023-04-24 00:15:51 +05:30
Sidharth Vinod
d09151e870 Update class grammar test 2023-04-24 00:15:30 +05:30
Sidharth Vinod
60db22886a fix Class diagram grammar 2023-04-24 00:15:07 +05:30
Sidharth Vinod
23bed7e653 Skip sourcemap 2023-04-23 23:40:24 +05:30
Sidharth Vinod
07dcb8cc44 Bump version 2023-04-23 23:38:34 +05:30
Sidharth Vinod
ba632ff65b Merge pull request #4303 from mermaid-js/MultipleUpdatesToClassDiagram
Multiple updates to class diagram
2023-04-23 23:37:24 +05:30
Sidharth Vinod
d79f946cbc Merge branch 'develop' into MultipleUpdatesToClassDiagram
* develop:
  Update deps
  Fix unit tests
2023-04-23 23:22:32 +05:30
Sidharth Vinod
b0c58a9764 Update deps 2023-04-23 23:15:26 +05:30
Sidharth Vinod
94991520ea Fix unit tests 2023-04-23 23:15:05 +05:30
Sidharth Vinod
05e7a6bd4b Merge branch 'develop' into MultipleUpdatesToClassDiagram
* develop: (30 commits)
  Update vite
  Fix applitools cypress
  Update packages/mermaid/package.json
  chore(deps): update dependency typescript to v5
  fix typedoc
  fix(deps): update all minor dependencies
  chore(deps): update pnpm to v8
  chore(deps): update fregante/setup-git-user action to v2
  fix(deps): update all minor dependencies
  chore(deps): update dependency start-server-and-test to v2
  chore(deps): update dependency rimraf to v5
  chore(deps): update dependency eslint-plugin-unicorn to v46
  chore(deps): update actions/deploy-pages action to v2
  chore(deps): update dependency eslint-plugin-jsdoc to v43
  Split docs into build and deploy
  fix(deps): update dependency dompurify to v3
  chore(deps): update dependency concurrently to v8
  Split renovate groups
  Upgrade vitepress
  chore(deps): update pnpm to v7.30.5
  ...
2023-04-23 22:50:40 +05:30
Sidharth Vinod
9298f631f9 Merge pull request #4296 from Will-Low/patch-2
Updating documentation on notes for classes within class diagrams
2023-04-23 22:48:15 +05:30
Sidharth Vinod
04305bdddc Merge pull request #4281 from mermaid-js/sidv/addUMD
Add UMD build Back
2023-04-23 22:40:01 +05:30
Sidharth Vinod
f018ca34da Merge pull request #4335 from mermaid-js/sidv/fixApplitools
Sidv/fix applitools
2023-04-23 22:35:48 +05:30
Sidharth Vinod
bbe3909627 Update vite 2023-04-23 22:34:19 +05:30
Sidharth Vinod
642397b36c Fix applitools cypress 2023-04-23 15:59:27 +05:30
Sidharth Vinod
562514964e Update packages/mermaid/package.json
Co-authored-by: Alois Klink <alois@aloisklink.com>
2023-04-23 15:49:20 +05:30
Sidharth Vinod
125be13df4 Merge branch 'develop' into sidv/addUMD
* develop: (69 commits)
  chore(deps): update dependency typescript to v5
  fix typedoc
  fix(deps): update all minor dependencies
  chore(deps): update pnpm to v8
  chore(deps): update fregante/setup-git-user action to v2
  fix(deps): update all minor dependencies
  chore(deps): update dependency start-server-and-test to v2
  chore(deps): update dependency rimraf to v5
  chore(deps): update dependency eslint-plugin-unicorn to v46
  chore(deps): update actions/deploy-pages action to v2
  chore(deps): update dependency eslint-plugin-jsdoc to v43
  Split docs into build and deploy
  fix(deps): update dependency dompurify to v3
  chore(deps): update dependency concurrently to v8
  Split renovate groups
  Upgrade vitepress
  chore(deps): update pnpm to v7.30.5
  pie diagram mermaid module import fix
  Update docs
  docs(flowchart): wrap br tag by codeblock
  ...
2023-04-23 15:31:17 +05:30
Sidharth Vinod
f3f7460706 Merge pull request #4328 from mermaid-js/renovate/typescript-5.x
chore(deps): update dependency typescript to v5
2023-04-23 13:09:44 +05:30
renovate[bot]
9e58f7bd99 chore(deps): update dependency typescript to v5 2023-04-22 20:51:24 +00:00
Sidharth Vinod
5110967bcd Merge pull request #4321 from mermaid-js/renovate/all-minor
fix(deps): update all minor dependencies (minor)
2023-04-23 02:20:12 +05:30
Sidharth Vinod
5c6773a47c Merge branch 'renovate/all-minor' of https://github.com/mermaid-js/mermaid into renovate/all-minor
* 'renovate/all-minor' of https://github.com/mermaid-js/mermaid:
  fix(deps): update all minor dependencies
  chore(deps): update pnpm to v8
  chore(deps): update fregante/setup-git-user action to v2
2023-04-23 02:04:28 +05:30
Sidharth Vinod
5634c6ca1a fix typedoc 2023-04-23 02:03:28 +05:30
renovate[bot]
1c17f4c770 fix(deps): update all minor dependencies 2023-04-22 20:30:38 +00:00
Sidharth Vinod
f17a325632 Merge pull request #4329 from mermaid-js/renovate/fregante-setup-git-user-2.x
chore(deps): update fregante/setup-git-user action to v2
2023-04-23 01:58:02 +05:30
Sidharth Vinod
9f2d14f676 Merge pull request #4330 from mermaid-js/renovate/pnpm-8.x
chore(deps): update pnpm to v8
2023-04-23 01:57:39 +05:30
renovate[bot]
bed9e8daab chore(deps): update pnpm to v8 2023-04-22 20:04:11 +00:00
renovate[bot]
4a258a0f2c chore(deps): update fregante/setup-git-user action to v2 2023-04-22 20:02:23 +00:00
renovate[bot]
e4bc3e2269 fix(deps): update all minor dependencies 2023-04-22 20:01:25 +00:00
Sidharth Vinod
a470407f02 Merge pull request #4327 from mermaid-js/renovate/start-server-and-test-2.x
chore(deps): update dependency start-server-and-test to v2
2023-04-23 01:30:16 +05:30
renovate[bot]
e6d11b9443 chore(deps): update dependency start-server-and-test to v2 2023-04-22 19:26:13 +00:00
Sidharth Vinod
c624cbb910 Merge pull request #4325 from mermaid-js/renovate/eslint-plugin-unicorn-46.x
chore(deps): update dependency eslint-plugin-unicorn to v46
2023-04-23 00:51:33 +05:30
Sidharth Vinod
b428c06bed Merge pull request #4326 from mermaid-js/renovate/rimraf-5.x
chore(deps): update dependency rimraf to v5
2023-04-23 00:51:08 +05:30
Sidharth Vinod
9cbc3d6fb9 Merge pull request #4322 from mermaid-js/renovate/actions-deploy-pages-2.x
chore(deps): update actions/deploy-pages action to v2
2023-04-23 00:50:58 +05:30
Sidharth Vinod
12ed81855a Add zenuml 2023-04-23 00:34:59 +05:30
renovate[bot]
f597f15f82 chore(deps): update dependency rimraf to v5 2023-04-22 18:53:56 +00:00
renovate[bot]
909c063a67 chore(deps): update dependency eslint-plugin-unicorn to v46 2023-04-22 18:51:50 +00:00
renovate[bot]
f93bac9dd4 chore(deps): update actions/deploy-pages action to v2 2023-04-22 18:49:44 +00:00
renovate[bot]
8cb3318d94 chore(deps): update dependency eslint-plugin-jsdoc to v43 2023-04-22 18:46:44 +00:00
Sidharth Vinod
cb1e8220a4 Merge pull request #4331 from mermaid-js/renovate/dompurify-3.x
fix(deps): update dependency dompurify to v3
2023-04-22 23:59:53 +05:30
Sidharth Vinod
49bb87e14a Split docs into build and deploy 2023-04-22 23:59:33 +05:30
renovate[bot]
ddde451915 fix(deps): update dependency dompurify to v3 2023-04-22 08:28:31 +00:00
Sidharth Vinod
40d2682b14 Merge pull request #4323 from mermaid-js/renovate/concurrently-8.x
chore(deps): update dependency concurrently to v8
2023-04-22 13:53:05 +05:30
renovate[bot]
93eb72248a chore(deps): update dependency concurrently to v8 2023-04-20 04:15:28 +00:00
Sidharth Vinod
6883a92672 Split renovate groups 2023-04-20 09:40:58 +05:30
Sidharth Vinod
2fe0a1360f Upgrade vitepress 2023-04-20 09:35:28 +05:30
Sidharth Vinod
8e0f947d6c Merge pull request #4304 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update pnpm to v7.30.5
2023-04-19 16:29:40 +05:30
renovate[bot]
411f868f22 chore(deps): update pnpm to v7.30.5 2023-04-19 07:00:59 +00:00
Sidharth Vinod
39f9277a73 Merge pull request #4316 from agentraghav/agentraghav-pie-chart-demos-fix
pie diagram mermaid module import fix
2023-04-19 12:25:59 +05:30
raghvendra
ff3104ad9c pie diagram mermaid module import fix 2023-04-19 11:25:32 +05:30
Will-Low
28155b0e0a Removing redundant code block 2023-04-17 10:24:06 -07:00
Will-Low
47edb2c0bb Merge branch 'mermaid-js:develop' into patch-2 2023-04-17 07:30:18 -07:00
Justin Greywolf
642bc1a739 update tests for package visibility 2023-04-17 07:24:27 -07:00
Justin Greywolf
5b0eb702e5 Merge branch 'develop' into MultipleUpdatesToClassDiagram 2023-04-17 06:51:51 -07:00
Justin Greywolf
62870597d6 Fixed failing tests 2023-04-17 06:51:08 -07:00
Justin Greywolf
59a85a7dfd Multiple Fixes to classes 2023-04-16 19:42:51 -07:00
Kazuki Tsunemi
204baa4586 Merge branch 'develop' into feature/class-namespace 2023-04-17 11:27:10 +09:00
Justin Greywolf
ac63788a9c organized and fixed tests 2023-04-14 10:13:21 -07:00
Will-Low
e3ca003127 Rebuilding docs 2023-04-13 09:59:11 -07:00
Will-Low
19363965ad Updating documentation on notes for classes
I was confused by the documentation on notes for a specific class. Updated the wording slightly and added an example for clarity.
2023-04-13 09:08:48 -07:00
Sidharth Vinod
24c9506935 fix version 2023-04-05 22:07:46 +05:30
Sidharth Vinod
ca8ce8201c Merge branch 'develop' into sidv/addUMD
* develop: (81 commits)
  revert pnpm changes
  doc update
  auto generated from pnpm run
  auto generated from pnpm run
  linting
  added example of Bar chart
  Update docs
  Adding rendering tests and unit tests
  Syntax for markdown strings is a single backtick.
  updated labels in the chart
  Update docs
  updated example data smaller
  Bar chart
  fix: Remove comment line completely
  fix: trimStart to text
  test: add space before init
  fix uncaughexception in tests
  fix(#4256): Keep error diagram on screen
  fix(#4137): Cleanup comments before parsing
  Update docs
  ...
2023-04-05 22:04:53 +05:30
Justin Greywolf
e357bbee42 Refactor to consolidate shared svgDraw components 2023-03-30 11:04:19 -07:00
Kazuki Tsunemi
b725b69fda Add package of class diagrams to doc 2023-03-17 14:47:32 +09:00
Kazuki Tsunemi
b51b2c7b04 Add namespace sample to README 2023-03-17 14:21:31 +09:00
Kazuki Tsunemi
434961b44a Implement rendering logic 2023-03-15 13:26:51 +09:00
Kazuki Tsunemi
75502d076e Implement database for namespace data 2023-03-15 12:43:21 +09:00
Kazuki Tsunemi
4017bb3c49 Implement namespace parser 2023-03-15 12:18:52 +09:00
Kazuki Tsunemi
c1df62638d class body grammer apply to class stetement only 2023-03-15 12:05:57 +09:00
Sidharth Vinod
533830172c Add UMD back 2023-03-07 09:38:51 +05:30
Sidharth Vinod
00a14ee811 WIP: Contributors 2023-02-24 23:03:51 +05:30
Sidharth Vinod
d688f1fe94 fix lockfile 2023-02-24 21:16:09 +05:30
Sidharth Vinod
8c2dbafdb5 Merge branch 'develop' into sidv/contributors
* develop: (309 commits)
  Update docs
  fix Lint
  Update CHANGELOG.md
  Update CHANGELOG.md
  fix: fix exports
  Doc (typo): remove duplicate "be"
  Fix readme link
  Regenerate mermaid docs
  Add deepdwn to cspell
  Add Deepdwn to native integrations list
  docs: Fix changelog
  docs: v10 breaking changes
  Remove `null` from diagrams before render
  fix docs diagram
  Updated version
  Minor cleanup to trigger build.
  Fix spellings
  Wrap option working in test case
  Fix typos
  Minor cleanup
  ...
2023-02-24 21:14:55 +05:30
Sidharth Vinod
1495ae623a WIP Adding contributors page from vitest 2022-12-21 10:54:23 +05:30
150 changed files with 13567 additions and 7714 deletions

View File

@@ -4,4 +4,5 @@ docs/Setup.md
cypress.config.js
cypress/plugins/index.js
coverage
*.json
*.json
node_modules

View File

@@ -3,6 +3,7 @@ description: Suggest a new Diagram Type to add to Mermaid.
labels:
- 'Status: Triage'
- 'Type: Enhancement'
- 'Type: New Diagram'
body:
- type: markdown
@@ -17,6 +18,14 @@ body:
- Use a clear and concise title
- Fill out the text fields with as much detail as possible.
- Never be shy to give us screenshots and/or code samples. It will help!
## Example issues
Refer to the discussions here to get an idea of how the diagram syntax is created.
- https://github.com/mermaid-js/mermaid/issues/4269
- https://github.com/mermaid-js/mermaid/issues/4282
- type: textarea
attributes:
label: Proposal
@@ -35,8 +44,17 @@ body:
description: If applicable, add screenshots to show possible examples of how the diagram may look like.
- type: textarea
attributes:
label: Code Sample
label: Syntax
description: |-
If applicable, add a code sample for how to implement this new diagram.
The text will automatically be rendered as JavaScript code.
render: javascript
If possible, include a syntax which could be used to write the diagram.
Try to add one or two examples of valid use-cases here.
- type: dropdown
id: implementation
attributes:
label: Implementation
description: |-
Would you like to implement this yourself, or is it a proposal for the community?
If there is no corresponding PR from your side after 30 days, the diagram will be open for everyone to implement.
options:
- I will try and implement it myself.
- This is a proposal which I'd love to see built into mermaid by the wonderful community.

29
.github/workflows/build-docs.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build Vitepress docs
on:
pull_request:
permissions:
contents: read
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: pnpm
node-version: 18
- name: Install Packages
run: pnpm install --frozen-lockfile
- name: Run Build
run: pnpm --filter mermaid run docs:build:vitepress

View File

@@ -38,15 +38,8 @@ jobs:
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
cache: pnpm
node-version: ${{ matrix.node-version }}
- name: Install Packages
run: |
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- if: ${{ env.USE_APPLI }}
name: Notify applitools of new batch
# Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
@@ -54,19 +47,22 @@ jobs:
env:
# e.g. mermaid-js/mermaid/my-branch
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
- name: Run E2E Tests
run: pnpm run e2e
- name: Cypress run
uses: cypress-io/github-action@v4
id: cypress
with:
start: pnpm run dev
wait-on: 'http://localhost:9000'
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
# Mermaid applitools.config.js uses this to pick batch name.
APPLI_BRANCH: ${{ github.ref_name }}
APPLITOOLS_BATCH_ID: ${{ github.sha }}
# e.g. mermaid-js/mermaid/my-branch
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'

View File

@@ -36,7 +36,7 @@ jobs:
restore-keys: cache-lychee-
- name: Link Checker
uses: lycheeverse/lychee-action@v1.6.1
uses: lycheeverse/lychee-action@v1.8.0
with:
args: >-
--verbose

View File

@@ -42,7 +42,7 @@ jobs:
if ! pnpm run lint; then
# print a nice error message on lint failure
ERROR_MESSAGE='Running `pnpm run lint` failed.'
ERROR_MESSAGE+=' Running `pnpm run lint:fix` may fix this issue. '
ERROR_MESSAGE+=' Running `pnpm -w run lint:fix` may fix this issue. '
ERROR_MESSAGE+=" If this error doesn't occur on your local machine,"
ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.'
ERROR_MESSAGE+=' You may also need to delete your prettier cache by running'

View File

@@ -5,10 +5,6 @@ on:
push:
branches:
- master
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
@@ -53,7 +49,6 @@ jobs:
# Deployment job
deploy:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
environment:
name: github-pages
runs-on: ubuntu-latest
@@ -61,4 +56,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v2

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: fregante/setup-git-user@v1
- uses: fregante/setup-git-user@v2
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json

3
.gitignore vendored
View File

@@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
knsv*.html
local*.html
stats/
**/user-avatars/*
**/contributor-names.json

View File

@@ -1,5 +1,5 @@
export default {
'!(docs/**/*)*.{ts,js,json,html,md,mts}': [
'!(docs/**/*)*.{ts,js,html,md,mts}': [
'eslint --cache --cache-strategy content --fix',
// don't cache prettier yet, since we use `prettier-plugin-jsdoc`,
// and prettier doesn't invalidate cache on plugin updates"

View File

@@ -4,4 +4,5 @@ cypress/platform/xss3.html
coverage
# Autogenerated by PNPM
pnpm-lock.yaml
stats
stats
packages/mermaid/src/docs/.vitepress/components.d.ts

View File

@@ -11,6 +11,7 @@ const visualize = process.argv.includes('--visualize');
const watch = process.argv.includes('--watch');
const mermaidOnly = process.argv.includes('--mermaid');
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const sourcemap = false;
type OutputOptions = Exclude<
Exclude<InlineConfig['build'], undefined>['rollupOptions'],
@@ -43,6 +44,11 @@ const packageOptions = {
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
};
interface BuildOptions {
@@ -60,9 +66,15 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
{
name,
format: 'esm',
sourcemap: true,
sourcemap,
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
},
{
name,
format: 'umd',
sourcemap,
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
},
];
if (core) {
@@ -79,7 +91,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
{
name,
format: 'esm',
sourcemap: true,
sourcemap,
entryFileNames: `${name}.core.mjs`,
},
];
@@ -139,6 +151,7 @@ if (watch) {
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
if (!mermaidOnly) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
}
} else if (visualize) {
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));

View File

@@ -15,6 +15,7 @@ async function createServer() {
app.use(cors());
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('./packages/mermaid-zenuml/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(vite.middlewares);
app.use(express.static('demos'));

View File

@@ -165,6 +165,13 @@ class Class10 {
int id
size()
}
namespace Namespace01 {
class Class11
class Class12 {
int id
size()
}
}
```
```mermaid
@@ -184,6 +191,13 @@ class Class10 {
int id
size()
}
namespace Namespace01 {
class Class11
class Class12 {
int id
size()
}
}
```
### State diagram [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]

View File

@@ -5,11 +5,14 @@
"acyclicer",
"adamiecki",
"alois",
"aloisklink",
"antiscript",
"antlr",
"appli",
"applitools",
"asciidoctor",
"ashish",
"ashishjain",
"astah",
"bbox",
"bilkent",
@@ -23,6 +26,7 @@
"classdef",
"codedoc",
"colour",
"commitlint",
"cpettitt",
"customizability",
"cuzon",
@@ -48,30 +52,42 @@
"grav",
"greywolf",
"huynh",
"huynhicode",
"inkdrop",
"jaoude",
"jgreywolf",
"jison",
"jiti",
"kaufmann",
"khroma",
"klemm",
"klink",
"knsv",
"knut",
"knutsveidqvist",
"laganeckas",
"linetype",
"lintstagedrc",
"logmsg",
"lucida",
"markdownish",
"matthieu",
"matthieumorel",
"mdast",
"mdbook",
"mermaidjs",
"mermerd",
"mindaugas",
"mindmap",
"mindmaps",
"mitigations",
"mkdocs",
"mmorel",
"mult",
"nextra",
"orlandoni",
"pathe",
"pbrolin",
"phpbb",
"plantuml",
"playfair",
@@ -83,6 +99,7 @@
"rect",
"rects",
"redmine",
"rehype",
"roledescription",
"sandboxed",
"setupgraphviewbox",
@@ -90,8 +107,12 @@
"sidharth",
"sidharthv",
"sphinxcontrib",
"startx",
"starty",
"statediagram",
"steph",
"stopx",
"stopy",
"stylis",
"substate",
"sveidqvist",
@@ -101,16 +122,22 @@
"textlength",
"treemap",
"ts-nocheck",
"tsdoc",
"tuleap",
"tylerlong",
"ugge",
"unist",
"unocss",
"valign",
"verdana",
"viewports",
"vinod",
"visio",
"vitepress",
"vueuse",
"xlink",
"yash"
"yash",
"zenuml"
],
"patterns": [
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
@@ -150,6 +177,7 @@
],
"ignorePaths": [
"packages/mermaid/src/docs/CHANGELOG.md",
"packages/mermaid/src/docs/.vitepress/redirect.ts"
"packages/mermaid/src/docs/.vitepress/redirect.ts",
"packages/mermaid/src/docs/.vitepress/contributor-names.json"
]
}

View File

@@ -60,7 +60,7 @@ export const renderGraph = (graphStr, options, api) => {
openURLAndVerifyRendering(url, options);
};
const openURLAndVerifyRendering = (url, options, validation = undefined) => {
export const openURLAndVerifyRendering = (url, options, validation = undefined) => {
const useAppli = Cypress.env('useAppli');
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');

View File

@@ -1,4 +1,4 @@
import { urlSnapshotTest } from '../../helpers/util.js';
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.js';
describe('CSS injections', () => {
it('should not allow CSS injections outside of the diagram', () => {
@@ -13,4 +13,11 @@ describe('CSS injections', () => {
flowchart: { htmlLabels: false },
});
});
it('should not allow manipulating styletags using arrowheads', () => {
openURLAndVerifyRendering('http://localhost:9000/xss23-css.html', {
logLevel: 1,
arrowMarkerAbsolute: false,
flowchart: { htmlLabels: true },
});
});
});

View File

@@ -548,4 +548,18 @@ class C13["With Città foreign language"]
`
);
});
it('should add classes namespaces', function () {
imgSnapshotTest(
`
classDiagram
namespace Namespace1 {
class C1
class C2
}
C1 --> C2
class C3
class C4
`
);
});
});

View File

@@ -286,7 +286,7 @@ describe('Class diagram', () => {
cy.get('svg');
});
it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
imgSnapshotTest(
`
classDiagram

View File

@@ -684,6 +684,20 @@ A --> B
{ titleTopMargin: 0 }
);
});
it('elk: should include classes on the edges', () => {
renderGraph(
`flowchart-elk TD
A --> B --> C --> D
`,
{}
);
cy.get('svg').should((svg) => {
const edges = svg.querySelectorAll('.edges > path');
edges.forEach((edge) => {
expect(edge).to.have.class('flowchart-link');
});
});
});
describe('Markdown strings flowchart-elk (#4220)', () => {
describe('html labels', () => {
it('With styling and classes', () => {

View File

@@ -52,6 +52,17 @@ root[A root with a long text that wraps to keep the node size in check]
);
});
it('a root with wrapping text and long words that exceed width', () => {
imgSnapshotTest(
`mindmap
root[A few smaller words but then averylongsetofcharacterswithoutwhitespacetoseparate that we expect to wrapontonextlinesandnotexceedwidthparameters]
`,
{},
undefined,
shouldHaveRoot
);
});
it('a root with an icon', () => {
imgSnapshotTest(
`mindmap

View File

@@ -0,0 +1,163 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
describe('Quadrant Chart', () => {
it('should render if only chart type is provided', () => {
imgSnapshotTest(
`
quadrantChart
`,
{}
);
cy.get('svg');
});
it('should render a complete quadrant chart', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
it('should render without points', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render y-axix on right side', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"yAxisPosition": "right"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render x-axix on bottom', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render x-axix on bottom and y-axis on right', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom", "yAxisPosition": "right"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should render without title', () => {
imgSnapshotTest(
`
quadrantChart
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should use all the config', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600, "titlePadding": 20, "titleFontSize": 10, "quadrantPadding": 20, "quadrantTextTopPadding": 40, "quadrantLabelFontSize": 20, "quadrantInternalBorderStrokeWidth": 3, "quadrantExternalBorderStrokeWidth": 5, "xAxisLabelPadding": 20, "xAxisLabelFontSize": 20, "yAxisLabelPadding": 20, "yAxisLabelFontSize": 20, "pointTextPadding": 20, "pointLabelFontSize": 20, "pointRadius": 10 }}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
it('should use all the theme variable', () => {
imgSnapshotTest(
`
%%{init: {"themeVariables": {"quadrant1Fill": "#b4dcff","quadrant2Fill": "#fef0ff", "quadrant3Fill": "#fffaf0", "quadrant4Fill": "#f0fff2", "quadrant1TextFill": "#ff0000", "quadrant2TextFill": "#2d00df", "quadrant3TextFill": "#00ffda", "quadrant4TextFill": "#e68300", "quadrantPointFill": "#0149ff", "quadrantPointTextFill": "#dc00ff", "quadrantXAxisTextFill": "#ffb500", "quadrantYAxisTextFill": "#fae604", "quadrantInternalBorderStrokeFill": "#3636f2", "quadrantExternalBorderStrokeFill": "#ff1010", "quadrantTitleFill": "#00ea19"} }}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
});

View File

@@ -88,6 +88,16 @@ context('Sequence diagram', () => {
{}
);
});
it('should handle empty lines', () => {
imgSnapshotTest(
`
sequenceDiagram
Alice->>John: Hello John<br/>
John-->>Alice: Great<br/><br/>day!
`,
{}
);
});
it('should handle line breaks and wrap annotations', () => {
imgSnapshotTest(
`
@@ -123,6 +133,29 @@ context('Sequence diagram', () => {
}
);
});
it('should render a sequence diagram with par_over', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice
participant Bob
participant John
par_over Section title
Alice ->> Bob: Message 1<br>Second line
Bob ->> John: Message 2
end
par_over Two line<br>section title
Note over Alice: Alice note
Note over Bob: Bob note<br>Second line
Note over John: John note
end
par_over Mixed section
Alice ->> Bob: Message 1
Note left of Bob: Alice/Bob Note
end
`
);
});
context('font settings', () => {
it('should render different note fonts when configured', () => {
imgSnapshotTest(

View File

@@ -0,0 +1,19 @@
import { imgSnapshotTest } from '../../helpers/util.js';
describe('Zen UML', () => {
it('Basic Zen UML diagram', () => {
imgSnapshotTest(
`
zenuml
A.method() {
if(x) {
B.method() {
selfCall() { return X }
}
}
}
`,
{}
);
});
});

View File

@@ -42,368 +42,86 @@
</style>
</head>
<body>
<pre class="mermaid2" style="width: 50%">
flowchart LR
subgraph one
direction LR
A[myClass1] --> B[default]
subgraph two
direction BT
C[myClass2] --> D[default]
end
end
<pre class="mermaid" style="width: 50%">
%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
b("`The dog in **the** hog.(1).. a a a a *very long text* about it
Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words.`") --apa--> c
</pre
>
<pre class="mermaid" style="width: 50%">
classDiagram-v2
classA -- classB : Inheritance
classA -- classC : link
classC -- classD : link
classB -- classD
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart LR
classDef aPID stroke:#4e4403,fill:#fdde29,color:#4e4403,rx:5px,ry:5px;
classDef crm stroke:#333333,fill:#DCDCDC,color:#333333,rx:5px,ry:5px;
classDef type stroke:#502604,fill:#FAB565,color:#502604,rx:20px,ry:20px;;
O0("Joe")
class O0 aPID;
O1("Person")
class O1 crm;
O0 -- has type -->O1["Person"]
O2("aat:300411314")
class O2 type;
click O2 function "Sorry the newline html tags are not being processed correctly<br/> So all of this appears on the <br/> same line."
O0 -- has type -->O2["Bug"]
click O0 function "Lots of great info about Joe<br>Lots of great info about Joe<br>burt<br>fred";
<pre class="mermaid" style="width: 50%">
sequenceDiagram
Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
loop Loopy
Bob->>Alice: Pasten
end
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart TD
subgraph test
direction TB
subgraph test2
direction LR
F --> D
end
subgraph test3
direction TB
G --> H
end
end
<pre class="mermaid" style="width: 50%">
%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
b("`The dog in **the** hog.(1)
NL`") --"`1o **bold**`"--> c[new strings svg labels]
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart TD
release-branch[Create Release Branch]:::relClass
develop-branch[Update Develop Branch]:::relClass
github-release-draft[GitHub Release Draft]:::relClass
trigger-pipeline[Trigger Jenkins pipeline]:::fixClass
github-release[GitHub Release]:::postClass
build-ready --> release-branch
build-ready --> develop-branch
release-branch --> jenkins-release-build
jenkins-release-build --> github-release-draft
jenkins-release-build --> install-release
install-release --> verify-release
jenkins-release-build --> announce
github-release-draft --> github-release
verify-release --> verify-check
verify-check -- Yes --> github-release
verify-check -- No --> release-fix
release-fix --> release-branch-pr
verify-check -- No --> delete-artifacts
release-branch-pr --> trigger-pipeline
delete-artifacts --> trigger-pipeline
trigger-pipeline --> jenkins-release-build
<pre class="mermaid" style="width: 50%">
%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
b("`The dog in **the** hog.(1)
NL`") --"`1o **bold**`"--> c[new strings html labels]
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart LR
a["<strong>Haiya</strong>"]===>b
<pre class="mermaid" style="width: 50%">
%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
b("The dog in the hog.(1)\nNL") --"1o bold"--> c[old strings svg labels]
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart TD
A --> B
A --> C
B --> C
</pre>
<pre class="mermaid2" style="width: 50%">
flowchart TD
A([stadium shape test])
A -->|Get money| B([Go shopping])
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
C -->|One| D([Laptop])
C -->|Two| E([iPhone])
C -->|Three| F([Car<br/>wroom wroom])
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
class C someclass;
</pre>
<pre class="mermaid2" style="width: 50%">
sequenceDiagram
title: My Sequence Diagram Title
accTitle: My Acc Sequence Diagram
accDescr: My Sequence Diagram Description
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
Alice-)John: See you later!
</pre>
<pre class="mermaid2" style="width: 50%">
graph TD
A -->|000| B
B -->|111| C
linkStyle 1 stroke:#ff3,stroke-width:4px,color:red;
</pre>
<pre class="mermaid2" style="width: 100%">
journey
accTitle: My User Journey Diagram
accDescr: My User Journey Diagram Description
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
</pre>
<pre class="mermaid2" style="width: 100%">
info
</pre>
<pre class="mermaid2" style="width: 100%">
requirementDiagram
accTitle: My req Diagram
accDescr: My req Diagram Description
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
functionalRequirement test_req2 {
id: 1.1
text: the second test text.
risk: low
verifymethod: inspection
}
performanceRequirement test_req3 {
id: 1.2
text: the third test text.
risk: medium
verifymethod: demonstration
}
element test_entity {
type: simulation
}
element test_entity2 {
type: word doc
docRef: reqs/test_entity
}
test_entity - satisfies -> test_req2
test_req - traces -> test_req2
test_req - contains -> test_req3
test_req <- copies - test_entity2
</pre>
<pre class="mermaid2" style="width: 100%">
gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
excludes weekends
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d
Functionality added :milestone, 2014-01-25, 0d
section Documentation
Describe gantt syntax :active, a1, after des1, 3d
Add gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h
section Last section
Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page :20h
Add another diagram to demo page :48h
</pre>
<pre class="mermaid2" style="width: 100%">
stateDiagram
state Active {
Idle
}
Inactive --> Idle: ACT
Active --> Active: LOG
</pre>
<pre class="mermaid2" style="width: 100%">
flowchart TB
accTitle: My flowchart
accDescr: My flowchart Description
subgraph One
a1-->a2-->a3
end
</pre>
<pre class="mermaid2" style="width: 100%">
sequenceDiagram
A ->> B: 1
rect rgb(204, 0, 102)
break yes
rect rgb(0, 204, 204)
C ->> C: 0
end
end
end
B ->> A: Return
</pre>
<pre class="mermaid2" style="width: 100%">
classDiagram
accTitle: My class diagram
accDescr: My class diagram Description
Class01 <|-- AveryLongClass : Cool
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
class Class10 {
int id
size()
}
</pre>
<pre class="mermaid2" style="width: 100%">
%%{init: {'config': {'wrap': true }}}%%
sequenceDiagram
participant A as Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
A->>Bob: Hola
Bob-->A: Pasten !
</pre>
<pre class="mermaid2" style="width: 100%">
gitGraph
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
branch featureA
commit id:"FIX"
commit id: "FIX-2"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
cherry-pick id:"FIX"
checkout develop
commit id:"C"
merge featureA
</pre>
<pre class="mermaid2" style="width: 100%">
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
</pre>
<pre class="mermaid2" style="width: 100%">
classDiagram
Animal "1" <|-- Duck
Animal <|-- Fish
Animal <--o Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
</pre>
<pre class="mermaid2" style="width: 100%">
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber
string make
string model
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string firstName
string lastName
int age
}
</pre>
<!-- <script src="./mermaid.js"></script> -->
<script src="./mermaid.js"></script>
<script>
<script type="module">
// import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs';
import mermaid from './mermaid.esm.mjs';
// await mermaid.registerExternalDiagrams([example]);
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
maxTextSize: 900000,
// theme: 'forest',
startOnLoad: true,
securityLevel: 'loose',
logLevel: 0,
fontFamily: 'courier',
flowchart: {
// curve: 'curveLinear',
useMaxWidth: true,
htmlLabels: false,
fontFamily: 'courier',
// defaultRenderer: 'elk',
useMaxWidth: false,
// htmlLabels: false,
htmlLabels: true,
},
lazyLoadedDiagrams: ['./mermaid-mindmap-detector.js'],
// htmlLabels: false,
gantt: {
useMaxWidth: false,
},
sequence: {
wrap: true,
},
useMaxWidth: false,
});
function callback() {
alert('It worked');
}
function clickByFlow(elemName) {
const div = document.createElement('div');
div.className = 'created-by-click';
div.style = 'padding: 20px; background: green; color: white;';
div.innerText = 'Clicked By Flow';
document.getElementsByTagName('body')[0].appendChild(div);
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
</script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import mermaid2 from './mermaid.esm.mjs';
import externalExample from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs';
import zenUml from '../../packages/mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
function b64ToUtf8(str) {
return decodeURIComponent(escape(window.atob(str)));
@@ -44,7 +45,7 @@ const contentLoaded = async function () {
document.getElementsByTagName('body')[0].appendChild(div);
}
await mermaid2.registerExternalDiagrams([externalExample]);
await mermaid2.registerExternalDiagrams([externalExample, zenUml]);
mermaid2.initialize(graphObj.mermaid);
await mermaid2.run();
}

View File

@@ -0,0 +1,85 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
</style>
</head>
<body>
<div>Security check</div>
<div class="flex">
<div id="diagram" class="mermaid"></div>
<div id="graph-div"></div>
<div id="res" class=""></div>
</div>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'base',
startOnLoad: false,
flowcharts: { htmlLabels: true },
});
function callback() {
alert('It worked');
}
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
let diagram = `graph TD
A[["a marker-end=#quot;url(<s title='#<style>*{background:red}</style>'>b"]]
`;
const el = document.querySelector('#graph-div');
console.log(diagram);
const { svg } = await mermaid.render('graph-div', diagram);
document.querySelector('#res').innerHTML = svg;
window.rendered = true;
</script>
</body>
</html>

View File

@@ -54,6 +54,9 @@
<li>
<h2><a href="./pie.html">Pie</a></h2>
</li>
<li>
<h2><a href="./quadrantchart.html">Quadrant charts</a></h2>
</li>
<li>
<h2><a href="./requirements.html">Requirements</a></h2>
</li>
@@ -63,6 +66,9 @@
<li>
<h2><a href="./state.html">State</a></h2>
</li>
<li>
<h2><a href="./zenuml.html">ZenUML</a></h2>
</li>
</ul>
</body>
</html>

View File

@@ -38,7 +38,7 @@
</pre>
<script type="module">
import mermaid from '../packages/mermaid/src/mermaid';
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'forest',
// themeCSS: '.node rect { fill: red; }',

55
demos/quadrantchart.html Normal file
View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h1>Quadrant chart demos</h1>
<pre class="mermaid">
%%{init: {"quadrantChart": {"quadrantPadding": 10}, "theme": "forest", "themeVariables": {"quadrant1TextFill": "blue"}} }%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> important
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
</pre>
<pre class="mermaid">
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" -->
y-axis Ability to Execute
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75]
Salesforce: [0.55, 0.60]
IBM: [0.51, 0.40]
Incorta: [0.20, 0.30]
</pre>
<hr />
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'default',
logLevel: 3,
securityLevel: 'loose',
});
</script>
</body>
</html>

View File

@@ -144,6 +144,26 @@
>
<hr />
<pre class="mermaid">
sequenceDiagram
participant Alice
participant Bob
participant John
par_over Section title
Alice ->> Bob: Message 1<br>Second line
Bob ->> John: Message 2
end
par_over Two line<br>section title
Note over Alice: Alice note
Note over Bob: Bob note<br>Second line
Note over John: John note
end
par_over Mixed section
Alice ->> Bob: Message 1
Note left of Bob: Alice/Bob Note
end
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({

53
demos/zenuml.html Normal file
View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Zenuml Test Page</title>
</head>
<body>
<h1>Zenuml demos</h1>
<pre class="mermaid">
zenuml
title Sync Messages (Design Pattern: Adapter)
@Starter(Client)
Adapter.interfaceMethod() {
translateParameter(parameter)
result = Implementation.implementationMethod()
translateResult()
return translatedResult
}
</pre>
<pre class="mermaid">
zenuml
title Async Messages (SPA Authentication)
// ```
// GET https://${account.namespace}/authorize/?
// response_type=token
// &client_id=${account.clientId}
// &redirect_url=YOUR_CALLBACK_URL
// &state=VALUE_THAT_SURVIVES_REDIRECTS
// &scope=openid
// ```
Browser->Auth0: 1. initiate the authentication
Auth0->"Identity Provider": 2. OAuth2 / SAML, etc
"Identity Provider"->"Identity Provider": 3. user gets authenticated
Auth0->Browser: 4. redirect to ${YOUR_CALLBACK_URL}/#id_token=e68...
Browser->Auth0: 5. validate id_token and get user profile
Browser->"Your API": 6. call API sending JWT in Authorization header
"Your API"->"Your API": 7. validate token
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import zenuml from './mermaid-zenuml.esm.mjs';
await mermaid.registerExternalDiagrams([zenuml]);
mermaid.initialize({
logLevel: 3,
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
#### Defined in
[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115)
[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293)
---

View File

@@ -96,7 +96,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
[mermaidAPI.ts:670](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L670)
## Functions
@@ -127,7 +127,7 @@ Return the last node appended
#### Defined in
[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312)
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
---
@@ -153,7 +153,7 @@ the cleaned up svgCode
#### Defined in
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
[mermaidAPI.ts:257](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L257)
---
@@ -179,7 +179,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192)
[mermaidAPI.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L186)
---
@@ -202,7 +202,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
[mermaidAPI.ts:234](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L234)
---
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176)
[mermaidAPI.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L170)
---
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in
[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
[mermaidAPI.ts:288](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L288)
---
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
#### Defined in
[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362)
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)

View File

@@ -112,10 +112,10 @@ A `securityLevel` configuration has to first be cleared. `securityLevel` sets th
Values:
- **strict**: (**default**) tags in text are encoded, click functionality is disabled
- **loose**: tags in text are allowed, click functionality is enabled
- **antiscript**: html tags in text are allowed, (only script element is removed), click functionality is enabled
- **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This prevent any JavaScript running in the context. This may hinder interactive functionality of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc.
- **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
- **antiscript**: HTML tags in text are allowed (only script elements are removed) and click functionality is enabled.
- **loose**: HTML tags in text are allowed and click functionality is enabled.
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
> **Note**
> This changes the default behaviour of mermaid so that after upgrade to 8.2, unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled.
@@ -348,10 +348,10 @@ mermaid.parseError = function (err, hash) {
displayErrorInGui(err);
};
const textFieldUpdated = function () {
const textFieldUpdated = async function () {
const textStr = getTextFromFormField('code');
if (mermaid.parse(textStr)) {
if (await mermaid.parse(textStr)) {
reRender(textStr);
}
};

View File

@@ -16,6 +16,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [Using code blocks](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) (**Native support**)
- [GitHub action: Compile mermaid to image](https://github.com/neenjaw/compile-mermaid-markdown-action)
- [svg-generator](https://github.com/SimonKenyonShepard/mermaidjs-github-svg-generator)
- [GitHub Writer](https://github.com/ckeditor/github-writer)
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) (**Native support**)
- [Gitea](https://gitea.io) (**Native support**)
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**)
@@ -58,6 +59,8 @@ They also serve as proof of concept, for the variety of things that can be built
- [hexo-filter-mermaid-diagrams](https://github.com/webappdevelp/hexo-filter-mermaid-diagrams)
- [hexo-tag-mermaid](https://github.com/JameChou/hexo-tag-mermaid)
- [hexo-mermaid-diagrams](https://github.com/mslxl/hexo-mermaid-diagrams)
- [Nextra](https://nextra.site/)
- [Mermaid](https://nextra.site/docs/guide/mermaid)
## CMS
@@ -142,13 +145,19 @@ They also serve as proof of concept, for the variety of things that can be built
- [Named block =Diagram](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
- [GNU Nano](https://www.nano-editor.org/)
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
- [CKEditor](https://github.com/ckeditor/ckeditor5)
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
## Document Generation
- [Sphinx](https://www.sphinx-doc.org/en/master/)
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
- [remark.js](https://remark.js.org/)
- [remark-mermaid](https://github.com/temando/remark-mermaid)
- [remark](https://remark.js.org/)
- [remark-mermaidjs](https://github.com/remcohaszing/remark-mermaidjs)
- [rehype](https://github.com/rehypejs/rehype)
- [rehype-mermaidjs](https://github.com/remcohaszing/rehype-mermaidjs)
- [Gatsby](https://www.gatsbyjs.com/)
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
- [jSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
- [MkDocs](https://www.mkdocs.org)
@@ -189,6 +198,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
- [Reveal CK](https://github.com/jedcn/reveal-ck)
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
- [ExDoc](https://github.com/elixir-lang/ex_doc)
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)

View File

@@ -235,6 +235,42 @@ journey
Sit down: 5: Me
```
### [Quadrant Chart](../syntax/quadrantChart.md)
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Installation
**In depth guides and examples can be found at [Getting Started](./n00b-gettingStarted.md) and [Usage](../config/usage.md).**

View File

@@ -6,8 +6,8 @@
# Announcements
## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here)
## [Bad documentation is bad for developers](https://www.mermaidchart.com/blog/posts/bad-documentation-is-bad-for-developers)
3 April 2023 · 3 mins
26 April 2023 · 11 mins
Markdown Strings reduce the hassle # Starting from v10.
Documentation tends to be bad because companies and projects dont fully realize the costs of bad documentation.

View File

@@ -6,6 +6,18 @@
# Blog
## [Bad documentation is bad for developers](https://www.mermaidchart.com/blog/posts/bad-documentation-is-bad-for-developers)
26 April 2023 · 11 mins
Documentation tends to be bad because companies and projects dont fully realize the costs of bad documentation.
## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here/)
3 April 2023 · 3 mins
Markdown Strings reduce the hassle # Starting from v10.
## [Mermaid Chart officially launched with sharable diagram links and presentation mode](https://www.mermaidchart.com/blog/posts/mermaid-chart-officially-launched-with-sharable-diagram-links-and-presentation-mode/)
27 March 2023 · 2 mins

View File

@@ -128,7 +128,7 @@ classDiagram
Vehicle <|-- Car
```
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
### Class labels
@@ -283,12 +283,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
- `#` Protected
- `~` Package/Internal
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
>
> - `*` Abstract e.g.: `someAbstractMethod()*`
> - `$` Static e.g.: `someStaticMethod()$`
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
>
> - `$` Static e.g.: `String someField$`
@@ -421,6 +421,34 @@ And `Link` can be one of:
| -- | Solid |
| .. | Dashed |
## Define Namespace
A namespace groups classes.
Code:
```mermaid-example
classDiagram
namespace BaseShapes {
class Triangle
class Rectangle {
double width
double height
}
}
```
```mermaid
classDiagram
namespace BaseShapes {
class Triangle
class Rectangle {
double width
double height
}
}
```
## Cardinality / Multiplicity on relations
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
@@ -604,10 +632,26 @@ You would define these actions on a separate line after all classes have been de
## Notes
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
### Examples
```mermaid-example
classDiagram
note "This is a general note"
note for MyClass "This is a note for a class"
class MyClass{
}
```
```mermaid
classDiagram
note "This is a general note"
note for MyClass "This is a note for a class"
class MyClass{
}
```
_URL Link:_
```mermaid-example

View File

@@ -54,7 +54,45 @@ flowchart LR
id1[This is the text in the box]
```
## Graph
#### Unicode text
Use `"` to enclose the unicode text.
```mermaid-example
flowchart LR
id["This ❤ Unicode"]
```
```mermaid
flowchart LR
id["This ❤ Unicode"]
```
#### Markdown formatting
Use double quotes and backticks "\` text \`" to enclose the markdown text.
```mermaid-example
%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
markdown["`This **is** _Markdown_`"]
newLines["`Line1
Line 2
Line 3`"]
markdown --> newLines
```
```mermaid
%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
markdown["`This **is** _Markdown_`"]
newLines["`Line1
Line 2
Line 3`"]
markdown --> newLines
```
### Direction
This statement declares the direction of the Flowchart.
@@ -82,15 +120,13 @@ flowchart LR
Start --> Stop
```
## Flowchart Orientation
Possible FlowChart orientations are:
- TB - top to bottom
- TD - top-down/ same as top to bottom
- BT - bottom to top
- RL - right to left
- LR - left to right
- TB - Top to bottom
- TD - Top-down/ same as top to bottom
- BT - Bottom to top
- RL - Right to left
- LR - Left to right
## Node shapes
@@ -633,7 +669,7 @@ flowchart TB
end
```
## flowcharts
### flowcharts
With the graphtype flowchart it is also possible to set edges to and from subgraphs as in the flowchart below.
@@ -671,7 +707,7 @@ flowchart TB
two --> c2
```
## Direction in subgraphs
### Direction in subgraphs
With the graphtype flowcharts you can use the direction statement to set the direction which the subgraph will render like in this example.
@@ -1002,7 +1038,7 @@ flowchart TD
B-->E(A fa:fa-camera-retro perhaps?)
```
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
## Graph declarations with spaces between vertices and link and without semicolon

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

View File

@@ -182,7 +182,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
## Icons
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
```mermaid-example
mindmap

View File

@@ -0,0 +1,168 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/quadrantChart.md](../../packages/mermaid/src/docs/syntax/quadrantChart.md).
# Quadrant Chart
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
## Example
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Syntax
> **Note**
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
> **Note**
> For points x and y value min value is 0 and max value is 1.
### Title
The title is a short description of the chart and it will always render on top of the chart.
#### Example
quadrantChart
title This is a sample example
### x-axis
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
#### Example
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
2. `x-axis <text>` only the left axis text will be rendered.
### y-axis
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
#### Example
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
2. `y-axis <text>` only the bottom axis text will be rendered.
### Quadrants text
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
#### Example
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
### Points
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
#### Example
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
## Chart Configurations
| Parameter | Description | Default value |
| --------------------------------- | ------------------------------------------------------------------------------------------------- | :-----------: |
| chartWidth | Width of the chart | 500 |
| chartHeight | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| quadrantPadding | Padding outside all the quadrants | 5 |
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
| quadrantLabelFontSize | Quadrant text font size | 16 |
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
| xAxisLabelFontSize | X-axis texts font size | 16 |
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will alway be rendered in bottom | 'top' |
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
| yAxisLabelFontSize | Y-axis texts font size | 16 |
| yAxisPosition | Position of y-axis (left , right) | 'left' |
| pointTextPadding | Padding between point and the below text | 5 |
| pointLabelFontSize | Point text font size | 12 |
| pointRadius | Radius of the point to be drawn | 5 |
## Chart Theme Variables
| Parameter | Description |
| -------------------------------- | --------------------------------------- |
| quadrant1Fill | Fill color of the top right quadrant |
| quadrant2Fill | Fill color of the top left quadrant |
| quadrant3Fill | Fill color of the bottom left quadrant |
| quadrant4Fill | Fill color of the bottom right quadrant |
| quadrant1TextFill | Text color of the top right quadrant |
| quadrant2TextFill | Text color of the top left quadrant |
| quadrant3TextFill | Text color of the bottom left quadrant |
| quadrant4TextFill | Text color of the bottom right quadrant |
| quadrantPointFill | Points fill color |
| quadrantPointTextFill | Points text color |
| quadrantXAxisTextFill | X-axis text color |
| quadrantYAxisTextFill | Y-axis text color |
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
| quadrantTitleFill | Title color |
## Example on config and theme
```mermaid-example
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> "Important ❤"
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
```
```mermaid
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> "Important ❤"
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
```

472
docs/syntax/zenuml.md Normal file
View File

@@ -0,0 +1,472 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/zenuml.md](../../packages/mermaid/src/docs/syntax/zenuml.md).
# ZenUML
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
syntax than the original Sequence Diagram in mermaid.
```mermaid-example
zenuml
title Demo
Alice->John: Hello John, how are you?
John->Alice: Great!
Alice->John: See you later!
```
```mermaid
zenuml
title Demo
Alice->John: Hello John, how are you?
John->Alice: Great!
Alice->John: See you later!
```
## Syntax
### Participants
The participants can be defined implicitly as in the first example on this page. The participants or actors are
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
different order than how they appear in the first message. It is possible to specify the actor's order of
appearance by doing the following:
```mermaid-example
zenuml
title Declare participant (optional)
Bob
Alice
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
```mermaid
zenuml
title Declare participant (optional)
Bob
Alice
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
### Annotators
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
```mermaid-example
zenuml
title Annotators
@Actor Alice
@Database Bob
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
```mermaid
zenuml
title Annotators
@Actor Alice
@Database Bob
Alice->Bob: Hi Bob
Bob->Alice: Hi Alice
```
Here are the available annotators:
![img.png](img/zenuml-participant-annotators.png)
### Aliases
The participants can have a convenient identifier and a descriptive label.
```mermaid-example
zenuml
title Aliases
A as Alice
J as John
A->J: Hello John, how are you?
J->A: Great!
```
```mermaid
zenuml
title Aliases
A as Alice
J as John
A->J: Hello John, how are you?
J->A: Great!
```
## Messages
Messages can be one of:
1. Sync message
2. Async message
3. Creation message
4. Reply message
### Sync message
You can think of a sync (blocking) method in a programming language.
```mermaid-example
zenuml
title Sync message
A.SyncMessage
A.SyncMessage(with, parameters) {
B.nestedSyncMessage()
}
```
```mermaid
zenuml
title Sync message
A.SyncMessage
A.SyncMessage(with, parameters) {
B.nestedSyncMessage()
}
```
### Async message
You can think of an async (non-blocking) method in a programming language.
Fire an event and forget about it.
```mermaid-example
zenuml
title Async message
Alice->Bob: How are you?
```
```mermaid
zenuml
title Async message
Alice->Bob: How are you?
```
### Creation message
We use `new` keyword to create an object.
```mermaid-example
zenuml
new A1
new A2(with, parameters)
```
```mermaid
zenuml
new A1
new A2(with, parameters)
```
### Reply message
There are three ways to express a reply message:
```mermaid-example
zenuml
// 1. assign a variable from a sync message.
a = A.SyncMessage()
// 1.1. optionally give the variable a type
SomeType a = A.SyncMessage()
// 2. use return keyword
A.SyncMessage() {
return result
}
// 3. use @return or @reply annotator on an async message
@return
A->B: result
```
```mermaid
zenuml
// 1. assign a variable from a sync message.
a = A.SyncMessage()
// 1.1. optionally give the variable a type
SomeType a = A.SyncMessage()
// 2. use return keyword
A.SyncMessage() {
return result
}
// 3. use @return or @reply annotator on an async message
@return
A->B: result
```
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
```mermaid-example
zenuml
title Reply message
Client->A.method() {
B.method() {
if(condition) {
return x1
// return early
@return
A->Client: x11
}
}
return x2
}
```
```mermaid
zenuml
title Reply message
Client->A.method() {
B.method() {
if(condition) {
return x1
// return early
@return
A->Client: x11
}
}
return x2
}
```
## Nesting
Sync messages and Creation messages are naturally nestable with `{}`.
```mermaid-example
zenuml
A.method() {
B.nested_sync_method()
B->C: nested async message
}
```
```mermaid
zenuml
A.method() {
B.nested_sync_method()
B->C: nested async message
}
```
## Comments
It is possible to add comments to a sequence diagram with `// comment` syntax.
Comments will be rendered above the messages or fragments. Comments on other places
are ignored. Markdown is supported.
See the example below:
```mermaid-example
zenuml
// a comment on a participant will not be rendered
BookService
// a comment on a message.
// **Markdown** is supported.
BookService.getBook()
```
```mermaid
zenuml
// a comment on a participant will not be rendered
BookService
// a comment on a message.
// **Markdown** is supported.
BookService.getBook()
```
## Loops
It is possible to express loops in a ZenUML diagram. This is done by any of the
following notations:
1. while
2. for
3. forEach, foreach
4. loop
```zenuml
while(condition) {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->John: Hello John, how are you?
while(true) {
John->Alice: Great!
}
```
```mermaid
zenuml
Alice->John: Hello John, how are you?
while(true) {
John->Alice: Great!
}
```
## Alt
It is possible to express alternative paths in a sequence diagram. This is done by the notation
```zenuml
if(condition1) {
...statements...
} else if(condition2) {
...statements...
} else {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->Bob: Hello Bob, how are you?
if(is_sick) {
Bob->Alice: Not so good :(
} else {
Bob->Alice: Feeling fresh like a daisy
}
```
```mermaid
zenuml
Alice->Bob: Hello Bob, how are you?
if(is_sick) {
Bob->Alice: Not so good :(
} else {
Bob->Alice: Feeling fresh like a daisy
}
```
## Opt
It is possible to render an `opt` fragment. This is done by the notation
```zenuml
opt {
...statements...
}
```
See the example below:
```mermaid-example
zenuml
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Not so good :(
opt {
Bob->Alice: Thanks for asking
}
```
```mermaid
zenuml
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Not so good :(
opt {
Bob->Alice: Thanks for asking
}
```
## Parallel
It is possible to show actions that are happening in parallel.
This is done by the notation
```zenuml
par {
statement1
statement2
statement3
}
```
See the example below:
```mermaid-example
zenuml
par {
Alice->Bob: Hello guys!
Alice->John: Hello guys!
}
```
```mermaid
zenuml
par {
Alice->Bob: Hello guys!
Alice->John: Hello guys!
}
```
## Try/Catch/Finally (Break)
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
This is done by the notation
try {
...statements...
} catch {
...statements...
} finally {
...statements...
}
See the example below:
```mermaid-example
zenuml
try {
Consumer->API: Book something
API->BookingService: Start booking process
} catch {
API->Consumer: show failure
} finally {
API->BookingService: rollback status
}
```
```mermaid
zenuml
try {
Consumer->API: Book something
API->BookingService: Start booking process
} catch {
API->Consumer: show failure
} finally {
API->BookingService: rollback status
}
```
## Integrating with your library/website.
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
You can use this method to add mermaid including the zenuml diagram to a web page:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
await mermaid.registerExternalDiagrams([zenuml]);
</script>
```

View File

@@ -1,10 +1,10 @@
{
"name": "mermaid-monorepo",
"private": true,
"version": "10.1.0",
"version": "10.2.3",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@7.30.1",
"packageManager": "pnpm@8.5.1",
"keywords": [
"diagram",
"markdown",
@@ -18,7 +18,7 @@
"build:vite": "ts-node-esm --transpileOnly .vite/build.ts",
"build:mermaid": "pnpm build:vite --mermaid",
"build:viz": "pnpm build:mermaid --visualize",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
@@ -26,6 +26,7 @@
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
"contributors": "ts-node-esm scripts/updateContributors.ts",
"cypress": "cypress run",
"cypress:open": "cypress open",
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
@@ -54,65 +55,65 @@
]
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.27.6",
"@commitlint/cli": "^17.2.0",
"@commitlint/config-conventional": "^17.2.0",
"@cspell/eslint-plugin": "^6.14.2",
"@rollup/plugin-typescript": "^11.0.0",
"@applitools/eyes-cypress": "^3.32.0",
"@commitlint/cli": "^17.6.1",
"@commitlint/config-conventional": "^17.6.1",
"@cspell/eslint-plugin": "^6.31.1",
"@rollup/plugin-typescript": "^11.1.0",
"@types/cors": "^2.8.13",
"@types/eslint": "^8.4.10",
"@types/eslint": "^8.37.0",
"@types/express": "^4.17.17",
"@types/js-yaml": "^4.0.5",
"@types/jsdom": "^21.0.0",
"@types/lodash": "^4.14.188",
"@types/mdast": "^3.0.10",
"@types/node": "^18.11.9",
"@types/prettier": "^2.7.1",
"@types/jsdom": "^21.1.1",
"@types/lodash": "^4.14.194",
"@types/mdast": "^3.0.11",
"@types/node": "^18.16.0",
"@types/prettier": "^2.7.2",
"@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"@vitest/coverage-c8": "^0.29.0",
"@vitest/spy": "^0.29.0",
"@vitest/ui": "^0.29.0",
"concurrently": "^7.5.0",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitest/coverage-c8": "^0.31.0",
"@vitest/spy": "^0.31.0",
"@vitest/ui": "^0.31.0",
"concurrently": "^8.0.1",
"cors": "^2.8.5",
"coveralls": "^3.1.1",
"cypress": "^12.0.0",
"cypress": "^12.10.0",
"cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.17.0",
"eslint": "^8.32.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-cypress": "^2.12.1",
"esbuild": "^0.17.18",
"eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-cypress": "^2.13.2",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-jest": "^27.1.5",
"eslint-plugin-jsdoc": "^39.6.2",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-jsdoc": "^43.0.7",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-markdown": "^3.0.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-tsdoc": "^0.2.17",
"eslint-plugin-unicorn": "^45.0.0",
"eslint-plugin-unicorn": "^46.0.0",
"express": "^4.18.2",
"globby": "^13.1.2",
"husky": "^8.0.2",
"jest": "^29.3.1",
"globby": "^13.1.4",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jison": "^0.4.18",
"js-yaml": "^4.1.0",
"jsdom": "^21.0.0",
"lint-staged": "^13.0.3",
"jsdom": "^21.1.1",
"lint-staged": "^13.2.1",
"path-browserify": "^1.0.1",
"pnpm": "^7.15.0",
"prettier": "^2.7.1",
"pnpm": "^8.3.1",
"prettier": "^2.8.8",
"prettier-plugin-jsdoc": "^0.4.2",
"rimraf": "^4.0.0",
"rollup-plugin-visualizer": "^5.8.3",
"start-server-and-test": "^1.15.4",
"rimraf": "^5.0.0",
"rollup-plugin-visualizer": "^5.9.0",
"start-server-and-test": "^2.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^4.1.1",
"vitest": "^0.29.0"
"typescript": "^5.0.4",
"vite": "^4.3.1",
"vitest": "^0.31.0"
},
"volta": {
"node": "18.15.0"
"node": "18.16.0"
}
}

View File

@@ -48,8 +48,8 @@
},
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"concurrently": "^7.5.0",
"rimraf": "^4.0.0",
"concurrently": "^8.0.0",
"rimraf": "^5.0.0",
"mermaid": "workspace:*"
},
"resolutions": {

View File

@@ -0,0 +1 @@
../mermaid/src/docs/syntax/zenuml.md

View File

@@ -0,0 +1,47 @@
{
"name": "@mermaid-js/mermaid-zenuml",
"version": "0.1.0",
"description": "MermaidJS plugin for ZenUML integration",
"module": "dist/mermaid-zenuml.core.mjs",
"types": "dist/detector.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-zenuml.core.mjs",
"types": "./dist/detector.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"zenuml",
"mermaid"
],
"scripts": {
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid",
"directory": "packages/mermaid-zenuml"
},
"contributors": [
"Peng Xiao (https://github.com/MrCoder)",
"Sidharth Vinod (https://sidharth.dev)",
"Dong Cai (https://github.com/dontry)"
],
"license": "MIT",
"dependencies": {
"@zenuml/core": "^3.0.0"
},
"devDependencies": {
"mermaid": "workspace:^"
},
"peerDependencies": {
"mermaid": "workspace:>=10.0.0"
},
"files": [
"dist"
]
}

View File

@@ -0,0 +1,21 @@
import type { ExternalDiagramDefinition } from 'mermaid';
const id = 'zenuml';
const regexp = /^\s*zenuml/;
const detector = (txt: string) => {
return txt.match(regexp) !== null;
};
const loader = async () => {
const { diagram } = await import('./zenuml-definition.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -0,0 +1,58 @@
import type { MermaidConfig } from 'mermaid';
const warning = (s: string) => {
// Todo remove debug code
// eslint-disable-next-line no-console
console.error('Log function was called before initialization', s);
};
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
export const LEVELS: Record<LogLevel, number> = {
trace: 0,
debug: 1,
info: 2,
warn: 3,
error: 4,
fatal: 5,
};
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
trace: warning,
debug: warning,
info: warning,
warn: warning,
error: warning,
fatal: warning,
};
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
export let getConfig: () => MermaidConfig;
export let sanitizeText: (str: string) => string;
// eslint-disable @typescript-eslint/no-explicit-any
export let setupGraphViewbox: (
graph: any,
svgElem: any,
padding: any,
useMaxWidth: boolean
) => void;
export const injectUtils = (
_log: Record<keyof typeof LEVELS, typeof console.log>,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => {
_log.info('Mermaid utils injected');
log.trace = _log.trace;
log.debug = _log.debug;
log.info = _log.info;
log.warn = _log.warn;
log.error = _log.error;
log.fatal = _log.fatal;
setLogLevel = _setLogLevel;
getConfig = _getConfig;
sanitizeText = _sanitizeText;
setupGraphViewbox = _setupGraphViewbox;
};

View File

@@ -0,0 +1,12 @@
/**
* ZenUML manage parsing internally. It uses Antlr4 to parse the DSL.
* The parser is defined in https://github.com/ZenUml/vue-sequence/blob/main/src/parser/index.js
*
* This is a dummy parser that satisfies the mermaid API logic.
*/
export default {
parser: { yy: {} },
parse: () => {
// no op
},
};

View File

@@ -0,0 +1,17 @@
import { injectUtils } from './mermaidUtils.js';
import parser from './parser.js';
import renderer from './zenumlRenderer.js';
export const diagram = {
db: {
clear: () => {
// no-op
},
},
renderer,
parser,
styles: () => {
// no-op
},
injectUtils,
};

View File

@@ -0,0 +1,68 @@
import { getConfig, log } from './mermaidUtils.js';
import ZenUml from '@zenuml/core';
const regexp = /^\s*zenuml/;
// Create a Zen UML container outside the svg first for rendering, otherwise the Zen UML diagram cannot be rendered properly
function createTemporaryZenumlContainer(id: string) {
const container = document.createElement('div');
container.id = `container-${id}`;
container.style.display = 'flex';
container.innerHTML = `<div id="zenUMLApp-${id}"></div>`;
const app = container.querySelector(`#zenUMLApp-${id}`) as HTMLElement;
return { container, app };
}
// Create a foreignObject to wrap the Zen UML container in the svg
function createForeignObject(id: string) {
const foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
foreignObject.setAttribute('x', '0');
foreignObject.setAttribute('y', '0');
foreignObject.setAttribute('width', '100%');
foreignObject.setAttribute('height', '100%');
const { container, app } = createTemporaryZenumlContainer(id);
foreignObject.appendChild(container);
return { foreignObject, container, app };
}
/**
* Draws a Zen UML in the tag with id: id based on the graph definition in text.
*
* @param text - The text of the diagram
* @param id - The id of the diagram which will be used as a DOM element id¨
*/
export const draw = async function (text: string, id: string) {
log.info('draw with Zen UML renderer', ZenUml);
text = text.replace(regexp, '');
const { securityLevel } = getConfig();
// Handle root and Document for when rendering in sandbox mode
let sandboxElement: HTMLIFrameElement | null = null;
if (securityLevel === 'sandbox') {
sandboxElement = document.getElementById('i' + id) as HTMLIFrameElement;
}
const root = securityLevel === 'sandbox' ? sandboxElement?.contentWindow?.document : document;
const svgContainer = root?.querySelector(`svg#${id}`);
if (!root || !svgContainer) {
log.error('Cannot find root or svgContainer');
return;
}
const { foreignObject, container, app } = createForeignObject(id);
svgContainer.appendChild(foreignObject);
// @ts-expect-error @zenuml/core@3.0.0 exports the wrong type for ZenUml
const zenuml = new ZenUml(app);
// default is a theme name. More themes to be added and will be configurable in the future
await zenuml.render(text, 'theme-mermaid');
const { width, height } = window.getComputedStyle(container);
log.debug('zenuml diagram size', width, height);
svgContainer.setAttribute('style', `width: ${width}; height: ${height};`);
};
export default {
draw,
};

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src/**/*.ts"],
"typeRoots": ["./src/types"]
}

View File

@@ -1,3 +1,6 @@
src/vitepress
src/docs/config/setup
README.*
README.*
src/docs/public/user-avatars/
src/docs/.vitepress/cache
src/docs/.vitepress/components.d.ts

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "10.1.0",
"version": "10.2.3",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -28,8 +28,8 @@
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
"docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"vitepress dev src/vitepress\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
"release": "pnpm build",
@@ -52,20 +52,20 @@
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"@khanacademy/simple-markdown": "^0.8.6",
"@braintree/sanitize-url": "^6.0.2",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"dagre-d3-es": "7.0.10",
"dayjs": "^1.11.7",
"dompurify": "2.4.5",
"dompurify": "3.0.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"mdast-util-from-markdown": "^1.3.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"stylis": "^4.1.3",
"ts-dedent": "^2.2.0",
"uuid": "^9.0.0",
"web-worker": "^1.2.0"
@@ -73,43 +73,46 @@
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.4.0",
"@types/jsdom": "^21.0.0",
"@types/dompurify": "^3.0.2",
"@types/jsdom": "^21.1.1",
"@types/lodash-es": "^4.17.7",
"@types/micromatch": "^4.0.2",
"@types/prettier": "^2.7.1",
"@types/prettier": "^2.7.2",
"@types/stylis": "^4.0.2",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"chokidar": "^3.5.3",
"concurrently": "^7.5.0",
"concurrently": "^8.0.1",
"coveralls": "^3.1.1",
"cpy-cli": "^4.2.0",
"cspell": "^6.14.3",
"cspell": "^6.31.1",
"csstree-validator": "^3.0.0",
"globby": "^13.1.2",
"globby": "^13.1.4",
"jison": "^0.4.18",
"js-base64": "^3.7.2",
"jsdom": "^21.0.0",
"js-base64": "^3.7.5",
"jsdom": "^21.1.1",
"micromatch": "^4.0.5",
"path-browserify": "^1.0.1",
"prettier": "^2.7.1",
"prettier": "^2.8.8",
"remark": "^14.0.2",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"rimraf": "^4.0.0",
"start-server-and-test": "^1.14.0",
"typedoc": "^0.23.18",
"typedoc-plugin-markdown": "^3.13.6",
"typescript": "^4.8.4",
"rimraf": "^5.0.0",
"start-server-and-test": "^2.0.0",
"typedoc": "^0.24.5",
"typedoc-plugin-markdown": "^3.15.2",
"typescript": "^5.0.4",
"unist-util-flatmap": "^1.0.0",
"vitepress": "^1.0.0-alpha.46",
"vitepress-plugin-search": "^1.0.4-alpha.19"
"vitepress": "^1.0.0-alpha.72",
"vitepress-plugin-search": "^1.0.4-alpha.20"
},
"files": [
"dist",
"dist/",
"README.md"
],
"sideEffects": false
"sideEffects": false,
"publishConfig": {
"access": "public"
}
}

View File

@@ -27,6 +27,7 @@ export interface MermaidConfig {
state?: StateDiagramConfig;
er?: ErDiagramConfig;
pie?: PieDiagramConfig;
quadrantChart?: QuadrantChartConfig;
requirement?: RequirementDiagramConfig;
mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
@@ -226,6 +227,27 @@ export interface PieDiagramConfig extends BaseDiagramConfig {
textPosition?: number;
}
export interface QuadrantChartConfig extends BaseDiagramConfig {
chartWidth: number;
chartHeight: number;
titleFontSize: number;
titlePadding: number;
quadrantPadding: number;
xAxisLabelPadding: number;
yAxisLabelPadding: number;
xAxisLabelFontSize: number;
yAxisLabelFontSize: number;
quadrantLabelFontSize: number;
quadrantTextTopPadding: number;
pointTextPadding: number;
pointLabelFontSize: number;
pointRadius: number;
xAxisPosition: 'top' | 'bottom';
yAxisPosition: 'left' | 'right';
quadrantInternalBorderStrokeWidth: number;
quadrantExternalBorderStrokeWidth: number;
}
export interface ErDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
diagramPadding?: number;

View File

@@ -88,13 +88,13 @@ const config: Partial<MermaidConfig> = {
*
* **Notes**:
*
* - **strict**: (**default**) tags in text are encoded, click functionality is disabled
* - **loose**: tags in text are allowed, click functionality is enabled
* - **antiscript**: html tags in text are allowed, (only script element is removed), click
* functionality is enabled
* - **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This
* - **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
* - **antiscript**: HTML tags in text are allowed (only script elements are removed), and click
* functionality is enabled.
* - **loose**: HTML tags in text are allowed and click functionality is enabled.
* - **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This
* prevent any JavaScript from running in the context. This may hinder interactive functionality
* of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc.
* of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
*/
securityLevel: 'strict',
@@ -1280,6 +1280,184 @@ const config: Partial<MermaidConfig> = {
textPosition: 0.75,
},
quadrantChart: {
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | chartWidth | Width of the chart | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 500
*/
chartWidth: 500,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | chartHeight | Height of the chart | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 500
*/
chartHeight: 500,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | titlePadding | Chart title top and bottom padding | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 10
*/
titlePadding: 10,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | titleFontSize | Chart title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 20
*/
titleFontSize: 20,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | quadrantPadding | Padding around the quadrant square | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
quadrantPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | -------------------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantTextTopPadding | quadrant title padding from top if the quadrant is rendered on top | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
quadrantTextTopPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | quadrantLabelFontSize | quadrant title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
quadrantLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------------------------- | ------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantInternalBorderStrokeWidth | stroke width of edges of the box that are inside the quadrant | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 1
*/
quadrantInternalBorderStrokeWidth: 1,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------------------------- | -------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantExternalBorderStrokeWidth | stroke width of edges of the box that are outside the quadrant | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 2
*/
quadrantExternalBorderStrokeWidth: 2,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | xAxisLabelPadding | Padding around x-axis labels | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
xAxisLabelPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | xAxisLabelFontSize | x-axis label font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
xAxisLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | xAxisPosition | position of x-axis labels | string | Optional | 'top' or 'bottom' |
*
* **Notes:**
* Default value: top
*/
xAxisPosition: 'top',
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | yAxisLabelPadding | Padding around y-axis labels | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
yAxisLabelPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | yAxisLabelFontSize | y-axis label font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
yAxisLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | yAxisPosition | position of y-axis labels | string | Optional | 'left' or 'right' |
*
* **Notes:**
* Default value: left
*/
yAxisPosition: 'left',
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | -------------------------------------- | ------- | -------- | ------------------- |
* | pointTextPadding | padding between point and point label | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
pointTextPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | ---------------------- | ------- | -------- | ------------------- |
* | pointTextPadding | point title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 12
*/
pointLabelFontSize: 12,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | pointRadius | radius of the point to be drawn | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
pointRadius: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
* | useMaxWidth | See Notes | boolean | Required | true, false |
*
* **Notes:**
*
* When this flag is set to true, the diagram width is locked to 100% and scaled based on
* available space. If set to false, the diagram reserves its absolute width.
*
* Default value: true
*/
useMaxWidth: true,
},
/** The object containing configurations specific for req diagrams */
requirement: {
useWidth: undefined,

View File

@@ -6,6 +6,7 @@ import git from '../diagrams/git/gitGraphDetector.js';
import gantt from '../diagrams/gantt/ganttDetector.js';
import info from '../diagrams/info/infoDetector.js';
import pie from '../diagrams/pie/pieDetector.js';
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
import requirement from '../diagrams/requirement/requirementDetector.js';
import sequence from '../diagrams/sequence/sequenceDetector.js';
import classDiagram from '../diagrams/class/classDetector.js';
@@ -77,6 +78,7 @@ export const addDiagrams = () => {
git,
stateV2,
state,
journey
journey,
quadrantChart
);
};

View File

@@ -1,28 +1,9 @@
import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon';
import { sanitizeUrl } from '@braintree/sanitize-url';
export const drawRect = function (elem, rectData) {
const rectElem = elem.append('rect');
rectElem.attr('x', rectData.x);
rectElem.attr('y', rectData.y);
rectElem.attr('fill', rectData.fill);
rectElem.attr('stroke', rectData.stroke);
rectElem.attr('width', rectData.width);
rectElem.attr('height', rectData.height);
rectElem.attr('rx', rectData.rx);
rectElem.attr('ry', rectData.ry);
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
for (let attrKey in rectData.attrs) {
rectElem.attr(attrKey, rectData.attrs[attrKey]);
}
}
if (rectData.class !== 'undefined') {
rectElem.attr('class', rectData.class);
}
return rectElem;
return svgDrawCommon.drawRect(elem, rectData);
};
export const drawImage = function (elem, width, height, x, y, link) {
@@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) {
// <rect fill="#08427B" height="119.2188" rx="2.5" ry="2.5" stroke="#073B6F" stroke-width="0.5" width="110" x="120" y="7"/>
// draw rect of c4Shape
const rect = getNoteRect();
const rect = svgDrawCommon.getNoteRect();
switch (c4Shape.typeC4Shape.text) {
case 'person':
case 'external_person':
@@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) {
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
};
export const insertArrowEnd = function (elem) {
elem
.append('defs')
@@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) {
.append('path')
.attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*
@@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
};
/**
* Setup node number. The result is appended to the svg.
*
@@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) {
.attr('r', 6);
// .style("fill", '#f00');
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*
@@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) {
// this is actual shape for arrowhead
};
export const getNoteRect = function () {
return {
x: 0,
y: 0,
fill: '#EDF2AE',
stroke: '#666',
width: 100,
anchor: 'start',
height: 100,
rx: 0,
ry: 0,
};
};
const getC4ShapeFont = (cnf, typeC4Shape) => {
return {
fontFamily: cnf[typeC4Shape + 'FontFamily'],
@@ -714,6 +686,4 @@ export default {
insertDatabaseIcon,
insertComputerIcon,
insertClockIcon,
getNoteRect,
sanitizeUrl, // TODO why is this exported?
};

View File

@@ -14,7 +14,14 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb.js';
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes.js';
import {
ClassRelation,
ClassNode,
ClassNote,
ClassMap,
NamespaceMap,
NamespaceNode,
} from './classTypes.js';
const MERMAID_DOM_ID_PREFIX = 'classId-';
@@ -22,6 +29,8 @@ let relations: ClassRelation[] = [];
let classes: ClassMap = {};
let notes: ClassNote[] = [];
let classCounter = 0;
let namespaces: NamespaceMap = {};
let namespaceCounter = 0;
let functions: any[] = [];
@@ -100,12 +109,15 @@ export const clear = function () {
notes = [];
functions = [];
functions.push(setupToolTips);
namespaces = {};
namespaceCounter = 0;
commonClear();
};
export const getClass = function (id: string) {
return classes[id];
};
export const getClasses = function () {
return classes;
};
@@ -170,9 +182,10 @@ export const addMember = function (className: string, member: string) {
const memberString = member.trim();
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
// Remove leading and trailing brackets
// its an annotation
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
} else if (memberString.indexOf(')') > 0) {
//its a method
theClass.methods.push(sanitizeText(memberString));
} else if (memberString) {
theClass.members.push(sanitizeText(memberString));
@@ -234,7 +247,12 @@ const setTooltip = function (ids: string, tooltip?: string) {
}
});
};
export const getTooltip = function (id: string) {
export const getTooltip = function (id: string, namespace?: string) {
if (namespace) {
return namespaces[namespace].classes[id].tooltip;
}
return classes[id].tooltip;
};
/**
@@ -392,6 +410,52 @@ const setDirection = (dir: string) => {
direction = dir;
};
/**
* Function called by parser when a namespace definition has been found.
*
* @param id - Id of the namespace to add
* @public
*/
export const addNamespace = function (id: string) {
if (namespaces[id] !== undefined) {
return;
}
namespaces[id] = {
id: id,
classes: {},
children: {},
domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter,
} as NamespaceNode;
namespaceCounter++;
};
const getNamespace = function (name: string): NamespaceNode {
return namespaces[name];
};
const getNamespaces = function (): NamespaceMap {
return namespaces;
};
/**
* Function called by parser when a namespace definition has been found.
*
* @param id - Id of the namespace to add
* @param classNames - Ids of the class to add
* @public
*/
export const addClassesToNamespace = function (id: string, classNames: string[]) {
if (namespaces[id] !== undefined) {
classNames.map((className) => {
namespaces[id].classes[className] = classes[className];
delete classes[className];
classCounter--;
});
}
};
export default {
parseDirective,
setAccTitle,
@@ -425,4 +489,8 @@ export default {
setDiagramTitle,
getDiagramTitle,
setClassLabel,
addNamespace,
addClassesToNamespace,
getNamespace,
getNamespaces,
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
import { LALRGenerator } from 'jison';
describe('class diagram grammar', function () {
it('should introduce no new conflicts', function () {
const file = require.resolve('./parser/classDiagram.jison');
const grammarSource = fs.readFileSync(file, 'utf8');
const grammarParser = new LALRGenerator(grammarSource, {});
expect(grammarParser.conflicts < 16).toBe(true);
});
});

View File

@@ -0,0 +1,16 @@
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
// @ts-ignore - no types
import { LALRGenerator } from 'jison';
const getAbsolutePath = (relativePath: string) => {
return fileURLToPath(new URL(relativePath, import.meta.url));
};
describe('class diagram grammar', function () {
it('should have no conflicts', async function () {
const grammarSource = await readFile(getAbsolutePath('./parser/classDiagram.jison'), 'utf8');
const grammarParser = new LALRGenerator(grammarSource, {});
expect(grammarParser.conflicts).toBe(0);
});
});

View File

@@ -0,0 +1,78 @@
import { setConfig } from '../../config.js';
import classDB from './classDb.js';
// @ts-ignore - no types in jison
import classDiagram from './parser/classDiagram.jison';
setConfig({
securityLevel: 'strict',
});
describe('when parsing class diagram', function () {
beforeEach(function () {
classDiagram.parser.yy = classDB;
classDiagram.parser.yy.clear();
});
it('should parse diagram with direction', () => {
classDiagram.parser.parse(`classDiagram
direction TB
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides`);
expect(Object.keys(classDB.getClasses()).length).toBe(3);
expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [],
"domId": "classId-Student-0",
"id": "Student",
"label": "Student",
"members": [
"-idCard : IdCard",
],
"methods": [],
"type": "",
}
`);
expect(classDB.getRelations().length).toBe(2);
expect(classDB.getRelations()).toMatchInlineSnapshot(`
[
{
"id1": "Student",
"id2": "IdCard",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "carries",
},
{
"id1": "Student",
"id2": "Bike",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "rides",
},
]
`);
});
});

View File

@@ -8,7 +8,7 @@ import utils from '../../utils.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import common from '../common/common.js';
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes.js';
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
@@ -19,6 +19,59 @@ let conf = {
curve: undefined,
};
interface RectParameters {
id: string;
shape: 'rect';
labelStyle: string;
domId: string;
labelText: string;
padding: number | undefined;
style?: string;
}
/**
* Function that adds the vertices found during parsing to the graph to be rendered.
*
* @param namespaces - Object containing the vertices.
* @param g - The graph that is to be drawn.
* @param _id - id of the graph
* @param diagObj - The diagram object
*/
export const addNamespaces = function (
namespaces: NamespaceMap,
g: graphlib.Graph,
_id: string,
diagObj: any
) {
const keys = Object.keys(namespaces);
log.info('keys:', keys);
log.info(namespaces);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
const vertex = namespaces[id];
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
const shape = 'rect';
const node: RectParameters = {
shape: shape,
id: vertex.id,
domId: vertex.domId,
labelText: sanitizeText(vertex.id),
labelStyle: '',
style: 'fill: none; stroke: black',
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
};
g.setNode(vertex.id, node);
addClasses(vertex.classes, g, _id, diagObj, vertex.id);
log.info('setNode', node);
});
};
/**
* Function that adds the vertices found during parsing to the graph to be rendered.
*
@@ -26,12 +79,14 @@ let conf = {
* @param g - The graph that is to be drawn.
* @param _id - id of the graph
* @param diagObj - The diagram object
* @param parent - id of the parent namespace, if it exists
*/
export const addClasses = function (
classes: ClassMap,
g: graphlib.Graph,
_id: string,
diagObj: any
diagObj: any,
parent?: string
) {
const keys = Object.keys(classes);
log.info('keys:', keys);
@@ -55,6 +110,7 @@ export const addClasses = function (
const vertexText = vertex.label ?? vertex.id;
const radius = 0;
const shape = 'class_box';
// Add the node
const node = {
labelStyle: styles.labelStyle,
@@ -67,7 +123,7 @@ export const addClasses = function (
style: styles.style,
id: vertex.id,
domId: vertex.domId,
tooltip: diagObj.db.getTooltip(vertex.id) || '',
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
haveCallback: vertex.haveCallback,
link: vertex.link,
width: vertex.type === 'group' ? 500 : undefined,
@@ -76,6 +132,11 @@ export const addClasses = function (
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
};
g.setNode(vertex.id, node);
if (parent) {
g.setParent(vertex.id, parent);
}
log.info('setNode', node);
});
};
@@ -275,10 +336,12 @@ export const draw = async function (text: string, id: string, _version: string,
});
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const namespaces: NamespaceMap = diagObj.db.getNamespaces();
const classes: ClassMap = diagObj.db.getClasses();
const relations: ClassRelation[] = diagObj.db.getRelations();
const notes: ClassNote[] = diagObj.db.getNotes();
log.info(relations);
addNamespaces(namespaces, g, id, diagObj);
addClasses(classes, g, id, diagObj);
addRelations(relations, g);
addNotes(notes, g, relations.length + 1, classes);

View File

@@ -52,4 +52,13 @@ export type ClassRelation = {
lineType: number;
};
};
export interface NamespaceNode {
id: string;
domId: string;
classes: ClassMap;
children: NamespaceMap;
}
export type ClassMap = Record<string, ClassNode>;
export type NamespaceMap = Record<string, NamespaceNode>;

View File

@@ -19,6 +19,10 @@
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x class
%x class-body
%x namespace
%x namespace-body
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
.*direction\s+TB[^\n]* return 'direction_tb';
@@ -41,35 +45,41 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\s*(\r?\n)+ return 'NEWLINE';
\s+ /* skip whitespace */
"classDiagram-v2" return 'CLASS_DIAGRAM';
"classDiagram" return 'CLASS_DIAGRAM';
[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
<struct><<EOF>> return "EOF_IN_STRUCT";
<struct>[{] return "OPEN_IN_STRUCT";
<struct>[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
<struct>[\n] /* nothing */
<struct>[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
"[*]" return 'EDGE_STATE';
"class" return 'CLASS';
"cssClass" return 'CSSCLASS';
"callback" return 'CALLBACK';
"link" return 'LINK';
"click" return 'CLICK';
"note for" return 'NOTE_FOR';
"note" return 'NOTE';
"<<" return 'ANNOTATION_START';
">>" return 'ANNOTATION_END';
[~] this.begin("generic");
<generic>[~] this.popState();
<generic>[^~]* return "GENERICTYPE";
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
<namespace>\s+ /* skip whitespace */
<namespace>[{] { this.begin("namespace-body"); return 'STRUCT_START';}
<namespace-body>[}] { this.popState(); return 'STRUCT_STOP'; }
<namespace-body><<EOF>> return "EOF_IN_STRUCT";
<namespace-body>\s*(\r?\n)+ return 'NEWLINE';
<namespace-body>\s+ /* skip whitespace */
<namespace-body>"[*]" return 'EDGE_STATE';
[`] this.begin("bqstring");
<bqstring>[`] this.popState();
<bqstring>[^`]+ return "BQUOTE_STR";
<INITIAL,namespace-body>"class" { this.begin('class'); return 'CLASS';}
<class>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
<class>\s+ /* skip whitespace */
<class>[}] { this.popState(); this.popState(); return 'STRUCT_STOP';}
<class>[{] { this.begin("class-body"); return 'STRUCT_START';}
<class-body>[}] { this.popState(); return 'STRUCT_STOP'; }
<class-body><<EOF>> return "EOF_IN_STRUCT";
<class-body>"[*]" { return 'EDGE_STATE';}
<class-body>[{] return "OPEN_IN_STRUCT";
<class-body>[\n] /* nothing */
<class-body>[^{}\n]* { return "MEMBER";}
<*>"cssClass" return 'CSSCLASS';
<*>"callback" return 'CALLBACK';
<*>"link" return 'LINK';
<*>"click" return 'CLICK';
<*>"note for" return 'NOTE_FOR';
<*>"note" return 'NOTE';
<*>"<<" return 'ANNOTATION_START';
<*>">>" return 'ANNOTATION_END';
/*
---interactivity command---
@@ -77,7 +87,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
line was introduced with 'click'.
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
*/
"href"[\s]+["] this.begin("href");
<*>"href"[\s]+["] this.begin("href");
<href>["] this.popState();
<href>[^"]* return 'HREF';
@@ -89,41 +99,53 @@ the line was introduced with 'click'.
arguments to the node that was specified by 'click'.
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
*/
"call"[\s]+ this.begin("callback_name");
<*>"call"[\s]+ this.begin("callback_name");
<callback_name>\([\s]*\) this.popState();
<callback_name>\( this.popState(); this.begin("callback_args");
<callback_name>[^(]* return 'CALLBACK_NAME';
<callback_args>\) this.popState();
<callback_args>[^)]* return 'CALLBACK_ARGS';
"_self" return 'LINK_TARGET';
"_blank" return 'LINK_TARGET';
"_parent" return 'LINK_TARGET';
"_top" return 'LINK_TARGET';
<generic>[~] this.popState();
<generic>[^~]* return "GENERICTYPE";
<*>[~] this.begin("generic");
\s*\<\| return 'EXTENSION';
\s*\|\> return 'EXTENSION';
\s*\> return 'DEPENDENCY';
\s*\< return 'DEPENDENCY';
\s*\* return 'COMPOSITION';
\s*o return 'AGGREGATION';
\s*\(\) return 'LOLLIPOP';
\-\- return 'LINE';
\.\. return 'DOTTED_LINE';
":"{1}[^:\n;]+ return 'LABEL';
":"{3} return 'STYLE_SEPARATOR';
\- return 'MINUS';
"." return 'DOT';
\+ return 'PLUS';
\% return 'PCT';
"=" return 'EQUALS';
\= return 'EQUALS';
\w+ return 'ALPHA';
"[" return 'SQS';
"]" return 'SQE';
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
[0-9]+ return 'NUM';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
<string>["] this.popState();
<string>[^"]* return "STR";
<*>["] this.begin("string");
<bqstring>[`] this.popState();
<bqstring>[^`]+ return "BQUOTE_STR";
<*>[`] this.begin("bqstring");
<*>"_self" return 'LINK_TARGET';
<*>"_blank" return 'LINK_TARGET';
<*>"_parent" return 'LINK_TARGET';
<*>"_top" return 'LINK_TARGET';
<*>\s*\<\| return 'EXTENSION';
<*>\s*\|\> return 'EXTENSION';
<*>\s*\> return 'DEPENDENCY';
<*>\s*\< return 'DEPENDENCY';
<*>\s*\* return 'COMPOSITION';
<*>\s*o return 'AGGREGATION';
<*>\s*\(\) return 'LOLLIPOP';
<*>\-\- return 'LINE';
<*>\.\. return 'DOTTED_LINE';
<*>":"{1}[^:\n;]+ return 'LABEL';
<*>":"{3} return 'STYLE_SEPARATOR';
<*>\- return 'MINUS';
<*>"." return 'DOT';
<*>\+ return 'PLUS';
<*>\% return 'PCT';
<*>"=" return 'EQUALS';
<*>\= return 'EQUALS';
<*>\w+ return 'ALPHA';
<*>"[" return 'SQS';
<*>"]" return 'SQE';
<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
<*>[0-9]+ return 'NUM';
<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
@@ -184,9 +206,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
return 'UNICODE_TEXT';
\s return 'SPACE';
<<EOF>> return 'EOF';
return 'UNICODE_TEXT';
<*>\s return 'SPACE';
<*><<EOF>> return 'EOF';
/lex
@@ -200,9 +222,8 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
start
: mermaidDoc
| statments
| direction
| directive start
| statements
;
direction
@@ -255,30 +276,50 @@ classLabel
: SQS STR SQE { $$=$2; }
;
namespaceName
: alphaNumToken { $$=$1; }
| alphaNumToken namespaceName { $$=$1+$2; }
;
className
: alphaNumToken { $$=$1; }
| classLiteralName { $$=$1; }
| alphaNumToken className { $$=$1+$2; }
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
| classLiteralName GENERICTYPE { $$=$1+'~'+$2; }
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
| classLiteralName GENERICTYPE { $$=$1+'~'+$2+'~'; }
;
statement
: relationStatement { yy.addRelation($1); }
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
| namespaceStatement
| classStatement
| methodStatement
| annotationStatement
| clickStatement
| cssClassStatement
| noteStatement
| directive
| direction
| 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($$); }
;
namespaceStatement
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
;
namespaceIdentifier
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
;
classStatements
: classStatement {$$=[$1]}
| classStatement NEWLINE {$$=[$1]}
| classStatement NEWLINE classStatements {$3.unshift($1); $$=$3}
;
classStatement
: classIdentifier
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
@@ -366,7 +407,7 @@ textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFA
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
alphaNumToken : UNICODE_TEXT | NUM | ALPHA | MINUS;
classLiteralName : BQUOTE_STR;

View File

@@ -199,11 +199,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
isFirst = false;
});
let classTitleString = classDef.id;
if (classDef.type !== undefined && classDef.type !== '') {
classTitleString += '<' + classDef.type + '>';
}
let classTitleString = getClassTitleString(classDef);
const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title');
@@ -291,6 +287,16 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
return classInfo;
};
export const getClassTitleString = function (classDef) {
let classTitleString = classDef.id;
if (classDef.type) {
classTitleString += '<' + classDef.type + '>';
}
return classTitleString;
};
/**
* Renders a note diagram
*
@@ -355,106 +361,59 @@ export const drawNote = function (elem, note, conf, diagObj) {
};
export const parseMember = function (text) {
const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/;
const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/;
let fieldMatch = text.match(fieldRegEx);
let methodMatch = text.match(methodRegEx);
if (fieldMatch && !methodMatch) {
return buildFieldDisplay(fieldMatch);
} else if (methodMatch) {
return buildMethodDisplay(methodMatch);
} else {
return buildLegacyDisplay(text);
}
};
const buildFieldDisplay = function (parsedText) {
let cssStyle = '';
let displayText = '';
try {
let visibility = parsedText[1] ? parsedText[1].trim() : '';
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
let classifier = parsedText[5] ? parsedText[5].trim() : '';
displayText = visibility + fieldType + genericType + ' ' + fieldName;
cssStyle = parseClassifier(classifier);
} catch (err) {
displayText = parsedText;
}
return {
displayText: displayText,
cssStyle: cssStyle,
};
};
const buildMethodDisplay = function (parsedText) {
let cssStyle = '';
let displayText = '';
try {
let visibility = parsedText[1] ? parsedText[1].trim() : '';
let methodName = parsedText[2] ? parsedText[2].trim() : '';
let parameters = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
let classifier = parsedText[4] ? parsedText[4].trim() : '';
let returnType = parsedText[5] ? ' : ' + parseGenericTypes(parsedText[5]).trim() : '';
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
cssStyle = parseClassifier(classifier);
} catch (err) {
displayText = parsedText;
}
return {
displayText: displayText,
cssStyle: cssStyle,
};
};
const buildLegacyDisplay = function (text) {
// if for some reason we don't have any match, use old format to parse text
let displayText = '';
let cssStyle = '';
let returnType = '';
let methodStart = text.indexOf('(');
let methodEnd = text.indexOf(')');
if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
let visibility = '';
let methodName = '';
let visibility = '';
let firstChar = text.substring(0, 1);
let lastChar = text.substring(text.length - 1, text.length);
let firstChar = text.substring(0, 1);
if (firstChar.match(/\w/)) {
methodName = text.substring(0, methodStart).trim();
} else {
if (firstChar.match(/[#+~-]/)) {
visibility = firstChar;
}
if (firstChar.match(/[#+~-]/)) {
visibility = firstChar;
}
methodName = text.substring(1, methodStart).trim();
}
let noClassifierRe = /[\s\w)~]/;
if (!lastChar.match(noClassifierRe)) {
cssStyle = parseClassifier(lastChar);
}
const startIndex = visibility === '' ? 0 : 1;
let endIndex = cssStyle === '' ? text.length : text.length - 1;
text = text.substring(startIndex, endIndex);
const methodStart = text.indexOf('(');
const methodEnd = text.indexOf(')');
const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length;
if (isMethod) {
let methodName = text.substring(0, methodStart).trim();
const parameters = text.substring(methodStart + 1, methodEnd);
const classifier = text.substring(methodEnd + 1, 1);
cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2));
displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
if (methodEnd < text.length) {
returnType = text.substring(methodEnd + 2).trim();
// special case: classifier after the closing parenthesis
let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
cssStyle = parseClassifier(potentialClassifier);
returnType = text.substring(methodEnd + 2).trim();
} else {
returnType = text.substring(methodEnd + 1).trim();
}
if (returnType !== '') {
if (returnType.charAt(0) === ':') {
returnType = returnType.substring(1).trim();
}
returnType = ' : ' + parseGenericTypes(returnType);
displayText += returnType;
}
}
} else {
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
displayText = parseGenericTypes(text);
displayText = visibility + parseGenericTypes(text);
}
return {
@@ -462,6 +421,7 @@ const buildLegacyDisplay = function (text) {
cssStyle,
};
};
/**
* Adds a <tspan> for a member in a diagram
*
@@ -502,6 +462,7 @@ const parseClassifier = function (classifier) {
};
export default {
getClassTitleString,
drawClass,
drawEdge,
drawNote,

View File

@@ -1,8 +1,19 @@
import svgDraw from './svgDraw.js';
describe('class member Renderer, ', function () {
describe('when parsing text to build method display string', function () {
it('should handle simple method declaration', function () {
describe('given a string representing class method, ', function () {
it('should handle class names with generics', function () {
const classDef = {
id: 'Car',
type: 'T',
label: 'Car',
};
let actual = svgDraw.getClassTitleString(classDef);
expect(actual).toBe('Car<T>');
});
describe('when parsing base method declaration', function () {
it('should handle simple declaration', function () {
const str = 'foo()';
let actual = svgDraw.parseMember(str);
@@ -10,71 +21,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle public visibility', function () {
const str = '+foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('+foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle private visibility', function () {
const str = '-foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('-foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle protected visibility', function () {
const str = '#foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('#foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle package/internal visibility', function () {
const str = '~foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('~foo()');
expect(actual.cssStyle).toBe('');
});
it('should ignore unknown character for visibility', function () {
const str = '!foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle abstract method classifier', function () {
const str = 'foo()*';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle static method classifier', function () {
const str = 'foo()$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should ignore unknown character for classifier', function () {
const str = 'foo()!';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with parameters', function () {
it('should handle declaration with parameters', function () {
const str = 'foo(int id)';
let actual = svgDraw.parseMember(str);
@@ -82,7 +29,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with multiple parameters', function () {
it('should handle declaration with multiple parameters', function () {
const str = 'foo(int id, object thing)';
let actual = svgDraw.parseMember(str);
@@ -90,7 +37,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with single item in parameters', function () {
it('should handle declaration with single item in parameters', function () {
const str = 'foo(id)';
let actual = svgDraw.parseMember(str);
@@ -98,7 +45,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle simple method declaration with single item in parameters with extra spaces', function () {
it('should handle declaration with single item in parameters with extra spaces', function () {
const str = ' foo ( id) ';
let actual = svgDraw.parseMember(str);
@@ -106,22 +53,6 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with return value', function () {
const str = 'foo(id) int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with generic return value', function () {
const str = 'foo(id) List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with generic parameter', function () {
const str = 'foo(List~int~)';
let actual = svgDraw.parseMember(str);
@@ -130,6 +61,46 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with normal and generic parameter', function () {
const str = 'foo(int, List~int~)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(int, List<int>)');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with return value', function () {
const str = 'foo(id) int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with colon return value', function () {
const str = 'foo(id) : int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with generic return value', function () {
const str = 'foo(id) List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with colon generic return value', function () {
const str = 'foo(id) : List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with all possible markup', function () {
const str = '+foo ( List~int~ ids )* List~Item~';
let actual = svgDraw.parseMember(str);
@@ -138,7 +109,7 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle method declaration with nested markup', function () {
it('should handle method declaration with nested generics', function () {
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
let actual = svgDraw.parseMember(str);
@@ -147,8 +118,134 @@ describe('class member Renderer, ', function () {
});
});
describe('when parsing text to build field display string', function () {
it('should handle simple field declaration', function () {
describe('when parsing method visibility', function () {
it('should correctly handle public', function () {
const str = '+foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('+foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle private', function () {
const str = '-foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('-foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle protected', function () {
const str = '#foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('#foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle package/internal', function () {
const str = '~foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('~foo()');
expect(actual.cssStyle).toBe('');
});
});
describe('when parsing method classifier', function () {
it('should handle abstract method', function () {
const str = 'foo()*';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle abstract method with return type', function () {
const str = 'foo(name: String) int*';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle abstract method classifier after parenthesis with return type', function () {
const str = 'foo(name: String)* int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle static method classifier', function () {
const str = 'foo()$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier with return type', function () {
const str = 'foo(name: String) int$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier with colon and return type', function () {
const str = 'foo(name: String): int$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier after parenthesis with return type', function () {
const str = 'foo(name: String)$ int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should ignore unknown character for classifier', function () {
const str = 'foo()!';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
});
});
describe('given a string representing class member, ', function () {
describe('when parsing member declaration', function () {
it('should handle simple field', function () {
const str = 'id';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('id');
expect(actual.cssStyle).toBe('');
});
it('should handle field with type', function () {
const str = 'int id';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('int id');
expect(actual.cssStyle).toBe('');
});
it('should handle field with type (name first)', function () {
const str = 'id: int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('id: int');
expect(actual.cssStyle).toBe('');
});
it('should handle array field', function () {
const str = 'int[] ids';
let actual = svgDraw.parseMember(str);
@@ -156,7 +253,15 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle field declaration with generic type', function () {
it('should handle array field (name first)', function () {
const str = 'ids: int[]';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('ids: int[]');
expect(actual.cssStyle).toBe('');
});
it('should handle field with generic type', function () {
const str = 'List~int~ ids';
let actual = svgDraw.parseMember(str);
@@ -164,12 +269,62 @@ describe('class member Renderer, ', function () {
expect(actual.cssStyle).toBe('');
});
it('should handle static field classifier', function () {
it('should handle field with generic type (name first)', function () {
const str = 'ids: List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('ids: List<int>');
expect(actual.cssStyle).toBe('');
});
});
describe('when parsing classifiers', function () {
it('should handle static field', function () {
const str = 'String foo$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('String foo');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field (name first)', function () {
const str = 'foo: String$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo: String');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field with generic type', function () {
const str = 'List~String~ foo$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('List<String> foo');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field with generic type (name first)', function () {
const str = 'foo: List~String~$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo: List<String>');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle field with nested generic type', function () {
const str = 'List~List~int~~ idLists';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('List<List<int>> idLists');
expect(actual.cssStyle).toBe('');
});
it('should handle field with nested generic type (name first)', function () {
const str = 'idLists: List~List~int~~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('idLists: List<List<int>>');
expect(actual.cssStyle).toBe('');
});
});
});

View File

@@ -1,6 +1,8 @@
import DOMPurify from 'dompurify';
import { MermaidConfig } from '../../config.type.js';
export const lineBreakRegex = /<br\s*\/?>/gi;
/**
* Gets the rows of lines in a string
*
@@ -65,8 +67,6 @@ export const sanitizeTextOrArray = (
return a.flat().map((x: string) => sanitizeText(x, config));
};
export const lineBreakRegex = /<br\s*\/?>/gi;
/**
* Whether or not a text has any line breaks
*
@@ -138,6 +138,32 @@ const getUrl = (useAbsolute: boolean): string => {
export const evaluate = (val?: string | boolean): boolean =>
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;
/**
* Wrapper around Math.max which removes non-numeric values
* Returns the larger of a set of supplied numeric expressions.
* @param values - Numeric expressions to be evaluated
* @returns The smaller value
*/
export const getMax = function (...values: number[]): number {
const newValues: number[] = values.filter((value) => {
return !isNaN(value);
});
return Math.max(...newValues);
};
/**
* Wrapper around Math.min which removes non-numeric values
* Returns the smaller of a set of supplied numeric expressions.
* @param values - Numeric expressions to be evaluated
* @returns The smaller value
*/
export const getMin = function (...values: number[]): number {
const newValues: number[] = values.filter((value) => {
return !isNaN(value);
});
return Math.min(...newValues);
};
/**
* Makes generics in typescript syntax
*
@@ -180,4 +206,6 @@ export default {
removeScript,
getUrl,
evaluate,
getMax,
getMin,
};

View File

@@ -0,0 +1,114 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
export const drawRect = function (elem, rectData) {
const rectElem = elem.append('rect');
rectElem.attr('x', rectData.x);
rectElem.attr('y', rectData.y);
rectElem.attr('fill', rectData.fill);
rectElem.attr('stroke', rectData.stroke);
rectElem.attr('width', rectData.width);
rectElem.attr('height', rectData.height);
rectElem.attr('rx', rectData.rx);
rectElem.attr('ry', rectData.ry);
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
for (let attrKey in rectData.attrs) {
rectElem.attr(attrKey, rectData.attrs[attrKey]);
}
}
if (rectData.class !== 'undefined') {
rectElem.attr('class', rectData.class);
}
return rectElem;
};
/**
* Draws a background rectangle
*
* @param {any} elem Diagram (reference for bounds)
* @param {any} bounds Shape of the rectangle
*/
export const drawBackgroundRect = function (elem, bounds) {
const rectElem = drawRect(elem, {
x: bounds.startx,
y: bounds.starty,
width: bounds.stopx - bounds.startx,
height: bounds.stopy - bounds.starty,
fill: bounds.fill,
stroke: bounds.stroke,
class: 'rect',
});
rectElem.lower();
};
export const drawText = function (elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.attr('class', 'legend');
textElem.style('text-anchor', textData.anchor);
if (textData.class !== undefined) {
textElem.attr('class', textData.class);
}
const span = textElem.append('tspan');
span.attr('x', textData.x + textData.textMargin * 2);
span.text(nText);
return textElem;
};
export const drawImage = function (elem, x, y, link) {
const imageElem = elem.append('image');
imageElem.attr('x', x);
imageElem.attr('y', y);
var sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', sanitizedLink);
};
export const drawEmbeddedImage = function (elem, x, y, link) {
const imageElem = elem.append('use');
imageElem.attr('x', x);
imageElem.attr('y', y);
const sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', '#' + sanitizedLink);
};
export const getNoteRect = function () {
return {
x: 0,
y: 0,
width: 100,
height: 100,
fill: '#EDF2AE',
stroke: '#666',
anchor: 'start',
rx: 0,
ry: 0,
};
};
export const getTextObj = function () {
return {
x: 0,
y: 0,
width: 100,
height: 100,
fill: undefined,
anchor: undefined,
'text-anchor': 'start',
style: '#666',
textMargin: 0,
rx: 0,
ry: 0,
tspan: true,
valign: undefined,
};
};

View File

@@ -20,6 +20,7 @@ const Cardinality = {
ZERO_OR_MORE: 'ZERO_OR_MORE',
ONE_OR_MORE: 'ONE_OR_MORE',
ONLY_ONE: 'ONLY_ONE',
MD_PARENT: 'MD_PARENT',
};
const Identification = {

View File

@@ -7,6 +7,8 @@ const ERMarkers = {
ONE_OR_MORE_END: 'ONE_OR_MORE_END',
ZERO_OR_MORE_START: 'ZERO_OR_MORE_START',
ZERO_OR_MORE_END: 'ZERO_OR_MORE_END',
MD_PARENT_END: 'MD_PARENT_END',
MD_PARENT_START: 'MD_PARENT_START',
};
/**
@@ -18,6 +20,30 @@ const ERMarkers = {
const insertMarkers = function (elem, conf) {
let marker;
elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.MD_PARENT_START)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
elem
.append('defs')
.append('marker')
.attr('id', ERMarkers.MD_PARENT_END)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
elem
.append('defs')
.append('marker')

View File

@@ -478,6 +478,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
case diagObj.db.Cardinality.ONLY_ONE:
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
break;
case diagObj.db.Cardinality.MD_PARENT:
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_END + ')');
break;
}
switch (rel.relSpec.cardB) {
@@ -502,6 +505,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
case diagObj.db.Cardinality.ONLY_ONE:
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
break;
case diagObj.db.Cardinality.MD_PARENT:
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_START + ')');
break;
}
// Now label the relationship

View File

@@ -57,6 +57,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
o\| return 'ZERO_OR_ONE';
o\{ return 'ZERO_OR_MORE';
\|\{ return 'ONE_OR_MORE';
\s*u return 'MD_PARENT';
\.\. return 'NON_IDENTIFYING';
\-\- return 'IDENTIFYING';
"to" return 'IDENTIFYING';
@@ -170,6 +171,7 @@ cardinality
| 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
| 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
| 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
| 'MD_PARENT' { $$ = yy.Cardinality.MD_PARENT; }
;
relType

View File

@@ -718,5 +718,14 @@ describe('when parsing ER diagram it...', function () {
const rels = erDb.getRelationships();
expect(rels[0].roleA).toBe('places');
});
it('should represent parent-child relationship correctly', function () {
erDiagram.parser.parse('erDiagram\nPROJECT u--o{ TEAM_MEMBER : "parent"');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.MD_PARENT);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
});
});

View File

@@ -33,6 +33,17 @@ const getStyles = (options) =>
font-size: 18px;
fill: ${options.textColor};
}
#MD_PARENT_START {
fill: #f5f5f5 !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
#MD_PARENT_END {
fill: #f5f5f5 !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
`;
export default getStyles;

View File

@@ -414,7 +414,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
edges.forEach(function (edge) {
// Identify Link
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
if (linkIdCnt[linkIdBase] === undefined) {
linkIdCnt[linkIdBase] = 0;
@@ -425,8 +425,8 @@ export const addEdges = function (edges, diagObj, graph, svg) {
}
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
const linkNameStart = 'LS-' + edge.start;
const linkNameEnd = 'LE-' + edge.end;
const edgeData = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;
@@ -717,7 +717,7 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
const edgePath = edgesEl
.insert('path')
.attr('d', curve(points))
.attr('class', 'path')
.attr('class', 'path ' + edgeData.classes)
.attr('fill', 'none');
const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel');
const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl));

View File

@@ -214,7 +214,7 @@ export const addEdges = function (edges, g, diagObj) {
cnt++;
// Identify Link
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
if (linkIdCnt[linkIdBase] === undefined) {
linkIdCnt[linkIdBase] = 0;
@@ -225,8 +225,8 @@ export const addEdges = function (edges, g, diagObj) {
}
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
const linkNameStart = 'LS-' + edge.start;
const linkNameEnd = 'LE-' + edge.end;
const edgeData = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;

View File

@@ -176,9 +176,9 @@ export const addEdges = function (edges, g, diagObj) {
cnt++;
// Identify Link
var linkId = 'L-' + edge.start + '-' + edge.end;
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
const linkId = 'L-' + edge.start + '-' + edge.end;
const linkNameStart = 'LS-' + edge.start;
const linkNameEnd = 'LE-' + edge.end;
const edgeData = {};

View File

@@ -1,3 +1,6 @@
// import khroma from 'khroma';
import * as khroma from 'khroma';
/** Returns the styles given options */
export interface FlowChartStyleOptions {
arrowheadColor: string;
@@ -15,6 +18,18 @@ export interface FlowChartStyleOptions {
titleColor: string;
}
const fade = (color: string, opacity: number) => {
// @ts-ignore TODO: incorrect types from khroma
const channel = khroma.channel;
const r = channel(color, 'r');
const g = channel(color, 'g');
const b = channel(color, 'b');
// @ts-ignore incorrect types from khroma
return khroma.rgba(r, g, b, opacity);
};
const getStyles = (options: FlowChartStyleOptions) =>
`.label {
font-family: ${options.fontFamily};
@@ -82,6 +97,12 @@ const getStyles = (options: FlowChartStyleOptions) =>
text-align: center;
}
/* For html labels only */
.labelBkg {
background-color: ${fade(options.edgeLabelBackground, 0.5)};
// background-color:
}
.cluster rect {
fill: ${options.clusterBkg};
stroke: ${options.clusterBorder};

View File

@@ -1,8 +1,8 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
import dayjs from 'dayjs/esm/index.js';
import dayjsIsoWeek from 'dayjs/esm/plugin/isoWeek/index.js';
import dayjsCustomParseFormat from 'dayjs/esm/plugin/customParseFormat/index.js';
import dayjsAdvancedFormat from 'dayjs/esm/plugin/advancedFormat/index.js';
import dayjs from 'dayjs';
import dayjsIsoWeek from 'dayjs/plugin/isoWeek.js';
import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js';
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js';
import { log } from '../../logger.js';
import * as configApi from '../../config.js';
import utils from '../../utils.js';
@@ -287,7 +287,17 @@ const getStartDate = function (prevTime, dateFormat, str) {
log.debug('Invalid date:' + str);
log.debug('With date format:' + dateFormat.trim());
const d = new Date(str);
if (d === undefined || isNaN(d.getTime())) {
if (
d === undefined ||
isNaN(d.getTime()) ||
// WebKit browsers can mis-parse invalid dates to be ridiculously
// huge numbers, e.g. new Date('202304') gets parsed as January 1, 202304.
// This can cause virtually infinite loops while rendering, so for the
// purposes of Gantt charts we'll just treat any date beyond 10,000 AD/BC as
// invalid.
d.getFullYear() < -10000 ||
d.getFullYear() > 10000
) {
throw new Error('Invalid date:' + str);
}
return d;

View File

@@ -1,5 +1,5 @@
// @ts-nocheck TODO: Fix TS
import dayjs from 'dayjs/esm/index.js';
import dayjs from 'dayjs';
import ganttDb from './ganttDb.js';
import { convert } from '../../tests/util.js';
@@ -432,4 +432,10 @@ describe('when using the ganttDb', function () {
ganttDb.setTodayMarker(expected);
expect(ganttDb.getTodayMarker()).toEqual(expected);
});
it('should reject dates with ridiculous years', function () {
ganttDb.setDateFormat('YYYYMMDD');
ganttDb.addTask('test1', 'id1,202304,1d');
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
});
});

View File

@@ -1,4 +1,4 @@
import dayjs from 'dayjs/esm/index.js';
import dayjs from 'dayjs';
import { log } from '../../logger.js';
import {
select,

View File

@@ -70,6 +70,7 @@ const defaultBkg = function (elem, node, section) {
.attr('x2', node.width)
.attr('y2', node.height);
};
const rectBkg = function (elem, node) {
elem
.append('rect')
@@ -78,6 +79,7 @@ const rectBkg = function (elem, node) {
.attr('height', node.height)
.attr('width', node.width);
};
const cloudBkg = function (elem, node) {
const w = node.width;
const h = node.height;
@@ -108,6 +110,7 @@ const cloudBkg = function (elem, node) {
H0 V0 Z`
);
};
const bangBkg = function (elem, node) {
const w = node.width;
const h = node.height;
@@ -139,6 +142,7 @@ const bangBkg = function (elem, node) {
H0 V0 Z`
);
};
const circleBkg = function (elem, node) {
elem
.append('circle')

View File

@@ -0,0 +1,185 @@
%lex
%options case-insensitive
%x string
%x string
%x md_string
%x title
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x point_start
%x point_x
%x point_y
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n\r]+ return 'NEWLINE';
\%\%[^\n]* /* do nothing */
title { this.begin("title");return 'title'; }
<title>(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
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";
" "*"x-axis"" "* return 'X-AXIS';
" "*"y-axis"" "* return 'Y-AXIS';
" "*\-\-+\>" "* return 'AXIS-TEXT-DELIMITER'
" "*"quadrant-1"" "* return 'QUADRANT_1';
" "*"quadrant-2"" "* return 'QUADRANT_2';
" "*"quadrant-3"" "* return 'QUADRANT_3';
" "*"quadrant-4"" "* return 'QUADRANT_4';
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
\s*\:\s*\[\s* {this.begin("point_start"); return 'point_start';}
<point_start>(1)|(0(.\d+)?) {this.begin('point_x'); return 'point_x';}
<point_start>\s*\]" "* {this.popState();}
<point_x>\s*\,\s* {this.popState(); this.begin('point_y');}
<point_y>(1)|(0(.\d+)?) {this.popState(); return 'point_y';}
" "*"quadrantChart"" "* return 'QUADRANT';
[A-Za-z]+ return 'ALPHA';
":" return 'COLON';
\+ return 'PLUS';
"," return 'COMMA';
"=" return 'EQUALS';
\= return 'EQUALS';
"*" return 'MULT';
\# return 'BRKT';
[\_] return 'UNDERSCORE';
"." return 'DOT';
"&" return 'AMP';
\- return 'MINUS';
[0-9]+ return 'NUM';
\s return 'SPACE';
";" return 'SEMI';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
<<EOF>> return 'EOF';
/lex
%start start
%% /* language grammar */
start
: eol start
| SPACE start
| directive start
| QUADRANT document
;
document
: /* empty */
| document line
;
line
: statement eol
;
statement
:
| SPACE statement
| axisDetails
| quadrantDetails
| points
| title title_value { $$=$2.trim();yy.setDiagramTitle($$); }
| 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);}
| directive
;
points
: text point_start point_x point_y {yy.addPoint($1, $3, $4);}
;
axisDetails
: X-AXIS text AXIS-TEXT-DELIMITER text {yy.setXAxisLeftText($2); yy.setXAxisRightText($4);}
| X-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setXAxisLeftText($2);}
| X-AXIS text {yy.setXAxisLeftText($2);}
| Y-AXIS text AXIS-TEXT-DELIMITER text {yy.setYAxisBottomText($2); yy.setYAxisTopText($4);}
| Y-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setYAxisBottomText($2);}
| Y-AXIS text {yy.setYAxisBottomText($2);}
;
quadrantDetails
: QUADRANT_1 text {yy.setQuadrant1Text($2)}
| QUADRANT_2 text {yy.setQuadrant2Text($2)}
| QUADRANT_3 text {yy.setQuadrant3Text($2)}
| QUADRANT_4 text {yy.setQuadrant4Text($2)}
;
directive
: openDirective typeDirective closeDirective
| openDirective typeDirective ':' argDirective closeDirective
;
eol
: NEWLINE
| SEMI
| EOF
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); }
;
text: alphaNumToken
{ $$={text:$1, type: 'text'};}
| text textNoTagsToken
{ $$={text:$1.text+''+$2, type: $1.type};}
| STR
{ $$={text: $1, type: 'text'};}
| MD_STR
{ $$={text: $1, type: 'markdown'};}
;
alphaNum
: alphaNumToken
{$$=$1;}
| alphaNum alphaNumToken
{$$=$1+''+$2;}
;
alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
textNoTagsToken: alphaNumToken | SPACE | MINUS;
%%

View File

@@ -0,0 +1,298 @@
// @ts-ignore: TODO Fix ts errors
import { parser } from './quadrant.jison';
import { Mock, vi } from 'vitest';
const parserFnConstructor = (str: string) => {
return () => {
parser.parse(str);
};
};
const mockDB: Record<string, Mock<any, any>> = {
setQuadrant1Text: vi.fn(),
setQuadrant2Text: vi.fn(),
setQuadrant3Text: vi.fn(),
setQuadrant4Text: vi.fn(),
setXAxisLeftText: vi.fn(),
setXAxisRightText: vi.fn(),
setYAxisTopText: vi.fn(),
setYAxisBottomText: vi.fn(),
setDiagramTitle: vi.fn(),
parseDirective: vi.fn(),
addPoint: vi.fn(),
};
function clearMocks() {
for (const key in mockDB) {
mockDB[key].mockRestore();
}
}
describe('Testing quadrantChart jison file', () => {
beforeEach(() => {
parser.yy = mockDB;
clearMocks();
});
it('should throw error if quadrantChart text is not there', () => {
const str = 'quadrant-1 do';
expect(parserFnConstructor(str)).toThrow();
});
it('should not throw error if only quadrantChart is there', () => {
const str = 'quadrantChart';
expect(parserFnConstructor(str)).not.toThrow();
});
it('should be able to parse directive', () => {
const str =
'%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
'arg_directive',
]);
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
'}%%',
'close_directive',
'quadrantChart',
]);
});
it('should be able to parse xAxis text', () => {
let str = 'quadrantChart\nx-axis urgent --> not urgent';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
clearMocks();
str = 'quadrantChart\n x-AxIs Urgent --> Not Urgent \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
clearMocks();
str =
'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
text: 'Not Urgent (* +=[❤',
type: 'text',
});
clearMocks();
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
clearMocks();
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Urgent(* +=[❤ ⟶ ',
type: 'text',
});
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
});
it('should be able to parse yAxis text', () => {
let str = 'quadrantChart\ny-axis urgent --> not urgent';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
clearMocks();
str = 'quadrantChart\n y-AxIs Urgent --> Not Urgent \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
clearMocks();
str =
'quadrantChart\n Y-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({
text: 'Not Urgent (* +=[❤',
type: 'text',
});
clearMocks();
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
clearMocks();
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤" --> ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Urgent(* +=[❤ ⟶ ',
type: 'text',
});
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
});
it('should be able to parse quadrant1 text', () => {
let str = 'quadrantChart\nquadrant-1 Plan';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-1 Plan ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-1 "Plan(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant2 text', () => {
let str = 'quadrantChart\nquadrant-2 do';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'do', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-2 Do ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-2 "Do(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant3 text', () => {
let str = 'quadrantChart\nquadrant-3 deligate';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'deligate', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-3 Deligate ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-3 "Deligate(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant4 text', () => {
let str = 'quadrantChart\nquadrant-4 delete';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'delete', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-4 Delete ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-4 "Delete(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete(* +=[❤', type: 'text' });
});
it('should be able to parse title', () => {
let str = 'quadrantChart\ntitle this is title';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this is title');
clearMocks();
str = 'QuadRantChart \n TiTle this Is title ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this Is title');
clearMocks();
str = 'QuadRantChart \n title "this is title (* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('"this is title (* +=[❤"');
});
it('should be able to parse points', () => {
let str = 'quadrantChart\npoint1: [0.1, 0.4]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'point1', type: 'text' }, '0.1', '0.4');
clearMocks();
str = 'QuadRantChart \n Point1 : [0.1, 0.4] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Point1', type: 'text' }, '0.1', '0.4');
clearMocks();
str = 'QuadRantChart \n "Point1 : (* +=[❤": [1, 0] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Point1 : (* +=[❤', type: 'text' },
'1',
'0'
);
clearMocks();
str = 'QuadRantChart \n Point1 : [1.2, 0.4] ';
expect(parserFnConstructor(str)).toThrow();
});
it('should be able to parse the whole chart', () => {
const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" --> "x-axis-2"
y-axis Ability to Execute --> "y-axis-2"
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75]
Salesforce: [0.55, 0.60]
IBM: [0.51, 0.40]
Incorta: [0.20, 0.30]`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
'arg_directive',
]);
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
'}%%',
'close_directive',
'quadrantChart',
]);
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Completeness of Vision ❤',
type: 'text',
});
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'x-axis-2', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'y-axis-2', type: 'text' });
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Ability to Execute',
type: 'text',
});
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Leaders', type: 'text' });
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Challengers', type: 'text' });
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Niche', type: 'text' });
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Microsoft', type: 'text' },
'0.75',
'0.75'
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Salesforce', type: 'text' },
'0.55',
'0.60'
);
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'IBM', type: 'text' }, '0.51', '0.40');
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Incorta', type: 'text' }, '0.20', '0.30');
});
});

View File

@@ -0,0 +1,601 @@
// @ts-ignore: TODO Fix ts errors
import { scaleLinear } from 'd3';
import { log } from '../../logger.js';
import { QuadrantChartConfig } from '../../config.type.js';
import defaultConfig from '../../defaultConfig.js';
import { getThemeVariables } from '../../themes/theme-default.js';
const defaultThemeVariables = getThemeVariables();
export type TextVerticalPos = 'left' | 'center' | 'right';
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
export interface Point {
x: number;
y: number;
}
export interface QuadrantPointInputType extends Point {
text: string;
}
export interface QuadrantTextType extends Point {
text: string;
fill: string;
verticalPos: TextVerticalPos;
horizontalPos: TextHorizontalPos;
fontSize: number;
rotation: number;
}
export interface QuadrantPointType extends Point {
fill: string;
radius: number;
text: QuadrantTextType;
}
export interface QuadrantQuadrantsType extends Point {
text: QuadrantTextType;
width: number;
height: number;
fill: string;
}
export interface QuadrantLineType {
strokeWidth: number;
strokeFill: string;
x1: number;
y1: number;
x2: number;
y2: number;
}
export interface QuadrantBuildType {
points: QuadrantPointType[];
quadrants: QuadrantQuadrantsType[];
axisLabels: QuadrantTextType[];
title?: QuadrantTextType;
borderLines?: QuadrantLineType[];
}
export interface quadrantBuilderData {
titleText: string;
quadrant1Text: string;
quadrant2Text: string;
quadrant3Text: string;
quadrant4Text: string;
xAxisLeftText: string;
xAxisRightText: string;
yAxisBottomText: string;
yAxisTopText: string;
points: QuadrantPointInputType[];
}
export interface QuadrantBuilderConfig extends QuadrantChartConfig {
showXAxis: boolean;
showYAxis: boolean;
showTitle: boolean;
}
export interface QuadrantBuilderThemeConfig {
quadrantTitleFill: string;
quadrant1Fill: string;
quadrant2Fill: string;
quadrant3Fill: string;
quadrant4Fill: string;
quadrant1TextFill: string;
quadrant2TextFill: string;
quadrant3TextFill: string;
quadrant4TextFill: string;
quadrantPointFill: string;
quadrantPointTextFill: string;
quadrantXAxisTextFill: string;
quadrantYAxisTextFill: string;
quadrantInternalBorderStrokeFill: string;
quadrantExternalBorderStrokeFill: string;
}
interface CalculateSpaceData {
xAxisSpace: {
top: number;
bottom: number;
};
yAxisSpace: {
left: number;
right: number;
};
titleSpace: {
top: number;
};
quadrantSpace: {
quadrantLeft: number;
quadrantTop: number;
quadrantWidth: number;
quadrantHalfWidth: number;
quadrantHeight: number;
quadrantHalfHeight: number;
};
}
export class QuadrantBuilder {
private config: QuadrantBuilderConfig;
private themeConfig: QuadrantBuilderThemeConfig;
private data: quadrantBuilderData;
constructor() {
this.config = this.getDefaultConfig();
this.themeConfig = this.getDefaultThemeConfig();
this.data = this.getDefaultData();
}
getDefaultData(): quadrantBuilderData {
return {
titleText: '',
quadrant1Text: '',
quadrant2Text: '',
quadrant3Text: '',
quadrant4Text: '',
xAxisLeftText: '',
xAxisRightText: '',
yAxisBottomText: '',
yAxisTopText: '',
points: [],
};
}
getDefaultConfig(): QuadrantBuilderConfig {
return {
showXAxis: true,
showYAxis: true,
showTitle: true,
chartHeight: defaultConfig.quadrantChart?.chartWidth || 500,
chartWidth: defaultConfig.quadrantChart?.chartHeight || 500,
titlePadding: defaultConfig.quadrantChart?.titlePadding || 10,
titleFontSize: defaultConfig.quadrantChart?.titleFontSize || 20,
quadrantPadding: defaultConfig.quadrantChart?.quadrantPadding || 5,
xAxisLabelPadding: defaultConfig.quadrantChart?.xAxisLabelPadding || 5,
yAxisLabelPadding: defaultConfig.quadrantChart?.yAxisLabelPadding || 5,
xAxisLabelFontSize: defaultConfig.quadrantChart?.xAxisLabelFontSize || 16,
yAxisLabelFontSize: defaultConfig.quadrantChart?.yAxisLabelFontSize || 16,
quadrantLabelFontSize: defaultConfig.quadrantChart?.quadrantLabelFontSize || 16,
quadrantTextTopPadding: defaultConfig.quadrantChart?.quadrantTextTopPadding || 5,
pointTextPadding: defaultConfig.quadrantChart?.pointTextPadding || 5,
pointLabelFontSize: defaultConfig.quadrantChart?.pointLabelFontSize || 12,
pointRadius: defaultConfig.quadrantChart?.pointRadius || 5,
xAxisPosition: defaultConfig.quadrantChart?.xAxisPosition || 'top',
yAxisPosition: defaultConfig.quadrantChart?.yAxisPosition || 'left',
quadrantInternalBorderStrokeWidth:
defaultConfig.quadrantChart?.quadrantInternalBorderStrokeWidth || 1,
quadrantExternalBorderStrokeWidth:
defaultConfig.quadrantChart?.quadrantExternalBorderStrokeWidth || 2,
};
}
getDefaultThemeConfig(): QuadrantBuilderThemeConfig {
return {
quadrant1Fill: defaultThemeVariables.quadrant1Fill,
quadrant2Fill: defaultThemeVariables.quadrant2Fill,
quadrant3Fill: defaultThemeVariables.quadrant3Fill,
quadrant4Fill: defaultThemeVariables.quadrant4Fill,
quadrant1TextFill: defaultThemeVariables.quadrant1TextFill,
quadrant2TextFill: defaultThemeVariables.quadrant2TextFill,
quadrant3TextFill: defaultThemeVariables.quadrant3TextFill,
quadrant4TextFill: defaultThemeVariables.quadrant4TextFill,
quadrantPointFill: defaultThemeVariables.quadrantPointFill,
quadrantPointTextFill: defaultThemeVariables.quadrantPointTextFill,
quadrantXAxisTextFill: defaultThemeVariables.quadrantXAxisTextFill,
quadrantYAxisTextFill: defaultThemeVariables.quadrantYAxisTextFill,
quadrantTitleFill: defaultThemeVariables.quadrantTitleFill,
quadrantInternalBorderStrokeFill: defaultThemeVariables.quadrantInternalBorderStrokeFill,
quadrantExternalBorderStrokeFill: defaultThemeVariables.quadrantExternalBorderStrokeFill,
};
}
clear() {
this.config = this.getDefaultConfig();
this.themeConfig = this.getDefaultThemeConfig();
this.data = this.getDefaultData();
log.info('clear called');
}
setData(data: Partial<quadrantBuilderData>) {
this.data = { ...this.data, ...data };
}
addPoints(points: QuadrantPointInputType[]) {
this.data.points = [...points, ...this.data.points];
}
setConfig(config: Partial<QuadrantBuilderConfig>) {
log.trace('setConfig called with: ', config);
this.config = { ...this.config, ...config };
}
setThemeConfig(themeConfig: Partial<QuadrantBuilderThemeConfig>) {
log.trace('setThemeConfig called with: ', themeConfig);
this.themeConfig = { ...this.themeConfig, ...themeConfig };
}
calculateSpace(
xAxisPosition: typeof this.config.xAxisPosition,
showXAxis: boolean,
showYAxis: boolean,
showTitle: boolean
): CalculateSpaceData {
const xAxisSpaceCalculation =
this.config.xAxisLabelPadding * 2 + this.config.xAxisLabelFontSize;
const xAxisSpace = {
top: xAxisPosition === 'top' && showXAxis ? xAxisSpaceCalculation : 0,
bottom: xAxisPosition === 'bottom' && showXAxis ? xAxisSpaceCalculation : 0,
};
const yAxisSpaceCalculation =
this.config.yAxisLabelPadding * 2 + this.config.yAxisLabelFontSize;
const yAxisSpace = {
left: this.config.yAxisPosition === 'left' && showYAxis ? yAxisSpaceCalculation : 0,
right: this.config.yAxisPosition === 'right' && showYAxis ? yAxisSpaceCalculation : 0,
};
const titleSpaceCalculation = this.config.titleFontSize + this.config.titlePadding * 2;
const titleSpace = {
top: showTitle ? titleSpaceCalculation : 0,
};
const quadrantLeft = this.config.quadrantPadding + yAxisSpace.left;
const quadrantTop = this.config.quadrantPadding + xAxisSpace.top + titleSpace.top;
const quadrantWidth =
this.config.chartWidth - this.config.quadrantPadding * 2 - yAxisSpace.left - yAxisSpace.right;
const quadrantHeight =
this.config.chartHeight -
this.config.quadrantPadding * 2 -
xAxisSpace.top -
xAxisSpace.bottom -
titleSpace.top;
const quadrantHalfWidth = quadrantWidth / 2;
const quadrantHalfHeight = quadrantHeight / 2;
const quadrantSpace = {
quadrantLeft,
quadrantTop,
quadrantWidth,
quadrantHalfWidth,
quadrantHeight,
quadrantHalfHeight,
};
return {
xAxisSpace,
yAxisSpace,
titleSpace,
quadrantSpace,
};
}
getAxisLabels(
xAxisPosition: typeof this.config.xAxisPosition,
showXAxis: boolean,
showYAxis: boolean,
spaceData: CalculateSpaceData
): QuadrantTextType[] {
const { quadrantSpace, titleSpace } = spaceData;
const {
quadrantHalfHeight,
quadrantHeight,
quadrantLeft,
quadrantHalfWidth,
quadrantTop,
quadrantWidth,
} = quadrantSpace;
const drawAxisLabelInMiddle = this.data.points.length === 0;
const axisLabels: QuadrantTextType[] = [];
if (this.data.xAxisLeftText && showXAxis) {
axisLabels.push({
text: this.data.xAxisLeftText,
fill: this.themeConfig.quadrantXAxisTextFill,
x: quadrantLeft + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
y:
xAxisPosition === 'top'
? this.config.xAxisLabelPadding + titleSpace.top
: this.config.xAxisLabelPadding +
quadrantTop +
quadrantHeight +
this.config.quadrantPadding,
fontSize: this.config.xAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: 0,
});
}
if (this.data.xAxisRightText && showXAxis) {
axisLabels.push({
text: this.data.xAxisRightText,
fill: this.themeConfig.quadrantXAxisTextFill,
x: quadrantLeft + quadrantHalfWidth + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
y:
xAxisPosition === 'top'
? this.config.xAxisLabelPadding + titleSpace.top
: this.config.xAxisLabelPadding +
quadrantTop +
quadrantHeight +
this.config.quadrantPadding,
fontSize: this.config.xAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: 0,
});
}
if (this.data.yAxisBottomText && showYAxis) {
axisLabels.push({
text: this.data.yAxisBottomText,
fill: this.themeConfig.quadrantYAxisTextFill,
x:
this.config.yAxisPosition === 'left'
? this.config.yAxisLabelPadding
: this.config.yAxisLabelPadding +
quadrantLeft +
quadrantWidth +
this.config.quadrantPadding,
y: quadrantTop + quadrantHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
fontSize: this.config.yAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: -90,
});
}
if (this.data.yAxisTopText && showYAxis) {
axisLabels.push({
text: this.data.yAxisTopText,
fill: this.themeConfig.quadrantYAxisTextFill,
x:
this.config.yAxisPosition === 'left'
? this.config.yAxisLabelPadding
: this.config.yAxisLabelPadding +
quadrantLeft +
quadrantWidth +
this.config.quadrantPadding,
y: quadrantTop + quadrantHalfHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
fontSize: this.config.yAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: -90,
});
}
return axisLabels;
}
getQuadrants(spaceData: CalculateSpaceData): QuadrantQuadrantsType[] {
const { quadrantSpace } = spaceData;
const { quadrantHalfHeight, quadrantLeft, quadrantHalfWidth, quadrantTop } = quadrantSpace;
const quadrants: QuadrantQuadrantsType[] = [
{
text: {
text: this.data.quadrant1Text,
fill: this.themeConfig.quadrant1TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft + quadrantHalfWidth,
y: quadrantTop,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant1Fill,
},
{
text: {
text: this.data.quadrant2Text,
fill: this.themeConfig.quadrant2TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft,
y: quadrantTop,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant2Fill,
},
{
text: {
text: this.data.quadrant3Text,
fill: this.themeConfig.quadrant3TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft,
y: quadrantTop + quadrantHalfHeight,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant3Fill,
},
{
text: {
text: this.data.quadrant4Text,
fill: this.themeConfig.quadrant4TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft + quadrantHalfWidth,
y: quadrantTop + quadrantHalfHeight,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant4Fill,
},
];
for (const quadrant of quadrants) {
quadrant.text.x = quadrant.x + quadrant.width / 2;
// place the text in the center of the box
if (this.data.points.length === 0) {
quadrant.text.y = quadrant.y + quadrant.height / 2;
quadrant.text.horizontalPos = 'middle';
// place the text top of the quadrant square
} else {
quadrant.text.y = quadrant.y + this.config.quadrantTextTopPadding;
quadrant.text.horizontalPos = 'top';
}
}
return quadrants;
}
getQuadrantPoints(spaceData: CalculateSpaceData): QuadrantPointType[] {
const { quadrantSpace } = spaceData;
const { quadrantHeight, quadrantLeft, quadrantTop, quadrantWidth } = quadrantSpace;
const xAxis = scaleLinear()
.domain([0, 1])
.range([quadrantLeft, quadrantWidth + quadrantLeft]);
const yAxis = scaleLinear()
.domain([0, 1])
.range([quadrantHeight + quadrantTop, quadrantTop]);
const points: QuadrantPointType[] = this.data.points.map((point) => {
const props: QuadrantPointType = {
x: xAxis(point.x),
y: yAxis(point.y),
fill: this.themeConfig.quadrantPointFill,
radius: this.config.pointRadius,
text: {
text: point.text,
fill: this.themeConfig.quadrantPointTextFill,
x: xAxis(point.x),
y: yAxis(point.y) + this.config.pointTextPadding,
verticalPos: 'center',
horizontalPos: 'top',
fontSize: this.config.pointLabelFontSize,
rotation: 0,
},
};
return props;
});
return points;
}
getBorders(spaceData: CalculateSpaceData): QuadrantLineType[] {
const halfExternalBorderWidth = this.config.quadrantExternalBorderStrokeWidth / 2;
const { quadrantSpace } = spaceData;
const {
quadrantHalfHeight,
quadrantHeight,
quadrantLeft,
quadrantHalfWidth,
quadrantTop,
quadrantWidth,
} = quadrantSpace;
const borderLines: QuadrantLineType[] = [
// top border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft - halfExternalBorderWidth,
y1: quadrantTop,
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
y2: quadrantTop,
},
// right border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft + quadrantWidth,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft + quadrantWidth,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// bottom border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft - halfExternalBorderWidth,
y1: quadrantTop + quadrantHeight,
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
y2: quadrantTop + quadrantHeight,
},
// left border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// vertical inner border
{
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
x1: quadrantLeft + quadrantHalfWidth,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft + quadrantHalfWidth,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// horizontal inner border
{
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
x1: quadrantLeft + halfExternalBorderWidth,
y1: quadrantTop + quadrantHalfHeight,
x2: quadrantLeft + quadrantWidth - halfExternalBorderWidth,
y2: quadrantTop + quadrantHalfHeight,
},
];
return borderLines;
}
getTitle(showTitle: boolean): QuadrantTextType | undefined {
if (showTitle) {
return {
text: this.data.titleText,
fill: this.themeConfig.quadrantTitleFill,
fontSize: this.config.titleFontSize,
horizontalPos: 'top',
verticalPos: 'center',
rotation: 0,
y: this.config.titlePadding,
x: this.config.chartWidth / 2,
};
}
return;
}
build(): QuadrantBuildType {
const showXAxis =
this.config.showXAxis && !!(this.data.xAxisLeftText || this.data.xAxisRightText);
const showYAxis =
this.config.showYAxis && !!(this.data.yAxisTopText || this.data.yAxisBottomText);
const showTitle = this.config.showTitle && !!this.data.titleText;
const xAxisPosition = this.data.points.length > 0 ? 'bottom' : this.config.xAxisPosition;
const calculatedSpace = this.calculateSpace(xAxisPosition, showXAxis, showYAxis, showTitle);
return {
points: this.getQuadrantPoints(calculatedSpace),
quadrants: this.getQuadrants(calculatedSpace),
axisLabels: this.getAxisLabels(xAxisPosition, showXAxis, showYAxis, calculatedSpace),
borderLines: this.getBorders(calculatedSpace),
title: this.getTitle(showTitle),
};
}
}

View File

@@ -0,0 +1,128 @@
import { log } from '../../logger.js';
import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
} from '../../commonDb.js';
import { QuadrantBuilder } from './quadrantBuilder.js';
const config = configApi.getConfig();
function textSanitizer(text: string) {
return sanitizeText(text.trim(), config);
}
type LexTextObj = { text: string; type: 'text' | 'markdown' };
const quadrantBuilder = new QuadrantBuilder();
function setQuadrant1Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant1Text: textSanitizer(textObj.text) });
}
function setQuadrant2Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant2Text: textSanitizer(textObj.text) });
}
function setQuadrant3Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant3Text: textSanitizer(textObj.text) });
}
function setQuadrant4Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant4Text: textSanitizer(textObj.text) });
}
function setXAxisLeftText(textObj: LexTextObj) {
quadrantBuilder.setData({ xAxisLeftText: textSanitizer(textObj.text) });
}
function setXAxisRightText(textObj: LexTextObj) {
quadrantBuilder.setData({ xAxisRightText: textSanitizer(textObj.text) });
}
function setYAxisTopText(textObj: LexTextObj) {
quadrantBuilder.setData({ yAxisTopText: textSanitizer(textObj.text) });
}
function setYAxisBottomText(textObj: LexTextObj) {
quadrantBuilder.setData({ yAxisBottomText: textSanitizer(textObj.text) });
}
function addPoint(textObj: LexTextObj, x: number, y: number) {
quadrantBuilder.addPoints([{ x, y, text: textSanitizer(textObj.text) }]);
}
function setWidth(width: number) {
quadrantBuilder.setConfig({ chartWidth: width });
}
function setHeight(height: number) {
quadrantBuilder.setConfig({ chartHeight: height });
}
function getQuadrantData() {
const config = configApi.getConfig();
const { themeVariables, quadrantChart: quadrantChartConfig } = config;
if (quadrantChartConfig) {
quadrantBuilder.setConfig(quadrantChartConfig);
}
quadrantBuilder.setThemeConfig({
quadrant1Fill: themeVariables.quadrant1Fill,
quadrant2Fill: themeVariables.quadrant2Fill,
quadrant3Fill: themeVariables.quadrant3Fill,
quadrant4Fill: themeVariables.quadrant4Fill,
quadrant1TextFill: themeVariables.quadrant1TextFill,
quadrant2TextFill: themeVariables.quadrant2TextFill,
quadrant3TextFill: themeVariables.quadrant3TextFill,
quadrant4TextFill: themeVariables.quadrant4TextFill,
quadrantPointFill: themeVariables.quadrantPointFill,
quadrantPointTextFill: themeVariables.quadrantPointTextFill,
quadrantXAxisTextFill: themeVariables.quadrantXAxisTextFill,
quadrantYAxisTextFill: themeVariables.quadrantYAxisTextFill,
quadrantExternalBorderStrokeFill: themeVariables.quadrantExternalBorderStrokeFill,
quadrantInternalBorderStrokeFill: themeVariables.quadrantInternalBorderStrokeFill,
quadrantTitleFill: themeVariables.quadrantTitleFill,
});
quadrantBuilder.setData({ titleText: getDiagramTitle() });
return quadrantBuilder.build();
}
export const parseDirective = function (statement: string, context: string, type: string) {
// @ts-ignore: TODO Fix ts errors
mermaidAPI.parseDirective(this, statement, context, type);
};
const clear = function () {
quadrantBuilder.clear();
commonClear();
};
export default {
setWidth,
setHeight,
setQuadrant1Text,
setQuadrant2Text,
setQuadrant3Text,
setQuadrant4Text,
setXAxisLeftText,
setXAxisRightText,
setYAxisTopText,
setYAxisBottomText,
addPoint,
getQuadrantData,
parseDirective,
clear,
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
};

View File

@@ -0,0 +1,20 @@
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
const id = 'quadrantChart';
const detector: DiagramDetector = (txt) => {
return txt.match(/^\s*quadrantChart/) !== null;
};
const loader = async () => {
const { diagram } = await import('./quadrantDiagram.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -0,0 +1,12 @@
import { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: TODO Fix ts errors
import parser from './parser/quadrant.jison';
import db from './quadrantDb.js';
import renderer from './quadrantRenderer.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles: () => '',
};

View File

@@ -0,0 +1,173 @@
// @ts-ignore: TODO Fix ts errors
import { select } from 'd3';
import * as configApi from '../../config.js';
import { log } from '../../logger.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { Diagram } from '../../Diagram.js';
import {
QuadrantBuildType,
QuadrantLineType,
QuadrantPointType,
QuadrantQuadrantsType,
QuadrantTextType,
TextHorizontalPos,
TextVerticalPos,
} from './quadrantBuilder.js';
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
function getDominantBaseLine(horizontalPos: TextHorizontalPos) {
return horizontalPos === 'top' ? 'hanging' : 'middle';
}
function getTextAnchor(verticalPos: TextVerticalPos) {
return verticalPos === 'left' ? 'start' : 'middle';
}
function getTransformation(data: { x: number; y: number; rotation: number }) {
return `translate(${data.x}, ${data.y}) rotate(${data.rotation || 0})`;
}
const conf = configApi.getConfig();
log.debug('Rendering quadrant chart\n' + txt);
const securityLevel = conf.securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const svg = root.select(`[id="${id}"]`);
const group = svg.append('g').attr('class', 'main');
const width = conf.quadrantChart?.chartWidth || 500;
const height = conf.quadrantChart?.chartHeight || 500;
configureSvgSize(svg, height, width, conf.quadrantChart?.useMaxWidth || true);
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
// @ts-ignore: TODO Fix ts errors
diagObj.db.setHeight(height);
// @ts-ignore: TODO Fix ts errors
diagObj.db.setWidth(width);
// @ts-ignore: TODO Fix ts errors
const quadrantData: QuadrantBuildType = diagObj.db.getQuadrantData();
const quadrantsGroup = group.append('g').attr('class', 'quadrants');
const borderGroup = group.append('g').attr('class', 'border');
const dataPointGroup = group.append('g').attr('class', 'data-points');
const labelGroup = group.append('g').attr('class', 'labels');
const titleGroup = group.append('g').attr('class', 'title');
if (quadrantData.title) {
titleGroup
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('fill', quadrantData.title.fill)
.attr('font-size', quadrantData.title.fontSize)
.attr('dominant-baseline', getDominantBaseLine(quadrantData.title.horizontalPos))
.attr('text-anchor', getTextAnchor(quadrantData.title.verticalPos))
.attr('transform', getTransformation(quadrantData.title))
.text(quadrantData.title.text);
}
if (quadrantData.borderLines) {
borderGroup
.selectAll('line')
.data(quadrantData.borderLines)
.enter()
.append('line')
.attr('x1', (data: QuadrantLineType) => data.x1)
.attr('y1', (data: QuadrantLineType) => data.y1)
.attr('x2', (data: QuadrantLineType) => data.x2)
.attr('y2', (data: QuadrantLineType) => data.y2)
.style('stroke', (data: QuadrantLineType) => data.strokeFill)
.style('stroke-width', (data: QuadrantLineType) => data.strokeWidth);
}
const quadrants = quadrantsGroup
.selectAll('g.quadrant')
.data(quadrantData.quadrants)
.enter()
.append('g')
.attr('class', 'quadrant');
quadrants
.append('rect')
.attr('x', (data: QuadrantQuadrantsType) => data.x)
.attr('y', (data: QuadrantQuadrantsType) => data.y)
.attr('width', (data: QuadrantQuadrantsType) => data.width)
.attr('height', (data: QuadrantQuadrantsType) => data.height)
.attr('fill', (data: QuadrantQuadrantsType) => data.fill);
quadrants
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('fill', (data: QuadrantQuadrantsType) => data.text.fill)
.attr('font-size', (data: QuadrantQuadrantsType) => data.text.fontSize)
.attr('dominant-baseline', (data: QuadrantQuadrantsType) =>
getDominantBaseLine(data.text.horizontalPos)
)
.attr('text-anchor', (data: QuadrantQuadrantsType) => getTextAnchor(data.text.verticalPos))
.attr('transform', (data: QuadrantQuadrantsType) => getTransformation(data.text))
.text((data: QuadrantQuadrantsType) => data.text.text);
const labels = labelGroup
.selectAll('g.label')
.data(quadrantData.axisLabels)
.enter()
.append('g')
.attr('class', 'label');
labels
.append('text')
.attr('x', 0)
.attr('y', 0)
.text((data: QuadrantTextType) => data.text)
.attr('fill', (data: QuadrantTextType) => data.fill)
.attr('font-size', (data: QuadrantTextType) => data.fontSize)
.attr('dominant-baseline', (data: QuadrantTextType) => getDominantBaseLine(data.horizontalPos))
.attr('text-anchor', (data: QuadrantTextType) => getTextAnchor(data.verticalPos))
.attr('transform', (data: QuadrantTextType) => getTransformation(data));
const dataPoints = dataPointGroup
.selectAll('g.data-point')
.data(quadrantData.points)
.enter()
.append('g')
.attr('class', 'data-point');
dataPoints
.append('circle')
.attr('cx', (data: QuadrantPointType) => data.x)
.attr('cy', (data: QuadrantPointType) => data.y)
.attr('r', (data: QuadrantPointType) => data.radius)
.attr('fill', (data: QuadrantPointType) => data.fill);
dataPoints
.append('text')
.attr('x', 0)
.attr('y', 0)
.text((data: QuadrantPointType) => data.text.text)
.attr('fill', (data: QuadrantPointType) => data.text.fill)
.attr('font-size', (data: QuadrantPointType) => data.text.fontSize)
.attr('dominant-baseline', (data: QuadrantPointType) =>
getDominantBaseLine(data.text.horizontalPos)
)
.attr('text-anchor', (data: QuadrantPointType) => getTextAnchor(data.text.verticalPos))
.attr('transform', (data: QuadrantPointType) => getTransformation(data.text));
};
export default {
draw,
};

View File

@@ -47,6 +47,7 @@
"alt" { this.begin('LINE'); return 'alt'; }
"else" { this.begin('LINE'); return 'else'; }
"par" { this.begin('LINE'); return 'par'; }
"par_over" { this.begin('LINE'); return 'par_over'; }
"and" { this.begin('LINE'); return 'and'; }
"critical" { this.begin('LINE'); return 'critical'; }
"option" { this.begin('LINE'); return 'option'; }
@@ -190,6 +191,14 @@ statement
// End
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
$$=$3;}
| par_over restOfLine par_sections end
{
// Parallel (overlapped) start
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_OVER_START});
// Content in par is already in $3
// End
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
$$=$3;}
| critical restOfLine option_sections end
{
// critical start

View File

@@ -286,6 +286,7 @@ export const LINETYPE = {
CRITICAL_END: 29,
BREAK_START: 30,
BREAK_END: 31,
PAR_OVER_START: 32,
};
export const ARROWTYPE = {

View File

@@ -1126,6 +1126,29 @@ end`;
expect(messages[1].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should handle par_over statements', async () => {
const str = `
sequenceDiagram
par_over Parallel overlap
Alice ->> Bob: Message
Note left of Alice: Alice note
Note right of Bob: Bob note
end`;
await mermaidAPI.parse(str);
const actors = diagram.db.getActors();
expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob');
const messages = diagram.db.getMessages();
expect(messages.length).toBe(5);
expect(messages[0].message).toBe('Parallel overlap');
expect(messages[1].from).toBe('Alice');
expect(messages[2].from).toBe('Alice');
expect(messages[3].from).toBe('Bob');
});
it('should handle special characters in signals', async () => {
const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment';

View File

@@ -3,6 +3,7 @@ import { select, selectAll } from 'd3';
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
import { log } from '../../logger.js';
import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon';
import * as configApi from '../../config.js';
import assignWithDepth from '../../assignWithDepth.js';
import utils from '../../utils.js';
@@ -130,10 +131,10 @@ export const bounds = {
this.activations.forEach(updateFn('activation'));
},
insert: function (startx, starty, stopx, stopy) {
const _startx = Math.min(startx, stopx);
const _stopx = Math.max(startx, stopx);
const _starty = Math.min(starty, stopy);
const _stopy = Math.max(starty, stopy);
const _startx = common.getMin(startx, stopx);
const _stopx = common.getMax(startx, stopx);
const _starty = common.getMin(starty, stopy);
const _stopy = common.getMax(starty, stopy);
this.updateVal(bounds.data, 'startx', _startx, Math.min);
this.updateVal(bounds.data, 'starty', _starty, Math.min);
@@ -183,6 +184,11 @@ export const bounds = {
endLoop: function () {
return this.sequenceItems.pop();
},
isLoopOverlap: function () {
return this.sequenceItems.length
? this.sequenceItems[this.sequenceItems.length - 1].overlap
: false;
},
addSectionToLoop: function (message) {
const loop = this.sequenceItems.pop();
loop.sections = loop.sections || [];
@@ -191,9 +197,19 @@ export const bounds = {
loop.sectionTitles.push(message);
this.sequenceItems.push(loop);
},
saveVerticalPos: function () {
if (this.isLoopOverlap()) {
this.savedVerticalPos = this.verticalPos;
}
},
resetVerticalPos: function () {
if (this.isLoopOverlap()) {
this.verticalPos = this.savedVerticalPos;
}
},
bumpVerticalPos: function (bump) {
this.verticalPos = this.verticalPos + bump;
this.data.stopy = this.verticalPos;
this.data.stopy = common.getMax(this.data.stopy, this.verticalPos);
},
getVerticalPos: function () {
return this.verticalPos;
@@ -225,7 +241,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
bounds.bumpVerticalPos(conf.boxMargin);
noteModel.height = conf.boxMargin;
noteModel.starty = bounds.getVerticalPos();
const rect = svgDraw.getNoteRect();
const rect = svgDrawCommon.getNoteRect();
rect.x = noteModel.startx;
rect.y = noteModel.starty;
rect.width = noteModel.width || conf.width;
@@ -233,7 +249,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
const g = elem.append('g');
const rectElem = svgDraw.drawRect(g, rect);
const textObj = svgDraw.getTextObj();
const textObj = svgDrawCommon.getTextObj();
textObj.x = noteModel.startx;
textObj.y = noteModel.starty;
textObj.width = rect.width;
@@ -316,7 +332,7 @@ function boundMessage(_diagram, msgModel): number {
lineStartY = bounds.getVerticalPos() + totalOffset;
}
totalOffset += 30;
const dx = Math.max(textWidth / 2, conf.width / 2);
const dx = common.getMax(textWidth / 2, conf.width / 2);
bounds.insert(
startx - dx,
bounds.getVerticalPos() - 10 + totalOffset,
@@ -347,7 +363,7 @@ function boundMessage(_diagram, msgModel): number {
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
const textObj = svgDraw.getTextObj();
const textObj = svgDrawCommon.getTextObj();
textObj.x = startx;
textObj.y = starty + 10;
textObj.width = stopx - startx;
@@ -373,9 +389,9 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
.append('path')
.attr(
'd',
`M ${startx},${lineStartY} H ${startx + Math.max(conf.width / 2, textWidth / 2)} V ${
lineStartY + 25
} H ${startx}`
`M ${startx},${lineStartY} H ${
startx + common.getMax(conf.width / 2, textWidth / 2)
} V ${lineStartY + 25} H ${startx}`
);
} else {
line = diagram
@@ -509,7 +525,7 @@ export const drawActors = function (
// Add some rendering data to the object
actor.width = actor.width || conf.width;
actor.height = Math.max(actor.height || conf.height, conf.height);
actor.height = common.getMax(actor.height || conf.height, conf.height);
actor.margin = actor.margin || conf.actorMargin;
actor.x = prevWidth + prevMargin;
@@ -517,7 +533,7 @@ export const drawActors = function (
// Draw the box with the attached line
const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
maxHeight = Math.max(maxHeight, height);
maxHeight = common.getMax(maxHeight, height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width + prevMargin;
@@ -589,10 +605,10 @@ const activationBounds = function (actor, actors) {
const activations = actorActivations(actor);
const left = activations.reduce(function (acc, activation) {
return Math.min(acc, activation.startx);
return common.getMin(acc, activation.startx);
}, actorObj.x + actorObj.width / 2);
const right = activations.reduce(function (acc, activation) {
return Math.max(acc, activation.stopx);
return common.getMax(acc, activation.stopx);
}, actorObj.x + actorObj.width / 2);
return [left, right];
};
@@ -609,7 +625,7 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
// const lines = common.splitBreaks(msg.message).length;
const textDims = utils.calculateTextDimensions(msg.message, textConf);
const totalOffset = Math.max(textDims.height, conf.labelBoxHeight);
const totalOffset = common.getMax(textDims.height, conf.labelBoxHeight);
heightAdjust = postMargin + totalOffset;
log.debug(`${totalOffset} - ${msg.message}`);
}
@@ -709,6 +725,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
switch (msg.type) {
case diagObj.db.LINETYPE.NOTE:
bounds.resetVerticalPos();
noteModel = msg.noteModel;
drawNote(diagram, noteModel);
break;
@@ -784,6 +801,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
bounds.models.addLoop(loopModel);
break;
case diagObj.db.LINETYPE.PAR_START:
case diagObj.db.LINETYPE.PAR_OVER_START:
adjustLoopHeightForWrap(
loopWidths,
msg,
@@ -791,6 +809,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
conf.boxMargin + conf.boxTextMargin,
(message) => bounds.newLoop(message)
);
bounds.saveVerticalPos();
break;
case diagObj.db.LINETYPE.PAR_AND:
adjustLoopHeightForWrap(
@@ -858,6 +877,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
default:
try {
// lastMsg = msg
bounds.resetVerticalPos();
msgModel = msg.msgModel;
msgModel.starty = bounds.getVerticalPos();
msgModel.sequenceIndex = sequenceIndex;
@@ -1025,45 +1045,45 @@ function getMaxMessageWidthPerActor(
* margin
*/
if (isMessage && msg.from === actor.nextActor) {
maxMessageWidthPerActor[msg.to] = Math.max(
maxMessageWidthPerActor[msg.to] = common.getMax(
maxMessageWidthPerActor[msg.to] || 0,
messageWidth
);
} else if (isMessage && msg.from === actor.prevActor) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] = common.getMax(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth
);
} else if (isMessage && msg.from === msg.to) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] = common.getMax(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth / 2
);
maxMessageWidthPerActor[msg.to] = Math.max(
maxMessageWidthPerActor[msg.to] = common.getMax(
maxMessageWidthPerActor[msg.to] || 0,
messageWidth / 2
);
} else if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] = common.getMax(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth
);
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
maxMessageWidthPerActor[actor.prevActor] || 0,
messageWidth
);
} else if (msg.placement === diagObj.db.PLACEMENT.OVER) {
if (actor.prevActor) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
maxMessageWidthPerActor[actor.prevActor] || 0,
messageWidth / 2
);
}
if (actor.nextActor) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] = common.getMax(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth / 2
);
@@ -1119,10 +1139,10 @@ function calculateActorMargins(
const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf));
actor.width = actor.wrap
? conf.width
: Math.max(conf.width, actDims.width + 2 * conf.wrapPadding);
: common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding);
actor.height = actor.wrap ? Math.max(actDims.height, conf.height) : conf.height;
maxHeight = Math.max(maxHeight, actor.height);
actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height;
maxHeight = common.getMax(maxHeight, actor.height);
});
for (const actorKey in actorToMessageWidth) {
@@ -1138,14 +1158,14 @@ function calculateActorMargins(
if (!nextActor) {
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2;
actor.margin = Math.max(actorWidth, conf.actorMargin);
actor.margin = common.getMax(actorWidth, conf.actorMargin);
continue;
}
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
actor.margin = Math.max(actorWidth, conf.actorMargin);
actor.margin = common.getMax(actorWidth, conf.actorMargin);
}
let maxBoxHeight = 0;
@@ -1161,8 +1181,8 @@ function calculateActorMargins(
}
const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont);
maxBoxHeight = Math.max(boxMsgDimensions.height, maxBoxHeight);
const minWidth = Math.max(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
maxBoxHeight = common.getMax(boxMsgDimensions.height, maxBoxHeight);
const minWidth = common.getMax(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
box.margin = conf.boxTextMargin;
if (totalWidth < minWidth) {
const missing = (minWidth - totalWidth) / 2;
@@ -1171,7 +1191,7 @@ function calculateActorMargins(
});
boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight));
return Math.max(maxHeight, conf.height);
return common.getMax(maxHeight, conf.height);
}
const buildNoteModel = function (msg, actors, diagObj) {
@@ -1186,7 +1206,7 @@ const buildNoteModel = function (msg, actors, diagObj) {
const noteModel = {
width: shouldWrap
? conf.width
: Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin),
: common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin),
height: 0,
startx: actors[msg.from].x,
stopx: 0,
@@ -1196,16 +1216,16 @@ const buildNoteModel = function (msg, actors, diagObj) {
};
if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
noteModel.width = shouldWrap
? Math.max(conf.width, textDimensions.width)
: Math.max(
? common.getMax(conf.width, textDimensions.width)
: common.getMax(
actors[msg.from].width / 2 + actors[msg.to].width / 2,
textDimensions.width + 2 * conf.noteMargin
);
noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2;
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
noteModel.width = shouldWrap
? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin)
: Math.max(
? common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin)
: common.getMax(
actors[msg.from].width / 2 + actors[msg.to].width / 2,
textDimensions.width + 2 * conf.noteMargin
);
@@ -1213,13 +1233,21 @@ const buildNoteModel = function (msg, actors, diagObj) {
} else if (msg.to === msg.from) {
textDimensions = utils.calculateTextDimensions(
shouldWrap
? utils.wrapLabel(msg.message, Math.max(conf.width, actors[msg.from].width), noteFont(conf))
? utils.wrapLabel(
msg.message,
common.getMax(conf.width, actors[msg.from].width),
noteFont(conf)
)
: msg.message,
noteFont(conf)
);
noteModel.width = shouldWrap
? Math.max(conf.width, actors[msg.from].width)
: Math.max(actors[msg.from].width, conf.width, textDimensions.width + 2 * conf.noteMargin);
? common.getMax(conf.width, actors[msg.from].width)
: common.getMax(
actors[msg.from].width,
conf.width,
textDimensions.width + 2 * conf.noteMargin
);
noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2;
} else {
noteModel.width =
@@ -1271,14 +1299,14 @@ const buildMessageModel = function (msg, actors, diagObj) {
if (msg.wrap && msg.message) {
msg.message = utils.wrapLabel(
msg.message,
Math.max(boundedWidth + 2 * conf.wrapPadding, conf.width),
common.getMax(boundedWidth + 2 * conf.wrapPadding, conf.width),
messageFont(conf)
);
}
const msgDims = utils.calculateTextDimensions(msg.message, messageFont(conf));
return {
width: Math.max(
width: common.getMax(
msg.wrap ? 0 : msgDims.width + 2 * conf.wrapPadding,
boundedWidth + 2 * conf.wrapPadding,
conf.width
@@ -1308,6 +1336,7 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
case diagObj.db.LINETYPE.ALT_START:
case diagObj.db.LINETYPE.OPT_START:
case diagObj.db.LINETYPE.PAR_START:
case diagObj.db.LINETYPE.PAR_OVER_START:
case diagObj.db.LINETYPE.CRITICAL_START:
case diagObj.db.LINETYPE.BREAK_START:
stack.push({
@@ -1367,10 +1396,10 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
msg.noteModel = noteModel;
stack.forEach((stk) => {
current = stk;
current.from = Math.min(current.from, noteModel.startx);
current.to = Math.max(current.to, noteModel.startx + noteModel.width);
current.from = common.getMin(current.from, noteModel.startx);
current.to = common.getMax(current.to, noteModel.startx + noteModel.width);
current.width =
Math.max(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
common.getMax(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
});
} else {
msgModel = buildMessageModel(msg, actors, diagObj);
@@ -1381,18 +1410,23 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
if (msgModel.startx === msgModel.stopx) {
const from = actors[msg.from];
const to = actors[msg.to];
current.from = Math.min(
current.from = common.getMin(
from.x - msgModel.width / 2,
from.x - from.width / 2,
current.from
);
current.to = Math.max(to.x + msgModel.width / 2, to.x + from.width / 2, current.to);
current.to = common.getMax(
to.x + msgModel.width / 2,
to.x + from.width / 2,
current.to
);
current.width =
Math.max(current.width, Math.abs(current.to - current.from)) - conf.labelBoxWidth;
common.getMax(current.width, Math.abs(current.to - current.from)) -
conf.labelBoxWidth;
} else {
current.from = Math.min(msgModel.startx, current.from);
current.to = Math.max(msgModel.stopx, current.to);
current.width = Math.max(current.width, msgModel.width) - conf.labelBoxWidth;
current.from = common.getMin(msgModel.startx, current.from);
current.to = common.getMax(msgModel.stopx, current.to);
current.width = common.getMax(current.width, msgModel.width) - conf.labelBoxWidth;
}
});
}

View File

@@ -1,33 +1,13 @@
import common from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon';
import { addFunction } from '../../interactionDb.js';
import { parseFontSize } from '../../utils.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
export const drawRect = function (elem, rectData) {
const rectElem = elem.append('rect');
rectElem.attr('x', rectData.x);
rectElem.attr('y', rectData.y);
rectElem.attr('fill', rectData.fill);
rectElem.attr('stroke', rectData.stroke);
rectElem.attr('width', rectData.width);
rectElem.attr('height', rectData.height);
rectElem.attr('rx', rectData.rx);
rectElem.attr('ry', rectData.ry);
if (rectData.class !== undefined) {
rectElem.attr('class', rectData.class);
}
return rectElem;
return svgDrawCommon.drawRect(elem, rectData);
};
// const sanitizeUrl = function (s) {
// return s
// .replace(/&/g, '&amp;')
// .replace(/</g, '&lt;')
// .replace(/javascript:/g, '');
// };
const addPopupInteraction = (id, actorCnt) => {
addFunction(() => {
const arr = document.querySelectorAll(id);
@@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => {
});
});
};
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
return { height: 0, width: 0 };
@@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
return { height: rectData.height + linkY, width: menuWidth };
};
export const drawImage = function (elem, x, y, link) {
const imageElem = elem.append('image');
imageElem.attr('x', x);
imageElem.attr('y', y);
var sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', sanitizedLink);
};
export const drawEmbeddedImage = function (elem, x, y, link) {
const imageElem = elem.append('use');
imageElem.attr('x', x);
imageElem.attr('y', y);
var sanitizedLink = sanitizeUrl(link);
imageElem.attr('xlink:href', '#' + sanitizedLink);
};
export const popupMenu = function (popid) {
return (
"var pu = document.getElementById('" +
@@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) {
pu.style.display = 'none';
}
};
export const drawText = function (elem, textData) {
let prevTextHeight = 0,
textHeight = 0;
let prevTextHeight = 0;
let textHeight = 0;
const lines = textData.text.split(common.lineBreakRegex);
const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize);
@@ -188,6 +154,7 @@ export const drawText = function (elem, textData) {
break;
}
}
if (
textData.anchor !== undefined &&
textData.textMargin !== undefined &&
@@ -217,6 +184,7 @@ export const drawText = function (elem, textData) {
break;
}
}
for (let [i, line] of lines.entries()) {
if (
textData.textMargin !== undefined &&
@@ -256,15 +224,16 @@ export const drawText = function (elem, textData) {
textElem.attr('dy', dy);
}
const text = line || ZERO_WIDTH_SPACE;
if (textData.tspan) {
const span = textElem.append('tspan');
span.attr('x', textData.x);
if (textData.fill !== undefined) {
span.attr('fill', textData.fill);
}
span.text(line);
span.text(text);
} else {
textElem.text(line);
textElem.text(text);
}
if (
textData.valign !== undefined &&
@@ -371,7 +340,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
}
}
const rect = getNoteRect();
const rect = svgDrawCommon.getNoteRect();
var cssclass = 'actor';
if (actor.properties != null && actor.properties['class']) {
cssclass = actor.properties['class'];
@@ -391,9 +360,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
if (actor.properties != null && actor.properties['icon']) {
const iconSrc = actor.properties['icon'].trim();
if (iconSrc.charAt(0) === '@') {
drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
} else {
drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
}
}
@@ -438,7 +407,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const actElem = elem.append('g');
actElem.attr('class', 'actor-man');
const rect = getNoteRect();
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actor.y;
rect.fill = '#eaeaea';
@@ -447,7 +416,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
// drawRect(actElem, rect);
actElem
.append('line')
@@ -532,6 +500,7 @@ export const drawBox = function (elem, box, conf) {
export const anchorElement = function (elem) {
return elem.append('g');
};
/**
* Draws an activation in the diagram
*
@@ -542,7 +511,7 @@ export const anchorElement = function (elem) {
* @param {any} actorActivations - Number of activations on the actor.
*/
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
const rect = getNoteRect();
const rect = svgDrawCommon.getNoteRect();
const g = bounds.anchored;
rect.x = bounds.startx;
rect.y = bounds.starty;
@@ -594,7 +563,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
});
}
let txt = getTextObj();
let txt = svgDrawCommon.getTextObj();
txt.text = labelText;
txt.x = loopModel.startx;
txt.y = loopModel.starty;
@@ -661,16 +630,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
* @param {any} bounds Shape of the rectangle
*/
export const drawBackgroundRect = function (elem, bounds) {
const rectElem = drawRect(elem, {
x: bounds.startx,
y: bounds.starty,
width: bounds.stopx - bounds.startx,
height: bounds.stopy - bounds.starty,
fill: bounds.fill,
stroke: bounds.stroke,
class: 'rect',
});
rectElem.lower();
svgDrawCommon.drawBackgroundRect(elem, bounds);
};
export const insertDatabaseIcon = function (elem) {
@@ -737,6 +697,7 @@ export const insertArrowHead = function (elem) {
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*
@@ -755,6 +716,7 @@ export const insertArrowFilledHead = function (elem) {
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
};
/**
* Setup node number. The result is appended to the svg.
*
@@ -776,6 +738,7 @@ export const insertSequenceNumber = function (elem) {
.attr('r', 6);
// .style("fill", '#f00');
};
/**
* Setup cross head and define the marker. The result is appended to the svg.
*
@@ -1062,8 +1025,6 @@ export default {
drawActor,
drawBox,
drawPopup,
drawImage,
drawEmbeddedImage,
anchorElement,
drawActivation,
drawLoop,

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