Compare commits

...

148 Commits

Author SHA1 Message Date
Sidharth Vinod
9a3498fba8 fix: Handle langium data loss warning
It's not an issue to actually lose the data, as we're just taking the last value anyways. But having the warnings clog up the console means we might miss actual warnings.
2025-04-15 09:48:50 +05:30
Sidharth Vinod
3ae87ca06a chore: Ignore timeout domains 2025-04-15 09:42:52 +05:30
Sidharth Vinod
3e8204aa21 Merge pull request #6365 from mermaid-js/renovate/major-all-major
fix(deps): update all major dependencies (major)
2025-04-15 03:13:33 +00:00
Sidharth Vinod
0b57984d27 Merge pull request #6475 from Shahir-47/feature/5806_xy-chart-data-labels
feat: Dynamically Render Data Labels Within Bar Charts
2025-04-15 03:13:03 +00:00
Sidharth Vinod
bcaa40f1d5 Merge pull request #6225 from Shahir-47/feature/3508_color-user-journey-title
feat: Add support for styling Journey Diagram title (color, font-family, and font-size)
2025-04-15 03:12:24 +00:00
autofix-ci[bot]
0107494b59 [autofix.ci] apply automated fixes 2025-04-15 02:32:13 +00:00
autofix-ci[bot]
11c3ac58fd [autofix.ci] apply automated fixes 2025-04-15 02:25:23 +00:00
Sidharth Vinod
af2eb13932 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-04-14 19:23:47 -07:00
Sidharth Vinod
41e84b726a Create neat-moose-compare.md 2025-04-14 19:23:19 -07:00
Sidharth Vinod
a1ba65c0c0 chore: Add changeset 2025-04-14 19:20:47 -07:00
Shahir Ahmed
5fe6e5dccc Merge branch 'develop' into feature/5806_xy-chart-data-labels
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-14 14:44:53 -04:00
pranavm2109
946452c7b4 updated tests
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-04-14 14:38:23 -04:00
pranavm2109
4bb6351489 modified description of showDataLabel in config schema and added it to docs
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-04-14 13:53:18 -04:00
renovate[bot]
8c63a2e411 fix(deps): update all major dependencies 2025-04-14 07:25:55 +00:00
Sidharth Vinod
4467fd4363 Merge pull request #6482 from mermaid-js/renovate/peter-evans-create-pull-request-digest
chore(deps): update peter-evans/create-pull-request digest to a7b20e1
2025-04-14 07:09:53 +00:00
renovate[bot]
7e567a8759 chore(deps): update peter-evans/create-pull-request digest to a7b20e1 2025-04-14 06:17:30 +00:00
Ashish Jain
edf062720d Merge pull request #6459 from mermaid-js/renovate/patch-dompurify
fix(deps): update dependency dompurify to ^3.2.5
2025-04-14 06:03:40 +00:00
renovate[bot]
3348eea6af fix(deps): update dependency dompurify to ^3.2.5 2025-04-14 04:46:04 +00:00
Sidharth Vinod
ae7ffab9ac Merge pull request #6483 from universeroc/develop
Add Mermaid plus for Confluence into integrations-community.md
2025-04-14 04:32:13 +00:00
autofix-ci[bot]
ecdbc676d2 [autofix.ci] apply automated fixes 2025-04-14 04:01:42 +00:00
universeroc
6a01b04e3c Update integrations-community.md 2025-04-14 11:55:45 +08:00
autofix-ci[bot]
aafe8de3d3 [autofix.ci] apply automated fixes 2025-04-14 03:52:31 +00:00
universeroc
24287637b5 Add Mermaid plus for Confluence into integrations-community.md 2025-04-14 11:43:32 +08:00
Shahir Ahmed
f69cc17795 refactor xy-chart tests
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-11 16:24:10 -04:00
Shahir Ahmed
934f4da507 Merge remote-tracking branch 'origin/develop' into feature/3508_color-user-journey-title
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-11 00:16:46 -04:00
Shahir Ahmed
6957f35782 Merge branch 'develop' into feature/5806_xy-chart-data-labels
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-10 21:42:37 -04:00
Shahir Ahmed
b00be59ea8 added new tests
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-10 21:22:36 -04:00
Shahir Ahmed
dff00f2c4f added new tests
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-10 21:04:55 -04:00
Sidharth Vinod
eb9987435a Merge pull request #6364 from mermaid-js/renovate/eslint
chore(deps): update eslint (minor)
2025-04-10 07:18:54 +00:00
Sidharth Vinod
a300d0db94 Merge pull request #6391 from regisbsb/patch-1
Remove duplicates in integrations-community.md
2025-04-10 07:16:37 +00:00
Sidharth Vinod
7e7f3c56c2 Merge pull request #6456 from nour0205/fix/edge-id-en-dash-docs-6455
fix(docs): fix edge ID example
2025-04-10 07:10:08 +00:00
renovate[bot]
ea987861f3 chore(deps): update eslint 2025-04-09 13:45:21 +00:00
Sidharth Vinod
178c7130c6 Merge pull request #6465 from aloisklink/build/fix-mermaid-zenuml
build: fix `mermaid-zenuml` IIFE build and add changesets
2025-04-09 01:42:37 -07:00
Sidharth Vinod
f87d0dd88a chore: Update change detection logic 2025-04-09 13:20:19 +05:30
Sidharth Vinod
2396f90269 Merge pull request #6467 from mermaid-js/sidv/checkTimings
Reduce timing update PRs
2025-04-08 23:54:15 -07:00
Sidharth Vinod
97e35fd30a chore: Use git to read old timings 2025-04-09 12:19:13 +05:30
Sidharth Vinod
7a5f999f42 chore: Add compare-timings script
Avoid creating unnecessary PRs when there is no significant timings change.
2025-04-09 12:08:57 +05:30
Sidharth Vinod
cd8d74bb96 Merge pull request #6274 from Shahir-47/bug/5955_adjust-diagram-position-with-legend-width
fix: Prevent Legend Labels from Overlapping Diagram Elements in Journey Diagrams
2025-04-08 17:17:06 +00:00
Sidharth Vinod
56c6853e05 Merge pull request #6463 from AaronMoat/undefined
Fix incorrect `style="undefined;"` output in some Mermaid diagrams
2025-04-08 12:46:11 +00:00
Sidharth Vinod
01e2af0cfd Merge pull request #6407 from thomascizeron/fix/6162_greedyRegexInCommonLangium
Fix: greedy regex in common langium
2025-04-08 11:33:43 +00:00
Sidharth Vinod
f55ff99f74 Merge branch 'develop' into fix/6162_greedyRegexInCommonLangium 2025-04-08 04:25:20 -07:00
Aaron Moat
d25770ee73 Format changeset message 2025-04-08 19:55:05 +10:00
Aaron Moat
2b05d7e1ed Fix incorrect style="undefined;" output in some Mermaid diagrams 2025-04-08 19:49:47 +10:00
Sidharth Vinod
30735266a4 Merge pull request #6461 from mermaid-js/update-timings
Update E2E Timings
2025-04-08 00:14:25 -07:00
github-actions[bot]
926862c196 chore: update E2E timings 2025-04-08 06:53:44 +00:00
Sidharth Vinod
44e668e704 chore: Update permission for timings action 2025-04-08 11:58:46 +05:30
Sidharth Vinod
404216273a chore: Fix PR action in e2e-timings 2025-04-08 11:01:28 +05:30
Sidharth Vinod
5a6831ae7e chore: Fix branch in e2e-timings 2025-04-08 10:13:59 +05:30
Alois Klink
03119fea2c chore(zenuml): add pending ZenUML changesets
Add ZenUML changesets for:

- Bumping the minimum version of ZenUML to 3.23.28
- Limiting the `peerDepdencies` to v10 and v11

See: 9d06d8f31e
See: 0ad44c12fe
2025-04-07 17:58:40 +08:00
Alois Klink
5351211256 build(zenuml): add back IIFE ZenUML builds
These are present in [v0.2.0][1] so removing these would be a breaking
change. I suspect we accidentally removed them when we moved from
Vite to ESBuild.

[1]: https://www.npmjs.com/package/@mermaid-js/mermaid-zenuml/v/0.2.0
2025-04-07 17:58:40 +08:00
Alois Klink
92c0aa4331 build(esbuild): support multiple IIFE packages
Support multiple IIFE packages by namespacing all of them into a
`globalThis.__esbuild_esm_mermaid_nm` object.
2025-04-07 17:58:40 +08:00
Alois Klink
7bd5c03d87 Merge pull request #6426 from internetisaiah/patch-1
Fix formatting of ELK example in ERD + clarify usage
2025-04-07 08:35:03 +00:00
Alois Klink
de72e18a7f docs: improve elk section of ER docs 2025-04-07 16:07:43 +08:00
nour kouider
4fdb1d5906 docs: correct edge syntax from -> to --> 2025-04-05 13:39:30 +01:00
Shahir Ahmed
a2bf5103ce test for vertical xy-axis
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-04 21:44:00 -04:00
Shahir Ahmed
8b7a4db2ef test for horzontal xy-axis
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-04 21:34:46 -04:00
Shahir Ahmed
73f8dee643 add config to test files
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-04-04 15:22:40 -04:00
pranavm2109
8bdd7ec719 updated tests to account for showDataLabel set to false as default
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-04-04 15:20:51 -04:00
nour kouider
7facc8f50d docs: fix edge ID example and regenerate flowchart output 2025-04-04 19:04:02 +01:00
autofix-ci[bot]
52cd9e8e55 [autofix.ci] apply automated fixes 2025-04-04 17:37:42 +00:00
nour kouider
9208e7faaf fix(docs): fix edge ID example 2025-04-04 18:22:56 +01:00
Shahir Ahmed
2798e27b1e dynamically resizes horizontal xychart bar
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-04-02 23:00:12 -04:00
Shahir Ahmed
f4c08a0c6f dynamically resizes vertical xychart bar
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-04-02 22:25:34 -04:00
Shahir Ahmed
6a538da07d Merge branch 'develop' into feature/5806_xy-chart-data-labels 2025-04-02 17:22:13 -04:00
Shahir Ahmed
b2dfa74865 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-04-02 17:19:44 -04:00
Shahir Ahmed
930e917215 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-04-02 17:18:45 -04:00
Thomas Di Cizerone
cdbd3e58a3 🆙 Run pnpm changeset 2025-04-02 18:50:20 +02:00
Thomas Di Cizerone
630c4d6954 🤏 Update Note comment 2025-04-02 18:50:20 +02:00
Thomas Di Cizerone
fd4493733f Add TODO for architecture support of title embedded in render 2025-04-02 18:50:20 +02:00
Thomas Di Cizerone
e588743bf4 ⚗️ Add unit tests and e2e for architecture diagram with titleAndAccessibilities 2025-04-02 18:50:20 +02:00
Thomas Di Cizerone
04d68e7f9a ⚗️ Add parser tests for architecture and other edge cases encountered 2025-04-02 18:50:20 +02:00
Thomas Di Cizerone
9795b6e089 🖋️ Refactor common grammar to avoid unwanted greedy terminals 2025-04-02 18:50:20 +02:00
Ashish Jain
ceb8d4c7ef Merge pull request #6445 from mermaid-js/omkarht/chore/downgrade-chokidar-to-3.6.0
chore: Downgrade chokidar to 3.6.0 for hot reloading fix
2025-04-01 12:43:53 +00:00
omkarht
32c70a0f6a chore: Downgrade chokidar to 3.6.0 for hot reloading fix 2025-03-31 20:38:12 +05:30
Knut Sveidqvist
af2632f14a Merge pull request #6442 from mermaid-js/renovate/major-eslint
chore(deps): update dependency eslint-plugin-unicorn to v58
2025-03-31 07:33:19 +00:00
renovate[bot]
8f89ba1930 chore(deps): update dependency eslint-plugin-unicorn to v58 2025-03-31 01:56:12 +00:00
Shahir Ahmed
ad6f855f5e adds dynamic text adjustment
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-28 16:04:51 -04:00
Shahir Ahmed
f2f2a1d275 adds test and horizontal support
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-27 23:09:27 -04:00
Shahir Ahmed
17fcf43cdb fix typo
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-27 22:14:07 -04:00
Shahir Ahmed
99a2dc7c1f adds label data to bar chart
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-27 21:51:21 -04:00
Shahir Ahmed
34e756fde6 add label config
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-27 20:32:35 -04:00
Shahir Ahmed
f74fad057a Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-03-27 20:16:45 -04:00
Shahir Ahmed
32ea973b12 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-03-27 20:14:13 -04:00
isaiah robinson
0104d19f66 Fix formatting of ELK example in ERD + clarify usage 2025-03-27 15:47:43 -07:00
Ashish Jain
936d1074b2 Merge pull request #6413 from mermaid-js/master
Merge back v11.6.0 to develop
2025-03-25 12:51:55 +00:00
Shahir Ahmed
f7e31a978b update docs
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 23:44:21 -04:00
Shahir Ahmed
2d583b186d fix some pixel issues
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 22:51:02 -04:00
Shahir Ahmed
b322392f97 refactor tests
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 21:18:45 -04:00
Shahir Ahmed
573b6d9ba7 remove redundant test
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 20:58:17 -04:00
Shahir Ahmed
ea39254556 ensure maxLabelWidth is a number
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 20:41:43 -04:00
Shahir Ahmed
044a3d9686 Set legend width baseline (conf.leftMargin) and expand dynamically
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-19 20:34:44 -04:00
autofix-ci[bot]
f28f7b713d [autofix.ci] apply automated fixes 2025-03-18 23:58:40 +00:00
Regis Bittencourt
cfbd05515e Remove duplicates in integrations-community.md 2025-03-18 20:53:19 -03:00
Shahir Ahmed
20927a1c8e remove max and min attributes for maxLabelWidth
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-18 15:39:58 -04:00
pranavm2109
b2ab34ca2b fixed linting issue
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-03-18 15:36:50 -04:00
Shahir Ahmed
55e1dd0ead implemented knuth-plass line-breaking algorithm
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-03-18 15:32:04 -04:00
Shahir Ahmed
a403f78168 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-03-18 15:17:45 -04:00
Shahir Ahmed
6c45aa3602 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-03-18 15:15:45 -04:00
Shahir Ahmed
91e2c04e56 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-02-27 15:33:52 -05:00
Shahir Ahmed
a2f0d8e4d6 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-02-27 15:32:51 -05:00
Shahir Ahmed
7c7fd4bc5e adds test for line wrapping
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-25 18:40:27 -05:00
pranavm2109
baa261fdd1 Merge branch 'bug/5955_adjust-diagram-position-with-legend-width' of https://github.com/SeniorSeniorMath-SciLib2k24/mermaid into bug/5955_adjust-diagram-position-with-legend-width 2025-02-25 16:13:28 -05:00
pranavm2109
aaf15fccc1 added first draft of test to see if label text is being wrapped in different lines
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-25 16:13:14 -05:00
autofix-ci[bot]
3724d11255 [autofix.ci] apply automated fixes 2025-02-25 20:55:26 +00:00
pranavm2109
5510f18d33 removed function to get rem in px since config schema now has maxLabelWidth
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-25 15:50:10 -05:00
Shahir Ahmed
edbf125c83 wraps long text into new line
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-25 15:43:00 -05:00
pranavm2109
ad6248147c revised current cypress test
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-25 15:29:29 -05:00
Shahir Ahmed
d47e4724cb wraps long text into new line
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-25 15:21:08 -05:00
Shahir Ahmed
4f592115e0 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-02-25 15:09:56 -05:00
Shahir Ahmed
d2d78a5576 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-02-25 15:07:54 -05:00
Shahir Ahmed
50816a7f98 remove magic value
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-21 17:35:32 -05:00
Shahir Ahmed
a318ea3692 removes cy.then and magic value
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-21 16:15:24 -05:00
Shahir Ahmed
1424127127 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-02-21 15:33:13 -05:00
Shahir Ahmed
9c27125f2d changes default values
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-21 15:27:01 -05:00
Shahir Ahmed
6140be24da Merge branch 'develop' into feature/3508_color-user-journey-title 2025-02-21 15:04:12 -05:00
Shahir Ahmed
5dab86a067 Merge branch 'bug/5955_adjust-diagram-position-with-legend-width' of https://github.com/Shahir-47/mermaid into bug/5955_adjust-diagram-position-with-legend-width 2025-02-18 16:12:37 -05:00
Shahir Ahmed
9ef35549f4 Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-02-18 16:05:16 -05:00
pranavm2109
4beed963b8 updated descriptions of parameters in schema
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-18 16:03:12 -05:00
pranavm2109
3122c3b75c updated descriptions of parameters in schema
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-18 16:01:40 -05:00
pranavm2109
dffa689e2b updated documentation
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-18 15:58:03 -05:00
pranavm2109
5ed2278887 Merge branch 'feature/3508_color-user-journey-title' of https://github.com/SeniorSeniorMath-SciLib2k24/mermaid into feature/3508_color-user-journey-title 2025-02-18 15:36:56 -05:00
pranavm2109
d6376ca1ff Reverted to using configObject in place of conf
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-18 15:36:51 -05:00
autofix-ci[bot]
99bd0c48fa [autofix.ci] apply automated fixes 2025-02-18 20:31:21 +00:00
pranavm2109
eb7289a65a Merge branch 'feature/3508_color-user-journey-title' of https://github.com/SeniorSeniorMath-SciLib2k24/mermaid into feature/3508_color-user-journey-title 2025-02-18 15:26:10 -05:00
pranavm2109
80c6b945fa Removed title style documentation from theming.md
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-18 15:25:12 -05:00
Sidharth Vinod
9badfe7489 Merge branch 'develop' into feature/3508_color-user-journey-title 2025-02-15 22:33:13 +05:30
Sidharth Vinod
27912bee8c Merge branch 'develop' into bug/5955_adjust-diagram-position-with-legend-width 2025-02-15 22:14:44 +05:30
Shahir Ahmed
7aef182fbf Merge branch 'develop' into feature/3508_color-user-journey-title 2025-02-14 16:45:20 -05:00
autofix-ci[bot]
2fb6ea7b77 [autofix.ci] apply automated fixes 2025-02-14 21:23:01 +00:00
pranavm2109
add48da4c8 modifed journey diagram config scheme to contain title specific styles, and undid previous theme-wide changes
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-14 16:17:09 -05:00
Shahir Ahmed
5f7c68def7 refactor: standardize variable naming and improve legend width calculations
Co-authored-by: pranavm2109 <mishrap@dickinson.edu>
2025-02-13 01:46:27 -05:00
Sidharth Vinod
82d019234a chore: Cleanup 2025-02-13 09:50:04 +05:30
Shahir Ahmed
d618b8398e adds test for journey
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-12 19:54:56 -05:00
Shahir Ahmed
db4ea020ba testing
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-12 18:39:03 -05:00
pranavm2109
5366e8b692 added first draft of cypress visual tests
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-02-12 15:19:37 -05:00
Shahir Ahmed
fbac4c61bb diagram adjusts with legend width changes
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-02-05 19:27:32 -05:00
Shahir Ahmed
d81ddf246c fixes titleColor default value
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-31 17:57:25 -05:00
Shahir Ahmed
ffe1bb359f update pnpm-lock.yaml
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-31 17:30:34 -05:00
Shahir Ahmed
88a39f8e16 update dependencies for CI/CD pipeline
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-31 17:28:55 -05:00
Shahir Ahmed
9fb46ae88f improves description for user journey diagram title test
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-29 16:20:43 -05:00
Shahir Ahmed
798fb98b2a Merge branch 'develop' into feature/3508_color-user-journey-title
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-29 16:15:00 -05:00
Shahir Ahmed
7c0af381d1 adds styling, font-weight and font-family tests for user jounrey title
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-29 16:11:57 -05:00
pranavm2109
384f59eee2 called getConfig() once in draw function and used returned value elsewhere within
Co-authored-by: Shahir Ahmed <ahmeds@dickinson.edu>
2025-01-29 14:45:42 -05:00
Shahir Ahmed
03ff28e927 removes console.warn()
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-25 22:12:16 -05:00
Shahir Ahmed
16a1a90705 adds font family and font size to the title of the user journey diagram
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-25 22:01:30 -05:00
Shahir Ahmed
3221cbfa0a add titleColor theme variable to theming.md
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-25 21:23:35 -05:00
Shahir Ahmed
f0a8ccb177 Merge branch 'develop' into feature/3508_color-user-journey-title
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
2025-01-25 21:12:11 -05:00
Shahir Ahmed
a30705cdcc Enhance user journey diagram title color configuration 2025-01-25 20:33:39 -05:00
57 changed files with 2567 additions and 1152 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Remove incorrect `style="undefined;"` attributes in some Mermaid diagrams

View File

@@ -0,0 +1,7 @@
---
'@mermaid-js/mermaid-zenuml': patch
---
chore: bump minimum ZenUML version to 3.23.28
commit: 9d06d8f31e7f12af9e9e092214f907f2dc93ad75

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Add support for styling Journey Diagram title (color, font-family, and font-size)

View File

@@ -0,0 +1,6 @@
---
'mermaid': patch
'@mermaid-js/parser': patch
---
Refactor grammar so that title don't break Architecture Diagrams

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Dynamically Render Data Labels Within Bar Charts

View File

@@ -0,0 +1,7 @@
---
'@mermaid-js/mermaid-zenuml': patch
---
fix(zenuml): limit `peerDependencies` to Mermaid v10 and v11
commit: 0ad44c12feead9d20c6a870a49327ada58d6e657

View File

@@ -34,6 +34,19 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
{ ...iifeOptions, minify: true, metafile: shouldVisualize }
);
}
if (entryName === 'mermaid-zenuml') {
const iifeOptions: MermaidBuildOptions = {
...commonOptions,
format: 'iife',
globalName: 'mermaid-zenuml',
};
buildConfigs.push(
// mermaid-zenuml.js
{ ...iifeOptions },
// mermaid-zenuml.min.js
{ ...iifeOptions, minify: true, metafile: shouldVisualize }
);
}
const results = await Promise.all(buildConfigs.map((option) => build(getBuildConfig(option))));

View File

@@ -58,6 +58,7 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
format,
minify,
options: { name, file, packageName },
globalName = 'mermaid',
} = options;
const external: string[] = ['require', 'fs', 'path'];
const outFileName = getFileName(name, options);
@@ -68,6 +69,7 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
},
metafile,
minify,
globalName,
logLevel: 'info',
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
define: {
@@ -89,11 +91,12 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
if (format === 'iife') {
output.format = 'iife';
output.splitting = false;
output.globalName = '__esbuild_esm_mermaid';
const originalGlobalName = output.globalName ?? 'mermaid';
output.globalName = `__esbuild_esm_mermaid_nm[${JSON.stringify(originalGlobalName)}]`;
// Workaround for removing the .default access in esbuild IIFE.
// https://github.com/mermaid-js/mermaid/pull/4109#discussion_r1292317396
output.footer = {
js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;',
js: `globalThis[${JSON.stringify(originalGlobalName)}] = globalThis.${output.globalName}.default;`,
};
output.outExtension = { '.js': '.js' };
} else {

4
.github/lychee.toml vendored
View File

@@ -50,7 +50,9 @@ exclude = [
"https://docs.swimm.io",
# Timeout
"https://huehive.co"
"https://huehive.co",
"https://foswiki.org",
"https://www.gnu.org",
]
# Exclude all private IPs from checking.

View File

@@ -11,6 +11,7 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
jobs:
timings:
@@ -29,6 +30,7 @@ jobs:
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress
@@ -44,15 +46,17 @@ jobs:
SPLIT: 1
SPLIT_INDEX: 0
SPLIT_FILE: 'cypress/timings.json'
- name: Commit changes
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
- name: Compare timings
run: pnpm tsx scripts/compare-timings.ts
- name: Commit and create pull request
uses: peter-evans/create-pull-request@a7b20e1da215b3ef3ccddb48ff65120256ed6226
with:
add: 'cypress/timings.json'
author_name: 'github-actions[bot]'
author_email: '41898282+github-actions[bot]@users.noreply.github.com'
message: 'chore: update E2E timings'
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
branch: release-promotion
add-paths: |
cypress/timings.json
commit-message: 'chore: update E2E timings'
branch: update-timings
title: Update E2E Timings
delete-branch: true
sign-commits: true

View File

@@ -19,6 +19,25 @@ describe.skip('architecture diagram', () => {
`
);
});
it('should render a simple architecture diagram with titleAndAccessabilities', () => {
imgSnapshotTest(
`architecture-beta
title Simple Architecture Diagram
accTitle: Accessibility Title
accDescr: Accessibility Description
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
`
);
});
it('should render an architecture diagram with groups within groups', () => {
imgSnapshotTest(
`architecture-beta
@@ -172,7 +191,7 @@ describe.skip('architecture diagram', () => {
);
});
it('should render an architecture diagram with a resonable height', () => {
it('should render an architecture diagram with a reasonable height', () => {
imgSnapshotTest(
`architecture-beta
group federated(cloud)[Federated Environment]

View File

@@ -63,4 +63,199 @@ section Checkout from website
{ journey: { useMaxWidth: false } }
);
});
it('should initialize with a left margin of 150px for user journeys', () => {
renderGraph(
`
---
config:
journey:
maxLabelWidth: 320
---
journey
title User Journey Example
section Onboarding
Sign Up: 5:
Browse Features: 3:
Use Core Functionality: 4:
section Engagement
Browse Features: 3
Use Core Functionality: 4
`,
{ journey: { useMaxWidth: true } }
);
let diagramStartX;
cy.contains('foreignobject', 'Sign Up').then(($diagram) => {
diagramStartX = parseFloat($diagram.attr('x'));
expect(diagramStartX).to.be.closeTo(150, 2);
});
});
it('should maintain sufficient space between legend and diagram when legend labels are longer', () => {
renderGraph(
`journey
title Web hook life cycle
section Darkoob
Make preBuilt:5: Darkoob user
register slug : 5: Darkoob userf deliberately increasing the size of this label to check if distance between legend and diagram is maintained
Map slug to a Prebuilt Job:5: Darkoob user
section External Service
set Darkoob slug as hook for an Event : 5 : admin Exjjjnjjjj qwerty
listen to the events : 5 : External Service
call darkoob endpoint : 5 : External Service
section Darkoob
check for inputs : 5 : DarkoobAPI
run the prebuilt job : 5 : DarkoobAPI
`,
{ journey: { useMaxWidth: true } }
);
let LabelEndX, diagramStartX;
// Get right edge of the legend
cy.contains('tspan', 'Darkoob userf').then((textBox) => {
const bbox = textBox[0].getBBox();
LabelEndX = bbox.x + bbox.width;
});
// Get left edge of the diagram
cy.contains('foreignobject', 'Make preBuilt').then((rect) => {
diagramStartX = parseFloat(rect.attr('x'));
});
// Assert right edge of the diagram is greater than or equal to the right edge of the label
cy.then(() => {
expect(diagramStartX).to.be.gte(LabelEndX);
});
});
it('should wrap a single long word with hyphenation', () => {
renderGraph(
`
---
config:
journey:
maxLabelWidth: 100
---
journey
title Long Word Test
section Test
VeryLongWord: 5: Supercalifragilisticexpialidocious
`,
{ journey: { useMaxWidth: true } }
);
// Verify that the line ends with a hyphen, indicating proper hyphenation for words exceeding maxLabelWidth.
cy.get('tspan').then((tspans) => {
const hasHyphen = [...tspans].some((t) => t.textContent.trim().endsWith('-'));
return expect(hasHyphen).to.be.true;
});
});
it('should wrap text on whitespace without adding hyphens', () => {
renderGraph(
`
---
config:
journey:
maxLabelWidth: 200
---
journey
title Whitespace Test
section Test
TextWithSpaces: 5: Gustavo Fring is played by Giancarlo Esposito and is a character in Breaking Bad.
`,
{ journey: { useMaxWidth: true } }
);
// Verify that none of the text spans end with a hyphen.
cy.get('tspan').each(($el) => {
const text = $el.text();
expect(text.trim()).not.to.match(/-$/);
});
});
it('should wrap long labels into multiple lines, keep them under max width, and maintain margins', () => {
renderGraph(
`
---
config:
journey:
maxLabelWidth: 320
---
journey
title User Journey Example
section Onboarding
Sign Up: 5: This is a long label that will be split into multiple lines to test the wrapping functionality
Browse Features: 3: This is another long label that will be split into multiple lines to test the wrapping functionality
Use Core Functionality: 4: This is yet another long label that will be split into multiple lines to test the wrapping functionality
section Engagement
Browse Features: 3
Use Core Functionality: 4
`,
{ journey: { useMaxWidth: true } }
);
let diagramStartX, maxLineWidth;
// Get the diagram's left edge x-coordinate
cy.contains('foreignobject', 'Sign Up')
.then(($diagram) => {
diagramStartX = parseFloat($diagram.attr('x'));
})
.then(() => {
cy.get('text.legend').then(($lines) => {
// Check that there are multiple lines
expect($lines.length).to.be.equal(9);
// Check that all lines are under the maxLabelWidth
$lines.each((index, el) => {
const bbox = el.getBBox();
expect(bbox.width).to.be.lte(320);
maxLineWidth = Math.max(maxLineWidth || 0, bbox.width);
});
/** The expected margin between the diagram and the legend is 150px, as defined by
* conf.leftMargin in user-journey-config.js
*/
expect(diagramStartX - maxLineWidth).to.be.closeTo(150, 2);
});
});
});
it('should correctly render the user journey diagram title with the specified styling', () => {
renderGraph(
`---
config:
journey:
titleColor: "#2900A5"
titleFontFamily: "Times New Roman"
titleFontSize: "5rem"
---
journey
title User Journey Example
section Onboarding
Sign Up: 5: John, Shahir
Complete Profile: 4: John
section Engagement
Browse Features: 3: John
Use Core Functionality: 4: John
section Retention
Revisit Application: 5: John
Invite Friends: 3: John
size: 2rem
`
);
cy.get('text').contains('User Journey Example').as('title');
cy.get('@title').then(($title) => {
expect($title).to.have.attr('fill', '#2900A5');
expect($title).to.have.attr('font-family', 'Times New Roman');
expect($title).to.have.attr('font-size', '5rem');
});
});
});

View File

@@ -179,6 +179,7 @@ describe('XY Chart', () => {
axisLineWidth: 5
chartOrientation: horizontal
plotReservedSpacePercent: 60
showDataLabel: true
---
xychart-beta
title "Sales Revenue"
@@ -315,4 +316,516 @@ describe('XY Chart', () => {
);
cy.get('svg');
});
it('should render vertical bar chart with labels', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('should render horizontal bar chart with labels', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('should render vertical bar chart without labels by default', () => {
imgSnapshotTest(
`
xychart-beta
title "Sales Revenue"
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('should render horizontal bar chart without labels by default', () => {
imgSnapshotTest(
`
---
config:
xyChart:
chartOrientation: horizontal
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
y-axis "Revenue (in $)" 4000 --> 11000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
`,
{}
);
});
it('should render multiple bar plots vertically with labels correctly', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Multiple Bar Plots"
x-axis Categories [A, B, C]
y-axis "Values" 0 --> 100
bar [10, 50, 90]
`,
{}
);
});
it('should render multiple bar plots horizontally with labels correctly', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Multiple Bar Plots"
x-axis Categories [A, B, C]
y-axis "Values" 0 --> 100
bar [10, 50, 90]
`,
{}
);
});
it('should render a single bar with label for a vertical xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Single Bar Chart"
x-axis Categories [A]
y-axis "Value" 0 --> 100
bar [75]
`,
{}
);
});
it('should render a single bar with label for a horizontal xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Single Bar Chart"
x-axis Categories [A]
y-axis "Value" 0 --> 100
bar [75]
`,
{}
);
});
it('should render negative and decimal values with correct labels for vertical xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Decimal and Negative Values"
x-axis Categories [A, B, C]
y-axis -10 --> 10
bar [ -2.5, 0.75, 5.1 ]
`,
{}
);
});
it('should render negative and decimal values with correct labels for horizontal xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Decimal and Negative Values"
x-axis Categories [A, B, C]
y-axis -10 --> 10
bar [ -2.5, 0.75, 5.1 ]
`,
{}
);
});
it('should render data labels within each bar in the vertical xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan,b,c]
y-axis "Revenue (in $)" 4000 --> 12000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
// Check horizontal alignment (within tolerance)
expect(textProps.x + textProps.width / 2).to.be.closeTo(
barProps.x + barProps.width / 2,
5
);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
});
});
});
});
it('should render data labels within each bar in the horizontal xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan,b,c]
y-axis "Revenue (in $)" 4000 --> 12000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
expect(textProps.y + textProps.height / 2).to.be.closeTo(
barProps.y + barProps.height / 2,
5
);
});
});
});
});
it('should render data labels within each bar in the vertical xy-chart with a lot of bars of different sizes', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
y-axis "Revenue (in $)" 4000 --> 12000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
// Check horizontal alignment (within tolerance)
expect(textProps.x + textProps.width / 2).to.be.closeTo(
barProps.x + barProps.width / 2,
5
);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
});
});
});
});
it('should render data labels within each bar in the horizontal xy-chart with a lot of bars of different sizes', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
y-axis "Revenue (in $)" 4000 --> 12000
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
expect(textProps.y + textProps.height / 2).to.be.closeTo(
barProps.y + barProps.height / 2,
5
);
});
});
});
});
it('should render data labels correctly for a bar in the vertical xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan]
y-axis "Revenue (in $)" 3000 --> 12000
bar [4000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
// Check horizontal alignment (within tolerance)
expect(textProps.x + textProps.width / 2).to.be.closeTo(
barProps.x + barProps.width / 2,
5
);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
});
});
});
});
it('should render data labels correctly for a bar in the horizontal xy-chart', () => {
imgSnapshotTest(
`
---
config:
xyChart:
showDataLabel: true
chartOrientation: horizontal
---
xychart-beta
title "Sales Revenue"
x-axis Months [jan]
y-axis "Revenue (in $)" 3000 --> 12000
bar [4000]
`,
{}
);
cy.get('g.bar-plot-0').within(() => {
cy.get('rect').each(($rect, index) => {
// Extract bar properties
const barProps = {
x: parseFloat($rect.attr('x')),
y: parseFloat($rect.attr('y')),
width: parseFloat($rect.attr('width')),
height: parseFloat($rect.attr('height')),
};
// Get the text element corresponding to this bar by index.
cy.get('text')
.eq(index)
.then(($text) => {
const bbox = $text[0].getBBox();
const textProps = {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
// Verify that the text label is positioned within the boundaries of the bar.
expect(textProps.x).to.be.greaterThan(barProps.x);
expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
expect(textProps.y).to.be.greaterThan(barProps.y);
expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
expect(textProps.y + textProps.height / 2).to.be.closeTo(
barProps.y + barProps.height / 2,
5
);
});
});
});
});
});

View File

@@ -2,151 +2,211 @@
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 4989
"duration": 5475
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 1382
"duration": 2037
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3178
"duration": 3207
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 1372
"duration": 1915
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 8998
"duration": 10952
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 1249
"duration": 1872
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 25664
"duration": 26686
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 1928
"duration": 2629
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
"duration": 2330
"duration": 104
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 11156
"duration": 14765
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 3418
"duration": 4913
},
{
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
"duration": 36667
},
{
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
"duration": 33813
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 14866
"duration": 20441
},
{
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
"duration": 32504
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 9894
"duration": 13772
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 5778
"duration": 7978
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 1690
"duration": 2101
},
{
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
"duration": 76556
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 9144
"duration": 12756
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 1951
"duration": 2766
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 2196
"duration": 35641
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 21029
"duration": 26915
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 16087
"duration": 21171
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 27465
"duration": 37844
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 20035
"duration": 26254
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 11366
"duration": 15149
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 34025
"duration": 45049
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 185902
"duration": 250225
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 41631
"duration": 51531
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 1736
"duration": 2455
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 2247
"duration": 3181
},
{
"spec": "cypress/integration/rendering/kanban.spec.ts",
"duration": 6298
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 2144
"duration": 3065
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 1646
"duration": 2521
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 6406
"duration": 9341
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 107219
"duration": 132809
},
{
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
"duration": 101299
},
{
"spec": "cypress/integration/rendering/packet.spec.ts",
"duration": 3481
},
{
"spec": "cypress/integration/rendering/pie.spec.ts",
"duration": 4878
},
{
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
"duration": 7416
},
{
"spec": "cypress/integration/rendering/radar.spec.js",
"duration": 4554
},
{
"spec": "cypress/integration/rendering/requirement.spec.js",
"duration": 2068
},
{
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
"duration": 47583
},
{
"spec": "cypress/integration/rendering/sankey.spec.ts",
"duration": 5792
},
{
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
"duration": 33035
},
{
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
"duration": 22716
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 15834
"duration": 13868
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 33240
"duration": 26376
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 7122
"duration": 5872
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 11127
"duration": 9469
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 2391
"duration": 2742
}
]
}

View File

@@ -40,6 +40,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Mermaid Charts & Diagrams for Jira](https://marketplace.atlassian.com/apps/1224537/)
- [Mermaid for Jira Cloud - Draw UML diagrams easily](https://marketplace.atlassian.com/apps/1223053/mermaid-for-jira-cloud-draw-uml-diagrams-easily?hosting=cloud&tab=overview)
- [CloudScript.io Mermaid Addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon?hosting=cloud&tab=overview)
- [Mermaid plus for Confluence](https://marketplace.atlassian.com/apps/1236814/mermaid-plus-for-confluence?hosting=cloud&tab=overview)
- [Azure Devops](https://learn.microsoft.com/en-us/azure/devops/project/wiki/markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) ✅
- [Deepdwn](https://billiam.itch.io/deepdwn) ✅
- [Doctave](https://www.doctave.com/) ✅
@@ -267,7 +268,5 @@ Communication tools and platforms
- [reveal.js-mermaid-plugin](https://github.com/ludwick/reveal.js-mermaid-plugin)
- [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)
<!--- cspell:ignore Blazorade HueHive --->

View File

@@ -625,17 +625,43 @@ erDiagram
## Configuration
### Renderer
### Layout
The layout of the diagram is done with the renderer. The default renderer is dagre.
The layout of the diagram is handled by [`render()`](../config/setup/mermaid/interfaces/Mermaid.md#render). The default layout is dagre.
You can opt to use an alternate renderer named elk by editing the configuration. The elk renderer is better for larger and/or more complex diagrams.
For larger or more-complex diagrams, you can alternatively apply the ELK (Eclipse Layout Kernel) layout using your YAML frontmatter's `config`. For more information, see [Customizing ELK Layout](../intro/syntax-reference.md#customizing-elk-layout).
```yaml
---
config:
layout: elk
---
```
Your Mermaid code should be similar to the following:
```mermaid-example
---
config:
layout: elk
title: Order example
config:
layout: elk
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
```mermaid
---
title: Order example
config:
layout: elk
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
> **Note**

View File

@@ -1193,12 +1193,12 @@ To give an edge an ID, prepend the edge syntax with the ID followed by an `@` ch
```mermaid-example
flowchart LR
A e1@> B
A e1@--> B
```
```mermaid
flowchart LR
A e1@> B
A e1@--> B
```
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
@@ -1229,13 +1229,13 @@ In the initial version, two animation speeds are supported: `fast` and `slow`. S
```mermaid-example
flowchart LR
A e1@> B
A e1@--> B
e1@{ animation: fast }
```
```mermaid
flowchart LR
A e1@> B
A e1@--> B
e1@{ animation: fast }
```
@@ -1247,14 +1247,14 @@ You can also animate edges by assigning a class to them and then defining animat
```mermaid-example
flowchart LR
A e1@> B
A e1@--> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```
```mermaid
flowchart LR
A e1@> B
A e1@--> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```

View File

@@ -107,17 +107,18 @@ xychart-beta
## Chart Configurations
| Parameter | Description | Default value |
| ------------------------ | ---------------------------------------------- | :-----------: |
| width | Width of the chart | 700 |
| height | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
| Parameter | Description | Default value |
| ------------------------ | ------------------------------------------------------------- | :-----------: |
| width | Width of the chart | 700 |
| height | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
| showDataLabel | Should show the value corresponding to the bar within the bar | false |
### AxisConfig
@@ -163,6 +164,7 @@ config:
xyChart:
width: 900
height: 600
showDataLabel: true
themeVariables:
xyChart:
titleColor: "#ff0000"
@@ -181,6 +183,7 @@ config:
xyChart:
width: 900
height: 600
showDataLabel: true
themeVariables:
xyChart:
titleColor: "#ff0000"

View File

@@ -64,12 +64,12 @@
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.44.9",
"@argos-ci/cypress": "^3.2.0",
"@argos-ci/cypress": "^4.0.3",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.27.12",
"@cspell/eslint-plugin": "^8.8.4",
"@cspell/eslint-plugin": "^8.18.1",
"@cypress/code-coverage": "^3.12.49",
"@eslint/js": "^9.4.0",
"@eslint/js": "^9.24.0",
"@rollup/plugin-typescript": "^12.1.2",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
@@ -93,19 +93,19 @@
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.14",
"esbuild": "^0.25.0",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-cypress": "^4.1.0",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-cypress": "^4.2.1",
"eslint-plugin-html": "^8.1.2",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-jsdoc": "^50.0.1",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-jsdoc": "^50.6.9",
"eslint-plugin-json": "^4.0.1",
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^57.0.0",
"express": "^4.19.2",
"eslint-plugin-unicorn": "^58.0.0",
"express": "^5.1.0",
"globals": "^16.0.0",
"globby": "^14.0.2",
"husky": "^9.1.7",
@@ -126,7 +126,7 @@
"tslib": "^2.8.1",
"tsx": "^4.7.3",
"typescript": "~5.7.3",
"typescript-eslint": "^8.24.1",
"typescript-eslint": "^8.29.1",
"vite": "^6.1.1",
"vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.0.6"

View File

@@ -78,7 +78,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.13",
"dompurify": "^3.2.4",
"dompurify": "^3.2.5",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",

View File

@@ -559,6 +559,10 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
* Margin between actors
*/
leftMargin?: number;
/**
* Maximum width of actor labels
*/
maxLabelWidth?: number;
/**
* Width of actor boxes
*/
@@ -617,6 +621,18 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
actorColours?: string[];
sectionFills?: string[];
sectionColours?: string[];
/**
* Color of the title text in Journey Diagrams
*/
titleColor?: string;
/**
* Font family to be used for the title text in Journey Diagrams
*/
titleFontFamily?: string;
/**
* Font size to be used for the title text in Journey Diagrams
*/
titleFontSize?: string;
}
/**
* This interface was referenced by `MermaidConfig`'s JSON-Schema
@@ -935,6 +951,10 @@ export interface XYChartConfig extends BaseDiagramConfig {
* Top and bottom space from the chart title
*/
titlePadding?: number;
/**
* Should show the value corresponding to the bar within the bar
*/
showDataLabel?: boolean;
/**
* Should show the chart title
*/

View File

@@ -0,0 +1,70 @@
import { it, describe, expect } from 'vitest';
import { db } from './architectureDb.js';
import { parser } from './architectureParser.js';
const {
clear,
getDiagramTitle,
getAccTitle,
getAccDescription,
getServices,
getGroups,
getEdges,
getJunctions,
} = db;
describe('architecture diagrams', () => {
beforeEach(() => {
clear();
});
describe('architecture diagram definitions', () => {
it('should handle the architecture keyword', async () => {
const str = `architecture-beta`;
await expect(parser.parse(str)).resolves.not.toThrow();
});
it('should handle an simple radar definition', async () => {
const str = `architecture-beta
service db
`;
await expect(parser.parse(str)).resolves.not.toThrow();
});
});
describe('should handle TitleAndAccessibilities', () => {
it('should handle title on the first line', async () => {
const str = `architecture-beta title Simple Architecture Diagram`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
});
it('should handle title on another line', async () => {
const str = `architecture-beta
title Simple Architecture Diagram
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
});
it('should handle accessibility title and description', async () => {
const str = `architecture-beta
accTitle: Accessibility Title
accDescr: Accessibility Description
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getAccTitle()).toBe('Accessibility Title');
expect(getAccDescription()).toBe('Accessibility Description');
});
it('should handle multiline accessibility description', async () => {
const str = `architecture-beta
accDescr {
Accessibility Description
}
`;
await expect(parser.parse(str)).resolves.not.toThrow();
expect(getAccDescription()).toBe('Accessibility Description');
});
});
});

View File

@@ -1,6 +1,6 @@
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import DEFAULT_CONFIG from '../../defaultConfig.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getConfig as commonGetConfig } from '../../config.js';
import type { D3Element } from '../../types.js';
import { ImperativeState } from '../../utils/imperativeState.js';
import {
@@ -33,6 +33,7 @@ import {
isArchitectureService,
shiftPositionByArchitectureDirectionPair,
} from './architectureTypes.js';
import { cleanAndMerge } from '../../utils.js';
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
DEFAULT_CONFIG.architecture;
@@ -316,6 +317,14 @@ const setElementForId = (id: string, element: D3Element) => {
};
const getElementById = (id: string) => state.records.elements[id];
const getConfig = (): Required<ArchitectureDiagramConfig> => {
const config = cleanAndMerge({
...DEFAULT_ARCHITECTURE_CONFIG,
...commonGetConfig().architecture,
});
return config;
};
export const db: ArchitectureDB = {
clear,
setDiagramTitle,
@@ -324,6 +333,7 @@ export const db: ArchitectureDB = {
getAccTitle,
setAccDescription,
getAccDescription,
getConfig,
addService,
getServices,
@@ -348,9 +358,5 @@ export const db: ArchitectureDB = {
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
field: T
): Required<ArchitectureDiagramConfig>[T] {
const arch = getConfig().architecture;
if (arch?.[field]) {
return arch[field] as Required<ArchitectureDiagramConfig>[T];
}
return DEFAULT_ARCHITECTURE_CONFIG[field];
return getConfig()[field];
}

View File

@@ -500,6 +500,8 @@ function layoutArchitecture(
}
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
// TODO: Add title support for architecture diagrams
const db = diagObj.db as ArchitectureDB;
const services = db.getServices();

View File

@@ -1,4 +1,4 @@
import type { DiagramDB } from '../../diagram-api/types.js';
import type { DiagramDBBase } from '../../diagram-api/types.js';
import type { ArchitectureDiagramConfig } from '../../config.type.js';
import type { D3Element } from '../../types.js';
import type cytoscape from 'cytoscape';
@@ -242,7 +242,7 @@ export interface ArchitectureEdge<DT = ArchitectureDirection> {
title?: string;
}
export interface ArchitectureDB extends DiagramDB {
export interface ArchitectureDB extends DiagramDBBase<ArchitectureDiagramConfig> {
clear: () => void;
addService: (service: Omit<ArchitectureService, 'edges'>) => void;
getServices: () => ArchitectureService[];

View File

@@ -2,13 +2,13 @@ import type { DiagramAST } from '@mermaid-js/parser';
import type { DiagramDB } from '../../diagram-api/types.js';
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {
if (ast.accDescr) {
db.setAccDescription?.(ast.accDescr);
if (ast.accDescr?.length > 0) {
db.setAccDescription?.(ast.accDescr.pop() ?? '');
}
if (ast.accTitle) {
db.setAccTitle?.(ast.accTitle);
if (ast.accTitle?.length > 0) {
db.setAccTitle?.(ast.accTitle.pop() ?? '');
}
if (ast.title) {
db.setDiagramTitle?.(ast.title);
if (ast.title?.length > 0) {
db.setDiagramTitle?.(ast.title.pop() ?? '');
}
}

View File

@@ -1,11 +1,9 @@
import { it, describe, expect } from 'vitest';
import { db } from './db.js';
import { parser } from './parser.js';
import { renderer, relativeRadius, closedRoundCurve } from './renderer.js';
import { relativeRadius, closedRoundCurve } from './renderer.js';
import { Diagram } from '../../Diagram.js';
import mermaidAPI from '../../mermaidAPI.js';
import { a } from 'vitest/dist/chunks/suite.qtkXWc6R.js';
import { buildRadarStyleOptions } from './styles.js';
const {
clear,

View File

@@ -13,15 +13,17 @@ export const setConf = function (cnf) {
};
const actors = {};
let maxWidth = 0;
/** @param diagram - The diagram to draw to. */
function drawActorLegend(diagram) {
const conf = getConfig().journey;
// Draw the actors
const maxLabelWidth = conf.maxLabelWidth;
maxWidth = 0;
let yPos = 60;
Object.keys(actors).forEach((person) => {
const colour = actors[person].color;
const circleData = {
cx: 20,
cy: yPos,
@@ -32,25 +34,97 @@ function drawActorLegend(diagram) {
};
svgDraw.drawCircle(diagram, circleData);
const labelData = {
x: 40,
y: yPos + 7,
fill: '#666',
text: person,
textMargin: conf.boxTextMargin | 5,
};
svgDraw.drawText(diagram, labelData);
// First, measure the full text width without wrapping.
let measureText = diagram.append('text').attr('visibility', 'hidden').text(person);
const fullTextWidth = measureText.node().getBoundingClientRect().width;
measureText.remove();
yPos += 20;
let lines = [];
// If the text is naturally within the max width, use it as a single line.
if (fullTextWidth <= maxLabelWidth) {
lines = [person];
} else {
// Otherwise, wrap the text using the knuth-plass algorithm.
const words = person.split(' '); // Split the text into words.
let currentLine = '';
measureText = diagram.append('text').attr('visibility', 'hidden');
words.forEach((word) => {
// check the width of the line with the new word.
const testLine = currentLine ? `${currentLine} ${word}` : word;
measureText.text(testLine);
const textWidth = measureText.node().getBoundingClientRect().width;
if (textWidth > maxLabelWidth) {
// If adding the new word exceeds max width, push the current line.
if (currentLine) {
lines.push(currentLine);
}
currentLine = word; // Start a new line with the current word.
// If the word itself is too long, break it with a hyphen.
measureText.text(word);
if (measureText.node().getBoundingClientRect().width > maxLabelWidth) {
let brokenWord = '';
for (const char of word) {
brokenWord += char;
measureText.text(brokenWord + '-');
if (measureText.node().getBoundingClientRect().width > maxLabelWidth) {
// Push the broken part with a hyphen.
lines.push(brokenWord.slice(0, -1) + '-');
brokenWord = char;
}
}
currentLine = brokenWord;
}
} else {
// If the line with the new word fits, add the new word to the current line.
currentLine = testLine;
}
});
// Push the last line.
if (currentLine) {
lines.push(currentLine);
}
measureText.remove(); // Remove the text element used for measuring.
}
lines.forEach((line, index) => {
const labelData = {
x: 40,
y: yPos + 7 + index * 20,
fill: '#666',
text: line,
textMargin: conf.boxTextMargin ?? 5,
};
// Draw the text and measure the width.
const textElement = svgDraw.drawText(diagram, labelData);
const lineWidth = textElement.node().getBoundingClientRect().width;
// Use conf.leftMargin as the initial spacing baseline,
// but expand maxWidth if the line is wider.
if (lineWidth > maxWidth && lineWidth > conf.leftMargin - lineWidth) {
maxWidth = lineWidth;
}
});
yPos += Math.max(20, lines.length * 20);
});
}
// TODO: Cleanup?
const conf = getConfig().journey;
const LEFT_MARGIN = conf.leftMargin;
let leftMargin = 0;
export const draw = function (text, id, version, diagObj) {
const conf = getConfig().journey;
const configObject = getConfig();
const titleColor = configObject.journey.titleColor;
const titleFontSize = configObject.journey.titleFontSize;
const titleFontFamily = configObject.journey.titleFontFamily;
const securityLevel = getConfig().securityLevel;
const securityLevel = configObject.securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
@@ -84,7 +158,8 @@ export const draw = function (text, id, version, diagObj) {
});
drawActorLegend(diagram);
bounds.insert(0, 0, LEFT_MARGIN, Object.keys(actors).length * 50);
leftMargin = conf.leftMargin + maxWidth;
bounds.insert(0, 0, leftMargin, Object.keys(actors).length * 50);
drawTasks(diagram, tasks, 0);
const box = bounds.getBounds();
@@ -92,23 +167,25 @@ export const draw = function (text, id, version, diagObj) {
diagram
.append('text')
.text(title)
.attr('x', LEFT_MARGIN)
.attr('font-size', '4ex')
.attr('x', leftMargin)
.attr('font-size', titleFontSize)
.attr('font-weight', 'bold')
.attr('y', 25);
.attr('y', 25)
.attr('fill', titleColor)
.attr('font-family', titleFontFamily);
}
const height = box.stopy - box.starty + 2 * conf.diagramMarginY;
const width = LEFT_MARGIN + box.stopx + 2 * conf.diagramMarginX;
const width = leftMargin + box.stopx + 2 * conf.diagramMarginX;
configureSvgSize(diagram, height, width, conf.useMaxWidth);
// Draw activity line
diagram
.append('line')
.attr('x1', LEFT_MARGIN)
.attr('x1', leftMargin)
.attr('y1', conf.height * 4) // One section head + one task + margins
.attr('x2', width - LEFT_MARGIN - 4) // Subtract stroke width so arrow point is retained
.attr('x2', width - leftMargin - 4) // Subtract stroke width so arrow point is retained
.attr('y2', conf.height * 4)
.attr('stroke-width', 4)
.attr('stroke', 'black')
@@ -234,7 +311,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
}
const section = {
x: i * conf.taskMargin + i * conf.width + LEFT_MARGIN,
x: i * conf.taskMargin + i * conf.width + leftMargin,
y: 50,
text: task.section,
fill,
@@ -258,7 +335,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
}, {});
// Add some rendering data to the object
task.x = i * conf.taskMargin + i * conf.width + LEFT_MARGIN;
task.x = i * conf.taskMargin + i * conf.width + leftMargin;
task.y = taskPos;
task.width = conf.diagramMarginX;
task.height = conf.diagramMarginY;

View File

@@ -93,6 +93,7 @@ export interface XYChartConfig {
titleFontSize: number;
titlePadding: number;
showTitle: boolean;
showDataLabel: boolean;
xAxis: XYChartAxisConfig;
yAxis: XYChartAxisConfig;
chartOrientation: 'vertical' | 'horizontal';

View File

@@ -195,6 +195,10 @@ function getChartConfig() {
return xyChartConfig;
}
function getXYChartData() {
return xyChartData;
}
const clear = function () {
commonClear();
plotIndex = 0;
@@ -226,4 +230,5 @@ export default {
setTmpSVGG,
getChartThemeConfig,
getChartConfig,
getXYChartData,
};

View File

@@ -14,6 +14,7 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
const db = diagObj.db as typeof XYChartDB;
const themeConfig = db.getChartThemeConfig();
const chartConfig = db.getChartConfig();
const labelData = db.getXYChartData().plots[0].data.map((data) => data[1]);
function getDominantBaseLine(horizontalPos: TextVerticalPos) {
return horizontalPos === 'top' ? 'text-before-edge' : 'middle';
}
@@ -49,6 +50,16 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
const groups: Record<string, any> = {};
interface BarItem {
data: {
x: number;
y: number;
width: number;
height: number;
};
label: string;
}
function getGroup(gList: string[]) {
let elem = group;
let prefix = '';
@@ -87,6 +98,113 @@ export const draw = (txt: string, id: string, _version: string, diagObj: Diagram
.attr('fill', (data) => data.fill)
.attr('stroke', (data) => data.strokeFill)
.attr('stroke-width', (data) => data.strokeWidth);
if (chartConfig.showDataLabel) {
if (chartConfig.chartOrientation === 'horizontal') {
// Factor to approximate each character's width.
const charWidthFactor = 0.7;
// Filter out bars that have zero width or height.
const validItems = shape.data
.map((d, i) => ({ data: d, label: labelData[i].toString() }))
.filter((item) => item.data.width > 0 && item.data.height > 0);
// Helper function to check if the text fits horizontally with a 10px right margin.
function fitsHorizontally(item: BarItem, fontSize: number): boolean {
const { data, label } = item;
// Approximate the text width.
const textWidth: number = fontSize * label.length * charWidthFactor;
// The available width is the bar's width minus a 10px right margin.
return textWidth <= data.width - 10;
}
// For each valid bar, start with an initial candidate font size (70% of the bar's height),
// then reduce it until the text fits horizontally.
const candidateFontSizes = validItems.map((item) => {
const { data } = item;
let fontSize = data.height * 0.7;
// Decrease fontSize until the text fits horizontally.
while (!fitsHorizontally(item, fontSize) && fontSize > 0) {
fontSize -= 1;
}
return fontSize;
});
// Choose the smallest candidate font size across all valid bars for uniformity.
const uniformFontSize = Math.floor(Math.min(...candidateFontSizes));
shapeGroup
.selectAll('text')
.data(validItems)
.enter()
.append('text')
.attr('x', (item) => item.data.x + item.data.width - 10)
.attr('y', (item) => item.data.y + item.data.height / 2)
.attr('text-anchor', 'end')
.attr('dominant-baseline', 'middle')
.attr('fill', 'black')
.attr('font-size', `${uniformFontSize}px`)
.text((item) => item.label);
} else {
const yOffset = 10;
// filter out bars that have zero width or height.
const validItems = shape.data
.map((d, i) => ({ data: d, label: labelData[i].toString() }))
.filter((item) => item.data.width > 0 && item.data.height > 0);
// Helper function that checks if the text with a given fontSize fits within the bar boundaries.
function fitsInBar(item: BarItem, fontSize: number, yOffset: number): boolean {
const { data, label } = item;
const charWidthFactor = 0.7;
const textWidth = fontSize * label.length * charWidthFactor;
// Compute horizontal boundaries using the center.
const centerX = data.x + data.width / 2;
const leftEdge = centerX - textWidth / 2;
const rightEdge = centerX + textWidth / 2;
// Check that text doesn't overflow horizontally.
const horizontalFits = leftEdge >= data.x && rightEdge <= data.x + data.width;
// For vertical placement, we use 'dominant-baseline: hanging' so that y marks the top of the text.
// Thus, the bottom edge is y + yOffset + fontSize.
const verticalFits = data.y + yOffset + fontSize <= data.y + data.height;
return horizontalFits && verticalFits;
}
// For each valid item, start with a candidate font size based on the width,
// then reduce it until the text fits within both the horizontal and vertical boundaries.
const candidateFontSizes = validItems.map((item) => {
const { data, label } = item;
let fontSize = data.width / (label.length * 0.7);
// Decrease the font size until the text fits or fontSize reaches 0.
while (!fitsInBar(item, fontSize, yOffset) && fontSize > 0) {
fontSize -= 1;
}
return fontSize;
});
// Choose the smallest candidate across all valid bars for uniformity.
const uniformFontSize = Math.floor(Math.min(...candidateFontSizes));
// Render text only for valid items.
shapeGroup
.selectAll('text')
.data(validItems)
.enter()
.append('text')
.attr('x', (item) => item.data.x + item.data.width / 2)
.attr('y', (item) => item.data.y + yOffset)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'hanging')
.attr('fill', 'black')
.attr('font-size', `${uniformFontSize}px`)
.text((item) => item.label);
}
}
break;
case 'text':
shapeGroup

View File

@@ -35,6 +35,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Mermaid Charts & Diagrams for Jira](https://marketplace.atlassian.com/apps/1224537/)
- [Mermaid for Jira Cloud - Draw UML diagrams easily](https://marketplace.atlassian.com/apps/1223053/mermaid-for-jira-cloud-draw-uml-diagrams-easily?hosting=cloud&tab=overview)
- [CloudScript.io Mermaid Addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon?hosting=cloud&tab=overview)
- [Mermaid plus for Confluence](https://marketplace.atlassian.com/apps/1236814/mermaid-plus-for-confluence?hosting=cloud&tab=overview)
- [Azure Devops](https://learn.microsoft.com/en-us/azure/devops/project/wiki/markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) ✅
- [Deepdwn](https://billiam.itch.io/deepdwn) ✅
- [Doctave](https://www.doctave.com/) ✅
@@ -262,7 +263,5 @@ Communication tools and platforms
- [reveal.js-mermaid-plugin](https://github.com/ludwick/reveal.js-mermaid-plugin)
- [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)
<!--- cspell:ignore Blazorade HueHive --->

View File

@@ -17,7 +17,7 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"@vueuse/core": "^12.7.0",
"@vueuse/core": "^13.1.0",
"font-awesome": "^4.7.0",
"jiti": "^2.4.2",
"mermaid": "workspace:^",
@@ -26,7 +26,7 @@
"devDependencies": {
"@iconify-json/carbon": "^1.1.37",
"@unocss/reset": "^66.0.0",
"@vite-pwa/vitepress": "^0.5.3",
"@vite-pwa/vitepress": "^1.0.0",
"@vitejs/plugin-vue": "^5.0.5",
"fast-glob": "^3.3.3",
"https-localhost": "^4.7.1",
@@ -34,7 +34,7 @@
"unocss": "^66.0.0",
"unplugin-vue-components": "^28.4.0",
"vite": "^6.1.1",
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-pwa": "^1.0.0",
"vitepress": "1.6.3",
"workbox-window": "^7.3.0"
}

View File

@@ -407,17 +407,31 @@ erDiagram
## Configuration
### Renderer
### Layout
The layout of the diagram is done with the renderer. The default renderer is dagre.
The layout of the diagram is handled by [`render()`](../config/setup/mermaid/interfaces/Mermaid.md#render). The default layout is dagre.
You can opt to use an alternate renderer named elk by editing the configuration. The elk renderer is better for larger and/or more complex diagrams.
For larger or more-complex diagrams, you can alternatively apply the ELK (Eclipse Layout Kernel) layout using your YAML frontmatter's `config`. For more information, see [Customizing ELK Layout](../intro/syntax-reference.md#customizing-elk-layout).
```yaml
---
config:
layout: elk
---
```
Your Mermaid code should be similar to the following:
```mermaid-example
---
config:
layout: elk
title: Order example
config:
layout: elk
---
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
```
```note

View File

@@ -721,7 +721,7 @@ To give an edge an ID, prepend the edge syntax with the ID followed by an `@` ch
```mermaid
flowchart LR
A e1@> B
A e1@--> B
```
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
@@ -746,7 +746,7 @@ In the initial version, two animation speeds are supported: `fast` and `slow`. S
```mermaid
flowchart LR
A e1@> B
A e1@--> B
e1@{ animation: fast }
```
@@ -758,7 +758,7 @@ You can also animate edges by assigning a class to them and then defining animat
```mermaid
flowchart LR
A e1@> B
A e1@--> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```

View File

@@ -95,17 +95,18 @@ xychart-beta
## Chart Configurations
| Parameter | Description | Default value |
| ------------------------ | ---------------------------------------------- | :-----------: |
| width | Width of the chart | 700 |
| height | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
| Parameter | Description | Default value |
| ------------------------ | ------------------------------------------------------------- | :-----------: |
| width | Width of the chart | 700 |
| height | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| showTitle | Title to be shown or not | true |
| xAxis | xAxis configuration | AxisConfig |
| yAxis | yAxis configuration | AxisConfig |
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
| showDataLabel | Should show the value corresponding to the bar within the bar | false |
### AxisConfig
@@ -152,6 +153,7 @@ config:
xyChart:
width: 900
height: 600
showDataLabel: true
themeVariables:
xyChart:
titleColor: "#ff0000"

View File

@@ -31,6 +31,7 @@ vi.mock('./diagrams/xychart/xychartRenderer.js');
vi.mock('./diagrams/requirement/requirementRenderer.js');
vi.mock('./diagrams/sequence/sequenceRenderer.js');
vi.mock('./diagrams/radar/renderer.js');
vi.mock('./diagrams/architecture/architectureRenderer.js');
// -------------------------------------
@@ -799,6 +800,7 @@ graph TD;A--x|text including URL space|B;`)
{ textDiagramType: 'sequenceDiagram', expectedType: 'sequence' },
{ textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' },
{ textDiagramType: 'radar-beta', expectedType: 'radar' },
{ textDiagramType: 'architecture-beta', expectedType: 'architecture' },
];
describe('accessibility', () => {

View File

@@ -562,7 +562,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
}
let svgPath;
let linePath = lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
const edgeStyles = Array.isArray(edge.style) ? edge.style : edge.style ? [edge.style] : [];
let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:'));
if (edge.look === 'handDrawn') {

View File

@@ -1228,6 +1228,10 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
type: number
default: 10
minimum: 0
showDataLabel:
description: Should show the value corresponding to the bar within the bar
type: boolean
default: false
showTitle:
description: Should show the chart title
type: boolean
@@ -1484,6 +1488,9 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
- bottomMarginAdj
- useMaxWidth
- rightAngles
- titleColor
- titleFontFamily
- titleFontSize
properties:
diagramMarginX:
$ref: '#/$defs/C4DiagramConfig/properties/diagramMarginX'
@@ -1496,6 +1503,10 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
type: integer
default: 150
minimum: 0
maxLabelWidth:
description: Maximum width of actor labels
type: integer
default: 360
width:
description: Width of actor boxes
type: integer
@@ -1584,6 +1595,18 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
items:
type: string
default: ['#fff']
titleColor:
description: Color of the title text in Journey Diagrams
type: string
default: ''
titleFontFamily:
description: Font family to be used for the title text in Journey Diagrams
type: string
default: '"trebuchet ms", verdana, arial, sans-serif'
titleFontSize:
description: Font size to be used for the title text in Journey Diagrams
type: string
default: '4ex'
TimelineDiagramConfig:
# added by https://github.com/mermaid-js/mermaid/commit/0d5246fbc730bf15463d7183fe4400a1e2fc492c

View File

@@ -0,0 +1,2 @@
terminal ARCH_ICON: /\([\w-:]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/;

View File

@@ -1,14 +1,15 @@
grammar Architecture
import "../common/common";
import "arch";
entry Architecture:
NEWLINE*
"architecture-beta"
(
NEWLINE* TitleAndAccessibilities
| NEWLINE* Statement*
| NEWLINE*
)
NEWLINE
| TitleAndAccessibilities
| Statement
)*
;
fragment Statement:
@@ -31,25 +32,21 @@ fragment Arrow:
;
Group:
'group' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
'group' id=ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ID)? EOL
;
Service:
'service' id=ARCH_ID (iconText=ARCH_TEXT_ICON | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
'service' id=ID (iconText=STRING | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ID)? EOL
;
Junction:
'junction' id=ARCH_ID ('in' in=ARCH_ID)? EOL
'junction' id=ID ('in' in=ID)? EOL
;
Edge:
lhsId=ARCH_ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ARCH_ID rhsGroup?=ARROW_GROUP? EOL
lhsId=ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ID rhsGroup?=ARROW_GROUP? EOL
;
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
terminal ARCH_ID: /[\w]+/;
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
terminal ARCH_ICON: /\([\w-:]+\)/;
terminal ARCH_TITLE: /\[[\w ]+\]/;
terminal ARROW_GROUP: /\{group\}/;
terminal ARROW_INTO: /<|>/;

View File

@@ -1,22 +1,35 @@
interface Common {
accDescr?: string;
accTitle?: string;
title?: string;
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
// Base terminals and fragments for common language constructs
// Terminal Precedence: Lazy to Greedy
// When imported, the terminals are considered after the terminals in the importing grammar
// Note: Hence, to add a terminal greedier than the common terminals, import it separately after the common import
fragment EOL returns string:
NEWLINE+ | EOF
;
terminal NEWLINE: /\r?\n/;
fragment TitleAndAccessibilities:
((accDescr+=ACC_DESCR | accTitle+=ACC_TITLE | title+=TITLE) EOL)+
;
terminal BOOLEAN returns boolean: 'true' | 'false';
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
terminal FLOAT returns number: /[0-9]+\.[0-9]+(?!\.)/;
terminal INT returns number: /0|[1-9][0-9]*(?!\.)/;
terminal NUMBER returns number: FLOAT | INT;
terminal STRING returns string: /"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/;
// Alphanumerics with underscores and dashes
// Must start with an alphanumeric or an underscore
// Cant end with a dash
terminal ID returns string: /[\w]([-\w]*\w)?/;
terminal NEWLINE: /\r?\n/;
hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;

View File

@@ -1,6 +1,5 @@
import type { GrammarAST, Stream, TokenBuilderOptions } from 'langium';
import type { TokenType } from 'chevrotain';
import { DefaultTokenBuilder } from 'langium';
export abstract class AbstractMermaidTokenBuilder extends DefaultTokenBuilder {

View File

@@ -1,6 +1,5 @@
import type { CstNode, GrammarAST, ValueType } from 'langium';
import { DefaultValueConverter } from 'langium';
import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './matcher.js';
const rulesRegexes: Record<string, RegExp> = {
@@ -31,14 +30,12 @@ export abstract class AbstractMermaidValueConverter extends DefaultValueConverte
): ValueType {
let value: ValueType | undefined = this.runCommonConverter(rule, input, cstNode);
if (value === undefined) {
value = this.runCustomConverter(rule, input, cstNode);
}
if (value === undefined) {
return super.runConverter(rule, input, cstNode);
value ??= this.runCustomConverter(rule, input, cstNode);
if (value !== undefined) {
return value;
}
return value;
return super.runConverter(rule, input, cstNode);
}
private runCommonConverter(

View File

@@ -1,39 +1,15 @@
grammar GitGraph
interface Common {
accDescr?: string;
accTitle?: string;
title?: string;
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
fragment EOL returns string:
NEWLINE+ | EOF
;
terminal NEWLINE: /\r?\n/;
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
import "../common/common";
import "reference";
entry GitGraph:
NEWLINE*
('gitGraph' | 'gitGraph' ':' | 'gitGraph:' | ('gitGraph' Direction ':'))
NEWLINE*
(
NEWLINE*
(TitleAndAccessibilities |
statements+=Statement |
NEWLINE)*
)
NEWLINE
| TitleAndAccessibilities
| statements+=Statement
)*
;
Statement
@@ -56,12 +32,12 @@ Commit:
|'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT')
)* EOL;
Branch:
'branch' name=(ID|STRING)
'branch' name=(REFERENCE|STRING)
('order:' order=INT)?
EOL;
Merge:
'merge' branch=(ID|STRING)
'merge' branch=(REFERENCE|STRING)
(
'id:' id=STRING
|'tag:' tags+=STRING
@@ -69,7 +45,7 @@ Merge:
)* EOL;
Checkout:
('checkout'|'switch') branch=(ID|STRING) EOL;
('checkout'|'switch') branch=(REFERENCE|STRING) EOL;
CherryPicking:
'cherry-pick'
@@ -78,10 +54,3 @@ CherryPicking:
|'tag:' tags+=STRING
|'parent:' parent=STRING
)* EOL;
terminal INT returns number: /[0-9]+(?=\s)/;
terminal ID returns string: /\w([-\./\w]*[-\w])?/;
terminal STRING: /"[^"]*"|'[^']*'/;

View File

@@ -0,0 +1,4 @@
// Alphanumerics with underscores, dashes, slashes, and dots
// Must start with an alphanumeric or an underscore
// Cant end with a dash, slash, or dot
terminal REFERENCE returns string: /\w([-\./\w]*[-\w])?/;

View File

@@ -12,7 +12,6 @@ export {
Commit,
Merge,
Statement,
isCommon,
isInfo,
isPacket,
isPacketBlock,

View File

@@ -5,15 +5,12 @@ entry Packet:
NEWLINE*
"packet-beta"
(
NEWLINE* TitleAndAccessibilities blocks+=PacketBlock*
| NEWLINE+ blocks+=PacketBlock+
| NEWLINE*
)
TitleAndAccessibilities
| blocks+=PacketBlock
| NEWLINE
)*
;
PacketBlock:
start=INT('-' end=INT)? ':' label=STRING EOL
;
terminal INT returns number: /0|[1-9][0-9]*/;
terminal STRING: /"[^"]*"|'[^']*'/;
;

View File

@@ -5,15 +5,12 @@ entry Pie:
NEWLINE*
"pie" showData?="showData"?
(
NEWLINE* TitleAndAccessibilities sections+=PieSection*
| NEWLINE+ sections+=PieSection+
| NEWLINE*
)
TitleAndAccessibilities
| sections+=PieSection
| NEWLINE
)*
;
PieSection:
label=PIE_SECTION_LABEL ":" value=PIE_SECTION_VALUE EOL
label=STRING ":" value=NUMBER EOL
;
terminal PIE_SECTION_LABEL: /"[^"]+"/;
terminal PIE_SECTION_VALUE returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;

View File

@@ -1,31 +1,5 @@
grammar Radar
// import "../common/common";
// Note: The import statement breaks TitleAndAccessibilities probably because of terminal order definition
// TODO: May need to change the common.langium to fix this
interface Common {
accDescr?: string;
accTitle?: string;
title?: string;
}
fragment TitleAndAccessibilities:
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
;
fragment EOL returns string:
NEWLINE+ | EOF
;
terminal NEWLINE: /\r?\n/;
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
hidden terminal WHITESPACE: /[\t ]+/;
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
import "../common/common";
entry Radar:
NEWLINE*
@@ -76,14 +50,6 @@ Option:
| name='min' value=NUMBER
| name='graticule' value=GRATICULE
)
;
;
terminal NUMBER returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
terminal BOOLEAN returns boolean: 'true' | 'false';
terminal GRATICULE returns string: 'circle' | 'polygon';
terminal ID returns string: /[a-zA-Z_][a-zA-Z0-9\-_]*/;
terminal STRING: /"[^"]*"|'[^']*'/;
terminal GRATICULE returns string: 'circle' | 'polygon';

View File

@@ -0,0 +1,87 @@
import { describe, expect, it } from 'vitest';
import { Architecture } from '../src/language/index.js';
import { expectNoErrorsOrAlternatives, architectureParse as parse } from './test-util.js';
describe('architecture', () => {
describe('should handle architecture definition', () => {
it.each([
`architecture-beta`,
` architecture-beta `,
`\tarchitecture-beta\t`,
`
\tarchitecture-beta
`,
])('should handle regular architecture', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
});
});
describe('should handle TitleAndAccessibilities', () => {
it.each([
`architecture-beta title sample title`,
` architecture-beta title sample title `,
`\tarchitecture-beta\ttitle sample title\t`,
`architecture-beta
\ttitle sample title
`,
])('should handle regular architecture + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title } = result.value;
expect(title).toEqual(['sample title']);
});
it.each([
`architecture-beta
title sample title`,
`architecture-beta
title sample title
`,
])('should handle regular architecture + title in next line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title } = result.value;
expect(title).toEqual(['sample title']);
});
it('should handle regular architecture + title + accTitle + accDescr', () => {
const context = `architecture-beta
title sample title
accTitle: sample accTitle
accDescr: sample accDescr
`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title, accTitle, accDescr } = result.value;
expect(title).toEqual(['sample title']);
expect(accTitle).toEqual(['sample accTitle']);
expect(accDescr).toEqual(['sample accDescr']);
});
it('should handle regular architecture + title + accTitle + multi-line accDescr', () => {
const context = `architecture-beta
title sample title
accTitle: sample accTitle
accDescr {
sample accDescr
}
`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Architecture);
const { title, accTitle, accDescr } = result.value;
expect(title).toEqual(['sample title']);
expect(accTitle).toEqual(['sample accTitle']);
expect(accDescr).toEqual(['sample accDescr']);
});
});
});

View File

@@ -63,6 +63,12 @@ describe('Parsing Branch Statements', () => {
expect(branch.name).toBe('master');
});
it('should parse a branch name starting with numbers', () => {
const result = parse(`gitGraph\n commit\n branch 1.0.1\n`);
const branch = result.value.statements[1] as Branch;
expect(branch.name).toBe('1.0.1');
});
it('should parse a branch with an order property', () => {
const result = parse(`gitGraph\n commit\n branch feature order:1\n`);
const branch = result.value.statements[1] as Branch;
@@ -160,14 +166,14 @@ describe('Parsing CherryPicking Statements', () => {
describe('Parsing with Accessibility Titles and Descriptions', () => {
it('should parse accessibility titles', () => {
const result = parse(`gitGraph\n accTitle: Accessible Graph\n commit\n`);
expect(result.value.accTitle).toBe('Accessible Graph');
expect(result.value.accTitle).toEqual(['Accessible Graph']);
});
it('should parse multiline accessibility descriptions', () => {
const result = parse(
`gitGraph\n accDescr {\n Detailed description\n across multiple lines\n }\n commit\n`
);
expect(result.value.accDescr).toBe('Detailed description\nacross multiple lines');
expect(result.value.accDescr).toEqual(['Detailed description\nacross multiple lines']);
});
});
@@ -183,7 +189,7 @@ describe('Parsing CherryPicking Statements', () => {
merge feature tag:"v1.0"
cherry-pick id:"feat1" tag:"critical fix"
`);
expect(result.value.accTitle).toBe('Complex Example');
expect(result.value.accTitle).toEqual(['Complex Example']);
expect(result.value.statements[0].$type).toBe('Commit');
expect(result.value.statements[1].$type).toBe('Branch');
expect(result.value.statements[2].$type).toBe('Commit');

View File

@@ -4,226 +4,247 @@ import { Pie } from '../src/language/index.js';
import { expectNoErrorsOrAlternatives, pieParse as parse } from './test-util.js';
describe('pie', () => {
it.each([
`pie`,
` pie `,
`\tpie\t`,
`
describe('should handle pie definition with or without showData', () => {
it.each([
`pie`,
` pie `,
`\tpie\t`,
`
\tpie
`,
])('should handle regular pie', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
});
])('should handle regular pie', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
});
it.each([
`pie showData`,
` pie showData `,
`\tpie\tshowData\t`,
`
it.each([
`pie showData`,
` pie showData `,
`\tpie\tshowData\t`,
`
pie\tshowData
`,
])('should handle regular showData', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
])('should handle regular showData', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData } = result.value;
expect(showData).toBeTruthy();
const { showData } = result.value;
expect(showData).toBeTruthy();
});
});
describe('should handle TitleAndAccessibilities', () => {
describe('should handle TitleAndAccessibilities without showData', () => {
it.each([
`pie title sample title`,
` pie title sample title `,
`\tpie\ttitle sample title\t`,
`pie
\ttitle sample title
`,
])('should handle regular pie + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
it.each([
`pie title sample title`,
` pie title sample title `,
`\tpie\ttitle sample title\t`,
`pie
\ttitle sample title
`,
])('should handle regular pie + title in same line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toEqual(['sample title']);
});
const { title } = result.value;
expect(title).toBe('sample title');
});
it.each([
`pie
title sample title`,
`pie
title sample title
`,
`pie
title sample title`,
`pie
title sample title
`,
])('should handle regular pie + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toBe('sample title');
});
it.each([
`pie showData title sample title`,
`pie showData title sample title
`,
])('should handle regular pie + showData + title', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
it.each([
`pie showData
title sample title`,
`pie showData
title sample title
`,
`pie showData
title sample title`,
`pie showData
title sample title
`,
])('should handle regular showData + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toBe('sample title');
});
describe('sections', () => {
describe('normal', () => {
it.each([
`pie
title sample title`,
`pie
title sample title
`,
`pie
title sample title`,
`pie
title sample title
`,
])('should handle regular pie + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title } = result.value;
expect(title).toEqual(['sample title']);
});
});
describe('should handle TitleAndAccessibilities with showData', () => {
it.each([
`pie showData title sample title`,
`pie showData title sample title
`,
])('should handle regular pie + showData + title', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toEqual(['sample title']);
});
it.each([
`pie showData
title sample title`,
`pie showData
title sample title
`,
`pie showData
title sample title`,
`pie showData
title sample title
`,
])('should handle regular showData + title in different line', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, title } = result.value;
expect(showData).toBeTruthy();
expect(title).toEqual(['sample title']);
});
});
});
describe('should handle sections', () => {
it.each([
`pie
"GitHub":100
"GitLab":50`,
`pie
`pie
"GitHub" : 100
"GitLab" : 50`,
`pie
`pie
"GitHub"\t:\t100
"GitLab"\t:\t50`,
`pie
`pie
\t"GitHub" \t : \t 100
\t"GitLab" \t : \t 50
`,
])('should handle regular sections', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
])('should handle regular sections', (context: string) => {
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { sections } = result.value;
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
const { sections } = result.value;
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
it('should handle sections with showData', () => {
const context = `pie showData
it('should handle sections with showData', () => {
const context = `pie showData
"GitHub": 100
"GitLab": 50`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { showData, sections } = result.value;
expect(showData).toBeTruthy();
const { showData, sections } = result.value;
expect(showData).toBeTruthy();
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
it('should handle sections with title', () => {
const context = `pie title sample wow
it('should handle sections with title', () => {
const context = `pie title sample wow
"GitHub": 100
"GitLab": 50`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { title, sections } = result.value;
expect(title).toBe('sample wow');
const { title, sections } = result.value;
expect(title).toEqual(['sample wow']);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
it('should handle sections with accTitle', () => {
const context = `pie accTitle: sample wow
it('should handle value with positive decimal', () => {
const context = `pie
"ash": 60.67
"bat": 40`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { sections } = result.value;
expect(sections[0].label).toBe('ash');
expect(sections[0].value).toBe(60.67);
expect(sections[1].label).toBe('bat');
expect(sections[1].value).toBe(40);
});
it('should handle sections with accTitle', () => {
const context = `pie accTitle: sample wow
"GitHub": 100
"GitLab": 50`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { accTitle, sections } = result.value;
expect(accTitle).toBe('sample wow');
const { accTitle, sections } = result.value;
expect(accTitle).toEqual(['sample wow']);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
it('should handle sections with single line accDescr', () => {
const context = `pie accDescr: sample wow
it('should handle sections with single line accDescr', () => {
const context = `pie accDescr: sample wow
"GitHub": 100
"GitLab": 50`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { accDescr, sections } = result.value;
expect(accDescr).toBe('sample wow');
const { accDescr, sections } = result.value;
expect(accDescr).toEqual(['sample wow']);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
it('should handle sections with multi line accDescr', () => {
const context = `pie accDescr {
it('should handle sections with multi line accDescr', () => {
const context = `pie accDescr {
sample wow
}
"GitHub": 100
"GitLab": 50`;
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const result = parse(context);
expectNoErrorsOrAlternatives(result);
expect(result.value.$type).toBe(Pie);
const { accDescr, sections } = result.value;
expect(accDescr).toBe('sample wow');
const { accDescr, sections } = result.value;
expect(accDescr).toEqual(['sample wow']);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[0].label).toBe('GitHub');
expect(sections[0].value).toBe(100);
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
expect(sections[1].label).toBe('GitLab');
expect(sections[1].value).toBe(50);
});
});
});

View File

@@ -35,7 +35,7 @@ describe('radar', () => {
expect(result.value.$type).toBe(Radar);
const { title } = result.value;
expect(title).toBe('My Title');
expect(title).toEqual(['My Title']);
});
it.each([
@@ -47,7 +47,7 @@ describe('radar', () => {
expect(result.value.$type).toBe(Radar);
const { accDescr } = result.value;
expect(accDescr).toBe('My Accessible Description');
expect(accDescr).toEqual(['My Accessible Description']);
});
it.each([
@@ -59,7 +59,7 @@ describe('radar', () => {
expect(result.value.$type).toBe(Radar);
const { accTitle } = result.value;
expect(accTitle).toBe('My Accessible Title');
expect(accTitle).toEqual(['My Accessible Title']);
});
it.each([
@@ -75,9 +75,9 @@ describe('radar', () => {
expect(result.value.$type).toBe(Radar);
const { title, accDescr, accTitle } = result.value;
expect(title).toBe('My Title');
expect(accDescr).toBe('My Accessible Description');
expect(accTitle).toBe('My Accessible Title');
expect(title).toEqual(['My Title']);
expect(accDescr).toEqual(['My Accessible Description']);
expect(accTitle).toEqual(['My Accessible Title']);
});
});

View File

@@ -1,6 +1,8 @@
import type { LangiumParser, ParseResult } from 'langium';
import { expect, vi } from 'vitest';
import type {
Architecture,
ArchitectureServices,
Info,
InfoServices,
Pie,
@@ -13,6 +15,7 @@ import type {
GitGraphServices,
} from '../src/language/index.js';
import {
createArchitectureServices,
createInfoServices,
createPieServices,
createRadarServices,
@@ -47,6 +50,17 @@ export function createInfoTestServices() {
}
export const infoParse = createInfoTestServices().parse;
const architectureServices: ArchitectureServices = createArchitectureServices().Architecture;
const architectureParser: LangiumParser = architectureServices.parser.LangiumParser;
export function createArchitectureTestServices() {
const parse = (input: string) => {
return architectureParser.parse<Architecture>(input);
};
return { services: architectureServices, parse };
}
export const architectureParse = createArchitectureTestServices().parse;
const pieServices: PieServices = createPieServices().Pie;
const pieParser: LangiumParser = pieServices.parser.LangiumParser;
export function createPieTestServices() {

1425
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

115
scripts/compare-timings.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* Compares new E2E test timings with previous timings and determines whether to keep the new timings.
*
* The script will:
* 1. Read old timings from git HEAD
* 2. Read new timings from the current file
* 3. Compare the timings and specs
* 4. Keep new timings if:
* - Specs were added/removed
* - Any timing changed by 20% or more
* 5. Revert to old timings if:
* - No significant timing changes
*
* This helps prevent unnecessary timing updates when test performance hasn't changed significantly.
*/
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
interface Timing {
spec: string;
duration: number;
}
interface TimingsFile {
durations: Timing[];
}
interface CleanupOptions {
keepNew: boolean;
reason: string;
}
const TIMINGS_FILE = 'cypress/timings.json';
const TIMINGS_PATH = path.join(process.cwd(), TIMINGS_FILE);
function log(message: string): void {
// eslint-disable-next-line no-console
console.log(message);
}
function readOldTimings(): TimingsFile {
try {
const oldContent = execSync(`git show HEAD:${TIMINGS_FILE}`, { encoding: 'utf8' });
return JSON.parse(oldContent);
} catch {
log('Error getting old timings, using empty file');
return { durations: [] };
}
}
function readNewTimings(): TimingsFile {
return JSON.parse(fs.readFileSync(TIMINGS_PATH, 'utf8'));
}
function cleanupFiles({ keepNew, reason }: CleanupOptions): void {
if (keepNew) {
log(`Keeping new timings: ${reason}`);
} else {
log(`Reverting to old timings: ${reason}`);
execSync(`git checkout HEAD -- ${TIMINGS_FILE}`);
}
}
function compareTimings(): void {
const oldTimings = readOldTimings();
const newTimings = readNewTimings();
const oldSpecs = new Set(oldTimings.durations.map((d) => d.spec));
const newSpecs = new Set(newTimings.durations.map((d) => d.spec));
// Check if specs were added or removed
const addedSpecs = [...newSpecs].filter((spec) => !oldSpecs.has(spec));
const removedSpecs = [...oldSpecs].filter((spec) => !newSpecs.has(spec));
if (addedSpecs.length > 0 || removedSpecs.length > 0) {
log('Specs changed:');
if (addedSpecs.length > 0) {
log(`Added: ${addedSpecs.join(', ')}`);
}
if (removedSpecs.length > 0) {
log(`Removed: ${removedSpecs.join(', ')}`);
}
return cleanupFiles({ keepNew: true, reason: 'Specs were added or removed' });
}
// Check timing variations
const timingChanges = newTimings.durations.map((newTiming) => {
const oldTiming = oldTimings.durations.find((d) => d.spec === newTiming.spec);
if (!oldTiming) {
throw new Error(`Could not find old timing for spec: ${newTiming.spec}`);
}
const change = Math.abs(newTiming.duration - oldTiming.duration);
const changePercent = change / oldTiming.duration;
return { spec: newTiming.spec, change, changePercent };
});
// Filter changes that's more than 5 seconds and 20% different
const significantChanges = timingChanges.filter((t) => t.change > 5000 && t.changePercent >= 0.2);
if (significantChanges.length === 0) {
log('No significant timing changes detected (threshold: 5s and 20%)');
return cleanupFiles({ keepNew: false, reason: 'No significant timing changes' });
}
log('Significant timing changes:');
significantChanges.forEach((t) => {
log(`${t.spec}: ${t.change.toFixed(1)}ms (${(t.changePercent * 100).toFixed(1)}%)`);
});
cleanupFiles({ keepNew: true, reason: 'Significant timing changes detected' });
}
compareTimings();