Compare commits

..

128 Commits

Author SHA1 Message Date
Per Brolin
97614b8af5 Updated to 9.4.0 2023-02-15 15:54:02 +01:00
Knut Sveidqvist
343e48302e Fix for direction issue with elk rendering 2023-02-15 15:12:52 +01:00
Per Brolin
aef989fe03 Corrected sequenceRenderer 2023-02-15 15:11:23 +01:00
Per Brolin
0c38f20281 Clearing sequence diagram before rendering 2023-02-15 14:17:46 +01:00
Knut Sveidqvist
eed427b4ac Adding simple direction logic 2023-02-15 14:10:38 +01:00
Knut Sveidqvist
027296df68 Working version before applying direction logic 2023-02-15 13:54:33 +01:00
Per Brolin
a956be7bbf Release candidate 2023-02-10 11:03:25 +01:00
Per Brolin
e0286be8d2 Corrected typo 2023-02-10 08:58:58 +01:00
Knut Sveidqvist
74df4a7a68 Mind map coming in from the cold. A lazy loaded diagram in the same way as timeline. 2023-02-08 17:42:58 +01:00
Knut Sveidqvist
38a84a7fe0 Merge branch 'develop' into release/9.4.0 2023-02-08 14:00:02 +01:00
Knut Sveidqvist
5e864c3b9b Merge branch 'timeline' into release/9.4.0 2023-02-08 13:58:56 +01:00
Knut Sveidqvist
b5a4cc0e17 Merge pull request #4014 from mermaid-js/timeline
Timeline Diagram
2023-02-08 13:57:36 +01:00
Knut Sveidqvist
82f7e1b754 Version updates 2023-02-08 13:50:58 +01:00
Knut Sveidqvist
580903051f Fixing broken test 2023-02-08 13:40:41 +01:00
Knut Sveidqvist
ce037a84ca Fixing build after last batch of changes 2023-02-08 13:16:33 +01:00
Sidharth Vinod
7f254e37e9 Cleanup 2023-02-08 16:01:29 +05:30
Sidharth Vinod
d562a81019 Merge branch 'develop' into timeline
* develop: (45 commits)
  Showcase section to the docs - keepings docs up to date (#4055)
  bugfix: add missing d3 curves to flowchart and docs
  fix(deps): update dependency dagre-d3-es to v7.0.8
  build(pre-commit): cache eslint in pre-commit
  build(lint): cache eslint with strategy content
  Update cypress/integration/rendering/sequencediagram.spec.js
  feat(er): allow leading underscore for attributes name
  ci(lint): show nice error on lint failure
  chore: add moment to dependencies
  Update docs
  Update mindmap.md
  chore: remove moment-mini
  docs(readme-ch): fix twitter link
  build(lint): cache prettier on `pnpm run lint`
  fix: moment-mini default exporter
  docs(readme): update broken twitter badge
  test(er): improve tests on multiple key constraints
  Fixes Typo, remove console.log
  doc(er): add documentation on multiple key constraints
  feat(er): allow multiple constraints on attributes
  ...
2023-02-08 15:57:09 +05:30
Knut Sveidqvist
6a045db83c Fix for e2e issue with mindmap tests, not related to timeline 2023-02-08 11:24:09 +01:00
Sidharth Vinod
563896400b Merge pull request #4057 from aloisklink/chore/cache-eslint-in-pre-commit
Cache `eslint` in pre-commit script (makes `git commit` 5x faster)
2023-02-08 00:14:37 +05:30
Omer Rosenbaum
73ee9e9a92 Showcase section to the docs - keepings docs up to date (#4055)
* Add a Showcase section to the docs with Swimm

* Build docs

* Move FAQ to Config

* Create showcases page

* Build docs

* Prettier

* feat: Redirect old docs URLs

---------

Co-authored-by: Sidharth Vinod <sidharthv96@gmail.com>
2023-02-07 23:15:48 +05:30
Sidharth Vinod
923ddc8309 Merge pull request #4038 from natasha-jarus/bug/missing-flowchart-curves
bugfix: add missing d3 curves to flowchart and docs
2023-02-07 14:00:53 +05:30
Sidharth Vinod
64096b22dc Merge pull request #3965 from oleveau/feature/AddBoxForSeqDiagrams
Add Box support in Sequence Diagrams
2023-02-07 13:58:32 +05:30
Natasha Jarus
84d563584f bugfix: add missing d3 curves to flowchart and docs 2023-02-06 13:28:40 -08:00
renovate[bot]
ac67794fb2 fix(deps): update dependency dagre-d3-es to v7.0.8 2023-02-06 01:07:08 +00:00
Alois Klink
98b2148352 build(pre-commit): cache eslint in pre-commit
Run eslint with `--cache` to speed up pre-commit scripts.

This was added to the `pnpm run lint` script in
b7f9495a (build: add eslint --cache file, 2022-08-27)
and doesn't seem to be causing any issues.

I haven't enabled `--cache` for `prettier` since as of prettier 2.8.0,
their cache invalidation doesn't yet work with prettier plugins.
2023-02-05 19:33:30 +00:00
Alois Klink
b3e509b7d4 build(lint): cache eslint with strategy content
Cache eslint using `--cache-strategy content` instead of the default
`--cache-strategy metadata`.

By default, `eslint` uses the file metadata (e.g. modification time)
to detect when the cache should be invalidated.

However, this is not efficient with `git`, since git constantly changes
the modification time,
e.g. running `git switch main && git switch original-branch` would not
change the file contents, but would change the file mtimes and force
eslint to re-lint everything.

Using the file contents is slower (~3% for me), but more resilient.

See
https://eslint.org/docs/latest/use/command-line-interface#--cache-strategy
2023-02-05 19:33:30 +00:00
Sidharth Vinod
8d6d90021a Update cypress/integration/rendering/sequencediagram.spec.js 2023-02-05 11:54:56 +05:30
Sidharth Vinod
71e5a53172 Merge pull request #4034 from emersonbottero/develop
fix: moment-mini default exporter
2023-02-05 11:48:56 +05:30
Sidharth Vinod
a75cacd4a4 Merge pull request #4033 from tomperr/feature/4031_er_leading_underscore_attribute_name
feat(er): allow leading underscore for attributes name
2023-01-30 18:56:15 +05:30
Tom PERRILLAT-COLLOMB
c2ec63d4fd feat(er): allow leading underscore for attributes name 2023-01-30 12:34:06 +01:00
Sidharth Vinod
7ecaaaf46f Merge pull request #4035 from aloisklink/chore/cache-prettier
build(lint): cache prettier on `pnpm run lint`
2023-01-30 10:55:50 +05:30
Alois Klink
4900647bf0 ci(lint): show nice error on lint failure
Prints a nice error on GitHub Actions if `pnpm run lint` fails.
2023-01-29 20:27:31 +00:00
Emerson Bottero
df10d64989 chore: add moment to dependencies 2023-01-29 11:24:48 -03:00
sidharthv96
b9bed14cda Update docs 2023-01-28 19:46:10 +00:00
Sidharth Vinod
cd007cabb5 Merge pull request #4042 from GavinPen/patch-1
Update mindmap.md
2023-01-29 01:12:30 +05:30
GavinPen
cb1a20264d Update mindmap.md
Minor Improvement to wording
2023-01-28 17:42:30 +00:00
Emerson Bottero
7179f1bcba chore: remove moment-mini 2023-01-26 20:55:57 -03:00
Alois Klink
b35e4a8c52 Merge pull request #4032 from LeoDog896/patch-1
docs(readme): update broken twitter badge
2023-01-26 17:28:02 +00:00
Tristan F
a89cb9f0d6 docs(readme-ch): fix twitter link 2023-01-26 12:07:44 -05:00
Alois Klink
8b4426aebf build(lint): cache prettier on pnpm run lint
[Prettier 2.7.0](https://prettier.io/blog/2022/06/14/2.7.0.html) added
a `--cache` CLI option to greatly speed up subsequent prettier runs.

By default, the cache is stored in
`./node_modules/.cache/prettier/.prettier-cache` and uses an `md5`
checksum of the contents as the cache-key.

On my PC, running `pnpm run lint` used to take 13.9 seconds, but now
it only takes 6 seconds.

Potential issues
----------------

Although updating Node.JS/Prettier will invalidate the cache,
updating or changing prettier plugins won't invalidate the cache.

Since we do use `prettier-plugin-jsdoc` in Mermaid, this might cause
a minor issue, but CI should catch it.
2023-01-26 16:31:40 +00:00
ashishjain0512
003997372e Update docs 2023-01-26 11:48:25 +00:00
ashishj
61f33567ae Making timeline diagram lazy-loaded internal, removing separate package 2023-01-26 10:29:35 +01:00
Olivier Leveau
9fb6f1998f Merge branch 'develop' into feature/AddBoxForSeqDiagrams 2023-01-26 09:14:18 +01:00
Emerson Bottero
19e3624e89 fix: moment-mini default exporter 2023-01-26 01:23:41 -03:00
Tristan F
be332cfdef docs(readme): update broken twitter badge
(From https://github.com/badges/shields/issues/8837):

The Twitter API has changed in recent days by closing support for 3rd party apps, breaking the Twitter follow count badge.
2023-01-25 15:34:24 -05:00
Sidharth Vinod
c51f6df82c Merge pull request #4030 from tomperr/feature/4009_er_multiple_constraints
feat(er): add multiple key constraints
2023-01-26 00:16:58 +05:30
Tom PERRILLAT-COLLOMB
dc0a46f742 test(er): improve tests on multiple key constraints 2023-01-25 19:40:40 +01:00
Sidharth Vinod
c76728b423 Merge pull request #3902 from weedySeaDragon/bug/3858_state_named_state_container
Bug/3858 [state] trailing whitespace in ids for named state container
2023-01-25 22:51:34 +05:30
Sidharth Vinod
0aa7da261f Merge pull request #3962 from aloisklink/build/add-autogenerated-header-after-yaml-codeblocks
Add support for YAML frontmatter in Markdown docs (used for Vitepress config)
2023-01-25 22:50:20 +05:30
ashishjain0512
5e6aac4377 Update docs 2023-01-25 17:06:30 +00:00
ashishj
52bd5181f9 More Lint Fixes 2023-01-25 18:03:04 +01:00
ashishj
49ce5222c9 Lint Fixes 2023-01-25 17:49:35 +01:00
ashishj
df1e9c4117 Added rendering test cases 2023-01-25 17:46:23 +01:00
ashishj
5c14df0aeb Added jest test cases 2023-01-25 17:45:47 +01:00
ashishj
8af5c4c341 Fix parser grammer conflicts 2023-01-25 17:44:34 +01:00
Olivier Leveau
f7756ccc00 Fixes Typo, remove console.log 2023-01-25 17:02:10 +01:00
Tom PERRILLAT-COLLOMB
3066a4b43a doc(er): add documentation on multiple key constraints 2023-01-25 15:13:58 +01:00
Tom PERRILLAT-COLLOMB
26e9b1790b feat(er): allow multiple constraints on attributes
little changes in grammar to get a list of constraints (PK, FK or UK), so little changes in renderer to handle this list
2023-01-24 21:20:11 +01:00
Sidharth Vinod
c256a6b887 Merge pull request #4024 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update pnpm to v7.25.1
2023-01-24 17:06:06 +05:30
Knut Sveidqvist
bd1449a0d3 Merge pull request #4004 from mermaid-js/sidv/fixUnhandledPromise
fix(#4003): Remove unhandled promises
2023-01-24 11:58:25 +01:00
renovate[bot]
c9833dcd79 chore(deps): update pnpm to v7.25.1 2023-01-24 10:43:54 +00:00
Sidharth Vinod
6792bb94b7 chore: Use logger 2023-01-24 16:11:04 +05:30
Sidharth Vinod
b36e5d0d3b fix: Remove unnecessary void's.
Co-authored-by: Alois Klink <alois@aloisklink.com>
2023-01-24 16:08:43 +05:30
Sidharth Vinod
ead4037963 chore: Enable 57-elk test 2023-01-24 16:00:59 +05:30
Sidharth Vinod
a28f6bf80c Merge branch 'develop' into sidv/fixUnhandledPromise
* develop:
  chore: Skip 57-elk test
  Update docs
  Update integrations.md
2023-01-24 16:00:35 +05:30
Sidharth Vinod
fb6ba231d0 chore: Skip 57-elk test 2023-01-24 15:59:37 +05:30
sidharthv96
e4491136c3 Update docs 2023-01-24 07:02:14 +00:00
Sidharth Vinod
e7451e7a4e Merge pull request #4011 from Barry1/patch-1
Update integrations.md
2023-01-24 12:29:04 +05:30
Alois Klink
816f2f512e build(docs): hide YAML when building for GitHub
YAML front-matter is currently only used for Vitepress.

Because of that, to avoid confusion, we can remove this YAML
front-matter when converting the Markdown in packages/mermaid/src/docs
to go into the `docs/` folder for GitHub browsing.
2023-01-22 19:23:57 +00:00
Alois Klink
76c3716b2d docs: add vitepress metadata to flowchart docs
Changes the title in Vitepress, as well as using `outline: "deep"`
for a better outline/table-of-contents for the page.

See https://vitepress.vuejs.org/config/theme-configs#outline
for docs on what `outline: "deep"` does.
2023-01-22 19:21:53 +00:00
Alois Klink
2f1a521db6 build(docs): add auto-generated header after YAML
Add the auto-generated header after any YAML front-matter blocks.

YAML front-matter is normally only valid in Markdown when it's at the
beginning of the Markdown file. GitHub/Vitepress may otherwise render
it incorrectly.
2023-01-22 19:21:53 +00:00
Alois Klink
8f4caa4537 refactor(docs): use remark-compatible plugin
Change the `transformBlocks` function, which transforms a markdown str,
and instead making it into a
`transformMarkdownAst` function, which transforms a Markdown AST.

This means we can use the remark/unifiedjs plugin infrastructure, see
https://unifiedjs.com/learn/guide/create-a-plugin/
2023-01-22 19:21:51 +00:00
Alois Klink
b26cdb3e46 build(docs): support YAML frontmatter in markdown
Vitepress uses YAML frontmatter to configure Vitepress specific
settings, see https://vitepress.vuejs.org/config/frontmatter-configs

We just need to use `remark-frontmatter` to add support for it.

GitHub also renders the YAML front-matter nicely in a table
automatically, but maybe we should instead strip it, if it's only
used by Vitepress?
2023-01-22 19:21:18 +00:00
Knut Sveidqvist
de8928b2d9 #4016 Fix for max_sections in mindmap renderer 2023-01-20 12:48:06 +01:00
pbrolin47
e5b33087f3 Merge pull request #4013 from mermaid-js/4012-handling-rows-wiht-only-spaces
Mindmaps: Handling rows with only spaces in them (#4012)
2023-01-20 10:23:06 +01:00
Knut Sveidqvist
bc56a7d4f1 #4012 Neater grammar for the rows before mindmap 2023-01-20 10:21:13 +01:00
Knut Sveidqvist
80903e427c #4012 Allowing multiple lines before the mindmap statement 2023-01-20 10:18:26 +01:00
pbrolin47
7b67f5783e Merge pull request #4018 from mermaid-js/4016-differentiate-colors
Mindmaps: differentiate the colors between the root node and the first section #4017
2023-01-20 09:16:02 +01:00
Knut Sveidqvist
023f2354cd 2023-01-20 08:31:01 +01:00
ashishj
7ef71cc04d Remove old test cases 2023-01-19 21:05:38 +01:00
Knut Sveidqvist
3cd15cdcf2 #4012 Handling rows with only spaces in them 2023-01-19 20:50:36 +01:00
ashishj
e865368649 Updating version and clean up package.json 2023-01-19 20:42:33 +01:00
ashishj
be818ad57f Pushing fix for broken themeVariable functionality 2023-01-19 20:30:11 +01:00
ashishj
ca22e85e55 Merge branch 'release/9.4.0' into timeline 2023-01-19 19:20:29 +01:00
ashishj
652a42fe1a Add directive parsing functionality to timeline diagram 2023-01-19 19:03:05 +01:00
Bastian Ebeling
3aeef7b846 Update integrations.md
Corrected `Vs Code` to `VS Code` and inserted `Markdown Preview Enhanced`
2023-01-19 07:15:32 +01:00
Olivier Leveau
250f1f9687 Merge branch 'develop' into feature/AddBoxForSeqDiagrams 2023-01-18 11:35:39 +01:00
Sidharth Vinod
5b9839cbd0 fix package.json
add `@types/cytoscape`
2023-01-18 00:53:00 +05:30
Sidharth Vinod
afe3f593e1 fix(#4003): Remove unhandled promises
Add eslint rules to check for unhandled promises
Fix all existing unhandled promise issues
2023-01-18 00:47:49 +05:30
Knut Sveidqvist
e573be4afd Merge branch 'develop' into release/9.4.0 2023-01-17 18:08:04 +01:00
Sidharth Vinod
549483d19b Update funding :) 2023-01-17 22:09:22 +05:30
Sidharth Vinod
8a485c3c88 Merge pull request #3993 from jonabc/bug/sequence-firefox-font-size
Parse style string and number font size values from configuration inputs
2023-01-17 22:07:11 +05:30
Sidharth Vinod
96dedc7b5e flowchart-elk repro 2023-01-17 21:37:32 +05:30
Jon Ruskin
9629c8d8d6 use undefined not null 2023-01-17 08:31:36 -07:00
Knut Sveidqvist
7e8c1b0393 Merge pull request #3984 from mermaid-js/sidv/internalFCV3
feat: Flowchart-elk integration
2023-01-17 14:18:29 +01:00
Knut Sveidqvist
e4bdfee85a Merge pull request #3995 from mermaid-js/sidv/properlyWaitTests
feat: Wait for rendering to finish before taking image snapshots
2023-01-17 14:15:28 +01:00
Sidharth Vinod
c0f9b3c00b Merge branch 'develop' into sidv/internalFCV3
* develop:
  Update docs
  docs(sequenceDiagram): subvert prettification of arrow types
2023-01-17 14:07:12 +05:30
Sidharth Vinod
fba3f8481e Merge branch 'develop' into sidv/internalFCV3
* develop:
  chore(deps): update all non-major dependencies
  fix(deps): update dependency dagre-d3-es to v7.0.6
2023-01-16 13:27:09 +05:30
Sidharth Vinod
ae7c9475ef Merge branch 'sidv/properlyWaitTests' into sidv/internalFCV3
* sidv/properlyWaitTests:
  fix tests
2023-01-16 13:18:31 +05:30
Sidharth Vinod
d320e788c7 fix tests 2023-01-16 13:18:16 +05:30
Sidharth Vinod
ded83b2ce7 Fix title 2023-01-16 02:17:25 +05:30
Sidharth Vinod
1c8c69f8db Merge branch 'sidv/properlyWaitTests' into sidv/internalFCV3
* sidv/properlyWaitTests:
  feat: Wait for rendering to finish before taking image snapshots
  Update docs
  chore(deps): update all non-major dependencies
  Fix: Too many `primaryBorderColor`
2023-01-16 02:11:59 +05:30
Sidharth Vinod
4d3f151cf0 feat: Wait for rendering to finish before taking image snapshots 2023-01-16 02:07:37 +05:30
Sidharth Vinod
8f8ae64ced upload error snapshots 2023-01-16 01:23:42 +05:30
Jon Ruskin
b93ce24c3d handle string and number font size configurations 2023-01-14 14:05:05 -07:00
Sidharth Vinod
05d1fc44e8 Merge branch 'develop' into sidv/internalFCV3
* develop:
  test(sequenceDiagram): mock more d3 curve* types
  test(docs): fix test failure due to bad merge
  chore(dev-deps): update vitest to `^0.27.1`
2023-01-13 10:23:59 +05:30
Sidharth Vinod
ee056e1ac3 test: Update width 2023-01-12 19:47:39 +05:30
Sidharth Vinod
0476bdc68f format 2023-01-12 19:04:06 +05:30
Sidharth Vinod
242a508d50 feat: Flowchart-elk integration 2023-01-12 18:26:08 +05:30
oleveau
7b5bebd9c4 Sanitize text of the box 2023-01-11 22:10:41 +01:00
ashishj
68609fd8d0 Merge develop in timeline branch 2023-01-11 18:13:26 +01:00
Olivier Leveau
117f0ab6da Fix test / Add Tests 2023-01-06 13:38:03 +01:00
Olivier Leveau
64a935515c Better color detection + fix win32 path handling 2023-01-06 12:47:09 +01:00
Olivier Leveau
e6befbaa3f Merge branch 'develop' into feature/AddBoxForSeqDiagrams 2023-01-06 11:06:00 +01:00
Olivier Leveau
e659601e03 Add Box support in Sequence Diagrams 2023-01-05 15:40:40 +01:00
Ashley Engelund
521a30dcd7 add a space near the start for symmetry and readability
Co-authored-by: Sidharth Vinod <sidharthv96@gmail.com>
2022-12-16 06:37:05 -08:00
ashishj
61ef4692b0 Updated documentation and examples 2022-12-15 20:24:13 +01:00
ashishj
6ab7eb55cb Updated styling and added documentation 2022-12-15 20:14:49 +01:00
ashishj
fefcc43dd4 Merged Develop in timeline branch 2022-12-15 17:10:56 +01:00
ashishj
2141057ab4 add more runnning examples 2022-12-15 17:00:53 +01:00
Ashley Engelund (weedySeaDragon @ github)
ae7fd777a7 + parsing specs that actually check results 2022-12-09 16:22:56 -08:00
Ashley Engelund (weedySeaDragon @ github)
d666981599 parser: remove extra popState; whitespace formatting; remove 'zxzx' weird string 2022-12-09 16:21:28 -08:00
Ashley Engelund (weedySeaDragon @ github)
f0b8657423 state demo: AS descriptions for states in composites 2022-12-09 16:01:13 -08:00
Ashley Engelund (weedySeaDragon @ github)
ae25a08fe3 (minor) add JSDOC comments 2022-12-09 13:35:44 -08:00
Ashley Engelund (weedySeaDragon @ github)
2b7aa3f99d start of parser spec; test for this bug 2022-12-09 13:35:23 -08:00
Ashley Engelund (weedySeaDragon @ github)
e7b4f7f6ca trim state ids as they are processed by docTranslator 2022-12-09 13:33:51 -08:00
ashishj
14ff8a8570 add runnning example 2022-11-23 19:32:27 +01:00
ashishj
0d5246fbc7 initial commit 2022-11-23 19:28:26 +01:00
145 changed files with 6490 additions and 2429 deletions

View File

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

150
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,150 @@
module.exports = {
env: {
browser: true,
es6: true,
'jest/globals': true,
node: true,
},
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 2020,
allowAutomaticSingleRunInference: true,
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
parser: '@typescript-eslint/parser',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:json/recommended',
'plugin:markdown/recommended',
'plugin:@cspell/recommended',
'prettier',
],
plugins: [
'@typescript-eslint',
'no-only-tests',
'html',
'jest',
'jsdoc',
'json',
'@cspell',
'lodash',
'unicorn',
],
rules: {
curly: 'error',
'no-console': 'error',
'no-prototype-builtins': 'off',
'no-unused-vars': 'off',
'cypress/no-async-tests': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description',
'ts-check': 'allow-with-description',
minimumDescriptionLength: 10,
},
],
'json/*': ['error', 'allowComments'],
'@cspell/spellchecker': [
'error',
{
checkIdentifiers: false,
checkStrings: false,
checkStringTemplates: false,
},
],
'no-empty': [
'error',
{
allowEmptyCatch: true,
},
],
'no-only-tests/no-only-tests': 'error',
'lodash/import-scope': ['error', 'method'],
'unicorn/better-regex': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-array-push-push': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-instanceof-array': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unsafe-regex': 'warn',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-object-from-entries': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
'unicorn/string-content': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/no-lonely-if': 'error',
},
overrides: [
{
files: ['cypress/**', 'demos/**'],
rules: {
'no-console': 'off',
},
},
{
files: ['*.{js,jsx,mjs,cjs}'],
extends: ['plugin:jsdoc/recommended'],
rules: {
'jsdoc/check-indentation': 'off',
'jsdoc/check-alignment': 'off',
'jsdoc/check-line-alignment': 'off',
'jsdoc/multiline-blocks': 'off',
'jsdoc/newline-after-description': 'off',
'jsdoc/tag-lines': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-returns-description': 'off',
},
},
{
files: ['*.{ts,tsx}'],
plugins: ['tsdoc'],
rules: {
'tsdoc/syntax': 'error',
},
},
{
files: ['*.spec.{ts,js}', 'cypress/**', 'demos/**', '**/docs/**'],
rules: {
'jsdoc/require-jsdoc': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
{
files: ['*.html', '*.md', '**/*.md/*'],
rules: {
'no-var': 'error',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
parserOptions: {
project: null,
},
},
],
};

View File

@@ -1,138 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"jest/globals": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended",
"plugin:markdown/recommended",
"plugin:@cspell/recommended",
"prettier"
],
"plugins": [
"@typescript-eslint",
"no-only-tests",
"html",
"jest",
"jsdoc",
"json",
"@cspell",
"lodash",
"unicorn"
],
"rules": {
"curly": "error",
"no-console": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"cypress/no-async-tests": "off",
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-expect-error": "allow-with-description",
"ts-ignore": "allow-with-description",
"ts-nocheck": "allow-with-description",
"ts-check": "allow-with-description",
"minimumDescriptionLength": 10
}
],
"json/*": ["error", "allowComments"],
"@cspell/spellchecker": [
"error",
{
"checkIdentifiers": false,
"checkStrings": false,
"checkStringTemplates": false
}
],
"no-empty": [
"error",
{
"allowEmptyCatch": true
}
],
"no-only-tests/no-only-tests": "error",
"lodash/import-scope": ["error", "method"],
"unicorn/better-regex": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-array-push-push": "error",
"unicorn/no-for-loop": "error",
"unicorn/no-null": "error",
"unicorn/no-instanceof-array": "error",
"unicorn/no-typeof-undefined": "error",
"unicorn/no-unnecessary-await": "error",
"unicorn/no-unsafe-regex": "warn",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-flat-map": "error",
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-default-parameters": "error",
"unicorn/prefer-includes": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-object-from-entries": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-string-trim-start-end": "error",
"unicorn/string-content": "error",
"unicorn/prefer-spread": "error",
"unicorn/no-lonely-if": "error"
},
"overrides": [
{
"files": ["cypress/**", "demos/**"],
"rules": {
"no-console": "off"
}
},
{
"files": ["*.{js,jsx,mjs,cjs}"],
"extends": ["plugin:jsdoc/recommended"],
"rules": {
"jsdoc/check-indentation": "off",
"jsdoc/check-alignment": "off",
"jsdoc/check-line-alignment": "off",
"jsdoc/multiline-blocks": "off",
"jsdoc/newline-after-description": "off",
"jsdoc/tag-lines": "off",
"jsdoc/require-param-description": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-returns-description": "off"
}
},
{
"files": ["*.{ts,tsx}"],
"plugins": ["tsdoc"],
"rules": {
"tsdoc/syntax": "error"
}
},
{
"files": ["*.spec.{ts,js}", "cypress/**", "demos/**", "**/docs/**"],
"rules": {
"jsdoc/require-jsdoc": "off",
"@typescript-eslint/no-unused-vars": "off"
}
},
{
"files": ["*.html", "*.md", "**/*.md/*"],
"rules": {
"no-var": "error",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}
]
}

4
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,8 @@
# These are supported funding model platforms
github: [knsv]
github:
- knsv
- sidharthv96
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username

View File

@@ -32,6 +32,7 @@ jobs:
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v4
id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
# Otherwise (e.g. if running from fork), we run on a single container only
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
@@ -44,3 +45,10 @@ jobs:
parallel: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
- name: Upload Artifacts
uses: actions/upload-artifact@v3
if: ${{ failure() && steps.cypress.conclusion == 'failure' }}
with:
name: error-snapshots
path: cypress/snapshots/**/__diff_output__/*

View File

@@ -37,7 +37,20 @@ jobs:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Linting
run: pnpm run lint
shell: bash
run: |
if ! pnpm run lint; then
# print a nice error message on lint failure
ERROR_MESSAGE='Running `pnpm run lint` failed.'
ERROR_MESSAGE+=' Running `pnpm run lint:fix` may fix this issue. '
ERROR_MESSAGE+=" If this error doesn't occur on your local machine,"
ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.'
ERROR_MESSAGE+=' You may also need to delete your prettier cache by running'
ERROR_MESSAGE+=' `rm ./node_modules/.cache/prettier/.prettier-cache`.'
echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
fi
- name: Verify Docs
id: verifyDocs

View File

@@ -1,5 +1,11 @@
export default {
'!(docs/**/*)*.{ts,js,json,html,md,mts}': ['eslint --fix', 'prettier --write'],
'!(docs/**/*)*.{ts,js,json,html,md,mts}': [
'eslint --cache --cache-strategy content --fix',
// don't cache prettier yet, since we use `prettier-plugin-jsdoc`,
// and prettier doesn't invalidate cache on plugin updates"
// https://prettier.io/docs/en/cli.html#--cache
'prettier --write',
],
'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'],
'**/*.jison': ['pnpm -w run lint:jison'],
};

View File

@@ -36,21 +36,11 @@ const packageOptions = {
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-mindmap': {
name: 'mermaid-mindmap',
packageName: 'mermaid-mindmap',
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-flowchart-v3': {
name: 'mermaid-flowchart-v3',
packageName: 'mermaid-flowchart-v3',
file: 'detector.ts',
},
// 'mermaid-example-diagram-detector': {
// name: 'mermaid-example-diagram-detector',
// packageName: 'mermaid-example-diagram',
// file: 'detector.ts',
// },
};
interface BuildOptions {
@@ -124,12 +114,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
if (watch && config.build) {
config.build.watch = {
include: [
'packages/mermaid-flowchart-v3/src/**',
'packages/mermaid-mindmap/src/**',
'packages/mermaid/src/**',
// 'packages/mermaid-example-diagram/src/**',
],
include: ['packages/mermaid-example-diagram/src/**', 'packages/mermaid/src/**'],
};
}
@@ -154,9 +139,7 @@ const main = async () => {
if (watch) {
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
if (!mermaidOnly) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-flowchart-v3' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
// build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
}
} else if (visualize) {
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));

View File

@@ -1,6 +1,5 @@
import express, { NextFunction, Request, Response } from 'express';
import { createServer as createViteServer } from 'vite';
// import { getBuildConfig } from './build';
const cors = (req: Request, res: Response, next: NextFunction) => {
res.header('Access-Control-Allow-Origin', '*');
@@ -22,8 +21,8 @@ async function createServer() {
app.use(cors);
app.use(express.static('./packages/mermaid/dist'));
// app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(express.static('./packages/mermaid-mindmap/dist'));
app.use(vite.middlewares);
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));
@@ -33,5 +32,4 @@ async function createServer() {
});
}
// build(getBuildConfig({ minify: false, watch: true }));
createServer();

View File

@@ -1,6 +1,6 @@
# mermaid
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_)
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_)
English | [简体中文](./README.zh-CN.md)

View File

@@ -1,6 +1,6 @@
# mermaid
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_)
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_)
[English](./README.md) | 简体中文

View File

@@ -6,6 +6,7 @@
"adamiecki",
"alois",
"antiscript",
"appli",
"applitools",
"asciidoctor",
"ashish",
@@ -13,6 +14,7 @@
"bbox",
"bilkent",
"bisheng",
"blrs",
"braintree",
"brkt",
"brolin",
@@ -54,8 +56,10 @@
"knut",
"laganeckas",
"lintstagedrc",
"logmsg",
"lucida",
"matthieu",
"mdast",
"mdbook",
"mermerd",
"mindaugas",
@@ -81,6 +85,7 @@
"setupgraphviewbox",
"shiki",
"sidharth",
"sidharthv",
"sphinxcontrib",
"statediagram",
"stylis",

View File

@@ -2,7 +2,7 @@ const utf8ToB64 = (str) => {
return window.btoa(unescape(encodeURIComponent(str)));
};
const batchId = 'mermid-batch' + new Date().getTime();
const batchId = 'mermaid-batch' + new Date().getTime();
export const mermaidUrl = (graphStr, options, api) => {
const obj = {
@@ -46,8 +46,22 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
if (!options.fontSize) {
options.fontSize = '16px';
}
const url = mermaidUrl(graphStr, options, api);
openURLAndVerifyRendering(url, options, validation);
};
export const urlSnapshotTest = (url, _options, api = false, validation) => {
const options = Object.assign(_options);
openURLAndVerifyRendering(url, options, validation);
};
export const renderGraph = (graphStr, options, api) => {
const url = mermaidUrl(graphStr, options, api);
openURLAndVerifyRendering(url, options);
};
const openURLAndVerifyRendering = (url, options, validation = undefined) => {
const useAppli = Cypress.env('useAppli');
cy.log('Hello ' + useAppli ? 'Appli' : 'image-snapshot');
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) {
@@ -60,82 +74,20 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
});
}
const url = mermaidUrl(graphStr, options, api);
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
if (validation) {
cy.get('svg').should(validation);
}
cy.get('svg');
// Default name to test title
if (useAppli) {
cy.log('Check eyes' + Cypress.spec.name);
cy.eyesCheckWindow('Click!');
cy.log('Closing eyes: ' + Cypress.spec.name);
cy.log('Closing eyes' + Cypress.spec.name);
cy.eyesClose();
} else {
cy.matchImageSnapshot(name);
}
};
export const urlSnapshotTest = (url, _options, api = false, validation) => {
cy.log(_options);
const options = Object.assign(_options);
if (!options.fontFamily) {
options.fontFamily = 'courier';
}
if (!options.sequence) {
options.sequence = {};
}
if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
options.sequence.actorFontFamily = 'courier';
}
if (options.sequence && !options.sequence.noteFontFamily) {
options.sequence.noteFontFamily = 'courier';
}
options.sequence.actorFontFamily = 'courier';
options.sequence.noteFontFamily = 'courier';
options.sequence.messageFontFamily = 'courier';
if (options.sequence && !options.sequence.actorFontFamily) {
options.sequence.actorFontFamily = 'courier';
}
if (!options.fontSize) {
options.fontSize = '16px';
}
const useAppli = Cypress.env('useAppli');
cy.log('Hello ' + useAppli ? 'Appli' : 'image-snapshot');
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) {
cy.log('Opening eyes 2' + Cypress.spec.name);
cy.eyesOpen({
appName: 'Mermaid',
testName: name,
batchName: Cypress.spec.name,
batchId: batchId + Cypress.spec.name,
});
}
cy.visit(url);
if (validation) {
cy.get('svg').should(validation);
}
cy.get('body');
// Default name to test title
if (useAppli) {
cy.log('Check eyes 2' + Cypress.spec.name);
cy.eyesCheckWindow('Click!');
cy.log('Closing eyes 2' + Cypress.spec.name);
cy.eyesClose();
} else {
cy.matchImageSnapshot(name);
}
};
export const renderGraph = (graphStr, options, api) => {
const url = mermaidUrl(graphStr, options, api);
cy.visit(url);
};

View File

@@ -2,8 +2,8 @@ import { urlSnapshotTest } from '../../helpers/util';
describe('mermaid', () => {
describe('registerDiagram', () => {
it('should work on @mermaid-js/mermaid-mindmap and mermaid-example-diagram', () => {
const url = 'http://localhost:9000/external-diagrams-mindmap.html';
it('should work on @mermaid-js/mermaid-example-diagram', () => {
const url = 'http://localhost:9000/external-diagrams-example-diagram.html';
urlSnapshotTest(url, {}, false, false);
});
});

View File

@@ -0,0 +1,687 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util';
describe('Flowchart ELK', () => {
it('1-elk: should render a simple flowchart', () => {
imgSnapshotTest(
`flowchart-elk TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{}
);
imgSnapshotTest(
`flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ flowchart: { defaultRenderer: 'elk' } }
);
});
it('2-elk: should render a simple flowchart with diagramPadding set to 0', () => {
imgSnapshotTest(
`flowchart-elk TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
%% this is a comment
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ flowchart: { diagramPadding: 0 } }
);
});
it('3-elk: a link with correct arrowhead to a subgraph', () => {
imgSnapshotTest(
`flowchart-elk TD
P1
P1 -->P1.5
subgraph P1.5
P2
P2.5(( A ))
P3
end
P2 --> P4
P3 --> P6
P1.5 --> P5
`,
{}
);
});
it('4-elk: Length of edges', () => {
imgSnapshotTest(
`flowchart-elk TD
L1 --- L2
L2 --- C
M1 ---> C
R1 .-> R2
R2 <.-> C
C -->|Label 1| E1
C <-- Label 2 ---> E2
C ----> E3
C <-...-> E4
C ======> E5
`,
{}
);
});
it('5-elk: should render escaped without html labels', () => {
imgSnapshotTest(
`flowchart-elk TD
a["<strong>Haiya</strong>"]---->b
`,
{ htmlLabels: false, flowchart: { htmlLabels: false } }
);
});
it('6-elk: should render non-escaped with html labels', () => {
imgSnapshotTest(
`flowchart-elk TD
a["<strong>Haiya</strong>"]===>b
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('7-elk: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph(
`flowchart-elk TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ flowchart: { useMaxWidth: true } }
);
cy.get('svg').should((svg) => {
expect(svg).to.have.attr('width', '100%');
// expect(svg).to.have.attr('height');
// use within because the absolute value can be slightly different depending on the environment ±5%
// const height = parseFloat(svg.attr('height'));
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(230 * 0.95, 230 * 1.05);
});
});
it('8-elk: should render a flowchart when useMaxWidth is false', () => {
renderGraph(
`flowchart-elk TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ flowchart: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
// const height = parseFloat(svg.attr('height'));
const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±5%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
expect(width).to.be.within(230 * 0.95, 230 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
it('V2 elk - 16: Render Stadium shape', () => {
imgSnapshotTest(
` flowchart-elk TD
A([stadium shape test])
A -->|Get money| B([Go shopping])
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
C -->|One| D([Laptop])
C -->|Two| E([iPhone])
C -->|Three| F([Car<br/>wroom wroom])
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
class C someclass;
`,
{ flowchart: { htmlLabels: false }, fontFamily: 'courier' }
);
});
it('50-elk: handle nested subgraphs in reverse order', () => {
imgSnapshotTest(
`flowchart-elk LR
a -->b
subgraph A
B
end
subgraph B
b
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('51-elk: handle nested subgraphs in reverse order', () => {
imgSnapshotTest(
`flowchart-elk LR
a -->b
subgraph A
B
end
subgraph B
b
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('52-elk: handle nested subgraphs in several levels', () => {
imgSnapshotTest(
`flowchart-elk TB
b-->B
a-->c
subgraph O
A
end
subgraph B
c
end
subgraph A
a
b
B
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('53-elk: handle nested subgraphs with edges in and out', () => {
imgSnapshotTest(
`flowchart-elk TB
internet
nat
routeur
lb1
lb2
compute1
compute2
subgraph project
routeur
nat
subgraph subnet1
compute1
lb1
end
subgraph subnet2
compute2
lb2
end
end
internet --> routeur
routeur --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('54-elk: handle nested subgraphs with outgoing links', () => {
imgSnapshotTest(
`flowchart-elk TD
subgraph main
subgraph subcontainer
subcontainer-child
end
subcontainer-child--> subcontainer-sibling
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('55-elk: handle nested subgraphs with outgoing links 2', () => {
imgSnapshotTest(
`flowchart-elk TD
subgraph one[One]
subgraph sub_one[Sub One]
_sub_one
end
subgraph sub_two[Sub Two]
_sub_two
end
_one
end
%% here, either the first or the second one
sub_one --> sub_two
_one --> b
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('56-elk: handle nested subgraphs with outgoing links 3', () => {
imgSnapshotTest(
`flowchart-elk TB
subgraph container_Beta
process_C-->Process_D
end
subgraph container_Alpha
process_A-->process_B
process_A-->|messages|process_C
end
process_B-->|via_AWSBatch|container_Beta
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('57-elk: handle nested subgraphs with outgoing links 4', () => {
imgSnapshotTest(
`flowchart-elk LR
subgraph A
a -->b
end
subgraph B
b
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('57-elk: handle nested subgraphs with outgoing links 2', () => {
imgSnapshotTest(
`flowchart-elk TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('57.x: handle nested subgraphs with outgoing links 5', () => {
imgSnapshotTest(
`%% this does not produce the desired result
flowchart-elk TB
subgraph container_Beta
process_C-->Process_D
end
subgraph container_Alpha
process_A-->process_B
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|process_C
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('58-elk: handle styling with style expressions', () => {
imgSnapshotTest(
`
flowchart-elk LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('59-elk: handle styling of subgraphs and links', () => {
imgSnapshotTest(
`
flowchart-elk TD
A[Christmas] ==> D
A[Christmas] -->|Get money| B(Go shopping)
A[Christmas] ==> C
subgraph T ["Test"]
A
B
C
end
classDef Test fill:#F84E68,stroke:#333,color:white;
class A,T Test
classDef TestSub fill:green;
class T TestSub
linkStyle 0,1 color:orange, stroke: orange;
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('60-elk: handle styling for all node shapes - v2', () => {
imgSnapshotTest(
`
flowchart-elk LR
A[red text] -->|default style| B(blue text)
C([red text]) -->|default style| D[[blue text]]
E[(red text)] -->|default style| F((blue text))
G>red text] -->|default style| H{blue text}
I{{red text}} -->|default style| J[/blue text/]
K[\\ red text\\] -->|default style| L[/blue text\\]
M[\\ red text/] -->|default style| N[blue text];
O(((red text))) -->|default style| P(((blue text)));
linkStyle default color:Sienna;
style A stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style B stroke:#0000ff,fill:#ccccff,color:#0000ff;
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style D stroke:#0000ff,fill:#ccccff,color:#0000ff;
style E stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style F stroke:#0000ff,fill:#ccccff,color:#0000ff;
style G stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style H stroke:#0000ff,fill:#ccccff,color:#0000ff;
style I stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style J stroke:#0000ff,fill:#ccccff,color:#0000ff;
style K stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style L stroke:#0000ff,fill:#ccccff,color:#0000ff;
style M stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style N stroke:#0000ff,fill:#ccccff,color:#0000ff;
style O stroke:#ff0000,fill:#ffcccc,color:#ff0000;
style P stroke:#0000ff,fill:#ccccff,color:#0000ff;
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', logLevel: 2 }
);
});
it('61-elk: fontawesome icons in edge labels', () => {
imgSnapshotTest(
`
flowchart-elk TD
C -->|fa:fa-car Car| F[fa:fa-car Car]
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('62-elk: should render styled subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk TB
A
B
subgraph foo[Foo SubGraph]
C
D
end
subgraph bar[Bar SubGraph]
E
F
end
G
A-->B
B-->C
C-->D
B-->D
D-->E
E-->A
E-->F
F-->D
F-->G
B-->G
G-->D
style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('63-elk: title on subgraphs should be themable', () => {
imgSnapshotTest(
`
%%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%%
flowchart-elk LR
subgraph A
a --> b
end
subgraph B
i -->f
end
A --> B
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('65-elk: text-color from classes', () => {
imgSnapshotTest(
`
flowchart-elk LR
classDef dark fill:#000,stroke:#000,stroke-width:4px,color:#fff
Lorem --> Ipsum --> Dolor
class Lorem,Dolor dark
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('66-elk: More nested subgraph cases (TB)', () => {
imgSnapshotTest(
`
flowchart-elk TB
subgraph two
b1
end
subgraph three
c2
end
three --> two
two --> c2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('67-elk: More nested subgraph cases (RL)', () => {
imgSnapshotTest(
`
flowchart-elk RL
subgraph two
b1
end
subgraph three
c2
end
three --> two
two --> c2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('68-elk: More nested subgraph cases (BT)', () => {
imgSnapshotTest(
`
flowchart-elk BT
subgraph two
b1
end
subgraph three
c2
end
three --> two
two --> c2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('69-elk: More nested subgraph cases (LR)', () => {
imgSnapshotTest(
`
flowchart-elk LR
subgraph two
b1
end
subgraph three
c2
end
three --> two
two --> c2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('70-elk: Handle nested subgraph cases (TB) link out and link between subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk TB
subgraph S1
sub1 -->sub2
end
subgraph S2
sub4
end
S1 --> S2
sub1 --> sub4
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('71-elk: Handle nested subgraph cases (RL) link out and link between subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk RL
subgraph S1
sub1 -->sub2
end
subgraph S2
sub4
end
S1 --> S2
sub1 --> sub4
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('72-elk: Handle nested subgraph cases (BT) link out and link between subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk BT
subgraph S1
sub1 -->sub2
end
subgraph S2
sub4
end
S1 --> S2
sub1 --> sub4
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('74-elk: Handle nested subgraph cases (RL) link out and link between subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk RL
subgraph S1
sub1 -->sub2
end
subgraph S2
sub4
end
S1 --> S2
sub1 --> sub4
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('74-elk: Handle labels for multiple edges from and to the same couple of nodes', () => {
imgSnapshotTest(
`
flowchart-elk RL
subgraph one
a1 -- l1 --> a2
a1 -- l2 --> a2
end
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('76-elk: handle unicode encoded character with HTML labels true', () => {
imgSnapshotTest(
`flowchart-elk TB
a{{"Lorem 'ipsum' dolor 'sit' amet, 'consectetur' adipiscing 'elit'."}}
--> b{{"Lorem #quot;ipsum#quot; dolor #quot;sit#quot; amet,#quot;consectetur#quot; adipiscing #quot;elit#quot;."}}
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('2050-elk: handling of different rendering direction in subgraphs', () => {
imgSnapshotTest(
`
flowchart-elk LR
subgraph TOP
direction TB
subgraph B1
direction RL
i1 -->f1
end
subgraph B2
direction BT
i2 -->f2
end
end
A --> TOP --> B
B1 --> B2
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('2388-elk: handling default in the node name', () => {
imgSnapshotTest(
`
flowchart-elk LR
default-index.js --> dot.template.js
index.js --> module-utl.js
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('2824-elk: Clipping of edges', () => {
imgSnapshotTest(
`
flowchart-elk TD
A --> B
A --> C
B --> C
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('1433-elk: should render a titled flowchart with titleTopMargin set to 0', () => {
imgSnapshotTest(
`---
title: Simple flowchart
---
flowchart-elk TD
A --> B
`,
{ titleTopMargin: 0 }
);
});
});

View File

@@ -1,4 +1,4 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
import { imgSnapshotTest } from '../../helpers/util.js';
/**
* Check whether the SVG Element has a Mindmap root
@@ -158,7 +158,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
it('rounded rect shape', () => {
imgSnapshotTest(
@@ -172,7 +171,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
it('circle shape', () => {
imgSnapshotTest(
@@ -186,7 +184,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
it('default shape', () => {
imgSnapshotTest(
@@ -198,7 +195,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
it('adding children', () => {
imgSnapshotTest(
@@ -212,7 +208,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
it('adding grand children', () => {
imgSnapshotTest(
@@ -227,7 +222,6 @@ mindmap
undefined,
shouldHaveRoot
);
cy.get('svg');
});
/* The end */
});

View File

@@ -3,6 +3,42 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util';
context('Sequence diagram', () => {
it('should render a sequence diagram with boxes', () => {
renderGraph(
`
sequenceDiagram
box LightGrey Alice and Bob
participant Alice
participant Bob
end
participant John as John<br/>Second Line
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
expect(width).to.be.within(830 * 0.95, 830 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
it('should render a simple sequence diagram', () => {
imgSnapshotTest(
`

View File

@@ -0,0 +1,164 @@
import { imgSnapshotTest } from '../../helpers/util.js';
describe('Timeline diagram', () => {
it('1: should render a simple timeline with no specific sections', () => {
imgSnapshotTest(
`timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
`,
{}
);
});
it('2: should render a timeline diagram with sections', () => {
imgSnapshotTest(
`timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, Internal combustion engine, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data,3D printing
`,
{}
);
});
it('3: should render a complex timeline with sections, and long events text with <br>', () => {
imgSnapshotTest(
`timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
section Broze Age
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
`,
{}
);
});
it('4: should render a simple timeline with directives and disableMultiColor:true ', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
`,
{}
);
});
it('5: should render a simple timeline with directive overriden colors', () => {
imgSnapshotTest(
` %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
'cScale0': '#ff0000',
'cScale1': '#00ff00',
'cScale2': '#0000ff'
} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
it('6: should render a simple timeline in base theme', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
it('7: should render a simple timeline in default theme', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
it('8: should render a simple timeline in dark theme', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
it('9: should render a simple timeline in neutral theme', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
it('10: should render a simple timeline in forest theme', () => {
imgSnapshotTest(
`%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
`,
{}
);
});
});

View File

@@ -0,0 +1,231 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
background-color: #eee;
background-image: radial-gradient(#fff 1%, transparent 11%),
radial-gradient(#fff 1%, transparent 11%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
</style>
</head>
<body>
<div>Security check</div>
<pre id="diagram" class="mermaid2">
timeline
title My day
section section with no tasks
section Go to work at the dog office
1930 : first step : second step is a long step
: third step
1940 : fourth step : fifth step
section Go home
1950 : India got independent and already won war against Pakistan
1960 : India fights poverty, looses war to China and gets nuclear weapons from USA and USSR
1970 : Green Revolution comes to india
section Another section with no tasks
I am a big big big tasks
I am not so big tasks
</pre>
<pre id="diagram" class="mermaid">
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
</pre>
<pre id="diagram" class="mermaid">
timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island. The people who live here are hunter-gatherers.
section Broze Age
2300 BC : People arrive from Europe and settle in Britain. They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge. People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
</pre>
<pre id="diagram" class="mermaid2">
%%{'init': { 'logLevel': 'debug', 'theme': 'default', 'timeline': {'disableMulticolor':false} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google : Pixar
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008s : Instagram
2010 : Pinterest
</pre>
<pre id="diagram" class="mermaid2">
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
'cScale0': '#ff0000',
'cScale1': '#00ff00',
'cScale2': '#ff0000'
} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google : Pixar
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008s : Instagram
2010 : Pinterest
</pre>
<pre id="diagram" class="mermaid2">
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
'cScale0': '#ff0000',
'cScale1': '#00ff00',
'cScale2': '#0000ff'
} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
</pre>
<pre id="diagram" class="mermaid2">
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008s : Instagram
2010 : Pinterest
</pre>
<pre id="diagram" class="mermaid2">
mindmap
root
child1((Circle))
grandchild 1
grandchild 2
child2(Round rectangle)
grandchild 3
grandchild 4
child3[Square]
grandchild 5
::icon(mdi mdi-fire)
gc6((grand<br/>child 6))
::icon(mdi mdi-fire)
gc7((grand<br/>grand<br/>child 8))
</pre>
<pre id="diagram" class="mermaid2">
flowchart-elk TB
a --> b
a --> c
b --> d
c --> d
</pre>
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<script type="module">
//import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
// import timeline from '../../packages/mermaid-timeline/src/detector';
import mermaid from '../../packages/mermaid/src/mermaid';
// await mermaid.registerExternalDiagrams([]);
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'base',
startOnLoad: true,
logLevel: 0,
flowchart: {
useMaxWidth: false,
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
timeline: {
disableMulticolor: false,
htmlLabels: false,
},
useMaxWidth: true,
lazyLoadedDiagrams: [
// './mermaid-mindmap-detector.esm.mjs',
// './mermaid-example-diagram-detector.esm.mjs',
//'./mermaid-timeline-detector.esm.mjs',
],
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
</script>
</body>
</html>

View File

@@ -46,13 +46,9 @@
<pre class="mermaid" style="width: 100%; height: 20%">
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%
classDiagram-v2
class BankAccount{
+String owner
+BigDecimal balance
+deposit(amount) bool
+withdrawl(amount) int
}
cssClass "BankAccount" customCss
classA <|-- classB : implements
classC *-- classD : composition
classE o-- classF : aggregation
</pre>
<pre class="mermaid2" style="width: 100%; height: 20%">
%%{init: {'theme': 'base', 'fontFamily': 'courier', 'themeVariables': { 'primaryColor': '#fff000'}}}%%

View File

@@ -2,34 +2,8 @@
<body>
<h1>Should correctly load a third-party diagram using registerDiagram</h1>
<pre id="diagram" class="mermaid">
mindmap
root
A
B
C
D
E
A2
B2
C2
D2
E2
child1((Circle))
grandchild 1
grandchild 2
child2(Round rectangle)
grandchild 3
grandchild 4
child3[Square]
grandchild 5
::icon(mdi mdi-fire)
gc6((grand<br/>child 6))
::icon(mdi mdi-fire)
gc7((grand<br/>grand<br/>child 8))
example-diagram
</pre>
<!-- <pre id="diagram" class="mermaid2">
example-diagram
</pre> -->
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
@@ -37,13 +11,16 @@ mindmap
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script type="module" src="./external-diagrams-mindmap.mjs" /> -->
<script type="module">
import mindmap from '../../packages/mermaid-mindmap/src/detector';
import exampleDiagram from '../../packages/mermaid-example-diagram/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
import mermaid from '../../packages/mermaid/src/mermaid';
await mermaid.registerExternalDiagrams([mindmap]);
await mermaid.registerExternalDiagrams([exampleDiagram]);
await mermaid.initialize({ logLevel: 0 });
await mermaid.initThrowsErrorsAsync();
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

View File

@@ -21,6 +21,9 @@
const diagram = document.getElementById('diagram');
const svg = mermaid.render('diagram-svg', graph);
diagram.innerHTML = svg;
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

View File

@@ -21,6 +21,9 @@
const diagram = document.getElementById('diagram');
const svg = mermaid.render('diagram-svg', graph);
diagram.innerHTML = svg;
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

View File

@@ -94,6 +94,9 @@
// document.querySelector('#diagram').innerHTML = diagram;
mermaid.render('diagram', diagram, (res) => {
document.querySelector('#res').innerHTML = res;
if (window.Cypress) {
window.rendered = true;
}
});
</script>
</body>

View File

@@ -56,13 +56,24 @@
<body>
<pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
graph TB
graph BT
a{The cat in the hat} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
</pre>
<pre id="diagram" class="mermaid2">
flowchart-elk TB
a --> b
a --> c
b --> d
c --> d
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
@@ -118,7 +129,7 @@ flowchart TB
</pre
>
<br />
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
@@ -221,13 +232,27 @@ sequenceDiagram
</pre
>
<pre id="diagram" class="mermaid2">
gantt
title Style today marker (vertical line should be 5px wide and half-transparent blue)
dateFormat YYYY-MM-DD
axisFormat %d
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Today: 1, -1h
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid
</pre>
<br />
<pre id="diagram" class="mermaid2">
example-diagram
</pre>
<!-- <div id="cy"></div> -->
@@ -237,18 +262,17 @@ sequenceDiagram
<!-- <script src="./mermaid.js"></script> -->
<script type="module">
import mindmap from '../../packages/mermaid-mindmap/src/detector';
import flowV3 from '../../packages/mermaid-flowchart-v3/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
// import mindmap from '../../packages/mermaid-mindmap/src/detector';
import example from '../../packages/mermaid-example-diagram/src/detector';
import mermaid from '../../packages/mermaid/src/mermaid';
await mermaid.registerExternalDiagrams([mindmap, flowV3]);
await mermaid.registerExternalDiagrams([example]);
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
// theme: 'forest',
theme: 'forest',
startOnLoad: true,
logLevel: 0,
logLevel: 5,
flowchart: {
// defaultRenderer: 'elk',
useMaxWidth: false,

View File

@@ -1,81 +1,93 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mindmap Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
background-color: #eee;
background-image: radial-gradient(#fff 1%, transparent 11%),
radial-gradient(#fff 1%, transparent 11%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<div>Security check</div>
<pre id="diagram" class="mermaid">
graph TD
A["test"] --"<p><style> * { display : none}</style>test</p>"--> B
<h1>Mindmap diagram demo</h1>
<pre class="mermaid">
mindmap
root
child1((Circle))
grandchild 1
grandchild 2
child2(Round rectangle)
grandchild 3
grandchild 4
child3[Square]
grandchild 5
::icon(mdi mdi-fire)
gc6((grand<br/>child 6))
::icon(mdi mdi-fire)
gc7((grand<br/>grand<br/>child 8))
</pre>
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script src="./mermaid.js"></script> -->
<h2>Mindmap with root wrapping text and a shape</h2>
<pre class="mermaid">
mindmap
root[A root with a long text that wraps to keep the node size in check]
</pre>
<script type="module">
import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
// import mermaid from './mermaid.esm.mjs';
import mermaid from '../../packages/mermaid/src/mermaid';
await mermaid.registerExternalDiagrams([mindmap]);
// import mermaidMindmap from './mermaid-mindmap.esm.mjs';
// import mermaidMindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.3.0/+esm';
// await mermaid.registerExternalDiagrams([mermaidMindmap]);
const ALLOWED_TAGS = [
'a',
'b',
'blockquote',
'br',
'dd',
'div',
'dl',
'dt',
'em',
'foreignObject',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'hr',
'i',
'li',
'ul',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'tr',
];
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'default',
theme: 'base',
startOnLoad: true,
logLevel: 0,
flowchart: {
@@ -94,10 +106,6 @@
console.error('In parse error:');
console.error(err);
};
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
</script>
</body>
</html>

View File

@@ -1,14 +1,283 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
background-color: #eee;
background-image: radial-gradient(#fff 1%, transparent 11%),
radial-gradient(#fff 1%, transparent 11%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
</style>
</head>
<body>
<pre class="mermaid">
none
hello world
<pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
graph TB
a --> b
a --> c
b --> d
c --> d
</pre>
<script src="./mermaid.js"></script>
<script>
<pre id="diagram" class="mermaid">
flowchart-elk LR
subgraph A
a -->b
end
subgraph B
b
end
</pre>
<pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd[AMD Latte GPU]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
<pre id="diagram" class="mermaid">
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd[AMD Latte GPU]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
&nbsp;
<pre id="diagram" class="mermaid2">
flowchart LR
B1 --be be--x B2
B1 --bo bo--o B3
subgraph Ugge
B2
B3
subgraph inner
B4
B5
end
subgraph inner2
subgraph deeper
C4
C5
end
C6
end
B4 --> C4
B3 -- X --> B4
B2 --> inner
C4 --> C5
end
subgraph outer
B6
end
B6 --> B5
</pre
>
<pre id="diagram" class="mermaid2">
sequenceDiagram
Customer->>+Stripe: Makes a payment request
Stripe->>+Bank: Forwards the payment request to the bank
Bank->>+Customer: Asks for authorization
Customer->>+Bank: Provides authorization
Bank->>+Stripe: Sends a response with payment details
Stripe->>+Merchant: Sends a notification of payment receipt
Merchant->>+Stripe: Confirms the payment
Stripe->>+Customer: Sends a confirmation of payment
Customer->>+Merchant: Receives goods or services
</pre
>
<pre id="diagram" class="mermaid2">
gantt
title Style today marker (vertical line should be 5px wide and half-transparent blue)
dateFormat YYYY-MM-DD
axisFormat %d
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Today: 1, -1h
</pre>
<!-- <div id="cy"></div> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script src="./mermaid.js"></script> -->
<script type="module">
import mindmap from '../../packages/mermaid-mindmap/src/detector';
// import example from '../../packages/mermaid-example-diagram/src/detector';
import mermaid from '../../packages/mermaid/src/mermaid';
await mermaid.registerExternalDiagrams([mindmap]);
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
logLevel: 1,
// theme: 'forest',
startOnLoad: true,
logLevel: 0,
flowchart: {
// defaultRenderer: 'elk',
useMaxWidth: false,
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
useMaxWidth: false,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
</script>
</body>
</html>

View File

@@ -1,10 +1,17 @@
import mermaid2 from '../../packages/mermaid/src/mermaid';
import mindmap from '../../packages/mermaid-mindmap/src/detector';
import externalExample from '../../packages/mermaid-example-diagram/src/detector';
function b64ToUtf8(str) {
return decodeURIComponent(escape(window.atob(str)));
}
// Adds a rendered flag to window when rendering is done, so cypress can wait for it.
function markRendered() {
if (window.Cypress) {
window.rendered = true;
}
}
/**
* ##contentLoaded Callback function that is called when page is loaded. This functions fetches
* configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
@@ -37,9 +44,10 @@ const contentLoaded = async function () {
document.getElementsByTagName('body')[0].appendChild(div);
}
await mermaid2.registerExternalDiagrams([mindmap]);
await mermaid2.registerExternalDiagrams([externalExample]);
mermaid2.initialize(graphObj.mermaid);
mermaid2.init();
await mermaid2.init();
markRendered();
}
};
@@ -128,6 +136,7 @@ const contentLoadedApi = function () {
);
}
}
markRendered();
};
if (typeof document !== 'undefined') {
@@ -142,7 +151,7 @@ if (typeof document !== 'undefined') {
contentLoadedApi();
} else {
this.console.log('Not using api');
contentLoaded();
void contentLoaded();
}
},
false

View File

@@ -71,6 +71,44 @@
</pre>
<hr />
<pre class="mermaid">
erDiagram
"HOSPITAL" {
int id PK
int doctor_id PK, FK
string address UK
string name
string phone_number
string fax_number
}
</pre
>
<hr />
<pre class="mermaid">
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber PK
string make
string model
string[] parts
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK,FK
}
MANUFACTURER only one to zero or more CAR : makes
</pre>
<script src="./mermaid.js"></script>
<script type="module">
mermaid.initialize({

View File

@@ -128,6 +128,22 @@
</pre>
<hr />
<pre class="mermaid">
sequenceDiagram
box lightgreen Alice & John
participant A
participant J
end
box Another Group very very long description not wrapped
participant B
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how are you ?
</pre
>
<hr />
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({

View File

@@ -161,12 +161,19 @@
First --> Second
First --> Third
state First {
state "the first composite" as First {
[*] --> 1st
1st --> [*]
state innerFirst {
state "1 in innerFirst" as 1st1st
1st2nd: 2 in innerFirst
[*] --> 1st1st
1st1st --> 1st2nd
%% 1st2nd --> 1st
}
1st --> innerFirst
innerFirst --> 2nd
}
state Second {
[*] --> 2nd
2nd --> [*]
}
state Third {

38
demos/timeline.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<pre class="mermaid">
timeline
title My day
section Go to work
1930 : first step : second step
: third step
1940 : fourth step : fifth step
</pre>
<script src="./mermaid.js"></script>
<script>
mermaid.initialize({
theme: 'forest',
logLevel: 1,
securityLevel: 'loose',
flowchart: { curve: 'basis' },
gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50 },
});
</script>
</body>
</html>

View File

@@ -2,7 +2,7 @@
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/faq.md](../../packages/mermaid/src/docs/misc/faq.md).
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/faq.md](../../packages/mermaid/src/docs/config/faq.md).
# Frequently Asked Questions

View File

@@ -14,7 +14,7 @@
#### Defined in
[defaultConfig.ts:1934](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1934)
[defaultConfig.ts:2084](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2084)
---

View File

@@ -20,7 +20,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in
[mermaidAPI.ts:73](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L73)
[mermaidAPI.ts:74](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L74)
## Variables
@@ -90,7 +90,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:962](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L962)
[mermaidAPI.ts:886](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L886)
## Functions
@@ -121,7 +121,7 @@ Return the last node appended
#### Defined in
[mermaidAPI.ts:286](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L286)
[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
---
@@ -147,7 +147,7 @@ the cleaned up svgCode
#### Defined in
[mermaidAPI.ts:237](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L237)
[mermaidAPI.ts:238](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L238)
---
@@ -173,7 +173,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:166](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L166)
[mermaidAPI.ts:167](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L167)
---
@@ -196,7 +196,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:214](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L214)
[mermaidAPI.ts:215](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L215)
---
@@ -223,7 +223,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:150](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L150)
[mermaidAPI.ts:151](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L151)
---
@@ -243,7 +243,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L130)
[mermaidAPI.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L131)
---
@@ -263,7 +263,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L101)
[mermaidAPI.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L102)
---
@@ -289,7 +289,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in
[mermaidAPI.ts:265](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L265)
[mermaidAPI.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L266)
---
@@ -314,4 +314,4 @@ Remove any existing elements from the given document
#### Defined in
[mermaidAPI.ts:336](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L336)
[mermaidAPI.ts:337](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L337)

View File

@@ -2,7 +2,7 @@
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/integrations.md](../../packages/mermaid/src/docs/misc/integrations.md).
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations.md](../../packages/mermaid/src/docs/ecosystem/integrations.md).
# Integrations
@@ -95,9 +95,10 @@ They also serve as proof of concept, for the variety of things that can be built
## Editor Plugins
- [Vs Code](https://code.visualstudio.com/)
- [VS Code](https://code.visualstudio.com/)
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
- [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview)
- [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced)
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)

View File

@@ -0,0 +1,9 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/showcases.md](../../packages/mermaid/src/docs/ecosystem/showcases.md).
# Showcases
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts).

View File

@@ -200,14 +200,13 @@ The `type` and `name` values must begin with an alphabetic character and may con
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be "PK", "FK" or "UK", for Primary Key, Foreign Key or Unique Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string allowedDriver FK "The license of the allowed driver"
string registrationNumber UK
string registrationNumber PK
string make
string model
string[] parts
@@ -217,17 +216,21 @@ erDiagram
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
MANUFACTURER only one to zero or more CAR
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
```
```mermaid
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string allowedDriver FK "The license of the allowed driver"
string registrationNumber UK
string registrationNumber PK
string make
string model
string[] parts
@@ -237,9 +240,14 @@ erDiagram
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
MANUFACTURER only one to zero or more CAR
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
```
### Other Things

View File

@@ -842,8 +842,8 @@ In the example below the style defined in the linkStyle statement will belong to
### Styling line curves
It is possible to style the type of curve used for lines between items, if the default method does not meet your needs.
Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`,
and `stepBefore`.
Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`,
`natural`, `step`, `stepAfter`, and `stepBefore`.
In this example, a left-to-right graph uses the `stepBefore` curve style:

View File

@@ -88,7 +88,7 @@ In this way we can use a text outline to generate a hierarchical mindmap.
## Different shapes
Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start.
Mermaid mindmaps can show nodes using different shapes. When specifying a shape for a node the syntax is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts, even though they are not all supported from the start.
Mindmap can show the following shapes:

View File

@@ -94,6 +94,59 @@ sequenceDiagram
J->>A: Great!
```
### Grouping / Box
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
box Aqua Group Description
... actors ...
end
box Group without description
... actors ...
end
box rgb(33,66,99)
... actors ...
end
> **Note**
> If your group name is a color you can force the color to be transparent:
box transparent Aqua
... actors ...
end
```mermaid-example
sequenceDiagram
box Purple Alice & John
participant A
participant J
end
box Another Group
participant B
participant C
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
```mermaid
sequenceDiagram
box Purple Alice & John
participant A
participant J
end
box Another Group
participant B
participant C
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
## Messages
Messages can be of two displayed either solid or with a dotted line.

462
docs/syntax/timeline.md Normal file
View File

@@ -0,0 +1,462 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/timeline.md](../../packages/mermaid/src/docs/syntax/timeline.md).
# Timeline Diagram
> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia
### An example of a timeline.
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook
: Google
2005 : Youtube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook
: Google
2005 : Youtube
2006 : Twitter
```
## Syntax
The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram.
After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text.
Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period.
```json
{time period} : {event}
```
or
```json
{time period} : {event} : {event}
```
or
```json
{time period} : {event}
: {event}
: {event}
```
NOTE: Both time period and event are simple text, and not limited to numbers.
Let us look at the syntax for the example above.
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
In this way we can use a text outline to generate a timeline diagram.
The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline.
Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom.
## Grouping of time periods in sections/ages
You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name.
All subsequent time periods will be placed in this section until a new section is defined.
If no section is defined, all time periods will be placed in the default section.
Let us look at an example, where we have grouped the time periods in sections.
```mermaid-example
timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, Internal combustion engine, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data,3D printing
```
```mermaid
timeline
title Timeline of Industrial Revolution
section 17th-20th century
Industry 1.0 : Machinery, Water power, Steam <br>power
Industry 2.0 : Electricity, Internal combustion engine, Mass production
Industry 3.0 : Electronics, Computers, Automation
section 21st century
Industry 4.0 : Internet, Robotics, Internet of Things
Industry 5.0 : Artificial intelligence, Big data,3D printing
```
As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined.
All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events.
## Wrapping of text for long time-periods or events
By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram.
You can also use `<br>` to force a line break.
Let us look at another example, where we have a long time period, and a long event.
```mermaid-example
timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
section Broze Age
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
```
```mermaid
timeline
title England's History Timeline
section Stone Age
7600 BC : Britain's oldest known house was built in Orkney, Scotland
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
section Broze Age
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
```
```mermaid-example
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```
```mermaid
timeline
title MermaidChart 2023 Timeline
section 2023 Q1 <br> Release Personal Tier
Buttet 1 : sub-point 1a : sub-point 1b
: sub-point 1c
Bullet 2 : sub-point 2a : sub-point 2b
section 2023 Q2 <br> Release XYZ Tier
Buttet 3 : sub-point <br> 3a : sub-point 3b
: sub-point 3c
Bullet 4 : sub-point 4a : sub-point 4b
```
## Styling of time periods and events
As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme.
However, if there is no section defined, then we have two possibilities:
1. Style time periods individually, i.e. each time period(and its coressponding events) will have its own color scheme. This is the DEFAULT behavior.
```mermaid-example
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
```mermaid
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme.
2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme.
You will need to add this option either via mermaid.intialize function or directives.
```javascript
mermaid.initialize({
theme: 'base',
startOnLoad: true,
logLevel: 0,
timeline: {
disableMulticolor: false,
},
...
...
```
let us look at same example, where we have disabled the multiColor option.
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
```
### Customizing Color scheme
You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
In case you have more than 12 sections, the color scheme will start to repeat.
NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
Example:
Now let's override the default values for the `cScale0` to `cScale2` variables:
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
'cScale0': '#ff0000',
'cScale1': '#00ff00',
'cScale2': '#0000ff'
} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
'cScale0': '#ff0000',
'cScale1': '#00ff00',
'cScale2': '#0000ff'
} } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
See how the colors are changed to the values specified in the theme variables.
## Themes
Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md).
The following are the different pre-defined theme options:
- `base`
- `forest`
- `dark`
- `default`
- `neutral`
**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md)
Let's put them to use, and see how our sample diagram looks in different themes:
### Base Theme
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Forest Theme
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Dark Theme
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Default Theme
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
### Neutral Theme
```mermaid-example
%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%%
timeline
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
2005 : Youtube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
2010 : Pinterest
```
You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done.

View File

@@ -1,10 +1,10 @@
{
"name": "mermaid-monorepo",
"private": true,
"version": "9.3.0-rc1",
"version": "9.4.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@7.25.0",
"packageManager": "pnpm@7.25.1",
"keywords": [
"diagram",
"markdown",
@@ -18,12 +18,12 @@
"build:vite": "ts-node-esm --transpileOnly .vite/build.ts",
"build:mermaid": "pnpm build:vite --mermaid",
"build:viz": "pnpm build:mermaid --visualize",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
"release": "pnpm build",
"lint": "eslint --cache --ignore-path .gitignore . && pnpm lint:jison && prettier --check .",
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
"cypress": "cypress run",
@@ -67,8 +67,8 @@
"@types/node": "^18.11.9",
"@types/prettier": "^2.7.1",
"@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"@vitest/coverage-c8": "^0.27.0",
"@vitest/ui": "^0.27.0",
"concurrently": "^7.5.0",
@@ -76,8 +76,8 @@
"cypress": "^10.11.0",
"cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.17.0",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
"eslint": "^8.32.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-jest": "^27.1.5",

View File

@@ -1,3 +0,0 @@
### Do not refer this package. It is not ready.
### Refer mermaid-mindmap instead.

View File

@@ -1,36 +1,25 @@
{
"name": "@mermaid-js/mermaid-example-diagram",
"version": "9.2.0-rc2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid-mindmap.core.mjs",
"module": "dist/mermaid-mindmap.core.mjs",
"version": "9.3.0",
"description": "Example of external diagram module for MermaidJS.",
"module": "dist/mermaid-example-diagram.core.mjs",
"types": "dist/detector.d.ts",
"type": "module",
"exports": {
".": {
"require": "./dist/mermaid-example-diagram.min.js",
"import": "./dist/mermaid-example-diagram.core.mjs"
"import": "./dist/mermaid-example-diagram.core.mjs",
"types": "./dist/detector.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"mindmap",
"example",
"mermaid"
],
"scripts": {
"clean": "rimraf dist",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:watch": "yarn build:code --watch",
"build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"",
"build": "yarn clean; yarn build:esbuild",
"dev": "node .esbuild/serve.cjs",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint:jison": "ts-node-esm src/jison/lint.mts",
"todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"",
"todo-pre-commit": "lint-staged"
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
@@ -48,9 +37,19 @@
"page"
]
},
"dependencies": {},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"khroma": "^2.0.0",
"non-layered-tidy-tree-layout": "^2.0.2"
},
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"concurrently": "^7.5.0",
"mermaid": "workspace:*",
"rimraf": "^3.0.2"
},
"resolutions": {

View File

@@ -1,18 +1,20 @@
// @ts-ignore: TODO Fix ts errors
export const id = 'example-diagram';
import type { ExternalDiagramDefinition } from 'mermaid';
/**
* Detector function that will be called by mermaid to determine if the diagram is this type of diagram.
*
* @param txt - The diagram text will be passed to the detector
* @returns True if the diagram text matches a diagram of this type
*/
const id = 'example-diagram';
export const detector = (txt: string) => {
const detector = (txt: string) => {
return txt.match(/^\s*example-diagram/) !== null;
};
export const loadDiagram = async () => {
const loader = async () => {
const { diagram } = await import('./diagram-definition');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -12,5 +12,3 @@ export const diagram = {
styles,
injectUtils,
};
export { detector, id } from './detector';

View File

@@ -1,5 +1,17 @@
import { parser } from './parser/exampleDiagram';
import db from './exampleDiagramDb';
import * as db from './exampleDiagramDb';
import { injectUtils } from './mermaidUtils';
// Todo fix utils functions for tests
import {
log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewBox,
} from '../../mermaid/src/diagram-api/diagramAPI';
injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox);
describe('when parsing an info graph it', function () {
let ex;
beforeEach(function () {

View File

@@ -1,4 +1,8 @@
const warning = () => undefined;
const warning = (s: string) => {
// Todo remove debug code
// eslint-disable-next-line no-console
console.error('Log function was called before initialization', s);
};
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
@@ -19,12 +23,11 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
error: warning,
fatal: warning,
};
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
export let getConfig: () => object;
export let sanitizeText: (str: string) => string;
/**
* Placeholder for the real function that will be injected by mermaid.
*/
export let commonDb: () => object;
// eslint-disable @typescript-eslint/no-explicit-any
export let setupGraphViewbox: (
graph: any,
@@ -33,23 +36,15 @@ export let setupGraphViewbox: (
useMaxWidth: boolean
) => void;
/**
* Function called by mermaid that injects utility functions that help the diagram to be a good citizen.
*
* @param _log - log from mermaid/src/diagramAPI.ts
* @param _setLogLevel - setLogLevel from mermaid/src/diagramAPI.ts
* @param _getConfig - getConfig from mermaid/src/diagramAPI.ts
* @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts
* @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts
*/
export const injectUtils = (
_log: Record<keyof typeof LEVELS, typeof console.log>,
_setLogLevel: typeof setLogLevel,
_getConfig: typeof getConfig,
_sanitizeText: typeof sanitizeText,
_setupGraphViewbox: typeof setupGraphViewbox
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any,
_commonDb: any
) => {
_log.debug('Mermaid utils injected into example-diagram');
_log.info('Mermaid utils injected');
log.trace = _log.trace;
log.debug = _log.debug;
log.info = _log.info;
@@ -60,4 +55,5 @@ export const injectUtils = (
getConfig = _getConfig;
sanitizeText = _sanitizeText;
setupGraphViewbox = _setupGraphViewbox;
commonDb = _commonDb;
};

View File

@@ -1,5 +1,6 @@
{
"extends": "../../tsconfig.json",
"module": "esnext",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"

View File

@@ -1,65 +0,0 @@
{
"name": "@mermaid-js/mermaid-flowchart-v3",
"version": "9.4.0",
"description": "An extension for the Mermaid diagramming library that utilizes elkjs for layout, enabling the creation of visually appealing and easy-to-understand flowcharts.",
"module": "dist/mermaid-flowchart-v3.core.mjs",
"types": "dist/detector.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-flowchart-v3.core.mjs",
"types": "./dist/detector.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"flowchart",
"mermaid",
"elkjs"
],
"scripts": {
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"standard": {
"ignore": [
"**/parser/*.js",
"dist/**/*.js",
"cypress/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"graphlib": "^2.1.0",
"dagre-d3-es": "7.0.6",
"elkjs": "^0.8.2",
"d3": "^7.0.0",
"khroma": "^2.0.0",
"non-layered-tidy-tree-layout": "^2.0.2"
},
"devDependencies": {
"concurrently": "^7.5.0",
"mermaid": "workspace:*",
"rimraf": "^3.0.2"
},
"resolutions": {
"d3": "^7.0.0"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}

View File

@@ -1,31 +0,0 @@
import type { ExternalDiagramDefinition } from 'mermaid';
const id = 'flowchart-v3';
const detector = (txt: string, config) => {
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
return false;
}
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
return false;
}
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
if (txt.match(/^\s*graph/) !== null) {
return true;
}
return txt.match(/^\s*flowchart/) !== null;
};
const loader = async () => {
const { diagram } = await import('./diagram-definition');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -1,14 +0,0 @@
// @ts-ignore: TODO Fix ts errors
import parser from '../../mermaid/src/diagrams/flowchart/parser/flow';
import * as db from '../../mermaid/src/diagrams/flowchart/flowDb';
import renderer from './flowRenderer-v3';
import styles from './styles';
import { injectUtils } from './mermaidUtils';
export const diagram = {
db,
renderer,
parser,
styles,
injectUtils,
};

View File

@@ -1,756 +0,0 @@
import graphlib from 'graphlib';
import { select, line, curveLinear, curveCardinal, curveBasis, selectAll } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
import dagre from 'cytoscape-dagre';
// Replace with other function to avoid dependency to dagre-d3
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import common, { evaluate } from '../../mermaid/src/diagrams/common/common';
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils';
import cytoscape from 'cytoscape';
cytoscape.use(dagre);
const conf = {};
export const setConf = function (cnf) {
const keys = Object.keys(cnf);
for (const key of keys) {
conf[key] = cnf[key];
}
};
// /**
// * Function that adds the vertices found during parsing to the graph to be rendered.
// *
// * @param vert Object containing the vertices.
// * @param g The graph that is to be drawn.
// * @param svgId
// * @param root
// * @param doc
// * @param diagObj
// */
export const addVertices = function (vert, svgId, root, doc, diagObj, parentLookUpDb, graph) {
const svg = root.select(`[id="${svgId}"]`);
const nodes = svg.insert('g').attr('class', 'nodes');
const keys = Object.keys(vert);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
const vertex = vert[id];
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let classStr = 'default';
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ');
}
const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(common.lineBreakRegex);
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
}
let radious = 0;
let _shape = '';
// Set the shape based parameters
switch (vertex.type) {
case 'round':
radious = 5;
_shape = 'rect';
break;
case 'square':
_shape = 'rect';
break;
case 'diamond':
_shape = 'question';
break;
case 'hexagon':
_shape = 'hexagon';
break;
case 'odd':
_shape = 'rect_left_inv_arrow';
break;
case 'lean_right':
_shape = 'lean_right';
break;
case 'lean_left':
_shape = 'lean_left';
break;
case 'trapezoid':
_shape = 'trapezoid';
break;
case 'inv_trapezoid':
_shape = 'inv_trapezoid';
break;
case 'odd_right':
_shape = 'rect_left_inv_arrow';
break;
case 'circle':
_shape = 'circle';
break;
case 'ellipse':
_shape = 'ellipse';
break;
case 'stadium':
_shape = 'stadium';
break;
case 'subroutine':
_shape = 'subroutine';
break;
case 'cylinder':
_shape = 'cylinder';
break;
case 'group':
_shape = 'rect';
break;
case 'doublecircle':
_shape = 'doublecircle';
break;
default:
_shape = 'rect';
}
// // Add the node
const node = {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
id: vertex.id,
link: vertex.link,
linkTarget: vertex.linkTarget,
tooltip: diagObj.db.getTooltip(vertex.id) || '',
domId: diagObj.db.lookUpDomId(vertex.id),
haveCallback: vertex.haveCallback,
width: vertex.type === 'group' ? 500 : undefined,
dir: vertex.dir,
type: vertex.type,
props: vertex.props,
padding: getConfig().flowchart.padding,
};
const nodeEl = insertNode(nodes, node, vertex.dir);
const boundingBox = nodeEl.node().getBBox();
const data = {
id: vertex.id,
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
link: vertex.link,
linkTarget: vertex.linkTarget,
tooltip: diagObj.db.getTooltip(vertex.id) || '',
domId: diagObj.db.lookUpDomId(vertex.id),
haveCallback: vertex.haveCallback,
width: vertex.type === 'group' ? 500 : undefined,
dir: vertex.dir,
type: vertex.type,
props: vertex.props,
padding: getConfig().flowchart.padding,
boundingBox,
el: nodeEl,
parent: parentLookUpDb.parentById[vertex.id],
};
// if (!Object.keys(parentLookUpDb.childrenById).includes(vertex.id)) {
graph.elements.nodes.push({
group: 'nodes',
// data,
data,
});
// }
log.trace('setNode', {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
id: vertex.id,
domId: diagObj.db.lookUpDomId(vertex.id),
width: vertex.type === 'group' ? 500 : undefined,
type: vertex.type,
dir: vertex.dir,
props: vertex.props,
padding: getConfig().flowchart.padding,
parent: parentLookUpDb.parentById[vertex.id],
});
});
return graph;
};
/**
* Add edges to graph based on parsed graph definition
*
* @param {object} edges The edges to add to the graph
* @param {object} g The graph object
* @param cy
* @param diagObj
* @param graph
*/
export const addEdges = function (edges, diagObj, graph) {
// log.info('abc78 edges = ', edges);
let cnt = 0;
let linkIdCnt = {};
let defaultStyle;
let defaultLabelStyle;
if (edges.defaultStyle !== undefined) {
const defaultStyles = getStylesFromArray(edges.defaultStyle);
defaultStyle = defaultStyles.style;
defaultLabelStyle = defaultStyles.labelStyle;
}
edges.forEach(function (edge) {
cnt++;
// Identify Link
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
if (linkIdCnt[linkIdBase] === undefined) {
linkIdCnt[linkIdBase] = 0;
log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
} else {
linkIdCnt[linkIdBase]++;
log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
}
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
var linkNameStart = 'LS-' + edge.start;
var linkNameEnd = 'LE-' + edge.end;
const edgeData = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;
//edgeData.id = 'id' + cnt;
// Set link type for rendering
if (edge.type === 'arrow_open') {
edgeData.arrowhead = 'none';
} else {
edgeData.arrowhead = 'normal';
}
// Check of arrow types, placed here in order not to break old rendering
edgeData.arrowTypeStart = 'arrow_open';
edgeData.arrowTypeEnd = 'arrow_open';
/* eslint-disable no-fallthrough */
switch (edge.type) {
case 'double_arrow_cross':
edgeData.arrowTypeStart = 'arrow_cross';
case 'arrow_cross':
edgeData.arrowTypeEnd = 'arrow_cross';
break;
case 'double_arrow_point':
edgeData.arrowTypeStart = 'arrow_point';
case 'arrow_point':
edgeData.arrowTypeEnd = 'arrow_point';
break;
case 'double_arrow_circle':
edgeData.arrowTypeStart = 'arrow_circle';
case 'arrow_circle':
edgeData.arrowTypeEnd = 'arrow_circle';
break;
}
let style = '';
let labelStyle = '';
switch (edge.stroke) {
case 'normal':
style = 'fill:none;';
if (defaultStyle !== undefined) {
style = defaultStyle;
}
if (defaultLabelStyle !== undefined) {
labelStyle = defaultLabelStyle;
}
edgeData.thickness = 'normal';
edgeData.pattern = 'solid';
break;
case 'dotted':
edgeData.thickness = 'normal';
edgeData.pattern = 'dotted';
edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
break;
case 'thick':
edgeData.thickness = 'thick';
edgeData.pattern = 'solid';
edgeData.style = 'stroke-width: 3.5px;fill:none;';
break;
}
if (edge.style !== undefined) {
const styles = getStylesFromArray(edge.style);
style = styles.style;
labelStyle = styles.labelStyle;
}
edgeData.style = edgeData.style += style;
edgeData.labelStyle = edgeData.labelStyle += labelStyle;
if (edge.interpolate !== undefined) {
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
} else if (edges.defaultInterpolate !== undefined) {
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
} else {
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
}
if (edge.text === undefined) {
if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
}
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
edgeData.id = linkId;
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
// Add the edge to the graph
graph.elements.edges.push({
group: 'edges',
data: { source: edge.start, target: edge.end, edgeData, id: cnt },
});
});
return graph;
};
const addmarkers = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute) {
// // TODO: Can we load this config only from the rendered graph type?
let url;
if (arrowMarkerAbsolute) {
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
}
switch (edgeData.arrowTypeStart) {
case 'arrow_cross':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
break;
case 'arrow_point':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
break;
case 'arrow_barb':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
break;
case 'arrow_circle':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
break;
case 'aggregation':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')');
break;
case 'extension':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
break;
case 'composition':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
break;
case 'dependency':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')');
break;
case 'lollipop':
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')');
break;
default:
}
switch (edgeData.arrowTypeEnd) {
case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
break;
case 'extension':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
break;
case 'composition':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
break;
case 'dependency':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')');
break;
case 'lollipop':
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')');
break;
default:
}
};
/**
* Returns the all the styles from classDef statements in the graph definition.
*
* @param text
* @param diagObj
* @returns {object} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
log.info('Extracting classes');
diagObj.db.clear('ver-2');
try {
// Parse the graph definition
diagObj.parse(text);
return diagObj.db.getClasses();
} catch (e) {
return;
}
};
const addSubGraphs = function (db) {
const parentLookUpDb = { parentById: {}, childrenById: {} };
const subgraphs = db.getSubGraphs();
log.info('Subgraphs - ', subgraphs);
subgraphs.forEach(function (subgraph) {
subgraph.nodes.forEach(function (node) {
parentLookUpDb.parentById[node] = subgraph.id;
if (parentLookUpDb.childrenById[subgraph.id] === undefined) {
parentLookUpDb.childrenById[subgraph.id] = [];
}
parentLookUpDb.childrenById[subgraph.id].push(node);
});
});
subgraphs.forEach(function (subgraph) {
const data = { id: subgraph.id };
if (parentLookUpDb.parentById[subgraph.id] !== undefined) {
data.parent = parentLookUpDb.parentById[subgraph.id];
}
// cy.add({
// group: 'nodes',
// data,
// });
});
return parentLookUpDb;
};
const insertEdge = function (edgesEl, edge, edgeData, bounds, diagObj) {
const src = edge.sourceEndpoint();
const segments = edge.segmentPoints();
// const dest = edge.target().position();
const dest = edge.targetEndpoint();
const segPoints = segments.map((segment) => [segment.x, segment.y]);
const points = [
[src.x, src.y],
[segments[0].x, segments[0].y],
[dest.x, dest.y],
];
// console.log('Edge ctrl points:', edge.segmentPoints(), 'Bounds:', bounds, edge.source(), points);
// console.log('Edge ctrl points:', points);
const curve = line().curve(curveCardinal);
const edge2 = edgesEl
.insert('path')
.attr('d', curve(points))
.attr('class', 'path')
.attr('fill', 'none');
addmarkers(edge2, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute);
// edgesEl
// .append('circle')
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', src.x)
// .attr('cy', src.y);
// edgesEl
// .append('circle')
// .style('stroke', 'white')
// .style('fill', 'white')
// .attr('r', 1)
// .attr('cx', segments[0].x)
// .attr('cy', segments[0].y);
// edgesEl
// .append('circle')
// .style('stroke', 'pink')
// .style('fill', 'pink')
// .attr('r', 1)
// .attr('cx', dest.x)
// .attr('cy', dest.y);
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
*
* @param text
* @param id
*/
export const draw = function (text, id, _version, diagObj) {
// Add temporary render element
diagObj.db.clear();
diagObj.db.setGen('gen-2');
// Parse the graph definition
diagObj.parser.parse(text);
return new Promise(function (resolve, reject) {
const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy');
// .attr('style', 'display:none')
let graph = {
styleEnabled: true,
// animate: false,
// ready: function () {
// log.info('Ready', this);
// },
container: document.getElementById('cy'), // container to render in
boxSelectionEnabled: false,
style: [
{
selector: 'node',
css: {
content: 'data(id)',
'text-valign': 'center',
'text-halign': 'center',
},
},
{
selector: ':parent',
css: {
'text-valign': 'top',
'text-halign': 'center',
},
},
{
selector: 'edge',
css: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle',
},
},
],
elements: {
nodes: [
{ data: { id: 'a', parent: 'b' } },
{ data: { id: 'b' } },
{ data: { id: 'c', parent: 'b' } },
{ data: { id: 'd' } },
{ data: { id: 'e' } },
{ data: { id: 'f', parent: 'e' } },
],
edges: [
{ data: { id: 'ad', source: 'a', target: 'd' } },
{ data: { id: 'eb', source: 'e', target: 'b' } },
],
},
};
log.info('Drawing flowchart using v3 renderer');
// Fetch the default direction, use TD if none was found
let dir = diagObj.db.getDirection();
if (dir === undefined) {
dir = 'TD';
}
const { securityLevel, flowchart: conf } = getConfig();
// Handle root and document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = root.select(`[id="${id}"]`);
const markers = ['point', 'circle', 'cross'];
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
log.info('Subgraph - ', subG);
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
const parentLookUpDb = addSubGraphs(diagObj.db);
graph = addVertices(vert, id, root, doc, diagObj, parentLookUpDb, graph);
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
const edges = diagObj.db.getEdges();
graph = addEdges(edges, diagObj, graph);
const cy = cytoscape(graph);
// c.style();
// Make cytoscape care about the dimensions of the nodes
cy.nodes().forEach(function (n) {
const boundingBox = n.data().boundingBox;
if (boundingBox) {
n.style('width', boundingBox.width);
n.style('height', boundingBox.height);
}
n.style('shape', 'rectangle');
// n.layoutDimensions = () => {
// // console.log('Node dimensions', boundingBox.width, boundingBox.height);
// if (boundingBox) {
// return { w: boundingBox.width, h: boundingBox.height };
// }
// // return { w: boundingBox.width, h: boundingBox.height };
// // const data = n.data();
// // return { w: data.width, h: data.height };
// return { w: 206, h: 160 };
// };
});
cy.layout({
// name: 'dagre',
// name: 'preset',
// name: 'cose',
// name: 'circle',
name: 'concentric',
headless: false,
styleEnabled: true,
animate: false,
}).run();
// function runLayouts(fit, callBack) {
// // step-1 position child nodes
// var parentNodes = cy.nodes(':parent');
// var grid_layout = parentNodes.descendants().layout({
// name: 'grid',
// cols: 1,
// fit: fit,
// });
// grid_layout.promiseOn('layoutstop').then(function (event) {
// // step-2 position parent nodes
// var dagre_layout = parentNodes.layout({
// name: 'dagre',
// rankDir: 'TB',
// fit: fit,
// });
// dagre_layout.promiseOn('layoutstop').then(function (event) {
// if (callBack) {
// callBack.call(cy, event);
// }
// });
// dagre_layout.run();
// });
// grid_layout.run();
// }
// runLayouts();
// log.info('Positions', cy.nodes().positions());
// window.cy = cy;
cy.ready((e) => {
log.info('Ready', e, cy.data());
// // setTimeout(() => {
cy.nodes().map((node, id) => {
const data = node.data();
log.info(
'Position: (',
node.position().x,
', ',
node.position().y,
')',
data,
cy.elements()[0].renderedBoundingBox()
);
if (data.el) {
data.el.attr('transform', `translate(${node.position().x}, ${node.position().y})`);
// document
// .querySelector(`[id="${data.domId}"]`)
// .setAttribute('transform', `translate(${node.position().x}, ${node.position().y})`);
log.info('Id = ', data.domId, svg.select(`[id="${data.domId}"]`), data.el.node());
}
// else {
// // console.log('No element found for node', data, node.position(), node.size());
// }
});
cy.edges().map((edge, id) => {
const data = edge.data();
if (edge[0]._private.bodyBounds) {
const bounds = edge[0]._private.rscratch;
// insertEdge(edgesEl, edge, data.edgeData, bounds, diagObj);
}
});
log.info(cy.json());
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
// Remove element after layout
// renderEl.remove();
resolve();
// }, 500);
});
});
};
export default {
// setConf,
// addVertices,
// addEdges,
getClasses,
draw,
};

View File

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

View File

@@ -1,24 +0,0 @@
import { findCommonAncestor } from './render-utils';
describe('when rendering a flowchart using elk ', function () {
let lookupDb;
beforeEach(function () {
lookupDb = JSON.parse(
'{"parentById":{"B4":"inner","B5":"inner","C4":"inner2","C5":"inner2","B2":"Ugge","B3":"Ugge","inner":"Ugge","inner2":"Ugge","B6":"outer"},"childrenById":{"inner":["B4","B5"],"inner2":["C4","C5"],"Ugge":["B2","B3","inner","inner2"],"outer":["B6"]}}'
);
});
it('Sieblings in a subgraph', function () {
expect(findCommonAncestor('B4', 'B5', lookupDb)).toBe('inner');
});
it('Find an uncle', function () {
expect(findCommonAncestor('B4', 'B2', lookupDb)).toBe('Ugge');
});
it('Find a cousin', function () {
expect(findCommonAncestor('B4', 'C4', lookupDb)).toBe('Ugge');
});
it('Find a grandparent', function () {
expect(findCommonAncestor('B4', 'B6', lookupDb)).toBe('root');
});
it('Sieblings in the root', function () {
expect(findCommonAncestor('B1', 'outer', lookupDb)).toBe('root');
});
});

View File

@@ -1,64 +0,0 @@
{
"name": "@mermaid-js/mermaid-mindmap",
"version": "9.3.0",
"description": "Mindmap diagram module for MermaidJS.",
"module": "dist/mermaid-mindmap.core.mjs",
"types": "dist/detector.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-mindmap.core.mjs",
"types": "./dist/detector.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"mindmap",
"mermaid"
],
"scripts": {
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"standard": {
"ignore": [
"**/parser/*.js",
"dist/**/*.js",
"cypress/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"khroma": "^2.0.0",
"non-layered-tidy-tree-layout": "^2.0.2"
},
"devDependencies": {
"concurrently": "^7.5.0",
"mermaid": "workspace:*",
"rimraf": "^3.0.2"
},
"resolutions": {
"d3": "^7.0.0"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "9.3.0",
"version": "9.4.0",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "./dist/mermaid.min.js",
"module": "./dist/mermaid.core.mjs",
@@ -53,18 +53,23 @@
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.0.0",
"dagre-d3-es": "7.0.6",
"dagre-d3-es": "7.0.8",
"dompurify": "2.4.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"moment-mini": "^2.24.0",
"moment": "^2.29.4",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"ts-dedent": "^2.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/cytoscape": "^3.19.9",
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.4.0",
"@types/jsdom": "^20.0.1",
@@ -85,10 +90,10 @@
"js-base64": "^3.7.2",
"jsdom": "^20.0.2",
"micromatch": "^4.0.5",
"moment": "^2.29.4",
"path-browserify": "^1.0.1",
"prettier": "^2.7.1",
"remark": "^14.0.2",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.14.0",

View File

@@ -6,7 +6,7 @@ import { extractFrontMatter } from './diagram-api/frontmatter';
import { isDetailedError } from './utils';
import type { DetailedError } from './utils';
export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void;
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
export class Diagram {
type = 'graph';
@@ -44,7 +44,7 @@ export class Diagram {
this.parser.parser.yy = this.db;
if (diagram.init) {
diagram.init(cnf);
log.debug('Initialized diagram ' + this.type, cnf);
log.info('Initialized diagram ' + this.type, cnf);
}
this.txt += '\n';

View File

@@ -26,6 +26,7 @@ export interface MermaidConfig {
sequence?: SequenceDiagramConfig;
gantt?: GanttDiagramConfig;
journey?: JourneyDiagramConfig;
timeline?: TimelineDiagramConfig;
class?: ClassDiagramConfig;
state?: StateDiagramConfig;
er?: ErDiagramConfig;
@@ -292,6 +293,30 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
sectionColours?: string[];
}
export interface TimelineDiagramConfig extends BaseDiagramConfig {
diagramMarginX?: number;
diagramMarginY?: number;
leftMargin?: number;
width?: number;
height?: number;
boxMargin?: number;
boxTextMargin?: number;
noteMargin?: number;
messageMargin?: number;
messageAlign?: string;
bottomMarginAdj?: number;
rightAngles?: boolean;
taskFontSize?: string | number;
taskFontFamily?: string;
taskMargin?: number;
activationWidth?: number;
textPlacement?: string;
actorColours?: string[];
sectionFills?: string[];
sectionColours?: string[];
disableMulticolor?: boolean;
}
export interface GanttDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
barHeight?: number;

View File

@@ -1007,6 +1007,7 @@ const class_box = (parent, node) => {
};
const shapes = {
rhombus: question,
question,
rect,
labelRect,

View File

@@ -862,6 +862,156 @@ const config: Partial<MermaidConfig> = {
sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'],
sectionColours: ['#fff'],
},
/** The object containing configurations specific for timeline diagrams */
timeline: {
/**
* | Parameter | Description | Type | Required | Values |
* | -------------- | ---------------------------------------------------- | ------- | -------- | ------------------ |
* | diagramMarginX | Margin to the right and left of the sequence diagram | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 50
*/
diagramMarginX: 50,
/**
* | Parameter | Description | Type | Required | Values |
* | -------------- | -------------------------------------------------- | ------- | -------- | ------------------ |
* | diagramMarginY | Margin to the over and under the sequence diagram. | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 10
*/
diagramMarginY: 10,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | --------------------- | ------- | -------- | ------------------ |
* | actorMargin | Margin between actors | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 50
*/
leftMargin: 150,
/**
* | Parameter | Description | Type | Required | Values |
* | --------- | -------------------- | ------- | -------- | ------------------ |
* | width | Width of actor boxes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 150
*/
width: 150,
/**
* | Parameter | Description | Type | Required | Values |
* | --------- | --------------------- | ------- | -------- | ------------------ |
* | height | Height of actor boxes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 65
*/
height: 50,
/**
* | Parameter | Description | Type | Required | Values |
* | --------- | ------------------------ | ------- | -------- | ------------------ |
* | boxMargin | Margin around loop boxes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 10
*/
boxMargin: 10,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | -------------------------------------------- | ------- | -------- | ------------------ |
* | boxTextMargin | Margin around the text in loop/alt/opt boxes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 5
*/
boxTextMargin: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ---------- | ------------------- | ------- | -------- | ------------------ |
* | noteMargin | Margin around notes | Integer | Required | Any Positive Value |
*
* **Notes:** Default value: 10
*/
noteMargin: 10,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ----------------------- | ------- | -------- | ------------------ |
* | messageMargin | Space between messages. | Integer | Required | Any Positive Value |
*
* **Notes:**
*
* Space between messages.
*
* Default value: 35
*/
messageMargin: 35,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------ | --------------------------- | ---- | -------- | ------------------------- |
* | messageAlign | Multiline message alignment | 3 | 4 | 'left', 'center', 'right' |
*
* **Notes:** Default value: 'center'
*/
messageAlign: 'center',
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ------------------------------------------ | ------- | -------- | ------------------ |
* | bottomMarginAdj | Prolongs the edge of the diagram downwards | Integer | 4 | Any Positive Value |
*
* **Notes:**
*
* Depending on css styling this might need adjustment.
*
* Default value: 1
*/
bottomMarginAdj: 1,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
* | useMaxWidth | See notes | boolean | 4 | true, false |
*
* **Notes:**
*
* When this flag is set the height and width is set to 100% and is then scaling with the
* available space if not the absolute space required is used.
*
* Default value: true
*/
useMaxWidth: true,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | --------------------------------- | ---- | -------- | ----------- |
* | rightAngles | Curved Arrows become Right Angles | 3 | 4 | true, false |
*
* **Notes:**
*
* This will display arrows that start and begin at the same node as right angles, rather than a
* curves
*
* Default value: false
*/
rightAngles: false,
taskFontSize: 14,
taskFontFamily: '"Open Sans", sans-serif',
taskMargin: 50,
// width of activation box
activationWidth: 10,
// text placement as: tspan | fo | old only text as before
textPlacement: 'fo',
actorColours: ['#8FBC8F', '#7CFC00', '#00FFFF', '#20B2AA', '#B0E0E6', '#FFFFE0'],
sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'],
sectionColours: ['#fff'],
disableMulticolor: false,
},
class: {
/**
* ### titleTopMargin

View File

@@ -1,6 +1,11 @@
import { MermaidConfig } from '../config.type';
import { log } from '../logger';
import { DetectorRecord, DiagramDetector, DiagramLoader } from './types';
import type {
DetectorRecord,
DiagramDetector,
DiagramLoader,
ExternalDiagramDefinition,
} from './types';
import { frontMatterRegex } from './frontmatter';
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
@@ -42,11 +47,18 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
throw new Error(`No diagram type detected for text: ${text}`);
};
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
for (const { id, detector, loader } of diagrams) {
addDetector(id, detector, loader);
}
};
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
if (detectors[key]) {
throw new Error(`Detector with key ${key} already exists`);
log.error(`Detector with key ${key} already exists`);
} else {
detectors[key] = { detector, loader };
}
detectors[key] = { detector, loader };
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
};

View File

@@ -1,5 +1,4 @@
import { registerDiagram } from './diagramAPI';
// @ts-ignore: TODO Fix ts errors
import gitGraphParser from '../diagrams/git/parser/gitGraph';
import { gitGraphDetector } from '../diagrams/git/gitGraphDetector';
@@ -94,6 +93,13 @@ import { setConfig } from '../config';
import errorRenderer from '../diagrams/error/errorRenderer';
import errorStyles from '../diagrams/error/styles';
import flowchartElk from '../diagrams/flowchart/elk/detector';
import { registerLazyLoadedDiagrams } from './detectType';
// Lazy loaded diagrams
import timelineDetector from '../diagrams/timeline/detector';
import mindmapDetector from '../diagrams/mindmap/detector';
let hasLoadedDiagrams = false;
export const addDiagrams = () => {
if (hasLoadedDiagrams) {
@@ -102,6 +108,8 @@ export const addDiagrams = () => {
// This is added here to avoid race-conditions.
// We could optimize the loading logic somehow.
hasLoadedDiagrams = true;
registerLazyLoadedDiagrams(flowchartElk, timelineDetector, mindmapDetector);
registerDiagram(
'error',
// Special diagram with error messages but setup as a regular diagram
@@ -141,12 +149,12 @@ export const addDiagrams = () => {
parse: () => {
throw new Error(
'Diagrams beginning with --- are not valid. ' +
'If you were trying to use a YAML front-matter, please ensure that ' +
"you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
'If you were trying to use a YAML front-matter, please ensure that ' +
"you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
);
},
},
init: () => undefined, // no op
init: () => null, // no op
},
(text) => {
return text.toLowerCase().trimStart().startsWith('---');

View File

@@ -5,6 +5,8 @@ import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
import { addStylesForDiagram } from '../styles';
import { DiagramDefinition, DiagramDetector } from './types';
import * as _commonDb from '../commonDb';
import { parseDirective as _parseDirective } from '../directiveUtils';
/*
Packaging and exposing resources for external diagrams so that they can import
@@ -16,6 +18,11 @@ export const setLogLevel = _setLogLevel;
export const getConfig = _getConfig;
export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
export const setupGraphViewbox = _setupGraphViewbox;
export const getCommonDb = () => {
return _commonDb;
};
export const parseDirective = (p: any, statement: string, context: string, type: string) =>
_parseDirective(p, statement, context, type);
const diagrams: Record<string, DiagramDefinition> = {};
export interface Detectors {
@@ -46,7 +53,15 @@ export const registerDiagram = (
addStylesForDiagram(id, diagram.styles);
if (diagram.injectUtils) {
diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
diagram.injectUtils(
log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewbox,
getCommonDb(),
parseDirective
);
}
};

View File

@@ -6,6 +6,8 @@ export interface InjectUtils {
_getConfig: any;
_sanitizeText: any;
_setupGraphViewbox: any;
_commonDb: any;
_parseDirective: any;
}
/**
@@ -29,7 +31,9 @@ export interface DiagramDefinition {
_setLogLevel: InjectUtils['_setLogLevel'],
_getConfig: InjectUtils['_getConfig'],
_sanitizeText: InjectUtils['_sanitizeText'],
_setupGraphViewbox: InjectUtils['_setupGraphViewbox']
_setupGraphViewbox: InjectUtils['_setupGraphViewbox'],
_commonDb: InjectUtils['_commonDb'],
_parseDirective: InjectUtils['_parseDirective']
) => void;
}

View File

@@ -12,8 +12,8 @@ let boundarys = [
alias: 'global',
label: { text: 'global' },
type: { text: 'global' },
tags: undefined,
link: undefined,
tags: null,
link: null,
parentBoundary: '',
},
];
@@ -728,8 +728,8 @@ export const clear = function () {
alias: 'global',
label: { text: 'global' },
type: { text: 'global' },
tags: undefined,
link: undefined,
tags: null,
link: null,
parentBoundary: '',
},
];

View File

@@ -347,7 +347,7 @@ let getIntersectPoint = function (fromNode, endPoint) {
let fromDYX = fromNode.height / fromNode.width;
let returnPoint = undefined;
let returnPoint = null;
if (y1 == y2 && x1 < x2) {
returnPoint = new Point(x1 + fromNode.width, fromCenterY);
@@ -599,7 +599,7 @@ export const draw = function (_text, id, _version, diagObj) {
c4ShapeInRow = db.getC4ShapeInRow();
c4BoundaryInRow = db.getC4BoundaryInRow();
log.debug(`C:${JSON.stringify(conf, undefined, 2)}`);
log.debug(`C:${JSON.stringify(conf, null, 2)}`);
const diagram =
securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : select(`[id="${id}"]`);

View File

@@ -59,7 +59,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Check to see if any of the attributes has a key or a comment
attributes.forEach((item) => {
if (item.attributeKeyType !== undefined) {
if (item.attributeKeyTypeList !== undefined && item.attributeKeyTypeList.length > 0) {
hasKeyType = true;
}
@@ -112,6 +112,9 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
nodeHeight = Math.max(typeBBox.height, nameBBox.height);
if (hasKeyType) {
const keyTypeNodeText =
item.attributeKeyTypeList !== undefined ? item.attributeKeyTypeList.join(',') : '';
const keyTypeNode = groupNode
.append('text')
.classed('er entityLabel', true)
@@ -122,7 +125,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
.style('text-anchor', 'left')
.style('font-family', getConfig().fontFamily)
.style('font-size', attrFontSize + 'px')
.text(item.attributeKeyType || '');
.text(keyTypeNodeText);
attributeNode.kn = keyTypeNode;
const keyTypeBBox = keyTypeNode.node().getBBox();

View File

@@ -28,10 +28,11 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\"[^"]*\" return 'WORD';
"erDiagram" return 'ER_DIAGRAM';
"{" { this.begin("block"); return 'BLOCK_START'; }
<block>"," return 'COMMA';
<block>\s+ /* skip whitespace in block */
<block>\b((?:PK)|(?:FK)|(?:UK))\b return 'ATTRIBUTE_KEY'
<block>(.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD';
<block>[A-Za-z][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD'
<block>[A-Za-z_][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD'
<block>\"[^"]*\" return 'COMMENT';
<block>[\n]+ /* nothing */
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
@@ -80,7 +81,7 @@ start
document
: /* empty */ { $$ = [] }
| document line {$1.push($2);$$ = $1}
| document line {$1.push($2);$$ = $1}
;
line
@@ -131,11 +132,12 @@ attributes
attribute
: attributeType attributeName { $$ = { attributeType: $1, attributeName: $2 }; }
| attributeType attributeName attributeKeyType { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3 }; }
| attributeType attributeName attributeKeyTypeList { $$ = { attributeType: $1, attributeName: $2, attributeKeyTypeList: $3 }; }
| attributeType attributeName attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeComment: $3 }; }
| attributeType attributeName attributeKeyType attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3, attributeComment: $4 }; }
| attributeType attributeName attributeKeyTypeList attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyTypeList: $3, attributeComment: $4 }; }
;
attributeType
: ATTRIBUTE_WORD { $$=$1; }
;
@@ -144,6 +146,11 @@ attributeName
: ATTRIBUTE_WORD { $$=$1; }
;
attributeKeyTypeList
: attributeKeyType { $$ = [$1]; }
| attributeKeyTypeList COMMA attributeKeyType { $1.push($3); $$ = $1; }
;
attributeKeyType
: ATTRIBUTE_KEY { $$=$1; }
;

View File

@@ -135,6 +135,37 @@ describe('when parsing ER diagram it...', function () {
});
});
describe('attribute name', () => {
it('should allow alphanumeric characters, dashes, underscores and brackets (not leading chars)', function () {
const entity = 'BOOK';
const attribute1 = 'string myBookTitle';
const attribute2 = 'string MYBOOKSUBTITLE_1';
const attribute3 = 'string author-ref[name](1)';
erDiagram.parser.parse(
`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n${attribute3}\n}`
);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(3);
expect(entities[entity].attributes[0].attributeName).toBe('myBookTitle');
expect(entities[entity].attributes[1].attributeName).toBe('MYBOOKSUBTITLE_1');
expect(entities[entity].attributes[2].attributeName).toBe('author-ref[name](1)');
});
it('should not allow leading numbers, dashes or brackets', function () {
const entity = 'BOOK';
const nonLeadingChars = '0-[]()';
[...nonLeadingChars].forEach((nonLeadingChar) => {
expect(() => {
const attribute = `string ${nonLeadingChar}author`;
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
}).toThrow();
});
});
});
it('should allow an entity with a single attribute to be defined', function () {
const entity = 'BOOK';
const attribute = 'string title';
@@ -190,6 +221,28 @@ describe('when parsing ER diagram it...', function () {
expect(entities[entity].attributes.length).toBe(4);
});
it('should allow an entity with attributes that have many constraints and comments', function () {
const entity = 'CUSTOMER';
const attribute1 = 'int customer_number PK, FK "comment1"';
const attribute2 = 'datetime customer_status_start_datetime PK,UK, FK';
const attribute3 = 'datetime customer_status_end_datetime PK , UK "comment3"';
const attribute4 = 'string customer_firstname';
const attribute5 = 'string customer_lastname "comment5"';
erDiagram.parser.parse(
`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n${attribute3}\n${attribute4}\n${attribute5}\n}`
);
const entities = erDb.getEntities();
expect(entities[entity].attributes[0].attributeKeyTypeList).toEqual(['PK', 'FK']);
expect(entities[entity].attributes[0].attributeComment).toBe('comment1');
expect(entities[entity].attributes[1].attributeKeyTypeList).toEqual(['PK', 'UK', 'FK']);
expect(entities[entity].attributes[2].attributeKeyTypeList).toEqual(['PK', 'UK']);
expect(entities[entity].attributes[2].attributeComment).toBe('comment3');
expect(entities[entity].attributes[3].attributeKeyTypeList).toBeUndefined();
expect(entities[entity].attributes[4].attributeKeyTypeList).toBeUndefined();
expect(entities[entity].attributes[4].attributeComment).toBe('comment5');
});
it('should allow an entity with attribute that has a generic type', function () {
const entity = 'BOOK';
const attribute1 = 'type~T~ type';

View File

@@ -0,0 +1,55 @@
import plugin from './detector';
import { describe, it } from 'vitest';
const { detector } = plugin;
describe('flowchart-elk detector', () => {
it('should fail for dagre-d3', () => {
expect(
detector('flowchart', {
flowchart: {
defaultRenderer: 'dagre-d3',
},
})
).toBe(false);
});
it('should fail for dagre-wrapper', () => {
expect(
detector('flowchart', {
flowchart: {
defaultRenderer: 'dagre-wrapper',
},
})
).toBe(false);
});
it('should succeed for elk', () => {
expect(
detector('flowchart', {
flowchart: {
defaultRenderer: 'elk',
},
})
).toBe(true);
expect(
detector('graph', {
flowchart: {
defaultRenderer: 'elk',
},
})
).toBe(true);
});
it('should detect flowchart-elk', () => {
expect(detector('flowchart-elk')).toBe(true);
});
it('should not detect class with defaultRenderer set to elk', () => {
expect(
detector('class', {
flowchart: {
defaultRenderer: 'elk',
},
})
).toBe(false);
});
});

View File

@@ -0,0 +1,29 @@
import type { MermaidConfig } from '../../../config.type';
import type { ExternalDiagramDefinition, DiagramDetector } from '../../../diagram-api/types';
const id = 'flowchart-elk';
const detector: DiagramDetector = (txt: string, config?: MermaidConfig): boolean => {
if (
// If diagram explicitly states flowchart-elk
txt.match(/^\s*flowchart-elk/) ||
// If a flowchart/graph diagram has their default renderer set to elk
(txt.match(/^\s*flowchart|graph/) && config?.flowchart?.defaultRenderer === 'elk')
) {
return true;
}
return false;
};
const loader = async () => {
const { diagram } = await import('./diagram-definition');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -0,0 +1,13 @@
// @ts-ignore: JISON typing missing
import parser from '../parser/flow';
import * as db from '../flowDb';
import renderer from './flowRenderer-elk';
import styles from './styles';
export const diagram = {
db,
renderer,
parser,
styles,
};

View File

@@ -1,24 +1,20 @@
import graphlib from 'graphlib';
import { select, line, curveLinear, curveCardinal, curveBasis, selectAll } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
import createLabel from '../../mermaid/src/dagre-wrapper/createLabel';
import { insertEdgeLabel, positionEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
import { select, line, curveLinear } from 'd3';
import { insertNode } from '../../../dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils';
// Replace with other function to avoid dependency to dagre-d3
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { getConfig } from '../../../config';
import { log } from '../../../logger';
import { setupGraphViewbox } from '../../../setupGraphViewbox';
import common, { evaluate } from '../../common/common';
import { interpolateToCurve, getStylesFromArray } from '../../../utils';
import common, { evaluate } from '../../mermaid/src/diagrams/common/common';
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils';
// import ELK from 'elkjs/lib/elk-api';
// const elk = new ELK({
// workerUrl: './elk-worker.min.js',
// });
import ELK from 'elkjs/lib/elk.bundled.js';
const elk = new ELK();
const portPos = {};
const conf = {};
export const setConf = function (cnf) {
const keys = Object.keys(cnf);
@@ -101,8 +97,36 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
labelData.labelNode = vertexNode;
}
const ports = [
{
id: vertex.id + '-west',
layoutOptions: {
'port.side': 'WEST',
},
},
{
id: vertex.id + '-east',
layoutOptions: {
'port.side': 'EAST',
},
},
{
id: vertex.id + '-south',
layoutOptions: {
'port.side': 'SOUTH',
},
},
{
id: vertex.id + '-north',
layoutOptions: {
'port.side': 'NORTH',
},
},
];
let radious = 0;
let _shape = '';
let layoutOptions = {};
// Set the shape based parameters
switch (vertex.type) {
case 'round':
@@ -114,6 +138,9 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
break;
case 'diamond':
_shape = 'question';
layoutOptions = {
portConstraints: 'FIXED_SIDE',
};
break;
case 'hexagon':
_shape = 'hexagon';
@@ -190,13 +217,15 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
const data = {
id: vertex.id,
ports: vertex.type === 'diamond' ? ports : [],
// labelStyle: styles.labelStyle,
// shape: _shape,
layoutOptions,
labelText: vertexText,
labelData,
// labels: [{ text: vertexText }],
// rx: radious,
// ry: radious,
// rx: radius,
// ry: radius,
// class: classStr,
// style: styles.style,
// link: vertex.link,
@@ -224,8 +253,8 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
// labelStyle: styles.labelStyle,
// shape: _shape,
// labelText: vertexText,
// rx: radious,
// ry: radious,
// rx: radius,
// ry: radius,
// class: classStr,
// style: styles.style,
// id: vertex.id,
@@ -241,6 +270,127 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
return graph;
};
const getNextPosition = (position, edgeDirection, graphDirection) => {
const portPos = {
TB: {
in: {
north: 'north',
},
out: {
south: 'west',
west: 'east',
east: 'south',
},
},
LR: {
in: {
west: 'west',
},
out: {
east: 'south',
south: 'north',
north: 'east',
},
},
RL: {
in: {
east: 'east',
},
out: {
west: 'north',
north: 'south',
south: 'west',
},
},
BT: {
in: {
south: 'south',
},
out: {
north: 'east',
east: 'west',
west: 'north',
},
},
};
portPos.TD = portPos.TB;
log.info('abc88', graphDirection, edgeDirection, position);
return portPos[graphDirection][edgeDirection][position];
// return 'south';
};
const getNextPort = (node, edgeDirection, graphDirection) => {
log.info('getNextPort abc88', { node, edgeDirection, graphDirection });
if (!portPos[node]) {
switch (graphDirection) {
case 'TB':
case 'TD':
portPos[node] = {
inPosition: 'north',
outPosition: 'south',
};
break;
case 'BT':
portPos[node] = {
inPosition: 'south',
outPosition: 'north',
};
break;
case 'RL':
portPos[node] = {
inPosition: 'east',
outPosition: 'west',
};
break;
case 'LR':
portPos[node] = {
inPosition: 'west',
outPosition: 'east',
};
break;
}
}
const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition;
if (edgeDirection === 'in') {
portPos[node].inPosition = getNextPosition(
portPos[node].inPosition,
edgeDirection,
graphDirection
);
} else {
portPos[node].outPosition = getNextPosition(
portPos[node].outPosition,
edgeDirection,
graphDirection
);
}
return result;
};
const getEdgeStartEndPoint = (edge, dir) => {
let source = edge.start;
let target = edge.end;
const startNode = nodeDb[source];
const endNode = nodeDb[target];
if (!startNode || !endNode) {
return { source, target };
}
if (startNode.type === 'diamond') {
source = `${source}-${getNextPort(source, 'out', dir)}`;
}
if (endNode.type === 'diamond') {
target = `${target}-${getNextPort(target, 'in', dir)}`;
}
// Add the edge to the graph
return { source, target };
};
/**
* Add edges to graph based on parsed graph definition
*
@@ -252,11 +402,10 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
* @param svg
*/
export const addEdges = function (edges, diagObj, graph, svg) {
// log.info('abc78 edges = ', edges);
log.info('abc78 edges = ', edges);
const labelsEl = svg.insert('g').attr('class', 'edgeLabels');
let cnt = 0;
let linkIdCnt = {};
let dir = diagObj.db.getDirection();
let defaultStyle;
let defaultLabelStyle;
@@ -267,8 +416,6 @@ export const addEdges = function (edges, diagObj, graph, svg) {
}
edges.forEach(function (edge) {
cnt++;
// Identify Link
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
// count the links from+to the same node to give unique id
@@ -382,19 +529,20 @@ export const addEdges = function (edges, diagObj, graph, svg) {
edgeData.id = linkId;
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
const edgesNode = select(edges);
const labelEl = insertEdgeLabel(labelsEl, edgeData);
// console.log('labelEl', labelEl, edgeData.width);
// calculate start and end points of the edge
const { source, target } = getEdgeStartEndPoint(edge, dir);
log.debug('abc78 source and target', source, target);
// Add the edge to the graph
graph.edges.push({
id: 'e' + edge.start + edge.end,
sources: [edge.start],
targets: [edge.end],
sources: [source],
targets: [target],
labelEl: labelEl,
labels: [
{
width: edgeData.width,
// width: 80,
height: edgeData.height,
orgWidth: edgeData.width,
orgHeight: edgeData.height,
@@ -406,8 +554,6 @@ export const addEdges = function (edges, diagObj, graph, svg) {
},
],
edgeData,
// targetPort: 'PortSide.NORTH',
// id: cnt,
});
});
return graph;
@@ -515,7 +661,7 @@ export const getClasses = function (text, diagObj) {
diagObj.parse(text);
return diagObj.db.getClasses();
} catch (e) {
return;
return {};
}
};
@@ -542,45 +688,17 @@ const addSubGraphs = function (db) {
return parentLookupDb;
};
/* Reverse engineered with trial and error */
const calcOffsetOld = function (src, dest, sourceId, targetId, srcDepth, targetDepth, so, to) {
// if (src === dest) {
// return src;
// }
// if (sourceId === 'B6') {
// return 0;
// }
// if (sourceId === 'B4') {
// return 318;
// }
if (srcDepth < targetDepth) {
return src;
}
if (srcDepth > targetDepth) {
return dest;
}
if (srcDepth === targetDepth) {
return src;
}
// if (src < dest) {
// return dest + src;
// }
return 0;
};
const calcOffset = function (src, dest, parentLookupDb) {
const ancestor = findCommonAncestor(src, dest, parentLookupDb);
if (ancestor === undefined || ancestor === 'root') {
return { x: 0, y: 0 };
}
const ancestoprOffset = nodeDb[ancestor].offset;
return { x: ancestoprOffset.posX, y: ancestoprOffset.posY };
const ancestorOffset = nodeDb[ancestor].offset;
return { x: ancestorOffset.posX, y: ancestorOffset.posY };
};
const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
const srcOffset = nodeDb[edge.sources[0]].offset;
const targetOffset = nodeDb[edge.targets[0]].offset;
const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb);
const src = edge.sections[0].startPoint;
@@ -647,7 +765,7 @@ const insertChildren = (nodeArray, parentLookupDb) => {
* @param id
*/
export const draw = function (text, id, _version, diagObj) {
export const draw = async function (text, id, _version, diagObj) {
// Add temporary render element
diagObj.db.clear();
nodeDb = {};
@@ -655,149 +773,133 @@ export const draw = function (text, id, _version, diagObj) {
// Parse the graph definition
diagObj.parser.parse(text);
return new Promise(function (resolve, reject) {
const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy');
// .attr('style', 'display:none')
let graph = {
id: 'root',
layoutOptions: {
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
// 'elk.hierarchyHandling': 'SEPARATE_CHILDREN',
'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]',
// 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 120,
// 'elk.layered.spacing.nodeNodeBetweenLayers': '140',
'elk.layered.spacing.edgeNodeBetweenLayers': '30',
// 'elk.algorithm': 'layered',
'elk.direction': 'DOWN',
// 'elk.port.side': 'SOUTH',
// 'nodePlacement.strategy': 'SIMPLE',
// 'org.eclipse.elk.spacing.labelLabel': 120,
// 'org.eclipse.elk.graphviz.concentrate': true,
// 'org.eclipse.elk.spacing.nodeNode': 120,
// 'org.eclipse.elk.spacing.edgeEdge': 120,
// 'org.eclipse.elk.spacing.edgeNode': 120,
// 'org.eclipse.elk.spacing.nodeEdge': 120,
// 'org.eclipse.elk.spacing.componentComponent': 120,
},
children: [],
edges: [],
};
log.info('Drawing flowchart using v3 renderer');
const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy');
let graph = {
id: 'root',
layoutOptions: {
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]',
'elk.layered.spacing.edgeNodeBetweenLayers': '30',
// 'elk.layered.mergeEdges': 'true',
'elk.direction': 'DOWN',
// 'elk.ports.sameLayerEdges': true,
// 'nodePlacement.strategy': 'SIMPLE',
},
children: [],
edges: [],
};
log.info('Drawing flowchart using v3 renderer', elk);
// Set the direction,
// Fetch the default direction, use TD if none was found
let dir = diagObj.db.getDirection();
switch (dir) {
case 'BT':
graph.layoutOptions['elk.direction'] = 'UP';
break;
case 'TB':
graph.layoutOptions['elk.direction'] = 'DOWN';
break;
case 'LR':
graph.layoutOptions['elk.direction'] = 'RIGHT';
break;
case 'RL':
graph.layoutOptions['elk.direction'] = 'LEFT';
break;
// Set the direction,
// Fetch the default direction, use TD if none was found
let dir = diagObj.db.getDirection();
switch (dir) {
case 'BT':
graph.layoutOptions['elk.direction'] = 'UP';
break;
case 'TB':
graph.layoutOptions['elk.direction'] = 'DOWN';
break;
case 'LR':
graph.layoutOptions['elk.direction'] = 'RIGHT';
break;
case 'RL':
graph.layoutOptions['elk.direction'] = 'LEFT';
break;
}
const { securityLevel, flowchart: conf } = getConfig();
// Find the root dom node to ne used in rendering
// Handle root and document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = root.select(`[id="${id}"]`);
// Define the supported markers for the diagram
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
// Setup nodes from the subgraphs with type group, these will be used
// as nodes with children in the subgraph
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
// Create the lookup db for the subgraphs and their children to used when creating
// the tree structured graph
const parentLookupDb = addSubGraphs(diagObj.db);
// Add the nodes to the graph, this will entail creating the actual nodes
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
// Fetch the edges form the parsed graph definition
const edges = diagObj.db.getEdges();
// Add the edges to the graph, this will entail creating the actual edges
graph = addEdges(edges, diagObj, graph, svg);
// Iterate through all nodes and add the top level nodes to the graph
const nodes = Object.keys(nodeDb);
nodes.forEach((nodeId) => {
const node = nodeDb[nodeId];
if (!node.parent) {
graph.children.push(node);
}
const { securityLevel, flowchart: conf } = getConfig();
// Find the root dom node to ne used in rendering
// Handle root and document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = root.select(`[id="${id}"]`);
// Define the supported markers for the diagram
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
// Setup nodes from the subgraphs with type group, these will be used
// as nodes with children in the subgraph
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
// Create the lookup db for the subgraphs and their children to used when creating
// the tree structured graph
const parentLookupDb = addSubGraphs(diagObj.db);
// Add the nodes to the graph, this will entail creating the actual nodes
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
// Fetch the edges form the parsed graph definition
const edges = diagObj.db.getEdges();
// Add the edges to the graph, this will entail creating the actual edges
graph = addEdges(edges, diagObj, graph, svg);
// Iterate through all nodes and add the top level nodes to the graph
const nodes = Object.keys(nodeDb);
nodes.forEach((nodeId) => {
const node = nodeDb[nodeId];
if (!node.parent) {
graph.children.push(node);
}
// node.nodePadding = [120, 50, 50, 50];
// node['org.eclipse.elk.spacing.nodeNode'] = 120;
// Subgraph
if (parentLookupDb.childrenById[nodeId] !== undefined) {
node.labels = [
{
text: node.labelText,
layoutOptions: {
'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
},
width: node.labelData.width,
height: node.labelData.height,
// Subgraph
if (parentLookupDb.childrenById[nodeId] !== undefined) {
node.labels = [
{
text: node.labelText,
layoutOptions: {
'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
},
];
delete node.x;
delete node.y;
delete node.width;
delete node.height;
}
});
insertChildren(graph.children, parentLookupDb);
elk.layout(graph).then(function (g) {
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
g.edges.map((edge, id) => {
insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
});
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
resolve();
});
// Remove element after layout
renderEl.remove();
width: node.labelData.width,
height: node.labelData.height,
},
];
delete node.x;
delete node.y;
delete node.width;
delete node.height;
}
});
insertChildren(graph.children, parentLookupDb);
log.info('after layout', JSON.stringify(graph, null, 2));
const g = await elk.layout(graph);
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
log.info('after layout', g);
g.edges?.map((edge) => {
insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
});
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
// Remove element after layout
renderEl.remove();
};
const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {
@@ -846,9 +948,6 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {
};
export default {
// setConf,
// addVertices,
// addEdges,
getClasses,
draw,
};

View File

@@ -0,0 +1,40 @@
import { findCommonAncestor, TreeData } from './render-utils';
describe('when rendering a flowchart using elk ', () => {
let lookupDb: TreeData;
beforeEach(() => {
lookupDb = {
parentById: {
B4: 'inner',
B5: 'inner',
C4: 'inner2',
C5: 'inner2',
B2: 'Ugge',
B3: 'Ugge',
inner: 'Ugge',
inner2: 'Ugge',
B6: 'outer',
},
childrenById: {
inner: ['B4', 'B5'],
inner2: ['C4', 'C5'],
Ugge: ['B2', 'B3', 'inner', 'inner2'],
outer: ['B6'],
},
};
});
it('to find parent of siblings in a subgraph', () => {
expect(findCommonAncestor('B4', 'B5', lookupDb)).toBe('inner');
});
it('to find an uncle', () => {
expect(findCommonAncestor('B4', 'B2', lookupDb)).toBe('Ugge');
});
it('to find a cousin', () => {
expect(findCommonAncestor('B4', 'C4', lookupDb)).toBe('Ugge');
});
it('to find a grandparent', () => {
expect(findCommonAncestor('B4', 'B6', lookupDb)).toBe('root');
});
it('to find ancestor of siblings in the root', () => {
expect(findCommonAncestor('B1', 'outer', lookupDb)).toBe('root');
});
});

View File

@@ -1,4 +1,9 @@
export const findCommonAncestor = (id1, id2, treeData) => {
export interface TreeData {
parentById: Record<string, string>;
childrenById: Record<string, string[]>;
}
export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) => {
const { parentById } = treeData;
const visited = new Set();
let currentId = id1;

View File

@@ -13,9 +13,10 @@ export interface FlowChartStyleOptions {
tertiaryColor: string;
textColor: string;
titleColor: string;
[key: string]: string;
}
const genSections = (options) => {
const genSections = (options: FlowChartStyleOptions) => {
let sections = '';
for (let i = 0; i < 5; i++) {

View File

@@ -238,6 +238,9 @@ export const setDirection = function (dir) {
if (direction.match(/.*v/)) {
direction = 'TB';
}
if (direction === 'TD') {
direction = 'TB';
}
};
/**

View File

@@ -82,6 +82,7 @@ that id.
<click>[\s\n] this.popState();
<click>[^\s\n]* return 'CLICK';
"flowchart-elk" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
"graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
"flowchart" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
"subgraph" return 'subgraph';

View File

@@ -1,4 +1,4 @@
import moment from 'moment-mini';
import moment from 'moment';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { log } from '../../logger';
import * as configApi from '../../config';
@@ -176,7 +176,7 @@ const checkTaskDates = function (task, dateFormat, excludes, includes) {
const fixTaskDates = function (startTime, endTime, dateFormat, excludes, includes) {
let invalid = false;
let renderEndTime = undefined;
let renderEndTime = null;
while (startTime <= endTime) {
if (!invalid) {
renderEndTime = endTime.toDate();
@@ -199,7 +199,7 @@ const getStartDate = function (prevTime, dateFormat, str) {
if (afterStatement !== null) {
// check all after ids and take the latest
let latestEndingTask = undefined;
let latestEndingTask = null;
afterStatement[1].split(' ').forEach(function (id) {
let task = findTaskById(id);
if (task !== undefined) {
@@ -419,7 +419,7 @@ export const addTask = function (descr, data) {
type: currentSection,
processed: false,
manualEndTime: false,
renderEndTime: undefined,
renderEndTime: null,
raw: { data: data },
task: descr,
classes: [],

View File

@@ -1,5 +1,5 @@
// @ts-nocheck TODO: Fix TS
import moment from 'moment-mini';
import moment from 'moment';
import ganttDb from './ganttDb';
import { convert } from '../../tests/util';
@@ -191,7 +191,7 @@ describe('when using the ganttDb', function () {
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[3].renderEndTime).toBeUndefined(); // Fixed end
expect(tasks[3].renderEndTime).toBeNull(); // Fixed end
expect(tasks[3].id).toEqual('id4');
expect(tasks[3].task).toEqual('test4');
@@ -360,13 +360,13 @@ describe('when using the ganttDb', function () {
expect(tasks[0].startTime).toEqual(moment('2019-09-30', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeUndefined(); // Fixed end
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-10-31', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeUndefined(); // Fixed end
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2');
});
@@ -387,7 +387,7 @@ describe('when using the ganttDb', function () {
expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeUndefined(); // Fixed end
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[1].manualEndTime).toBeTruthy();
expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2');

View File

@@ -1,4 +1,4 @@
import moment from 'moment-mini';
import moment from 'moment';
import { log } from '../../logger';
import {
select,
@@ -434,7 +434,7 @@ export const draw = function (text, id, version, diagObj) {
}
const excludeRanges = [];
let range = undefined;
let range = null;
let d = moment(minTime);
while (d.valueOf() <= maxTime) {
if (diagObj.db.isInvalidDate(d, dateFormat, excludes, includes)) {
@@ -449,7 +449,7 @@ export const draw = function (text, id, version, diagObj) {
} else {
if (range) {
excludeRanges.push(range);
range = undefined;
range = null;
}
}
d.add(1, 'd');

View File

@@ -125,7 +125,6 @@ describe('when parsing a gantt diagram it', function () {
'click cl2 call ganttTestClick()\n';
expect(parserFnConstructor(str)).not.toThrow();
// eslint-disable-next-line unicorn/no-null
expect(ganttDb.setClickEvent).toHaveBeenCalledWith('cl2', 'ganttTestClick', null);
});
it('should parse callback specifier with arbitrary number of args', function () {

View File

@@ -17,7 +17,7 @@ import {
let mainBranchName = getConfig().gitGraph.mainBranchName;
let mainBranchOrder = getConfig().gitGraph.mainBranchOrder;
let commits = {};
let head = undefined;
let head = null;
let branchesConfig = {};
branchesConfig[mainBranchName] = { name: mainBranchName, order: mainBranchOrder };
let branches = {};
@@ -120,7 +120,7 @@ export const commit = function (msg, id, type, tag) {
seq: seq++,
type: type ? type : commitType.NORMAL,
tag: tag ? tag : '',
parents: head == undefined ? [] : [head.id],
parents: head == null ? [] : [head.id],
branch: curBranch,
};
head = commit;
@@ -246,7 +246,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
id: custom_id ? custom_id : seq + '-' + getId(),
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
seq: seq++,
parents: [head?.id, branches[otherBranch]],
parents: [head == null ? null : head.id, branches[otherBranch]],
branch: curBranch,
type: commitType.MERGE,
customType: override_type,
@@ -330,7 +330,7 @@ export const cherryPick = function (sourceId, targetId, tag) {
id: seq + '-' + getId(),
message: 'cherry-picked ' + sourceCommit + ' into ' + curBranch,
seq: seq++,
parents: [head?.id, sourceCommit.id],
parents: [head == null ? null : head.id, sourceCommit.id],
branch: curBranch,
type: commitType.CHERRY_PICK,
tag: tag ?? 'cherry-pick:' + sourceCommit.id,
@@ -443,7 +443,7 @@ export const prettyPrint = function () {
export const clear = function () {
commits = {};
head = undefined;
head = null;
let mainBranch = getConfig().gitGraph.mainBranchName;
let mainBranchOrder = getConfig().gitGraph.mainBranchOrder;
branches = {};

View File

@@ -851,9 +851,15 @@ describe('when parsing a gitGraph', function () {
merge test1
`;
expect(() => parser.parse(str)).to.throw(
'Incorrect usage of "merge". Branch to be merged (test1) has no commits'
);
try {
parser.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe(
'Incorrect usage of "merge". Branch to be merged (test1) has no commits'
);
}
});
describe('accessibility', () => {
it('should handle a title and a description (accDescr)', () => {

View File

@@ -6,10 +6,10 @@ export const getCommits = () => {
seq: 1,
message: '',
branch: 'master',
parents: undefined,
parents: null,
tag: 'v0.1',
commitType: 'normal',
note: undefined,
note: null,
},
'0000002': {
id: '0000002',
@@ -17,9 +17,9 @@ export const getCommits = () => {
message: '',
branch: 'develop',
parents: ['0000001'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000003': {
id: '0000003',
@@ -27,9 +27,9 @@ export const getCommits = () => {
message: '',
branch: 'featureB',
parents: ['0000002'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000004': {
id: '0000004',
@@ -37,9 +37,9 @@ export const getCommits = () => {
message: '',
branch: 'hotfix',
parents: ['0000001'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000005': {
id: '0000005',
@@ -47,9 +47,9 @@ export const getCommits = () => {
message: '',
branch: 'develop',
parents: ['0000002'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000006': {
id: '0000006',
@@ -57,9 +57,9 @@ export const getCommits = () => {
message: '',
branch: 'featureB',
parents: ['0000003'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000007': {
id: '0000007',
@@ -69,7 +69,7 @@ export const getCommits = () => {
parents: ['0000004'],
tag: 'v0.2',
commitType: 'normal',
note: undefined,
note: null,
},
'0000008': {
id: '0000008',
@@ -77,9 +77,9 @@ export const getCommits = () => {
message: '',
branch: 'featureB',
parents: ['0000006'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000009': {
id: '0000009',
@@ -87,9 +87,9 @@ export const getCommits = () => {
message: '',
branch: 'featureA',
parents: ['0000005'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000010': {
id: '0000010',
@@ -97,9 +97,9 @@ export const getCommits = () => {
message: '',
branch: 'develop',
parents: ['0000004', '0000005'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000011': {
id: '0000011',
@@ -107,7 +107,7 @@ export const getCommits = () => {
message: '',
branch: 'featureA',
parents: ['0000009'],
tag: undefined,
tag: null,
commitType: 'normal',
note: '',
},
@@ -117,9 +117,9 @@ export const getCommits = () => {
message: '',
branch: 'featureB',
parents: ['0000008'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000013': {
id: '0000013',
@@ -127,9 +127,9 @@ export const getCommits = () => {
message: '',
branch: 'develop',
parents: ['0000010', '0000011'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000014': {
id: '0000014',
@@ -137,9 +137,9 @@ export const getCommits = () => {
message: '',
branch: 'release',
parents: ['0000013'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000015': {
id: '0000015',
@@ -147,9 +147,9 @@ export const getCommits = () => {
message: '',
branch: 'master',
parents: ['0000007'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
'0000016': {
id: '0000016',
@@ -159,7 +159,7 @@ export const getCommits = () => {
parents: ['0000014', '0000015'],
tag: 'v1.0',
commitType: 'normal',
note: undefined,
note: null,
},
'0000017': {
id: '0000017',
@@ -167,9 +167,9 @@ export const getCommits = () => {
message: '',
branch: 'develop',
parents: ['0000013', '0000016'],
tag: undefined,
tag: null,
commitType: 'normal',
note: undefined,
note: null,
},
};
};

View File

@@ -1,5 +1,4 @@
import type { ExternalDiagramDefinition } from 'mermaid';
import type { ExternalDiagramDefinition } from '../../diagram-api/types';
const id = 'mindmap';
const detector = (txt: string) => {

View File

@@ -3,12 +3,10 @@ import mindmapParser from './parser/mindmap';
import * as mindmapDb from './mindmapDb';
import mindmapRenderer from './mindmapRenderer';
import mindmapStyles from './styles';
import { injectUtils } from './mermaidUtils';
export const diagram = {
db: mindmapDb,
renderer: mindmapRenderer,
parser: mindmapParser,
styles: mindmapStyles,
injectUtils,
};

View File

@@ -1,16 +1,16 @@
import { parser as mindmap } from './parser/mindmap';
import * as mindmapDB from './mindmapDb';
import { injectUtils } from './mermaidUtils';
// import { injectUtils } from './mermaidUtils';
// Todo fix utils functions for tests
import {
log,
// log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewBox,
} from '../../mermaid/src/diagram-api/diagramAPI';
// getConfig,
// sanitizeText,
// setupGraphViewBox,
} from '../../diagram-api/diagramAPI';
injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox);
// injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox);
describe('when parsing a mindmap ', function () {
beforeEach(function () {
@@ -347,4 +347,40 @@ root
expect(child.children.length).toEqual(2);
expect(child.children[1].nodeId).toEqual('b');
});
it('MMP-23 Rows with only spaces should not interfere', function () {
let str = 'mindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.children.length).toEqual(2);
const child = mm.children[0];
expect(child.nodeId).toEqual('A');
const child2 = mm.children[1];
expect(child2.nodeId).toEqual('B');
});
it('MMP-24 Handle rows above the mindmap declarations', function () {
let str = '\n \nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.children.length).toEqual(2);
const child = mm.children[0];
expect(child.nodeId).toEqual('A');
const child2 = mm.children[1];
expect(child2.nodeId).toEqual('B');
});
it('MMP-25 Handle rows above the mindmap declarations, no space', function () {
let str = '\n\n\nmindmap\nroot\n A\n \n\n B';
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.nodeId).toEqual('root');
expect(mm.children.length).toEqual(2);
const child = mm.children[0];
expect(child.nodeId).toEqual('A');
const child2 = mm.children[1];
expect(child2.nodeId).toEqual('B');
});
});

View File

@@ -1,5 +1,8 @@
/** Created by knut on 15-01-14. */
import { sanitizeText, getConfig, log } from './mermaidUtils';
import { getConfig } from '../../config';
import { sanitizeText as _sanitizeText } from '../../diagrams/common/common';
import { log } from '../../logger';
export const sanitizeText = (text) => _sanitizeText(text, getConfig());
let nodes = [];
let cnt = 0;
@@ -17,11 +20,11 @@ const getParent = function (level) {
}
}
// No parent found
return undefined;
return null;
};
export const getMindmap = () => {
return nodes.length > 0 ? nodes[0] : undefined;
return nodes.length > 0 ? nodes[0] : null;
};
export const addNode = (level, id, descr, type) => {
log.info('addNode', level, id, descr, type);

View File

@@ -1,6 +1,8 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import svgDraw from './svgDraw';
import cytoscape from 'cytoscape';
import coseBilkent from 'cytoscape-cose-bilkent';
@@ -89,6 +91,7 @@ function addNodes(mindmap, cy, conf, level) {
/**
* @param node
* @param conf
* @param cy
*/
function layoutMindmap(node, conf) {
return new Promise((resolve) => {
@@ -131,7 +134,10 @@ function layoutMindmap(node, conf) {
});
}
/**
* @param node
* @param cy
* @param positionedMindmap
* @param conf
*/
function positionNodes(cy) {
cy.nodes().map((node, id) => {

View File

@@ -25,6 +25,7 @@
<CLASS>\n { this.popState();}
// [\s]*"::icon(" { this.begin('ICON'); }
"::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); }
[\s]+[\n] {yy.getLogger().trace('SPACELINE');return 'SPACELINE' /* skip all whitespace */ ;}
[\n]+ return 'NL';
<ICON>[^\)]+ { return 'ICON'; }
<ICON>\) {yy.getLogger().trace('end icon');this.popState();}
@@ -64,14 +65,25 @@
start
// %{ : info document 'EOF' { return yy; } }
: MINDMAP document { return yy; }
| MINDMAP NL document { return yy; }
| SPACELIST MINDMAP document { return yy; }
;
: mindMap
| spaceLines mindMap
;
spaceLines
: SPACELINE
| spaceLines SPACELINE
| spaceLines NL
;
mindMap
: MINDMAP document { return yy; }
| MINDMAP NL document { return yy; }
;
stop
: NL {yy.getLogger().trace('Stop NL ');}
| EOF {yy.getLogger().trace('Stop EOF ');}
| SPACELINE
| stop NL {yy.getLogger().trace('Stop NL2 ');}
| stop EOF {yy.getLogger().trace('Stop EOF2 ');}
;
@@ -81,9 +93,10 @@ document
;
statement
: SPACELIST node { yy.getLogger().trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
: SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
| SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
| SPACELINE { yy.getLogger().trace('SPACELIST');}
| node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
| ICON { yy.decorateNode({icon: $1}); }
| CLASS { yy.decorateNode({class: $1}); }

View File

@@ -19,7 +19,7 @@ function wrap(text, width) {
y = text.attr('y'),
dy = parseFloat(text.attr('dy')),
tspan = text
.text(undefined)
.text(null)
.append('tspan')
.attr('x', 0)
.attr('y', y)
@@ -203,15 +203,14 @@ const roundedRectBkg = function (elem, node) {
* @returns {number} The height nodes dom element
*/
export const drawNode = function (elem, node, fullSection, conf) {
const section = (fullSection % MAX_SECTIONS) - 1;
const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g');
node.section = section;
nodeElem.attr(
'class',
(node.class ? node.class + ' ' : '') +
'mindmap-node ' +
(section < 0 ? 'section-root' : 'section-' + section)
);
let sectionClass = 'section-' + section;
if (section < 0) {
sectionClass += ' section-root';
}
nodeElem.attr('class', (node.class ? node.class + ' ' : '') + 'mindmap-node ' + sectionClass);
const bkgElem = nodeElem.append('g');
// Create the wrapped text element
@@ -305,7 +304,7 @@ export const drawNode = function (elem, node, fullSection, conf) {
};
export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) {
const section = (fullSection % MAX_SECTIONS) - 1;
const section = fullSection % (MAX_SECTIONS - 1);
const sx = parent.x + parent.width / 2;
const sy = parent.y + parent.height / 2;
const ex = mindmap.x + mindmap.width / 2;

View File

@@ -35,8 +35,9 @@
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM';
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
@@ -117,16 +118,30 @@ line
| NEWLINE { $$=[]; }
;
box_section
: /* empty */ { $$ = [] }
| box_section box_line {$1.push($2);$$ = $1}
;
box_line
: SPACE participant_statement { $$ = $2 }
| participant_statement { $$ = $1 }
| NEWLINE { $$=[]; }
;
directive
: openDirective typeDirective closeDirective 'NEWLINE'
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
;
statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
: participant_statement
| 'box' restOfLine box_section end
{
$3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
$3.push({type: 'boxEnd', boxText:$2});
$$=$3;}
| signal 'NEWLINE'
| autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};}
| autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};}
@@ -209,6 +224,13 @@ else_sections
{ $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
;
participant_statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
;
note_statement
: 'note' placement actor text2
{

View File

@@ -14,45 +14,81 @@ import {
let prevActor = undefined;
let actors = {};
let boxes = [];
let messages = [];
const notes = [];
let sequenceNumbersEnabled = false;
let wrapEnabled;
let currentBox = undefined;
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export const addBox = function (data) {
boxes.push({
name: data.text,
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
fill: data.color,
actorKeys: [],
});
currentBox = boxes.slice(-1)[0];
};
export const addActor = function (id, name, description, type) {
// Don't allow description nulling
let assignedBox = currentBox;
const old = actors[id];
if (old && name === old.name && description == undefined) {
return;
if (old) {
// If already set and trying to set to a new one throw error
if (currentBox && old.box && currentBox !== old.box) {
throw new Error(
'A same participant should only be defined in one Box: ' +
old.name +
" can't be in '" +
old.box.name +
"' and in '" +
currentBox.name +
"' at the same time."
);
}
// Don't change the box if already
assignedBox = old.box ? old.box : currentBox;
old.box = assignedBox;
// Don't allow description nulling
if (old && name === old.name && description == null) {
return;
}
}
// Don't allow null descriptions, either
if (description == undefined || description.text == undefined) {
description = { text: name, wrap: undefined, type };
if (description == null || description.text == null) {
description = { text: name, wrap: null, type };
}
if (type == undefined || description.text == undefined) {
description = { text: name, wrap: undefined, type };
if (type == null || description.text == null) {
description = { text: name, wrap: null, type };
}
actors[id] = {
box: assignedBox,
name: name,
description: description.text,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
prevActor: prevActor,
links: {},
properties: {},
actorCnt: undefined,
rectData: undefined,
actorCnt: null,
rectData: null,
type: type || 'participant',
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
}
if (currentBox) {
currentBox.actorKeys.push(id);
}
prevActor = id;
};
@@ -111,10 +147,21 @@ export const addSignal = function (
return true;
};
export const hasAtLeastOneBox = function () {
return boxes.length > 0;
};
export const hasAtLeastOneBoxWithTitle = function () {
return boxes.some((b) => b.name);
};
export const getMessages = function () {
return messages;
};
export const getBoxes = function () {
return boxes;
};
export const getActors = function () {
return actors;
};
@@ -147,6 +194,7 @@ export const autoWrap = () => {
export const clear = function () {
actors = {};
boxes = [];
messages = [];
sequenceNumbersEnabled = false;
commonClear();
@@ -167,6 +215,47 @@ export const parseMessage = function (str) {
return message;
};
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
export const parseBoxData = function (str) {
const match = str.match(/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/);
let color = match != null && match[1] ? match[1].trim() : 'transparent';
let title = match != null && match[2] ? match[2].trim() : undefined;
// check that the string is a color
if (window && window.CSS) {
if (!window.CSS.supports('color', color)) {
color = 'transparent';
title = str.trim();
}
} else {
const style = new Option().style;
style.color = color;
if (style.color !== color) {
color = 'transparent';
title = str.trim();
}
}
const boxData = {
color: color,
text:
title !== undefined
? sanitizeText(title.replace(/^:?(?:no)?wrap:/, ''), configApi.getConfig())
: undefined,
wrap:
title !== undefined
? title.match(/^:?wrap:/) !== null
? true
: title.match(/^:?nowrap:/) !== null
? false
: undefined
: undefined,
};
return boxData;
};
export const LINETYPE = {
SOLID: 0,
DOTTED: 1,
@@ -274,10 +363,13 @@ export const addALink = function (actorId, text) {
* @param {any} links
*/
function insertLinks(actor, links) {
actor.links = {
...actor.links,
...links,
};
if (actor.links == null) {
actor.links = links;
} else {
for (let key in links) {
actor.links[key] = links[key];
}
}
}
export const addProperties = function (actorId, text) {
@@ -299,7 +391,7 @@ export const addProperties = function (actorId, text) {
* @param {any} properties
*/
function insertProperties(actor, properties) {
if (actor.properties == undefined) {
if (actor.properties == null) {
actor.properties = properties;
} else {
for (let key in properties) {
@@ -308,6 +400,13 @@ function insertProperties(actor, properties) {
}
}
/**
*
*/
function boxEnd() {
currentBox = undefined;
}
export const addDetails = function (actorId, text) {
// find the actor
const actor = getActor(actorId);
@@ -388,6 +487,12 @@ export const apply = function (param) {
case 'addMessage':
addSignal(param.from, param.to, param.msg, param.signalType);
break;
case 'boxStart':
addBox(param.boxData);
break;
case 'boxEnd':
boxEnd();
break;
case 'loopStart':
addSignal(undefined, undefined, param.loopText, param.signalType);
break;
@@ -464,12 +569,14 @@ export default {
getActorKeys,
getActorProperty,
getAccTitle,
getBoxes,
getDiagramTitle,
setDiagramTitle,
parseDirective,
getConfig: () => configApi.getConfig().sequence,
clear,
parseMessage,
parseBoxData,
LINETYPE,
ARROWTYPE,
PLACEMENT,
@@ -478,4 +585,6 @@ export default {
apply,
setAccDescription,
getAccDescription,
hasAtLeastOneBox,
hasAtLeastOneBoxWithTitle,
};

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