mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-23 02:06:45 +02:00
Compare commits
275 Commits
3192_invis
...
4220-strin
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c777f9193d | ||
![]() |
917a54f3cd | ||
![]() |
471c842a58 | ||
![]() |
99f65813a1 | ||
![]() |
7e610d13dc | ||
![]() |
f9c0f1d46f | ||
![]() |
3823ecafb1 | ||
![]() |
057c9e4b81 | ||
![]() |
c4e4efd4b8 | ||
![]() |
da066553bd | ||
![]() |
006da82470 | ||
![]() |
1945a62990 | ||
![]() |
d16894daf4 | ||
![]() |
4d933f6b72 | ||
![]() |
7739302ee8 | ||
![]() |
46ab6f46f2 | ||
![]() |
48d267c6dc | ||
![]() |
15af3ea585 | ||
![]() |
2dd6329872 | ||
![]() |
507a518a91 | ||
![]() |
4caf7d7c7b | ||
![]() |
89193d7360 | ||
![]() |
89eec225ce | ||
![]() |
cbc2df1ff6 | ||
![]() |
e4a2c74b1b | ||
![]() |
63160293c7 | ||
![]() |
9c0cb3f320 | ||
![]() |
fbeb016398 | ||
![]() |
093f1697e1 | ||
![]() |
f56e0bd530 | ||
![]() |
022e6670d0 | ||
![]() |
e3760d1709 | ||
![]() |
0475591fb6 | ||
![]() |
ba1c5dc6c7 | ||
![]() |
2f8c571a5c | ||
![]() |
a70b3a881d | ||
![]() |
950f560d81 | ||
![]() |
fd9680a050 | ||
![]() |
2d815e9626 | ||
![]() |
8c0550b2b7 | ||
![]() |
f054609e02 | ||
![]() |
45c0c5fee0 | ||
![]() |
a535fe1679 | ||
![]() |
e4d2118d4b | ||
![]() |
1184fce148 | ||
![]() |
a79f118323 | ||
![]() |
6cba2ea02d | ||
![]() |
3add711c55 | ||
![]() |
1481a8ccb1 | ||
![]() |
b3b7108d59 | ||
![]() |
fd9ad95346 | ||
![]() |
a1c50b8079 | ||
![]() |
8b37ceffe1 | ||
![]() |
7647ae317a | ||
![]() |
8c69ecd5ac | ||
![]() |
2dd906d809 | ||
![]() |
fb70091046 | ||
![]() |
ddd245de71 | ||
![]() |
853d9b7f98 | ||
![]() |
f3a9f81bfb | ||
![]() |
4b462d717c | ||
![]() |
273a9e7ad6 | ||
![]() |
f75bd397f8 | ||
![]() |
f70d52510a | ||
![]() |
f0c24d9ec5 | ||
![]() |
708633f639 | ||
![]() |
2e174bb3b6 | ||
![]() |
160fe0f971 | ||
![]() |
58d4ba0d8f | ||
![]() |
155e729722 | ||
![]() |
a17463307b | ||
![]() |
4e4f2fcfc5 | ||
![]() |
759ab0c0f9 | ||
![]() |
1a7b8d3897 | ||
![]() |
f5e7abb71f | ||
![]() |
1412bb4e94 | ||
![]() |
328f3968d1 | ||
![]() |
c965e4c456 | ||
![]() |
86aa7ab91e | ||
![]() |
141d38b4e7 | ||
![]() |
98af37f09f | ||
![]() |
a57e392ed4 | ||
![]() |
993a19e15b | ||
![]() |
7bea44e752 | ||
![]() |
f0a73696f5 | ||
![]() |
878c9f1d9d | ||
![]() |
8ebd550e0b | ||
![]() |
6b5221e465 | ||
![]() |
8f0cb695e7 | ||
![]() |
1913aad03f | ||
![]() |
b80da0daa1 | ||
![]() |
c2035c3709 | ||
![]() |
d77be9546d | ||
![]() |
e9d49e6b98 | ||
![]() |
6c2c28940b | ||
![]() |
72c94b6e6e | ||
![]() |
44d806e7f5 | ||
![]() |
727c56dbb1 | ||
![]() |
526e8fa1ad | ||
![]() |
969088187c | ||
![]() |
649e6820cc | ||
![]() |
8027a0c55d | ||
![]() |
d65d4fc39f | ||
![]() |
f7f6cc73f5 | ||
![]() |
55ebfadb32 | ||
![]() |
c5a5a22b72 | ||
![]() |
ad52d7d823 | ||
![]() |
b9576b4bbe | ||
![]() |
f57fed0eb4 | ||
![]() |
4e344df204 | ||
![]() |
43762c4d7f | ||
![]() |
4d1d1c36de | ||
![]() |
e603840395 | ||
![]() |
c7bcd74d56 | ||
![]() |
20298d243a | ||
![]() |
51c6462f1d | ||
![]() |
6f3077c856 | ||
![]() |
4a9d96aaba | ||
![]() |
4275aa613c | ||
![]() |
a65fb3b979 | ||
![]() |
c0dba713c5 | ||
![]() |
8810b378b3 | ||
![]() |
c3064f396c | ||
![]() |
82f5b4ca39 | ||
![]() |
73ce499863 | ||
![]() |
1ecf15669a | ||
![]() |
d24ddca03f | ||
![]() |
a7847038a5 | ||
![]() |
b6db75fe3e | ||
![]() |
2a838e645c | ||
![]() |
807e1f303d | ||
![]() |
65f5f9dc45 | ||
![]() |
b8b8c4740a | ||
![]() |
61bf7c577c | ||
![]() |
704506835f | ||
![]() |
f8f7d94d5a | ||
![]() |
114ab87816 | ||
![]() |
4a6056b558 | ||
![]() |
3a56af9633 | ||
![]() |
fda0c8d0a9 | ||
![]() |
b932cd0930 | ||
![]() |
2f06b41f5f | ||
![]() |
1ab3ed1a1a | ||
![]() |
1981f12976 | ||
![]() |
733967f65a | ||
![]() |
56d27d555b | ||
![]() |
7cee8cb6dc | ||
![]() |
c91fa192aa | ||
![]() |
3f93edaaf3 | ||
![]() |
0bed5d717b | ||
![]() |
1b56071eb3 | ||
![]() |
e050a5aaa1 | ||
![]() |
9b2f503dc7 | ||
![]() |
c7bdc6ad92 | ||
![]() |
a5db04b01c | ||
![]() |
06640aba06 | ||
![]() |
3bed70a0c5 | ||
![]() |
b079fb4710 | ||
![]() |
8b5cb75ef7 | ||
![]() |
a2855931d2 | ||
![]() |
50db9dcf8e | ||
![]() |
fa8a887ae1 | ||
![]() |
6e4e529af2 | ||
![]() |
275a54a562 | ||
![]() |
1bace23cea | ||
![]() |
f62c4831ad | ||
![]() |
7b4ce7c6ea | ||
![]() |
dda0d00fb9 | ||
![]() |
4bf5c9f3d8 | ||
![]() |
0409c5ac27 | ||
![]() |
19e5ccfdda | ||
![]() |
b13707fa7b | ||
![]() |
716a4d2cbc | ||
![]() |
3b2d55efec | ||
![]() |
870550bd7e | ||
![]() |
89f1ea49ba | ||
![]() |
7372d7d6c5 | ||
![]() |
0206ff540a | ||
![]() |
1e5d9ae1f4 | ||
![]() |
378e6b59e6 | ||
![]() |
8910ecb463 | ||
![]() |
ca97210d67 | ||
![]() |
f8abc9c6d5 | ||
![]() |
ef20e0b77a | ||
![]() |
f3b313ec1d | ||
![]() |
8f830a1698 | ||
![]() |
6a6b200a04 | ||
![]() |
15231924cd | ||
![]() |
7d4692f7b2 | ||
![]() |
285a7448ae | ||
![]() |
fd6ce89933 | ||
![]() |
c8e351c2bb | ||
![]() |
a59904cf16 | ||
![]() |
df36968ec8 | ||
![]() |
2ab1e15b86 | ||
![]() |
eca4163363 | ||
![]() |
1ac219282b | ||
![]() |
0df8c149f9 | ||
![]() |
bdf2667389 | ||
![]() |
b868777184 | ||
![]() |
fe2ef5e0c6 | ||
![]() |
ac21fe2d5c | ||
![]() |
6b251de227 | ||
![]() |
bb56492afe | ||
![]() |
2a9e846a49 | ||
![]() |
3b25cd3238 | ||
![]() |
4bc997cb8f | ||
![]() |
555d4f2cdc | ||
![]() |
75633ba125 | ||
![]() |
ec5fa31a11 | ||
![]() |
bfb8a75fca | ||
![]() |
067b6adc20 | ||
![]() |
a8162634cd | ||
![]() |
14c15b221a | ||
![]() |
8743e9e30e | ||
![]() |
22b18a4320 | ||
![]() |
786023ffa6 | ||
![]() |
6ded32880d | ||
![]() |
68cdc759a7 | ||
![]() |
dde8330888 | ||
![]() |
2272af38b9 | ||
![]() |
3e480612c7 | ||
![]() |
f66b524585 | ||
![]() |
c9c4320f89 | ||
![]() |
543e4de0c8 | ||
![]() |
8174c7ca16 | ||
![]() |
014ab85420 | ||
![]() |
e51817b735 | ||
![]() |
c7d9103ede | ||
![]() |
92cd5ed133 | ||
![]() |
6fb17bb405 | ||
![]() |
638362baea | ||
![]() |
e6b4e2c084 | ||
![]() |
03419c691c | ||
![]() |
941b959da3 | ||
![]() |
ae36586b58 | ||
![]() |
0c18c0309b | ||
![]() |
bb8bd111f8 | ||
![]() |
aad147c219 | ||
![]() |
10e6c92766 | ||
![]() |
e1710fddd9 | ||
![]() |
bcfefefbd4 | ||
![]() |
ef4fbd8bb3 | ||
![]() |
17e317385a | ||
![]() |
102900749e | ||
![]() |
46f2aebabc | ||
![]() |
b9c2f62b47 | ||
![]() |
f791cd2b24 | ||
![]() |
15cfa5d40d | ||
![]() |
bf53a03c9d | ||
![]() |
3b32f44a60 | ||
![]() |
a8cd5e675d | ||
![]() |
bc269a966d | ||
![]() |
d39606cb47 | ||
![]() |
b04517b146 | ||
![]() |
024ee4213f | ||
![]() |
e861fbb517 | ||
![]() |
2e028ce36d | ||
![]() |
7306b5ac45 | ||
![]() |
0854bab124 | ||
![]() |
fa51121f29 | ||
![]() |
0b4c6f6477 | ||
![]() |
e5768454f1 | ||
![]() |
89b5eb56f2 | ||
![]() |
616c969a03 | ||
![]() |
4fd826ac8c | ||
![]() |
6d5a6ad0c8 | ||
![]() |
e0cd76e6fd | ||
![]() |
f03364f328 | ||
![]() |
25bc381361 | ||
![]() |
38e5c3a81e | ||
![]() |
43aa831dd2 | ||
![]() |
4492c5ed4e | ||
![]() |
5dec9eb2f5 | ||
![]() |
02903be558 |
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -51,18 +51,10 @@ body:
|
||||
label: Setup
|
||||
description: |-
|
||||
Please fill out the below info.
|
||||
Note that you only need to fill out one and not both sections.
|
||||
Note that you only need to fill out the relevant section
|
||||
value: |-
|
||||
**Desktop**
|
||||
|
||||
- OS and Version: [Windows, Linux, Mac, ...]
|
||||
- Mermaid version:
|
||||
- Browser and Version: [Chrome, Edge, Firefox]
|
||||
|
||||
**Smartphone**
|
||||
|
||||
- Device: [Samsung, iPhone, ...]
|
||||
- OS and Version: [Android, iOS, ...]
|
||||
- Browser and Version: [Chrome, Safari, ...]
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Context
|
||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/mermaid-js/mermaid/discussions
|
||||
|
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
@@ -21,11 +21,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
# Need to skip setup if Cypress run is skipped, otherwise an error
|
||||
# is thrown since the pnpm cache step fails
|
||||
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
# Install NPM dependencies, cache them correctly
|
||||
|
12
.github/workflows/link-checker.yml
vendored
12
.github/workflows/link-checker.yml
vendored
@@ -14,6 +14,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '30 8 * * *'
|
||||
@@ -35,9 +36,16 @@ jobs:
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
uses: lycheeverse/lychee-action@v1.5.4
|
||||
uses: lycheeverse/lychee-action@v1.6.1
|
||||
with:
|
||||
args: --verbose --no-progress --cache --max-cache-age 1d packages/mermaid/src/docs/**/*.md README.md README.zh-CN.md
|
||||
args: >-
|
||||
--verbose
|
||||
--no-progress
|
||||
--cache
|
||||
--max-cache-age 1d
|
||||
packages/mermaid/src/docs/**/*.md
|
||||
README.md
|
||||
README.zh-CN.md
|
||||
fail: true
|
||||
jobSummary: true
|
||||
env:
|
||||
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -33,6 +33,14 @@ jobs:
|
||||
run: |
|
||||
pnpm run ci --coverage
|
||||
|
||||
- name: Run ganttDb tests using California timezone
|
||||
env:
|
||||
# Makes sure that gantt db works even in a timezone that has daylight savings
|
||||
# since some days have 25 hours instead of 24.
|
||||
TZ: America/Los_Angeles
|
||||
run: |
|
||||
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
|
||||
|
||||
- name: Upload Coverage to Coveralls
|
||||
# it feels a bit weird to use @master, but that's what the docs use
|
||||
# (coveralls also doesn't publish a @v1 we can use)
|
||||
|
@@ -4,12 +4,7 @@
|
||||
# Network error: Forbidden
|
||||
https://codepen.io
|
||||
|
||||
# Network error: The certificate was not trusted
|
||||
https://mkdocs.org/
|
||||
https://osawards.com/javascript/#nominees
|
||||
https://osawards.com/javascript/2019
|
||||
|
||||
# Timeout error, maybe Twitter has anti-bot defences against GitHub's CI servers?
|
||||
# Timeout error, maybe Twitter has anti-bot defenses against GitHub's CI servers?
|
||||
https://twitter.com/mermaidjs_
|
||||
|
||||
# Don't check files that are generated during the build via `pnpm docs:code`
|
||||
|
1
.npmrc
1
.npmrc
@@ -1,3 +1,2 @@
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
use-inline-specifiers-lockfile-format=true
|
||||
|
@@ -8,8 +8,9 @@ async function createServer() {
|
||||
// Create Vite server in middleware mode
|
||||
const vite = await createViteServer({
|
||||
configFile: './vite.config.ts',
|
||||
mode: 'production',
|
||||
server: { middlewareMode: true },
|
||||
appType: 'custom', // don't include Vite's default HTML handling middlewares
|
||||
appType: 'custom', // don't include Vite's default HTML handling middleware
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
|
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,6 +1,105 @@
|
||||
# Change Log
|
||||
# Changelog
|
||||
|
||||
// TODO: Populate changelog
|
||||
## [10.0.0](https://github.com/mermaid-js/mermaid/releases/tag/v10.0.0)
|
||||
|
||||
### Mermaid is ESM only!
|
||||
|
||||
We've dropped CJS support. So, you will have to update your import scripts as follows.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
```
|
||||
|
||||
You can keep using v9 by adding the `@9` in the CDN URL.
|
||||
|
||||
```diff
|
||||
- <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.js"></script>
|
||||
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.js"></script>
|
||||
```
|
||||
|
||||
### mermaid.render is async and doesn't accept callbacks
|
||||
|
||||
```js
|
||||
// < v10
|
||||
mermaid.render('id', 'graph TD;\nA-->B', (svg, bindFunctions) => {
|
||||
element.innerHTML = svg;
|
||||
if (bindFunctions) {
|
||||
bindFunctions(element);
|
||||
}
|
||||
});
|
||||
|
||||
// Shorter syntax
|
||||
if (bindFunctions) {
|
||||
bindFunctions(element);
|
||||
}
|
||||
// can be replaced with the `?.` shorthand
|
||||
bindFunctions?.(element);
|
||||
|
||||
// >= v10 with async/await
|
||||
const { svg, bindFunctions } = await mermaid.render('id', 'graph TD;\nA-->B');
|
||||
element.innerHTML = svg;
|
||||
bindFunctions?.(element);
|
||||
|
||||
// >= v10 with promise.then
|
||||
mermaid.render('id', 'graph TD;A-->B').then(({ svg, bindFunctions }) => {
|
||||
element.innerHTML = svg;
|
||||
bindFunctions?.(element);
|
||||
});
|
||||
```
|
||||
|
||||
### mermaid.parse is async and ParseError is removed
|
||||
|
||||
```js
|
||||
// < v10
|
||||
mermaid.parse(text, parseError);
|
||||
|
||||
//>= v10
|
||||
await mermaid.parse(text).catch(parseError);
|
||||
// or
|
||||
try {
|
||||
await mermaid.parse(text);
|
||||
} catch (err) {
|
||||
parseError(err);
|
||||
}
|
||||
```
|
||||
|
||||
### Init deprecated and InitThrowsErrors removed
|
||||
|
||||
The config passed to `init` was not being used eariler.
|
||||
It will now be used.
|
||||
The `init` function is deprecated and will be removed in the next major release.
|
||||
init currently works as a wrapper to `initialize` and `run`.
|
||||
|
||||
```js
|
||||
// < v10
|
||||
mermaid.init(config, selector, cb);
|
||||
|
||||
//>= v10
|
||||
mermaid.initialize(config);
|
||||
mermaid.run({
|
||||
querySelector: selector,
|
||||
postRenderCallback: cb,
|
||||
suppressErrors: true,
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// < v10
|
||||
mermaid.initThrowsErrors(config, selector, cb);
|
||||
|
||||
//>= v10
|
||||
mermaid.initialize(config);
|
||||
mermaid.run({
|
||||
querySelector: selector,
|
||||
postRenderCallback: cb,
|
||||
suppressErrors: false,
|
||||
});
|
||||
```
|
||||
|
||||
// TODO: Populate changelog pre v10
|
||||
|
||||
- Config has a lot of changes
|
||||
- globalReset resets to `defaultConfig` instead of current config. Use `reset` instead.
|
||||
|
@@ -55,6 +55,8 @@ The documentation is written in **Markdown**. For more information about Markdow
|
||||
The source files for the project documentation are located in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory. This is where you should make changes.
|
||||
The files under `/packages/mermaid/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory.
|
||||
|
||||
After editing files in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory, be sure to run `pnpm install` and `pnpm run --filter mermaid docs:build` locally to build the `/docs` directory.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
classDef default fill:#fff,color:black,stroke:black
|
||||
|
@@ -8,7 +8,7 @@ Mermaid
|
||||
Generate diagrams from markdown-like text.
|
||||
<p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/vitest"><img src="https://img.shields.io/npm/v/mermaid?color=ff3670&label="></a>
|
||||
<a href="https://www.npmjs.com/package/mermaid"><img src="https://img.shields.io/npm/v/mermaid?color=ff3670&label="></a>
|
||||
<p>
|
||||
|
||||
<p align="center">
|
||||
|
@@ -8,7 +8,7 @@ Mermaid
|
||||
通过解析类 Markdown 的文本语法来实现图表的创建和动态修改。
|
||||
<p>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/vitest"><img src="https://img.shields.io/npm/v/mermaid?color=ff3670&label="></a>
|
||||
<a href="https://www.npmjs.com/package/mermaid"><img src="https://img.shields.io/npm/v/mermaid?color=ff3670&label="></a>
|
||||
<p>
|
||||
|
||||
<p align="center">
|
||||
|
@@ -1,58 +0,0 @@
|
||||
# A collection of updates that change the behavior
|
||||
|
||||
## Async
|
||||
|
||||
`parse`, `render` are now async.
|
||||
|
||||
## Lazy loading and asynchronisity
|
||||
|
||||
- Invalid dates are rendered as syntax error instead of returning best guess or the current date
|
||||
|
||||
## ParseError is removed
|
||||
|
||||
```js
|
||||
//< v10.0.0
|
||||
mermaid.parse(text, parseError);
|
||||
|
||||
//>= v10.0.0
|
||||
await mermaid.parse(text).catch(parseError);
|
||||
// or
|
||||
try {
|
||||
await mermaid.parse(text);
|
||||
} catch (err) {
|
||||
parseError(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Init deprecated and InitThrowsErrors removed
|
||||
|
||||
The config passed to `init` was not being used eariler.
|
||||
It will now be used.
|
||||
The `init` function is deprecated and will be removed in the next major release.
|
||||
init currently works as a wrapper to `initialize` and `run`.
|
||||
|
||||
```js
|
||||
//< v10.0.0
|
||||
mermaid.init(config, selector, cb);
|
||||
|
||||
//>= v10.0.0
|
||||
mermaid.initialize(config);
|
||||
mermaid.run({
|
||||
querySelector: selector,
|
||||
postRenderCallback: cb,
|
||||
suppressErrors: true,
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
//< v10.0.0
|
||||
mermaid.initThrowsErrors(config, selector, cb);
|
||||
|
||||
//>= v10.0.0
|
||||
mermaid.initialize(config);
|
||||
mermaid.run({
|
||||
querySelector: selector,
|
||||
postRenderCallback: cb,
|
||||
suppressErrors: false,
|
||||
});
|
||||
```
|
@@ -19,6 +19,7 @@
|
||||
"brkt",
|
||||
"brolin",
|
||||
"brotli",
|
||||
"città",
|
||||
"classdef",
|
||||
"codedoc",
|
||||
"colour",
|
||||
@@ -27,6 +28,7 @@
|
||||
"cuzon",
|
||||
"cytoscape",
|
||||
"dagre",
|
||||
"deepdwn",
|
||||
"descr",
|
||||
"docsify",
|
||||
"docsy",
|
||||
|
@@ -22,7 +22,7 @@ export const mermaidUrl = (graphStr, options, api) => {
|
||||
return url;
|
||||
};
|
||||
|
||||
export const imgSnapshotTest = (graphStr, _options, api = false, validation) => {
|
||||
export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation = undefined) => {
|
||||
cy.log(_options);
|
||||
const options = Object.assign(_options);
|
||||
if (!options.fontFamily) {
|
||||
|
@@ -13,7 +13,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('1: should render a simple class diagram', () => {
|
||||
@@ -47,7 +46,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('2: should render a simple class diagrams with cardinality', () => {
|
||||
@@ -76,7 +74,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple class diagram with different visibilities', () => {
|
||||
@@ -94,7 +91,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render multiple class diagrams', () => {
|
||||
@@ -147,7 +143,6 @@ describe('Class diagram V2', () => {
|
||||
],
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('4: should render a simple class diagram with comments', () => {
|
||||
@@ -177,7 +172,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('5: should render a simple class diagram with abstract method', () => {
|
||||
@@ -189,7 +183,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('6: should render a simple class diagram with static method', () => {
|
||||
@@ -201,7 +194,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('7: should render a simple class diagram with Generic class', () => {
|
||||
@@ -221,7 +213,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('8: should render a simple class diagram with Generic class and relations', () => {
|
||||
@@ -242,7 +233,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('9: should render a simple class diagram with clickable link', () => {
|
||||
@@ -264,7 +254,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('10: should render a simple class diagram with clickable callback', () => {
|
||||
@@ -286,7 +275,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('11: should render a simple class diagram with return type on method', () => {
|
||||
@@ -301,7 +289,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('12: should render a simple class diagram with generic types', () => {
|
||||
@@ -317,7 +304,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('13: should render a simple class diagram with css classes applied', () => {
|
||||
@@ -335,7 +321,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('14: should render a simple class diagram with css classes applied directly', () => {
|
||||
@@ -351,7 +336,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
|
||||
@@ -365,7 +349,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('16a: should render a simple class diagram with static field', () => {
|
||||
@@ -378,7 +361,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('16b: should handle the direction statement with TB', () => {
|
||||
@@ -403,7 +385,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('18: should handle the direction statement with LR', () => {
|
||||
@@ -428,7 +409,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('17a: should handle the direction statement with BT', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -452,7 +432,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('17b: should handle the direction statement with RL', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -476,7 +455,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('18: should render a simple class diagram with notes', () => {
|
||||
@@ -493,7 +471,6 @@ describe('Class diagram V2', () => {
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('1433: should render a simple class with a title', () => {
|
||||
@@ -503,8 +480,72 @@ title: simple class diagram
|
||||
---
|
||||
classDiagram-v2
|
||||
class Class10
|
||||
`,
|
||||
{}
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a class with text label', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
C1 --> C2`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render two classes with text labels', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
class C2["Class 2 with chars @?"]
|
||||
C1 --> C2`
|
||||
);
|
||||
});
|
||||
it('should render a class with a text label, members and annotation', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
<<interface>>
|
||||
+member1
|
||||
}
|
||||
C1 --> C2`
|
||||
);
|
||||
});
|
||||
it('should render multiple classes with same text labels', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
class C1["Class with text label"]
|
||||
class C2["Class with text label"]
|
||||
class C3["Class with text label"]
|
||||
C1 --> C2
|
||||
C3 ..> C2
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should render classes with different text labels', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
class C1["OneWord"]
|
||||
class C2["With, Comma"]
|
||||
class C3["With (Brackets)"]
|
||||
class C4["With [Brackets]"]
|
||||
class C5["With {Brackets}"]
|
||||
class C7["With 1 number"]
|
||||
class C8["With . period..."]
|
||||
class C9["With - dash"]
|
||||
class C10["With _ underscore"]
|
||||
class C11["With ' single quote"]
|
||||
class C12["With ~!@#$%^&*()_+=-/?"]
|
||||
class C13["With Città foreign language"]
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render classLabel if class has already been defined earlier', () => {
|
||||
imgSnapshotTest(
|
||||
`classDiagram
|
||||
Animal <|-- Duck
|
||||
class Duck["Duck with text label"]
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -10,7 +10,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagram with a recursive relationship', () => {
|
||||
@@ -23,7 +22,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagram with multiple relationships between the same two entities', () => {
|
||||
@@ -35,7 +33,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a cyclical ER diagram', () => {
|
||||
@@ -48,7 +45,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a not-so-simple ER diagram', () => {
|
||||
@@ -66,7 +62,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render multiple ER diagrams', () => {
|
||||
@@ -85,7 +80,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
],
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagram with blank or empty labels', () => {
|
||||
@@ -98,7 +92,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render an ER diagrams when useMaxWidth is true (default)', () => {
|
||||
@@ -151,7 +144,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ er: { useMaxWidth: false } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with and without attributes', () => {
|
||||
@@ -164,7 +156,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with generic and array attributes', () => {
|
||||
@@ -179,7 +170,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with length in attributes type', () => {
|
||||
@@ -188,12 +178,11 @@ describe('Entity Relationship Diagram', () => {
|
||||
erDiagram
|
||||
CLUSTER {
|
||||
varchar(99) name
|
||||
string(255) description
|
||||
string(255) description
|
||||
}
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities and attributes with big and small entity names', () => {
|
||||
@@ -209,7 +198,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with keys', () => {
|
||||
@@ -228,7 +216,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with comments', () => {
|
||||
@@ -247,7 +234,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with keys and comments', () => {
|
||||
@@ -267,7 +253,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with aliases', () => {
|
||||
@@ -285,7 +270,6 @@ describe('Entity Relationship Diagram', () => {
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('1433: should render a simple ER diagram with a title', () => {
|
||||
|
45
cypress/integration/rendering/errorDiagram.spec.js
Normal file
45
cypress/integration/rendering/errorDiagram.spec.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Error Diagrams', () => {
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', (err) => {
|
||||
expect(err.message).to.include('Parse error');
|
||||
// return false to prevent the error from
|
||||
// failing this test
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a simple ER diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
error
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render error diagram for actual errors', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
flowchart TD
|
||||
A[Christmas] --|Get money| B(Go shopping)
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render error for wrong ER diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
|
||||
MONGODB-CLUSTERS ||..|{
|
||||
ATLAS-TEAMS ||..|{
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
});
|
@@ -684,4 +684,149 @@ A --> B
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
describe('Markdown strings flowchart-elk (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart-elk LR
|
||||
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
|
||||
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
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart-elk LR
|
||||
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
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart-elk LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart-elk LR
|
||||
b(\`The dog in **the** hog.(1).. a a a a *very long text* about it
|
||||
Word!
|
||||
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`) --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart-elk LR
|
||||
subgraph "One"
|
||||
a("\`The **cat**
|
||||
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
|
||||
end
|
||||
subgraph "\`**Two**\`"
|
||||
c("\`The **cat**
|
||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('svg text labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
|
||||
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
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
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
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
|
||||
Word!
|
||||
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
subgraph "One"
|
||||
a("\`The **cat**
|
||||
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
|
||||
end
|
||||
subgraph "\`**Two**\`"
|
||||
c("\`The **cat**
|
||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -674,10 +674,10 @@ A --> B
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('3192: It should be possieble to render flowcharts with invisisble edges', () => {
|
||||
it('3192: It should be possieble to render flowcharts with invisible edges', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
title: Simple flowchart with invisisble edges
|
||||
title: Simple flowchart with invisible edges
|
||||
---
|
||||
flowchart TD
|
||||
A ~~~ B
|
||||
@@ -685,4 +685,149 @@ A ~~~ B
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
describe('Markdown strings flowchart (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
|
||||
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
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
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
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
|
||||
Word!
|
||||
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
subgraph "One"
|
||||
a("\`The **cat**
|
||||
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
|
||||
end
|
||||
subgraph "\`**Two**\`"
|
||||
c("\`The **cat**
|
||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('svg text labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
|
||||
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
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
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
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
|
||||
Word!
|
||||
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
subgraph "One"
|
||||
a("\`The **cat**
|
||||
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
|
||||
end
|
||||
subgraph "\`**Two**\`"
|
||||
c("\`The **cat**
|
||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -133,6 +133,24 @@ describe('Gantt diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should default to showing today marker', () => {
|
||||
// This test only works if the environment thinks today is 1010-10-10
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title Show today marker (vertical line should be visible)
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %d
|
||||
%% Should default to being on
|
||||
%% todayMarker on
|
||||
section Section1
|
||||
Yesterday: 1010-10-09, 1d
|
||||
Today: 1010-10-10, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should hide today marker', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -142,7 +160,8 @@ describe('Gantt diagram', () => {
|
||||
axisFormat %d
|
||||
todayMarker off
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
Yesterday: 1010-10-09, 1d
|
||||
Today: 1010-10-10, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
@@ -157,7 +176,8 @@ describe('Gantt diagram', () => {
|
||||
axisFormat %d
|
||||
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||
section Section1
|
||||
Today: 1, -1h
|
||||
Yesterday: 1010-10-09, 1d
|
||||
Today: 1010-10-10, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
@@ -435,4 +455,39 @@ describe('Gantt diagram', () => {
|
||||
{ gantt: { topAxis: true } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when compact is true', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title GANTT compact
|
||||
dateFormat HH:mm:ss
|
||||
axisFormat %Hh%M
|
||||
|
||||
section DB Clean
|
||||
Clean: 12:00:00, 10m
|
||||
Clean: 12:30:00, 12m
|
||||
Clean: 13:00:00, 8m
|
||||
Clean: 13:30:00, 9m
|
||||
Clean: 14:00:00, 13m
|
||||
Clean: 14:30:00, 10m
|
||||
Clean: 15:00:00, 11m
|
||||
|
||||
section Sessions
|
||||
A: 12:00:00, 63m
|
||||
B: 12:30:00, 12m
|
||||
C: 13:05:00, 12m
|
||||
D: 13:06:00, 33m
|
||||
E: 13:15:00, 55m
|
||||
F: 13:20:00, 12m
|
||||
G: 13:32:00, 18m
|
||||
H: 13:50:00, 20m
|
||||
I: 14:10:00, 10m
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -223,5 +223,18 @@ mindmap
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
describe('Markdown strings mindmaps (#4220)', () => {
|
||||
it('Formatted label with linebreak and a wrapping label and emojis', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
id1[\`**Start** with
|
||||
a second line 😎\`]
|
||||
id2[\`The dog in **the** hog... a *very long text* about it
|
||||
Word!\`]
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
});
|
||||
/* The end */
|
||||
});
|
||||
|
@@ -75,4 +75,15 @@ describe('Pie Chart', () => {
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
it('should render a pie diagram when textPosition is set', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
pie
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`,
|
||||
{ logLevel: 1, pie: { textPosition: 0.9 } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
|
@@ -116,7 +116,11 @@ context('Sequence diagram', () => {
|
||||
loop Loopy
|
||||
Bob->>Alice: Pasten
|
||||
end `,
|
||||
{ wrap: true }
|
||||
{
|
||||
sequence: {
|
||||
wrap: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
|
@@ -12,7 +12,6 @@
|
||||
<style>
|
||||
body {
|
||||
background: rgb(221, 208, 208);
|
||||
/*background:#333;*/
|
||||
font-family: 'Arial';
|
||||
}
|
||||
h1 {
|
||||
@@ -120,17 +119,9 @@ classE o-- classF : aggregation
|
||||
};
|
||||
mermaid.initialize({
|
||||
theme: 'default',
|
||||
// arrowMarkerAbsolute: true,
|
||||
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
|
||||
logLevel: 0,
|
||||
flowchart: { curve: 'linear', htmlLabels: true },
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
// fontFamily: '"arial", sans-serif',
|
||||
// themeVariables: {
|
||||
// fontFamily: '"arial", sans-serif',
|
||||
// },
|
||||
curve: 'linear',
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
|
46
cypress/platform/flow2.html
Normal file
46
cypress/platform/flow2.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
body {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
graph TB
|
||||
subgraph One
|
||||
a1-->a2-->a3
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
graph TB
|
||||
a_a --> b_b:::apa --> c_c:::apa
|
||||
classDef apa fill:#f9f,stroke:#333,stroke-width:4px;
|
||||
class a_a apa;
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
graph TB
|
||||
a_a(Aftonbladet) --> b_b[gorilla]:::apa --> c_c{chimp}:::apa -->a_a
|
||||
a_a --> c --> d_d --> c_c
|
||||
classDef apa fill:#f9f,stroke:#333,stroke-width:4px;
|
||||
class a_a apa;
|
||||
click a_a "http://www.aftonbladet.se" "apa"
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '../../packages/mermaid/src/mermaid';
|
||||
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
logLevel: 3,
|
||||
flowchart: { curve: 'linear' },
|
||||
gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50 },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -51,27 +51,48 @@
|
||||
font-family: monospace;
|
||||
font-size: 72px;
|
||||
}
|
||||
/* tspan {
|
||||
font-size: 6px !important;
|
||||
} */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
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>
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart-elk LR
|
||||
b(`The dog in **the** hog(2)... a a a a *very long text* about it
|
||||
Word!
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `)
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
flowchart-elk TB
|
||||
a --> b
|
||||
a --> c
|
||||
b --> d
|
||||
c --> d
|
||||
flowchart-elk LR
|
||||
b("The dog in the hog... a very<br/>long text about it<br/>Word!")
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
flowchart-elk LR
|
||||
b("The dog in the hog... a very<br/>long text about it<br/>Word!")
|
||||
</pre>
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
mindmap
|
||||
id1[`**Start2**
|
||||
second line 😎 with long text that is wrapping to the next line`]
|
||||
id2[`Child **with bold** text`]
|
||||
id3[`Children of which some
|
||||
is using *italic type of* text`]
|
||||
id4[Child]
|
||||
id5[`Child
|
||||
Row
|
||||
and another
|
||||
`]
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
mindmap
|
||||
id1["`**Start** with
|
||||
a second line 😎`"]
|
||||
id2["`The dog in **the** hog... a *very long text* about it
|
||||
Word!`"]
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
@@ -272,12 +293,14 @@ mindmap
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
startOnLoad: true,
|
||||
logLevel: 5,
|
||||
logLevel: 0,
|
||||
flowchart: {
|
||||
// defaultRenderer: 'elk',
|
||||
useMaxWidth: false,
|
||||
htmlLabels: true,
|
||||
htmlLabels: false,
|
||||
// htmlLabels: true,
|
||||
},
|
||||
// htmlLabels: true,
|
||||
gantt: {
|
||||
useMaxWidth: false,
|
||||
},
|
||||
|
@@ -1,246 +1,17 @@
|
||||
<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 id="diagram" class="mermaid">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
graph TB
|
||||
a --> b
|
||||
a --> c
|
||||
b --> d
|
||||
c --> d
|
||||
</pre>
|
||||
<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 />
|
||||
|
||||
<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="d2"></div>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '../../packages/mermaid/src/mermaid';
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
mermaid.parseError = function (err, hash) {
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
@@ -265,6 +36,11 @@ sequenceDiagram
|
||||
console.error('In parse error:');
|
||||
console.error(err);
|
||||
};
|
||||
const value = `graph TD\nHello --> World`;
|
||||
const el = document.getElementById('d2');
|
||||
const { svg } = await mermaid.render('d22', value);
|
||||
console.log(svg);
|
||||
el.innerHTML = svg;
|
||||
// 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));
|
||||
|
@@ -46,8 +46,7 @@ const contentLoaded = async function () {
|
||||
|
||||
await mermaid2.registerExternalDiagrams([externalExample]);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
await mermaid2.init();
|
||||
markRendered();
|
||||
await mermaid2.run();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,7 +122,6 @@ const contentLoadedApi = async function () {
|
||||
bindFunctions(div);
|
||||
}
|
||||
}
|
||||
markRendered();
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
@@ -135,10 +133,10 @@ if (typeof document !== 'undefined') {
|
||||
function () {
|
||||
if (this.location.href.match('xss.html')) {
|
||||
this.console.log('Using api');
|
||||
void contentLoadedApi();
|
||||
void contentLoadedApi().finally(markRendered);
|
||||
} else {
|
||||
this.console.log('Not using api');
|
||||
void contentLoaded();
|
||||
void contentLoaded().finally(markRendered);
|
||||
}
|
||||
},
|
||||
false
|
||||
|
38
demos/error.html
Normal file
38
demos/error.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Error | Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
|
||||
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
|
||||
MONGODB-CLUSTERS ||..|{
|
||||
ATLAS-TEAMS ||..|{
|
||||
</pre>
|
||||
<hr />
|
||||
<pre class="mermaid">
|
||||
flowchart TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
flowchart TD
|
||||
A[Christmas] --|Get money| B(Go shopping)
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -78,7 +78,7 @@
|
||||
axisFormat %d/%m
|
||||
todayMarker off
|
||||
section Section1
|
||||
Today: 1, -01:00, 5min
|
||||
Today: 1, 08-08-09-01:00, 5min
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
axisFormat %d/%m
|
||||
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||
section Section1
|
||||
Today: 1, -01:00, 5min
|
||||
Today: 1, 08-08-09-01:00, 5min
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
@@ -166,6 +166,37 @@
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title GANTT compact
|
||||
dateFormat HH:mm:ss
|
||||
axisFormat %Hh%M
|
||||
|
||||
section DB Clean
|
||||
Clean: 12:00:00, 10m
|
||||
Clean: 12:30:00, 12m
|
||||
Clean: 13:00:00, 8m
|
||||
Clean: 13:30:00, 9m
|
||||
Clean: 14:00:00, 13m
|
||||
Clean: 14:30:00, 10m
|
||||
Clean: 15:00:00, 11m
|
||||
|
||||
section Sessions
|
||||
A: 12:00:00, 63m
|
||||
B: 12:30:00, 12m
|
||||
C: 13:05:00, 12m
|
||||
D: 13:06:00, 33m
|
||||
E: 13:15:00, 55m
|
||||
F: 13:20:00, 12m
|
||||
G: 13:32:00, 18m
|
||||
H: 13:50:00, 20m
|
||||
I: 14:10:00, 10m
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<script>
|
||||
function ganttTestClick(a, b, c) {
|
||||
console.log('a:', a);
|
||||
|
@@ -17,7 +17,7 @@
|
||||
<h1>Journey diagram demo</h1>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
title: My working day
|
||||
title: My working day
|
||||
---
|
||||
journey
|
||||
accTitle: Very simple journey demo
|
||||
|
@@ -40,7 +40,6 @@
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import mermaidMindmap from './mermaid-mindmap.esm.mjs';
|
||||
|
||||
const ALLOWED_TAGS = [
|
||||
'a',
|
||||
@@ -82,7 +81,6 @@
|
||||
mermaid.parseError = function (err, hash) {
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
await mermaid.registerExternalDiagrams([mermaidMindmap]);
|
||||
mermaid.initialize({
|
||||
theme: 'base',
|
||||
startOnLoad: true,
|
||||
|
@@ -26,6 +26,7 @@
|
||||
|
||||
<hr />
|
||||
<pre class="mermaid">
|
||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
||||
pie
|
||||
title Key elements in Product X
|
||||
accTitle: Key elements in Product X
|
||||
@@ -37,7 +38,7 @@
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '../packages/mermaid';
|
||||
import mermaid from '../packages/mermaid/src/mermaid';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
# Tutorials
|
||||
|
||||
This is list of publicly available Tutorials for using Mermaid.JS . This is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
|
||||
This is a list of publicly available Tutorials for using Mermaid.JS and is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
|
||||
|
||||
**Note that these tutorials might display an older interface, but the usage of the live-editor will largely be the same.**
|
||||
|
||||
|
@@ -16,4 +16,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70)
|
||||
[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
|
||||
|
@@ -8,41 +8,6 @@
|
||||
|
||||
[mermaidAPI](../modules/mermaidAPI.md).RenderResult
|
||||
|
||||
Function that renders an svg with a graph from a chart definition. Usage example below.
|
||||
|
||||
```javascript
|
||||
mermaidAPI.initialize({
|
||||
startOnLoad: true,
|
||||
});
|
||||
$(function () {
|
||||
const graphDefinition = 'graph TB\na-->b';
|
||||
const cb = function (svgGraph) {
|
||||
console.log(svgGraph);
|
||||
};
|
||||
mermaidAPI.render('id1', graphDefinition, cb);
|
||||
});
|
||||
```
|
||||
|
||||
**`Param`**
|
||||
|
||||
The id for the SVG element (the element to be rendered)
|
||||
|
||||
**`Param`**
|
||||
|
||||
The text for the graph definition
|
||||
|
||||
**`Param`**
|
||||
|
||||
Callback which is called after rendering is finished with the svg code as in param.
|
||||
|
||||
**`Param`**
|
||||
|
||||
HTML element where the svg will be inserted. (Is usually element with the .mermaid class)
|
||||
If no svgContainingElement is provided then the SVG element will be appended to the body.
|
||||
Selector to element in which a div with the graph temporarily will be
|
||||
inserted. If one is provided a hidden div will be inserted in the body of the page instead. The
|
||||
element will be removed when rendering is completed.
|
||||
|
||||
## Properties
|
||||
|
||||
### bindFunctions
|
||||
@@ -53,6 +18,15 @@ element will be removed when rendering is completed.
|
||||
|
||||
▸ (`element`): `void`
|
||||
|
||||
Bind function to be called after the svg has been inserted into the DOM.
|
||||
This is necessary for adding event listeners to the elements in the svg.
|
||||
|
||||
```js
|
||||
const { svg, bindFunctions } = mermaidAPI.render('id1', 'graph TD;A-->B');
|
||||
div.innerHTML = svg;
|
||||
bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
```
|
||||
|
||||
##### Parameters
|
||||
|
||||
| Name | Type |
|
||||
@@ -65,7 +39,7 @@ element will be removed when rendering is completed.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:384](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L384)
|
||||
[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
|
||||
|
||||
---
|
||||
|
||||
@@ -73,6 +47,8 @@ element will be removed when rendering is completed.
|
||||
|
||||
• **svg**: `string`
|
||||
|
||||
The svg code for the rendered graph.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:383](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L383)
|
||||
[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:2084](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2084)
|
||||
[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115)
|
||||
|
||||
---
|
||||
|
||||
|
@@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75)
|
||||
[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
|
||||
|
||||
## Variables
|
||||
|
||||
### mermaidAPI
|
||||
|
||||
• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean` | `void`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
|
||||
• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
|
||||
|
||||
## mermaidAPI configuration defaults
|
||||
|
||||
@@ -88,6 +88,7 @@ const config = {
|
||||
numberSectionStyles: 4,
|
||||
axisFormat: '%Y-%m-%d',
|
||||
topAxis: false,
|
||||
displayMode: '',
|
||||
},
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
@@ -95,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668)
|
||||
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -126,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
|
||||
[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312)
|
||||
|
||||
---
|
||||
|
||||
@@ -152,7 +153,7 @@ the cleaned up svgCode
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:242](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L242)
|
||||
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
|
||||
|
||||
---
|
||||
|
||||
@@ -178,7 +179,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L171)
|
||||
[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192)
|
||||
|
||||
---
|
||||
|
||||
@@ -201,7 +202,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:219](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L219)
|
||||
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
|
||||
|
||||
---
|
||||
|
||||
@@ -228,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
|
||||
[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176)
|
||||
|
||||
---
|
||||
|
||||
@@ -248,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:135](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L135)
|
||||
[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156)
|
||||
|
||||
---
|
||||
|
||||
@@ -268,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L106)
|
||||
[mermaidAPI.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L127)
|
||||
|
||||
---
|
||||
|
||||
@@ -294,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L270)
|
||||
[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
|
||||
|
||||
---
|
||||
|
||||
@@ -319,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:341](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L341)
|
||||
[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362)
|
||||
|
@@ -261,6 +261,34 @@ The theming engine will only recognize hex colors and not color names. So, the v
|
||||
| activationBkgColor | secondaryColor | Activation Background Color |
|
||||
| sequenceNumberColor | calculated from lineColor | Sequence Number Color |
|
||||
|
||||
## Pie Diagram Variables
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| ------------------- | ------------------------------ | ------------------------------------------ |
|
||||
| pie1 | primaryColor | Fill for 1st section in pie diagram |
|
||||
| pie2 | secondaryColor | Fill for 2nd section in pie diagram |
|
||||
| pie3 | calculated from tertiary | Fill for 3rd section in pie diagram |
|
||||
| pie4 | calculated from primaryColor | Fill for 4th section in pie diagram |
|
||||
| pie5 | calculated from secondaryColor | Fill for 5th section in pie diagram |
|
||||
| pie6 | calculated from tertiaryColor | Fill for 6th section in pie diagram |
|
||||
| pie7 | calculated from primaryColor | Fill for 7th section in pie diagram |
|
||||
| pie8 | calculated from primaryColor | Fill for 8th section in pie diagram |
|
||||
| pie9 | calculated from primaryColor | Fill for 9th section in pie diagram |
|
||||
| pie10 | calculated from primaryColor | Fill for 10th section in pie diagram |
|
||||
| pie11 | calculated from primaryColor | Fill for 11th section in pie diagram |
|
||||
| pie12 | calculated from primaryColor | Fill for 12th section in pie diagram |
|
||||
| pieTitleTextSize | 25px | Title text size |
|
||||
| pieTitleTextColor | taskTextDarkColor | Title text color |
|
||||
| pieSectionTextSize | 17px | Text size of individual section labels |
|
||||
| pieSectionTextColor | textColor | Text color of individual section labels |
|
||||
| pieLegendTextSize | 17px | Text size of labels in diagram legend |
|
||||
| pieLegendTextColor | taskTextDarkColor | Text color of labels in diagram legend |
|
||||
| pieStrokeColor | black | Border color of individual pie sections |
|
||||
| pieStrokeWidth | 2px | Border width of individual pie sections |
|
||||
| pieOuterStrokeWidth | 2px | Border width of pie diagram's outer circle |
|
||||
| pieOuterStrokeColor | black | Border color of pie diagram's outer circle |
|
||||
| pieOpacity | 0.7 | Opacity of individual pie sections |
|
||||
|
||||
## State Colors
|
||||
|
||||
| Variable | Default value | Description |
|
||||
|
@@ -20,21 +20,24 @@ Please note that you can switch versions through the dropdown box at the top rig
|
||||
|
||||
For the majority of users, Using the [Live Editor](https://mermaid.live/) would be sufficient, however you may also opt to deploy mermaid as a dependency or using the [Mermaid API](./setup/README.md).
|
||||
|
||||
We have compiled some Video [Tutorials](./Tutorials.md) on how to use the mermaid Live Editor.
|
||||
We have compiled some Video [Tutorials](./Tutorials.md) on how to use the Mermaid Live Editor.
|
||||
|
||||
### Installing and Hosting Mermaid on a Webpage
|
||||
|
||||
**Using the npm package:**
|
||||
|
||||
1. You will need to install `node v16`, which would have npm.
|
||||
Requirements:
|
||||
|
||||
2. Download `yarn` using npm.
|
||||
- Node >= 16
|
||||
|
||||
3. Enter the following command: `yarn add mermaid`.
|
||||
|
||||
4. At this point, you can add mermaid as a dev dependency using this command: `yarn add --dev mermaid`.
|
||||
|
||||
5. Alternatively, you can also deploy mermaid using the script tag in an HTML file with mermaid diagram descriptions as is shown in the example below.
|
||||
```bash
|
||||
# NPM
|
||||
npm install mermaid
|
||||
# Yarn
|
||||
yarn add mermaid
|
||||
# PNPM
|
||||
pnpm add mermaid
|
||||
```
|
||||
|
||||
**Hosting mermaid on a web page:**
|
||||
|
||||
@@ -42,7 +45,9 @@ We have compiled some Video [Tutorials](./Tutorials.md) on how to use the mermai
|
||||
|
||||
The easiest way to integrate mermaid on a web page requires two elements:
|
||||
|
||||
- A graph definition, inside `<pre>` tags labeled `class=mermaid`. Example:
|
||||
- A graph definition, inside `<pre>` tags labeled `class=mermaid`.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<pre class="mermaid">
|
||||
@@ -53,14 +58,13 @@ The easiest way to integrate mermaid on a web page requires two elements:
|
||||
</pre>
|
||||
```
|
||||
|
||||
- Inclusion of the mermaid address in the html page body using a `script` tag as an ESM import, and the `mermaidAPI` call.
|
||||
- The mermaid js script. Added using a `script` tag as an ESM import.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -71,9 +75,6 @@ Example:
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
graph LR
|
||||
@@ -83,7 +84,6 @@ Example:
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -95,11 +95,12 @@ An id attribute is also added to mermaid tags without one.
|
||||
|
||||
Mermaid can load multiple diagrams, in the same page.
|
||||
|
||||
> Try it out, save this code as HTML and load it using any browser.(Except Internet Explorer, please don't use Internet Explorer.)
|
||||
> Try it out, save this code as HTML and load it using any browser.
|
||||
> (Except Internet Explorer, please don't use Internet Explorer.)
|
||||
|
||||
## Enabling Click Event and Tags in Nodes
|
||||
|
||||
A `securityLevel` configuration has to first be cleared, `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduce in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduce in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||
|
||||
**It is the site owner's responsibility to discriminate between trustworthy and untrustworthy user-bases and we encourage the use of discretion.**
|
||||
|
||||
@@ -107,7 +108,7 @@ A `securityLevel` configuration has to first be cleared, `securityLevel` sets th
|
||||
|
||||
| Parameter | Description | Type | Required | Values |
|
||||
| ------------- | --------------------------------- | ------ | -------- | ------------------------------------------ |
|
||||
| securityLevel | Level of trust for parsed diagram | String | Required | 'sandbox', 'strict', 'loose', 'antiscript' |
|
||||
| securityLevel | Level of trust for parsed diagram | String | Optional | 'sandbox', 'strict', 'loose', 'antiscript' |
|
||||
|
||||
Values:
|
||||
|
||||
@@ -122,26 +123,17 @@ Values:
|
||||
|
||||
**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing . This allows clicks and tags are allowed.**
|
||||
|
||||
**To change `securityLevel`, you have to call `mermaidAPI.initialize`:**
|
||||
**To change `securityLevel`, you have to call `mermaid.initialize`:**
|
||||
|
||||
```javascript
|
||||
mermaidAPI.initialize({
|
||||
mermaid.initialize({
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
```
|
||||
|
||||
### Labels out of bounds
|
||||
|
||||
If you use dynamically loaded fonts that are loaded through CSS, such as Google fonts, mermaid should wait for the
|
||||
whole page to load (dom + assets, particularly the fonts file).
|
||||
|
||||
```javascript
|
||||
$(document).load(function () {
|
||||
mermaid.initialize();
|
||||
});
|
||||
```
|
||||
|
||||
or
|
||||
If you use dynamically loaded fonts that are loaded through CSS, such as fonts, mermaid should wait for the whole page to load (dom + assets, particularly the fonts file).
|
||||
|
||||
```javascript
|
||||
$(document).ready(function () {
|
||||
@@ -154,12 +146,54 @@ Not doing so will most likely result in mermaid rendering graphs that have label
|
||||
If your page has other fonts in its body those might be used instead of the mermaid font. Specifying the font in your styling is a workaround for this.
|
||||
|
||||
```css
|
||||
div.mermaid {
|
||||
pre.mermaid {
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
```
|
||||
|
||||
### Calling `mermaid.init`
|
||||
### Using `mermaid.run`
|
||||
|
||||
mermaid.run was added in v10 and is the preferred way of handling more complex integration.
|
||||
By default, `mermaid.run` will be called when the document is ready, rendering all elements with `class="mermaid"`.
|
||||
|
||||
You can customize that behavior by calling `await mermaid.run(<config>)`.
|
||||
|
||||
`mermaid.initialize({startOnLoad: false})` will prevent `mermaid.run` from being called automatically after load.
|
||||
|
||||
Render all elements with querySelector ".someOtherClass"
|
||||
|
||||
```js
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
await mermaid.run({
|
||||
querySelector: '.someOtherClass',
|
||||
});
|
||||
```
|
||||
|
||||
Render all elements passed as an array
|
||||
|
||||
```js
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
await mermaid.run({
|
||||
nodes: [document.getElementById('someId'), document.getElementById('anotherId')],
|
||||
});
|
||||
await mermaid.run({
|
||||
nodes: document.querySelectorAll('.yetAnotherClass'),
|
||||
});
|
||||
```
|
||||
|
||||
Render all `.mermaid` elements while suppressing any error
|
||||
|
||||
```js
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
await mermaid.run({
|
||||
suppressErrors: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Calling `mermaid.init` - Deprecated
|
||||
|
||||
> **Warning**
|
||||
> mermaid.init is deprecated in v10 and will be removed in v11. Please use mermaid.run instead.
|
||||
|
||||
By default, `mermaid.init` will be called when the document is ready, finding all elements with
|
||||
`class="mermaid"`. If you are adding content after mermaid is loaded, or otherwise need
|
||||
@@ -192,25 +226,41 @@ mermaid fully supports webpack. Here is a [working demo](https://github.com/merm
|
||||
|
||||
## API usage
|
||||
|
||||
The main idea of the API is to be able to call a render function with the graph definition as a string. The render function
|
||||
will render the graph and call a callback with the resulting SVG code. With this approach it is up to the site creator to
|
||||
fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
|
||||
The main idea of the API is to be able to call a render function with the graph definition as a string. The render function will render the graph and call a callback with the resulting SVG code. With this approach it is up to the site creator to fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
|
||||
|
||||
The example below show an outline of how this could be used. The example just logs the resulting SVG to the JavaScript console.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.mjs';
|
||||
mermaid.mermaidAPI.initialize({ startOnLoad: false });
|
||||
$(async function () {
|
||||
// Example of using the API var
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
|
||||
// Example of using the render function
|
||||
const drawDiagram = async function () {
|
||||
element = document.querySelector('#graphDiv');
|
||||
const insertSvg = function (svgCode, bindFunctions) {
|
||||
element.innerHTML = svgCode;
|
||||
};
|
||||
const graphDefinition = 'graph TB\na-->b';
|
||||
const graph = await mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
|
||||
});
|
||||
const { svg } = await mermaid.render('graphDiv', graphDefinition);
|
||||
element.innerHTML = svg;
|
||||
};
|
||||
|
||||
await drawDiagram();
|
||||
</script>
|
||||
```
|
||||
|
||||
To determine the type of diagram present in a given text, you can utilize the `mermaid.detectType` function, as demonstrated in the example below.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
const graphDefinition = `sequenceDiagram
|
||||
Pumbaa->>Timon:I ate like a pig.
|
||||
Timon->>Pumbaa:Pumbaa, you ARE a pig.`;
|
||||
try {
|
||||
const type = mermaid.detectType(graphDefinition);
|
||||
console.log(type); // 'sequence'
|
||||
} catch (error) {
|
||||
// UnknownDiagramError
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -223,17 +273,17 @@ The example code below is an extract of what mermaid does when using the API. Th
|
||||
bind events to an SVG when using the API for rendering.
|
||||
|
||||
```javascript
|
||||
const insertSvg = function (svgCode, bindFunctions) {
|
||||
element.innerHTML = svgCode;
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(id);
|
||||
// Example of using the bindFunctions
|
||||
const drawDiagram = async function () {
|
||||
element = document.querySelector('#graphDiv');
|
||||
const graphDefinition = 'graph TB\na-->b';
|
||||
const { svg, bindFunctions } = await mermaid.render('graphDiv', graphDefinition);
|
||||
element.innerHTML = svg;
|
||||
// This can also be written as `bindFunctions?.(element);` using the `?` shorthand.
|
||||
if (bindFunctions) {
|
||||
bindFunctions(element);
|
||||
}
|
||||
bindFunctions(element);
|
||||
};
|
||||
|
||||
const id = 'theGraph';
|
||||
|
||||
mermaidAPI.render(id, txt, insertSvg, element);
|
||||
```
|
||||
|
||||
1. The graph is generated using the render call.
|
||||
|
@@ -20,6 +20,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Gitea](https://gitea.io) (**Native support**)
|
||||
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**)
|
||||
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**)
|
||||
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) (**Native support**)
|
||||
- [Deepdwn](https://billiam.itch.io/deepdwn) (**Native support**)
|
||||
- [Joplin](https://joplinapp.org) (**Native support**)
|
||||
- [Swimm](https://swimm.io) (**Native support**)
|
||||
- [Notion](https://notion.so) (**Native support**)
|
||||
@@ -87,7 +89,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [FosWiki](https://foswiki.org)
|
||||
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
|
||||
- [DokuWiki](https://dokuwiki.org)
|
||||
- [Flowcharts](https://www.dokuwiki.org/plugin:flowcharts?s[]=mermaid)
|
||||
- [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
|
||||
- [ComboStrap](https://combostrap.com/mermaid)
|
||||
- [TiddlyWiki](https://tiddlywiki.com/)
|
||||
- [mermaid-tw5: full js library](https://github.com/efurlanm/mermaid-tw5)
|
||||
@@ -148,7 +150,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [remark-mermaid](https://github.com/temando/remark-mermaid)
|
||||
- [jSDoc](https://jsdoc.app/)
|
||||
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
|
||||
- [MkDocs](https://mkdocs.org)
|
||||
- [MkDocs](https://www.mkdocs.org)
|
||||
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
|
||||
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
|
||||
- [Type Doc](https://typedoc.org/)
|
||||
@@ -188,3 +190,6 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
- [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
|
||||
- [NiceGUI: Let any browser be the frontend of your Python code](https://nicegui.io)
|
||||
- [ui.mermaid(...)](https://nicegui.io/reference#mermaid_diagrams)
|
||||
- [ui.markdown(..., extras=\['mermaid'\])](https://nicegui.io/reference#markdown_element)
|
||||
|
@@ -103,7 +103,7 @@ When writing the .html file, we give two instructions inside the html code to th
|
||||
|
||||
a. The mermaid code for the diagram we want to create.
|
||||
|
||||
b. The importing of mermaid library through the `mermaid.esm.js` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process .
|
||||
b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process .
|
||||
|
||||
**a. The embedded mermaid diagram definition inside a `<pre class="mermaid">`:**
|
||||
|
||||
@@ -135,7 +135,7 @@ b. The importing of mermaid library through the `mermaid.esm.js` or `mermaid.esm
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can place `mermaid.initialize()` inside `mermaid.esm.min.mjs` for brevity. However, doing the opposite lets you control when it starts looking for `<div>`tags inside the web page with `mermaid.initialize()`. This is useful when you think that not all `<div>` tags may have loaded on the execution of `mermaid.esm.min.mjs` file.
|
||||
Rendering in Mermaid is initialized by `mermaid.initialize()` call. However, doing the opposite lets you control when it starts looking for `<pre>` tags inside the web page with `mermaid.initialize()`. This is useful when you think that not all `<pre>` tags may have loaded on the execution of `mermaid.esm.min.mjs` file.
|
||||
|
||||
`startOnLoad` is one of the parameters that can be defined by `mermaid.initialize()`
|
||||
|
||||
@@ -143,10 +143,6 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can plac
|
||||
| ----------- | --------------------------------- | ------- | ----------- |
|
||||
| startOnLoad | Toggle for Rendering upon loading | Boolean | true, false |
|
||||
|
||||
### Adding external diagrams to mermaid
|
||||
|
||||
Please refer to the [Mindmap](../syntax/mindmap.md?id=integrating-with-your-librarywebsite) section for more information.
|
||||
|
||||
### Working Examples
|
||||
|
||||
**Here is a full working example of the mermaidAPI being called through the CDN:**
|
||||
|
@@ -130,6 +130,40 @@ classDiagram
|
||||
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
|
||||
|
||||
### Class labels
|
||||
|
||||
In case you need to provide a label for a class, you can use the following syntax:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
class Animal["Animal with a label"]
|
||||
class Car["Car with *! symbols"]
|
||||
Animal --> Car
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Animal["Animal with a label"]
|
||||
class Car["Car with *! symbols"]
|
||||
Animal --> Car
|
||||
```
|
||||
|
||||
You can also use backticks to escape special characters in the label:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
class `Animal Class!`
|
||||
class `Car Class`
|
||||
`Animal Class!` --> `Car Class`
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class `Animal Class!`
|
||||
class `Car Class`
|
||||
`Animal Class!` --> `Car Class`
|
||||
```
|
||||
|
||||
## Defining Members of a class
|
||||
|
||||
UML provides mechanisms to represent class members such as attributes and methods, as well as additional information about them.
|
||||
@@ -692,11 +726,11 @@ Beginner's tip—a full example using interactive links in an HTML page:
|
||||
|
||||
### Styling a node
|
||||
|
||||
It is possible to apply specific styles such as a thicker border or a different background color to individual nodes. This is done by predefining classes in css styles that can be applied from the graph definition:
|
||||
It is possible to apply specific styles such as a thicker border or a different background color to individual nodes. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
|
||||
|
||||
```html
|
||||
<style>
|
||||
.cssClass > rect {
|
||||
.styleClass > rect {
|
||||
fill: #ff0000;
|
||||
stroke: #ffff00;
|
||||
stroke-width: 4px;
|
||||
@@ -706,29 +740,29 @@ It is possible to apply specific styles such as a thicker border or a different
|
||||
|
||||
Then attaching that class to a specific node:
|
||||
|
||||
cssClass "nodeId1" cssClass;
|
||||
cssClass "nodeId1" styleClass;
|
||||
|
||||
It is also possible to attach a class to a list of nodes in one statement:
|
||||
|
||||
cssClass "nodeId1,nodeId2" cssClass;
|
||||
cssClass "nodeId1,nodeId2" styleClass;
|
||||
|
||||
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
class Animal:::cssClass
|
||||
class Animal:::styleClass
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Animal:::cssClass
|
||||
class Animal:::styleClass
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
class Animal:::cssClass {
|
||||
class Animal:::styleClass {
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
@@ -736,7 +770,7 @@ classDiagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Animal:::cssClass {
|
||||
class Animal:::styleClass {
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
|
@@ -183,20 +183,6 @@ flowchart LR
|
||||
|
||||
### A hexagon node
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
id1{{This is the text in the box}}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id1{{This is the text in the box}}
|
||||
```
|
||||
|
||||
Render:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
id1{{This is the text in the box}}
|
||||
@@ -391,9 +377,9 @@ flowchart LR
|
||||
A == text ==> B
|
||||
```
|
||||
|
||||
### An invisisble link
|
||||
### An invisible link
|
||||
|
||||
This can be a usefull tool in some instances where you want to alter the default positining of a node.
|
||||
This can be a useful tool in some instances where you want to alter the default positioning of a node.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
|
@@ -184,7 +184,7 @@ The following formatting options are supported:
|
||||
| `YY` | 14 | 2 digit year |
|
||||
| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. |
|
||||
| `M MM` | 1..12 | Month number |
|
||||
| `MMM MMMM` | January..Dec | Month name in locale set by `moment.locale()` |
|
||||
| `MMM MMMM` | January..Dec | Month name in locale set by `dayjs.locale()` |
|
||||
| `D DD` | 1..31 | Day of month |
|
||||
| `Do` | 1st..31st | Day of month with ordinal |
|
||||
| `DDD DDDD` | 1..365 | Day of year |
|
||||
@@ -200,7 +200,7 @@ The following formatting options are supported:
|
||||
| `SSS` | 0..999 | Thousandths of a second |
|
||||
| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z |
|
||||
|
||||
More info in: <https://momentjs.com/docs/#/parsing/string-format/>
|
||||
More info in: <https://day.js.org/docs/en/parse/string-format/>
|
||||
|
||||
### Output date format on the axis
|
||||
|
||||
@@ -257,9 +257,41 @@ The pattern is:
|
||||
|
||||
More info in: <https://github.com/d3/d3-time#interval_every>
|
||||
|
||||
## Output in compact mode
|
||||
|
||||
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceeding YAML settings.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
|
@@ -224,7 +224,7 @@ mindmap
|
||||
C
|
||||
```
|
||||
|
||||
_These classes needs top be supplied by the site administrator._
|
||||
_These classes need to be supplied by the site administrator._
|
||||
|
||||
## Unclear indentation
|
||||
|
||||
|
@@ -48,6 +48,7 @@ Drawing a pie chart is really simple in mermaid.
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
||||
pie showData
|
||||
title Key elements in Product X
|
||||
"Calcium" : 42.96
|
||||
@@ -57,6 +58,7 @@ pie showData
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
||||
pie showData
|
||||
title Key elements in Product X
|
||||
"Calcium" : 42.96
|
||||
@@ -64,3 +66,11 @@ pie showData
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Possible pie diagram configuration parameters:
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `textPosition` | The axial position of the pie slice labels, from 0.0 at the center to 1.0 at the outside edge of the circle. | `0.75` |
|
||||
|
@@ -595,7 +595,7 @@ It is possible to get a sequence number attached to each arrow in a sequence dia
|
||||
</script>
|
||||
```
|
||||
|
||||
It can also be be turned on via the diagram code as in the diagram:
|
||||
It can also be turned on via the diagram code as in the diagram:
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
> 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
|
||||
"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." Wikipedia
|
||||
|
||||
### An example of a timeline.
|
||||
|
||||
@@ -213,7 +213,7 @@ However, if there is no section defined, then we have two possibilities:
|
||||
|
||||
```
|
||||
|
||||
Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme.
|
||||
Note that there are no sections 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.
|
||||
|
||||
@@ -257,7 +257,7 @@ let us look at same example, where we have disabled the multiColor option.
|
||||
|
||||
### 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.
|
||||
You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12 sections, 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.
|
||||
|
14
package.json
14
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "mermaid-monorepo",
|
||||
"private": true,
|
||||
"version": "9.4.0",
|
||||
"version": "10.0.2",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@7.27.0",
|
||||
"packageManager": "pnpm@7.30.1",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@@ -70,9 +70,9 @@
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"@vitest/coverage-c8": "^0.28.4",
|
||||
"@vitest/spy": "^0.28.4",
|
||||
"@vitest/ui": "^0.28.4",
|
||||
"@vitest/coverage-c8": "^0.29.0",
|
||||
"@vitest/spy": "^0.29.0",
|
||||
"@vitest/ui": "^0.29.0",
|
||||
"concurrently": "^7.5.0",
|
||||
"cors": "^2.8.5",
|
||||
"coveralls": "^3.1.1",
|
||||
@@ -109,9 +109,9 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.28.5"
|
||||
"vitest": "^0.29.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.14.0"
|
||||
"node": "18.15.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.0.0-rc.3",
|
||||
"version": "10.0.2",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
"types": "./dist/mermaid.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/mermaid.d.ts",
|
||||
"import": "./dist/mermaid.core.mjs",
|
||||
"types": "./dist/mermaid.d.ts"
|
||||
"default": "./dist/mermaid.core.mjs"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
@@ -52,16 +53,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@khanacademy/simple-markdown": "^0.8.6",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.8",
|
||||
"dompurify": "2.4.3",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "2.4.5",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"moment-mini": "^2.29.4",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2",
|
||||
"stylis": "^4.1.2",
|
||||
"ts-dedent": "^2.2.0",
|
||||
@@ -73,7 +75,7 @@
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/micromatch": "^4.0.2",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/stylis": "^4.0.2",
|
||||
|
@@ -5,8 +5,14 @@ import { detectType, getDiagramLoader } from './diagram-api/detectType';
|
||||
import { extractFrontMatter } from './diagram-api/frontmatter';
|
||||
import { UnknownDiagramError } from './errors';
|
||||
import { DetailedError } from './utils';
|
||||
import { cleanupComments } from './diagram-api/comments';
|
||||
|
||||
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
|
||||
|
||||
/**
|
||||
* An object representing a parsed mermaid diagram definition.
|
||||
* @privateRemarks This is exported as part of the public mermaidAPI.
|
||||
*/
|
||||
export class Diagram {
|
||||
type = 'graph';
|
||||
parser;
|
||||
@@ -38,7 +44,10 @@ export class Diagram {
|
||||
// Similarly, we can't do this in getDiagramFromText() because some code
|
||||
// calls diagram.db.clear(), which would reset anything set by
|
||||
// extractFrontMatter().
|
||||
this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
|
||||
|
||||
this.parser.parse = (text: string) =>
|
||||
originalParse(cleanupComments(extractFrontMatter(text, this.db)));
|
||||
|
||||
this.parser.parser.yy = this.db;
|
||||
if (diagram.init) {
|
||||
diagram.init(cnf);
|
||||
@@ -68,8 +77,17 @@ export class Diagram {
|
||||
}
|
||||
}
|
||||
|
||||
export const getDiagramFromText = async (txt: string): Promise<Diagram> => {
|
||||
const type = detectType(txt, configApi.getConfig());
|
||||
/**
|
||||
* Parse the text asynchronously and generate a Diagram object asynchronously.
|
||||
* **Warning:** This function may be changed in the future.
|
||||
* @alpha
|
||||
* @param text - The mermaid diagram definition.
|
||||
* @returns A the Promise of a Diagram object.
|
||||
* @throws {@link UnknownDiagramError} if the diagram type can not be found.
|
||||
* @privateRemarks This is exported as part of the public mermaidAPI.
|
||||
*/
|
||||
export const getDiagramFromText = async (text: string): Promise<Diagram> => {
|
||||
const type = detectType(text, configApi.getConfig());
|
||||
try {
|
||||
// Trying to find the diagram
|
||||
getDiagram(type);
|
||||
@@ -83,5 +101,5 @@ export const getDiagramFromText = async (txt: string): Promise<Diagram> => {
|
||||
const { id, diagram } = await loader();
|
||||
registerDiagram(id, diagram);
|
||||
}
|
||||
return new Diagram(txt);
|
||||
return new Diagram(text);
|
||||
};
|
||||
|
@@ -3,6 +3,7 @@ import { getConfig } from './config';
|
||||
let title = '';
|
||||
let diagramTitle = '';
|
||||
let description = '';
|
||||
|
||||
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
|
||||
|
||||
export const clear = function (): void {
|
||||
@@ -36,10 +37,10 @@ export const getDiagramTitle = function (): string {
|
||||
};
|
||||
|
||||
export default {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setAccTitle,
|
||||
getDiagramTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle: getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear,
|
||||
|
@@ -243,6 +243,7 @@ const checkConfig = (config: MermaidConfig) => {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
// @ts-expect-error Properties were removed in v10. Warning should exist.
|
||||
if (config.lazyLoadedDiagrams || config.loadExternalDiagramsAtStartup) {
|
||||
issueWarning('LAZY_LOAD_DEPRECATED');
|
||||
}
|
||||
|
@@ -3,10 +3,6 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
export interface MermaidConfig {
|
||||
/** @deprecated use mermaid.registerLazyDiagrams instead */
|
||||
lazyLoadedDiagrams?: string[];
|
||||
/** @deprecated use mermaid.registerLazyDiagrams instead */
|
||||
loadExternalDiagramsAtStartup?: boolean;
|
||||
theme?: string;
|
||||
themeVariables?: any;
|
||||
themeCSS?: string;
|
||||
@@ -226,7 +222,9 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
|
||||
maxNodeWidth: number;
|
||||
}
|
||||
|
||||
export type PieDiagramConfig = BaseDiagramConfig;
|
||||
export interface PieDiagramConfig extends BaseDiagramConfig {
|
||||
textPosition?: number;
|
||||
}
|
||||
|
||||
export interface ErDiagramConfig extends BaseDiagramConfig {
|
||||
titleTopMargin?: number;
|
||||
@@ -268,6 +266,10 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
|
||||
padding?: number;
|
||||
textHeight?: number;
|
||||
defaultRenderer?: string;
|
||||
nodeSpacing?: number;
|
||||
rankSpacing?: number;
|
||||
diagramPadding?: number;
|
||||
htmlLabels?: boolean;
|
||||
}
|
||||
|
||||
export interface JourneyDiagramConfig extends BaseDiagramConfig {
|
||||
@@ -299,6 +301,7 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
|
||||
leftMargin?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
padding?: number;
|
||||
boxMargin?: number;
|
||||
boxTextMargin?: number;
|
||||
noteMargin?: number;
|
||||
@@ -315,6 +318,7 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
|
||||
sectionFills?: string[];
|
||||
sectionColours?: string[];
|
||||
disableMulticolor?: boolean;
|
||||
useMaxWidth?: boolean;
|
||||
}
|
||||
|
||||
export interface GanttDiagramConfig extends BaseDiagramConfig {
|
||||
@@ -331,6 +335,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
|
||||
axisFormat?: string;
|
||||
tickInterval?: string;
|
||||
topAxis?: boolean;
|
||||
displayMode?: string;
|
||||
}
|
||||
|
||||
export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
||||
@@ -381,6 +386,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
||||
curve?: string;
|
||||
padding?: number;
|
||||
defaultRenderer?: string;
|
||||
wrappingWidth?: number;
|
||||
}
|
||||
|
||||
export interface FontConfig {
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import intersectRect from './intersect/intersect-rect';
|
||||
import { log } from '../logger';
|
||||
import createLabel from './createLabel';
|
||||
import { createText } from '../rendering-util/createText';
|
||||
import { select } from 'd3';
|
||||
import { getConfig } from '../config';
|
||||
import { evaluate } from '../diagrams/common/common';
|
||||
|
||||
const rect = (parent, node) => {
|
||||
log.trace('Creating subgraph rect for ', node.id, node);
|
||||
log.info('Creating subgraph rect for ', node.id, node);
|
||||
|
||||
// Add outer g element
|
||||
const shapeSvg = parent
|
||||
@@ -17,12 +18,18 @@ const rect = (parent, node) => {
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
||||
|
||||
// Create the label and insert it after the rect
|
||||
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
|
||||
|
||||
const text = label
|
||||
.node()
|
||||
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||
// const text = label
|
||||
// .node()
|
||||
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||
const text =
|
||||
node.labelType === 'markdown'
|
||||
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
|
||||
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||
|
||||
// Get the size of the label
|
||||
let bbox = text.getBBox();
|
||||
@@ -61,7 +68,7 @@ const rect = (parent, node) => {
|
||||
'transform',
|
||||
// This puts the labal on top of the box instead of inside it
|
||||
// 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2 - bbox.height) + ')'
|
||||
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
|
||||
'translate(' + node.x + ', ' + (node.y - node.height / 2) + ')'
|
||||
);
|
||||
|
||||
const rectBox = rect.node().getBBox();
|
||||
|
@@ -41,7 +41,13 @@ function addHtmlLabel(node) {
|
||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
return fo.node();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _vertexText
|
||||
* @param style
|
||||
* @param isTitle
|
||||
* @param isNode
|
||||
* @deprecated svg-util/createText instead
|
||||
*/
|
||||
const createLabel = (_vertexText, style, isTitle, isNode) => {
|
||||
let vertexText = _vertexText || '';
|
||||
if (typeof vertexText === 'object') {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { log } from '../logger';
|
||||
import createLabel from './createLabel';
|
||||
import { createText } from '../rendering-util/createText';
|
||||
import { line, curveBasis, select } from 'd3';
|
||||
import { getConfig } from '../config';
|
||||
import utils from '../utils';
|
||||
@@ -14,8 +15,17 @@ export const clear = () => {
|
||||
};
|
||||
|
||||
export const insertEdgeLabel = (elem, edge) => {
|
||||
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
||||
// Create the actual text element
|
||||
const labelElement = createLabel(edge.label, edge.labelStyle);
|
||||
const labelElement =
|
||||
edge.labelType === 'markdown'
|
||||
? createText(elem, edge.label, {
|
||||
style: edge.labelStyle,
|
||||
useHtmlLabels,
|
||||
addSvgBackground: true,
|
||||
})
|
||||
: createLabel(edge.label, edge.labelStyle);
|
||||
log.info('abc82', edge, edge.labelType);
|
||||
|
||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
||||
@@ -26,7 +36,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
||||
|
||||
// Center the label
|
||||
let bbox = labelElement.getBBox();
|
||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||
if (useHtmlLabels) {
|
||||
const div = labelElement.children[0];
|
||||
const dv = select(labelElement);
|
||||
bbox = div.getBoundingClientRect();
|
||||
|
@@ -313,19 +313,18 @@ const cylinder = (parent, node) => {
|
||||
const rect = (parent, node) => {
|
||||
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
|
||||
|
||||
log.trace('Classes = ', node.classes);
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
const totalWidth = bbox.width + node.padding;
|
||||
const totalHeight = bbox.height + node.padding;
|
||||
const totalWidth = bbox.width + node.padding * 2;
|
||||
const totalHeight = bbox.height + node.padding * 2;
|
||||
rect
|
||||
.attr('class', 'basic label-container')
|
||||
.attr('style', node.style)
|
||||
.attr('rx', node.rx)
|
||||
.attr('ry', node.ry)
|
||||
.attr('x', -bbox.width / 2 - halfPadding)
|
||||
.attr('y', -bbox.height / 2 - halfPadding)
|
||||
.attr('x', -bbox.width / 2 - node.padding)
|
||||
.attr('y', -bbox.height / 2 - node.padding)
|
||||
.attr('width', totalWidth)
|
||||
.attr('height', totalHeight);
|
||||
|
||||
@@ -352,7 +351,7 @@ const rect = (parent, node) => {
|
||||
const labelRect = (parent, node) => {
|
||||
const { shapeSvg } = labelHelper(parent, node, 'label', true);
|
||||
|
||||
log.trace('Classes = ', node.classes);
|
||||
log.info('Classes = ', node.classes);
|
||||
// add the rect
|
||||
const rect = shapeSvg.insert('rect', ':first-child');
|
||||
|
||||
@@ -772,7 +771,7 @@ const class_box = (parent, node) => {
|
||||
maxWidth += interfaceBBox.width;
|
||||
}
|
||||
|
||||
let classTitleString = node.classData.id;
|
||||
let classTitleString = node.classData.label;
|
||||
|
||||
if (node.classData.type !== undefined && node.classData.type !== '') {
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
@@ -927,61 +926,6 @@ const class_box = (parent, node) => {
|
||||
);
|
||||
verticalPos += classTitleBBox.height + rowPadding;
|
||||
});
|
||||
//
|
||||
// let bbox;
|
||||
// if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||
// const div = interfaceLabel.children[0];
|
||||
// const dv = select(interfaceLabel);
|
||||
// bbox = div.getBoundingClientRect();
|
||||
// dv.attr('width', bbox.width);
|
||||
// dv.attr('height', bbox.height);
|
||||
// }
|
||||
// bbox = labelContainer.getBBox();
|
||||
|
||||
// log.info('Text 2', text2);
|
||||
// const textRows = text2.slice(1, text2.length);
|
||||
// let titleBox = text.getBBox();
|
||||
// const descr = label
|
||||
// .node()
|
||||
// .appendChild(createLabel(textRows.join('<br/>'), node.labelStyle, true, true));
|
||||
|
||||
// if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||
// const div = descr.children[0];
|
||||
// const dv = select(descr);
|
||||
// bbox = div.getBoundingClientRect();
|
||||
// dv.attr('width', bbox.width);
|
||||
// dv.attr('height', bbox.height);
|
||||
// }
|
||||
// // bbox = label.getBBox();
|
||||
// // log.info(descr);
|
||||
// select(descr).attr(
|
||||
// 'transform',
|
||||
// 'translate( ' +
|
||||
// // (titleBox.width - bbox.width) / 2 +
|
||||
// (bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) +
|
||||
// ', ' +
|
||||
// (titleBox.height + halfPadding + 5) +
|
||||
// ')'
|
||||
// );
|
||||
// select(text).attr(
|
||||
// 'transform',
|
||||
// 'translate( ' +
|
||||
// // (titleBox.width - bbox.width) / 2 +
|
||||
// (bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) +
|
||||
// ', ' +
|
||||
// 0 +
|
||||
// ')'
|
||||
// );
|
||||
// // Get the size of the label
|
||||
|
||||
// // Bounding box for title and text
|
||||
// bbox = label.node().getBBox();
|
||||
|
||||
// // Center the label
|
||||
// label.attr(
|
||||
// 'transform',
|
||||
// 'translate(' + -bbox.width / 2 + ', ' + (-bbox.height / 2 - halfPadding + 3) + ')'
|
||||
// );
|
||||
|
||||
rect
|
||||
.attr('class', 'outer title-state')
|
||||
@@ -990,13 +934,6 @@ const class_box = (parent, node) => {
|
||||
.attr('width', maxWidth + node.padding)
|
||||
.attr('height', maxHeight + node.padding);
|
||||
|
||||
// innerLine
|
||||
// .attr('class', 'divider')
|
||||
// .attr('x1', -bbox.width / 2 - halfPadding)
|
||||
// .attr('x2', bbox.width / 2 + halfPadding)
|
||||
// .attr('y1', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding)
|
||||
// .attr('y2', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding);
|
||||
|
||||
updateNodeBounds(node, rect);
|
||||
|
||||
node.intersect = function (point) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import createLabel from '../createLabel';
|
||||
import { createText } from '../../rendering-util/createText';
|
||||
import { getConfig } from '../../config';
|
||||
import { decodeEntities } from '../../mermaidAPI';
|
||||
import { select } from 'd3';
|
||||
@@ -27,9 +28,17 @@ export const labelHelper = (parent, node, _classes, isNode) => {
|
||||
labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0];
|
||||
}
|
||||
|
||||
const text = label
|
||||
.node()
|
||||
.appendChild(
|
||||
const textNode = label.node();
|
||||
let text;
|
||||
if (node.labelType === 'markdown') {
|
||||
// text = textNode;
|
||||
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
|
||||
useHtmlLabels: getConfig().flowchart.htmlLabels,
|
||||
width: node.width || getConfig().flowchart.wrappingWidth,
|
||||
classes: 'markdown-node-label',
|
||||
});
|
||||
} else {
|
||||
text = textNode.appendChild(
|
||||
createLabel(
|
||||
sanitizeText(decodeEntities(labelText), getConfig()),
|
||||
node.labelStyle,
|
||||
@@ -37,6 +46,7 @@ export const labelHelper = (parent, node, _classes, isNode) => {
|
||||
isNode
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get the size of the label
|
||||
let bbox = text.getBBox();
|
||||
@@ -52,8 +62,12 @@ export const labelHelper = (parent, node, _classes, isNode) => {
|
||||
const halfPadding = node.padding / 2;
|
||||
|
||||
// Center the label
|
||||
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||
} else {
|
||||
label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
|
||||
}
|
||||
label.insert('rect', ':first-child');
|
||||
return { shapeSvg, bbox, halfPadding, label };
|
||||
};
|
||||
|
||||
|
@@ -258,6 +258,18 @@ const config: Partial<MermaidConfig> = {
|
||||
* Default value: 'dagre-wrapper'
|
||||
*/
|
||||
defaultRenderer: 'dagre-wrapper',
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ----------- | ------- | -------- | ----------------------- |
|
||||
* | wrappingWidth | See notes | number | 4 | width of nodes where text is wrapped |
|
||||
*
|
||||
* **Notes:**
|
||||
*
|
||||
* When using markdown strings the text ius wrapped automatically, this
|
||||
* value sets the max width of a text before it continues on a new line.
|
||||
* Default value: 'dagre-wrapper'
|
||||
*/
|
||||
wrappingWidth: 200,
|
||||
},
|
||||
|
||||
/** The object containing configurations specific for sequence diagrams */
|
||||
@@ -659,6 +671,17 @@ const config: Partial<MermaidConfig> = {
|
||||
*/
|
||||
numberSectionStyles: 4,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | ------------------------- | ------ | -------- | --------- |
|
||||
* | displayMode | Controls the display mode | string | 4 | 'compact' |
|
||||
*
|
||||
* **Notes**:
|
||||
*
|
||||
* - **compact**: Enables displaying multiple tasks on the same row.
|
||||
*/
|
||||
displayMode: '',
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ---------- | ---------------------------- | ---- | -------- | ---------------- |
|
||||
@@ -684,7 +707,6 @@ const config: Partial<MermaidConfig> = {
|
||||
* Default value: undefined
|
||||
*/
|
||||
tickInterval: undefined,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | ----------- | ------- | -------- | ----------- |
|
||||
@@ -1247,6 +1269,15 @@ const config: Partial<MermaidConfig> = {
|
||||
* Default value: true
|
||||
*/
|
||||
useMaxWidth: true,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------ | -------------------------------------------------------------------------------- | ------- | -------- | ------------------- |
|
||||
* | textPosition | Axial position of slice's label from zero at the center to 1 at the outside edge | Number | Optional | Decimal from 0 to 1 |
|
||||
*
|
||||
* **Notes:** Default value: 0.75
|
||||
*/
|
||||
textPosition: 0.75,
|
||||
},
|
||||
|
||||
/** The object containing configurations specific for req diagrams */
|
||||
|
94
packages/mermaid/src/diagram-api/comments.spec.ts
Normal file
94
packages/mermaid/src/diagram-api/comments.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
// tests to check that comments are removed
|
||||
|
||||
import { cleanupComments } from './comments';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('comments', () => {
|
||||
it('should remove comments', () => {
|
||||
const text = `
|
||||
|
||||
%% This is a comment
|
||||
%% This is another comment
|
||||
graph TD
|
||||
A-->B
|
||||
%% This is a comment
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should keep init statements when removing comments', () => {
|
||||
const text = `
|
||||
%% This is a comment
|
||||
|
||||
%% This is another comment
|
||||
%%{init: {'theme': 'forest'}}%%
|
||||
%%{ init: {'theme': 'space before init'}}%%
|
||||
%%{init: {'theme': 'space after ending'}}%%
|
||||
graph TD
|
||||
A-->B
|
||||
|
||||
B-->C
|
||||
%% This is a comment
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"%%{init: {'theme': 'forest'}}%%
|
||||
%%{ init: {'theme': 'space before init'}}%%
|
||||
%%{init: {'theme': 'space after ending'}}%%
|
||||
graph TD
|
||||
A-->B
|
||||
|
||||
B-->C
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove indented comments', () => {
|
||||
const text = `
|
||||
%% This is a comment
|
||||
graph TD
|
||||
A-->B
|
||||
%% This is a comment
|
||||
C-->D
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
C-->D
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove empty newlines from start', () => {
|
||||
const text = `
|
||||
|
||||
|
||||
|
||||
|
||||
%% This is a comment
|
||||
graph TD
|
||||
A-->B
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should remove comments at end of text with no newline', () => {
|
||||
const text = `
|
||||
graph TD
|
||||
A-->B
|
||||
%% This is a comment`;
|
||||
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
8
packages/mermaid/src/diagram-api/comments.ts
Normal file
8
packages/mermaid/src/diagram-api/comments.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Remove all lines starting with `%%` from the text that don't contain a `%%{`
|
||||
* @param text - The text to remove comments from
|
||||
* @returns cleaned text
|
||||
*/
|
||||
export const cleanupComments = (text: string): string => {
|
||||
return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
|
||||
};
|
@@ -7,6 +7,7 @@ import type {
|
||||
ExternalDiagramDefinition,
|
||||
} from './types';
|
||||
import { frontMatterRegex } from './frontmatter';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI';
|
||||
import { UnknownDiagramError } from '../errors';
|
||||
|
||||
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
@@ -45,15 +46,63 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnknownDiagramError(`No diagram type detected for text: ${text}`);
|
||||
throw new UnknownDiagramError(
|
||||
`No diagram type detected matching given configuration for text: ${text}`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers lazy-loaded diagrams to Mermaid.
|
||||
*
|
||||
* The diagram function is loaded asynchronously, so that diagrams are only loaded
|
||||
* if the diagram is detected.
|
||||
*
|
||||
* @remarks
|
||||
* Please note that the order of diagram detectors is important.
|
||||
* The first detector to return `true` is the diagram that will be loaded
|
||||
* and used, so put more specific detectors at the beginning!
|
||||
*
|
||||
* @param diagrams - Diagrams to lazy load, and their detectors, in order of importance.
|
||||
*/
|
||||
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
|
||||
for (const { id, detector, loader } of diagrams) {
|
||||
addDetector(id, detector, loader);
|
||||
}
|
||||
};
|
||||
|
||||
export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
} catch (error) {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
delete detectors[key];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
log.error(`Failed to load ${failed.length} external diagrams`);
|
||||
for (const res of failed) {
|
||||
log.error(res);
|
||||
}
|
||||
throw new Error(`Failed to load ${failed.length} external diagrams`);
|
||||
}
|
||||
};
|
||||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
@@ -63,4 +112,6 @@ export const addDetector = (key: string, detector: DiagramDetector, loader?: Dia
|
||||
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
|
||||
};
|
||||
|
||||
export const getDiagramLoader = (key: string) => detectors[key].loader;
|
||||
export const getDiagramLoader = (key: string) => {
|
||||
return detectors[key].loader;
|
||||
};
|
||||
|
@@ -0,0 +1,82 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { detectType } from './detectType';
|
||||
import { addDiagrams } from './diagram-orchestration';
|
||||
|
||||
describe('diagram-orchestration', () => {
|
||||
it('should register diagrams', () => {
|
||||
expect(() => detectType('graph TD; A-->B')).toThrow();
|
||||
addDiagrams();
|
||||
expect(detectType('graph TD; A-->B')).toBe('flowchart');
|
||||
});
|
||||
|
||||
describe('proper diagram types should be detetced', () => {
|
||||
beforeAll(() => {
|
||||
addDiagrams();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ text: 'graph TD;', expected: 'flowchart' },
|
||||
{ text: 'flowchart TD;', expected: 'flowchart-v2' },
|
||||
{ text: 'flowchart-v2 TD;', expected: 'flowchart-v2' },
|
||||
{ text: 'flowchart-elk TD;', expected: 'flowchart-elk' },
|
||||
{ text: 'error', expected: 'error' },
|
||||
{ text: 'C4Context;', expected: 'c4' },
|
||||
{ text: 'classDiagram', expected: 'class' },
|
||||
{ text: 'classDiagram-v2', expected: 'classDiagram' },
|
||||
{ text: 'erDiagram', expected: 'er' },
|
||||
{ text: 'journey', expected: 'journey' },
|
||||
{ text: 'gantt', expected: 'gantt' },
|
||||
{ text: 'pie', expected: 'pie' },
|
||||
{ text: 'requirementDiagram', expected: 'requirement' },
|
||||
{ text: 'info', expected: 'info' },
|
||||
{ text: 'sequenceDiagram', expected: 'sequence' },
|
||||
{ text: 'mindmap', expected: 'mindmap' },
|
||||
{ text: 'timeline', expected: 'timeline' },
|
||||
{ text: 'gitGraph', expected: 'gitGraph' },
|
||||
{ text: 'stateDiagram', expected: 'state' },
|
||||
{ text: 'stateDiagram-v2', expected: 'stateDiagram' },
|
||||
])(
|
||||
'should $text be detected as $expected',
|
||||
({ text, expected }: { text: string; expected: string }) => {
|
||||
expect(detectType(text)).toBe(expected);
|
||||
}
|
||||
);
|
||||
|
||||
it('should detect proper flowchart type based on config', () => {
|
||||
// graph & dagre-d3 => flowchart
|
||||
expect(detectType('graph TD; A-->B')).toBe('flowchart');
|
||||
// graph & dagre-d3 => flowchart
|
||||
expect(detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })).toBe(
|
||||
'flowchart'
|
||||
);
|
||||
// flowchart & dagre-d3 => error
|
||||
expect(() =>
|
||||
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
'"No diagram type detected matching given configuration for text: flowchart TD; A-->B"'
|
||||
);
|
||||
|
||||
// graph & dagre-wrapper => flowchart-v2
|
||||
expect(
|
||||
detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
|
||||
).toBe('flowchart-v2');
|
||||
// flowchart ==> flowchart-v2
|
||||
expect(detectType('flowchart TD; A-->B')).toBe('flowchart-v2');
|
||||
// flowchart && dagre-wrapper ==> flowchart-v2
|
||||
expect(
|
||||
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
|
||||
).toBe('flowchart-v2');
|
||||
// flowchart && elk ==> flowchart-elk
|
||||
expect(detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'elk' } })).toBe(
|
||||
'flowchart-elk'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not detect flowchart if pie contains flowchart', () => {
|
||||
expect(
|
||||
detectType(`pie title: "flowchart"
|
||||
flowchart: 1 "pie" pie: 2 "pie"`)
|
||||
).toBe('pie');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,104 +1,24 @@
|
||||
import { registerDiagram } from './diagramAPI';
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import gitGraphParser from '../diagrams/git/parser/gitGraph';
|
||||
import { gitGraphDetector } from '../diagrams/git/gitGraphDetector';
|
||||
import gitGraphDb from '../diagrams/git/gitGraphAst';
|
||||
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
|
||||
import gitGraphStyles from '../diagrams/git/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import c4Parser from '../diagrams/c4/parser/c4Diagram';
|
||||
import { c4Detector } from '../diagrams/c4/c4Detector';
|
||||
import c4Db from '../diagrams/c4/c4Db';
|
||||
import c4Renderer from '../diagrams/c4/c4Renderer';
|
||||
import c4Styles from '../diagrams/c4/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import classParser from '../diagrams/class/parser/classDiagram';
|
||||
import { classDetector } from '../diagrams/class/classDetector';
|
||||
import { classDetectorV2 } from '../diagrams/class/classDetector-V2';
|
||||
import classDb from '../diagrams/class/classDb';
|
||||
import classRenderer from '../diagrams/class/classRenderer';
|
||||
import classRendererV2 from '../diagrams/class/classRenderer-v2';
|
||||
import classStyles from '../diagrams/class/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import erParser from '../diagrams/er/parser/erDiagram';
|
||||
import { erDetector } from '../diagrams/er/erDetector';
|
||||
import erDb from '../diagrams/er/erDb';
|
||||
import erRenderer from '../diagrams/er/erRenderer';
|
||||
import erStyles from '../diagrams/er/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import flowParser from '../diagrams/flowchart/parser/flow';
|
||||
import { flowDetector } from '../diagrams/flowchart/flowDetector';
|
||||
import { flowDetectorV2 } from '../diagrams/flowchart/flowDetector-v2';
|
||||
import flowDb from '../diagrams/flowchart/flowDb';
|
||||
import flowRenderer from '../diagrams/flowchart/flowRenderer';
|
||||
import flowRendererV2 from '../diagrams/flowchart/flowRenderer-v2';
|
||||
import flowStyles from '../diagrams/flowchart/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import ganttParser from '../diagrams/gantt/parser/gantt';
|
||||
import { ganttDetector } from '../diagrams/gantt/ganttDetector';
|
||||
import ganttDb from '../diagrams/gantt/ganttDb';
|
||||
import ganttRenderer from '../diagrams/gantt/ganttRenderer';
|
||||
import ganttStyles from '../diagrams/gantt/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import infoParser from '../diagrams/info/parser/info';
|
||||
import infoDb from '../diagrams/info/infoDb';
|
||||
import infoRenderer from '../diagrams/info/infoRenderer';
|
||||
import { infoDetector } from '../diagrams/info/infoDetector';
|
||||
import infoStyles from '../diagrams/info/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import pieParser from '../diagrams/pie/parser/pie';
|
||||
import { pieDetector } from '../diagrams/pie/pieDetector';
|
||||
import pieDb from '../diagrams/pie/pieDb';
|
||||
import pieRenderer from '../diagrams/pie/pieRenderer';
|
||||
import pieStyles from '../diagrams/pie/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import requirementParser from '../diagrams/requirement/parser/requirementDiagram';
|
||||
import { requirementDetector } from '../diagrams/requirement/requirementDetector';
|
||||
import requirementDb from '../diagrams/requirement/requirementDb';
|
||||
import requirementRenderer from '../diagrams/requirement/requirementRenderer';
|
||||
import requirementStyles from '../diagrams/requirement/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import sequenceParser from '../diagrams/sequence/parser/sequenceDiagram';
|
||||
import { sequenceDetector } from '../diagrams/sequence/sequenceDetector';
|
||||
import sequenceDb from '../diagrams/sequence/sequenceDb';
|
||||
import sequenceRenderer from '../diagrams/sequence/sequenceRenderer';
|
||||
import sequenceStyles from '../diagrams/sequence/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import stateParser from '../diagrams/state/parser/stateDiagram';
|
||||
import { stateDetector } from '../diagrams/state/stateDetector';
|
||||
import { stateDetectorV2 } from '../diagrams/state/stateDetector-V2';
|
||||
import stateDb from '../diagrams/state/stateDb';
|
||||
import stateRenderer from '../diagrams/state/stateRenderer';
|
||||
import stateRendererV2 from '../diagrams/state/stateRenderer-v2';
|
||||
import stateStyles from '../diagrams/state/styles';
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import journeyParser from '../diagrams/user-journey/parser/journey';
|
||||
import { journeyDetector } from '../diagrams/user-journey/journeyDetector';
|
||||
import journeyDb from '../diagrams/user-journey/journeyDb';
|
||||
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
|
||||
import journeyStyles from '../diagrams/user-journey/styles';
|
||||
import { setConfig } from '../config';
|
||||
|
||||
import errorRenderer from '../diagrams/error/errorRenderer';
|
||||
import errorStyles from '../diagrams/error/styles';
|
||||
|
||||
import c4 from '../diagrams/c4/c4Detector';
|
||||
import flowchart from '../diagrams/flowchart/flowDetector';
|
||||
import flowchartV2 from '../diagrams/flowchart/flowDetector-v2';
|
||||
import er from '../diagrams/er/erDetector';
|
||||
import git from '../diagrams/git/gitGraphDetector';
|
||||
import gantt from '../diagrams/gantt/ganttDetector';
|
||||
import info from '../diagrams/info/infoDetector';
|
||||
import pie from '../diagrams/pie/pieDetector';
|
||||
import requirement from '../diagrams/requirement/requirementDetector';
|
||||
import sequence from '../diagrams/sequence/sequenceDetector';
|
||||
import classDiagram from '../diagrams/class/classDetector';
|
||||
import classDiagramV2 from '../diagrams/class/classDetector-V2';
|
||||
import state from '../diagrams/state/stateDetector';
|
||||
import stateV2 from '../diagrams/state/stateDetector-V2';
|
||||
import journey from '../diagrams/user-journey/journeyDetector';
|
||||
import errorDiagram from '../diagrams/error/errorDiagram';
|
||||
import flowchartElk from '../diagrams/flowchart/elk/detector';
|
||||
import timeline from '../diagrams/timeline/detector';
|
||||
import mindmap from '../diagrams/mindmap/detector';
|
||||
import { registerLazyLoadedDiagrams } from './detectType';
|
||||
|
||||
// Lazy loaded diagrams
|
||||
import timelineDetector from '../diagrams/timeline/detector';
|
||||
import mindmapDetector from '../diagrams/mindmap/detector';
|
||||
import { registerDiagram } from './diagramAPI';
|
||||
|
||||
let hasLoadedDiagrams = false;
|
||||
export const addDiagrams = () => {
|
||||
@@ -108,31 +28,9 @@ 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
|
||||
{
|
||||
db: {
|
||||
clear: () => {
|
||||
// Quite ok, clear needs to be there for error to work as a regular diagram
|
||||
},
|
||||
},
|
||||
styles: errorStyles,
|
||||
renderer: errorRenderer,
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
},
|
||||
init: () => {
|
||||
// no op
|
||||
},
|
||||
},
|
||||
(text) => text.toLowerCase().trim() === 'error'
|
||||
);
|
||||
registerDiagram('error', errorDiagram, (text) => {
|
||||
return text.toLowerCase().trim() === 'error';
|
||||
});
|
||||
registerDiagram(
|
||||
'---',
|
||||
// --- diagram type may appear if YAML front-matter is not parsed correctly
|
||||
@@ -142,15 +40,15 @@ export const addDiagrams = () => {
|
||||
// Quite ok, clear needs to be there for --- to work as a regular diagram
|
||||
},
|
||||
},
|
||||
styles: errorStyles, // should never be used
|
||||
renderer: errorRenderer, // should never be used
|
||||
styles: {}, // should never be used
|
||||
renderer: {}, // should never be used
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
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"
|
||||
"you've correctly opened and closed the YAML front-matter with un-indented `---` blocks"
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -160,220 +58,25 @@ export const addDiagrams = () => {
|
||||
return text.toLowerCase().trimStart().startsWith('---');
|
||||
}
|
||||
);
|
||||
|
||||
registerDiagram(
|
||||
'c4',
|
||||
{
|
||||
parser: c4Parser,
|
||||
db: c4Db,
|
||||
renderer: c4Renderer,
|
||||
styles: c4Styles,
|
||||
init: (cnf) => {
|
||||
c4Renderer.setConf(cnf.c4);
|
||||
},
|
||||
},
|
||||
c4Detector
|
||||
);
|
||||
registerDiagram(
|
||||
'class',
|
||||
{
|
||||
parser: classParser,
|
||||
db: classDb,
|
||||
renderer: classRenderer,
|
||||
styles: classStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.class) {
|
||||
cnf.class = {};
|
||||
}
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
classDb.clear();
|
||||
},
|
||||
},
|
||||
classDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'classDiagram',
|
||||
{
|
||||
parser: classParser,
|
||||
db: classDb,
|
||||
renderer: classRendererV2,
|
||||
styles: classStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.class) {
|
||||
cnf.class = {};
|
||||
}
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
classDb.clear();
|
||||
},
|
||||
},
|
||||
classDetectorV2
|
||||
);
|
||||
registerDiagram(
|
||||
'er',
|
||||
{
|
||||
parser: erParser,
|
||||
db: erDb,
|
||||
renderer: erRenderer,
|
||||
styles: erStyles,
|
||||
},
|
||||
erDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'gantt',
|
||||
{
|
||||
parser: ganttParser,
|
||||
db: ganttDb,
|
||||
renderer: ganttRenderer,
|
||||
styles: ganttStyles,
|
||||
},
|
||||
ganttDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'info',
|
||||
{
|
||||
parser: infoParser,
|
||||
db: infoDb,
|
||||
renderer: infoRenderer,
|
||||
styles: infoStyles,
|
||||
},
|
||||
infoDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'pie',
|
||||
{
|
||||
parser: pieParser,
|
||||
db: pieDb,
|
||||
renderer: pieRenderer,
|
||||
styles: pieStyles,
|
||||
},
|
||||
pieDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'requirement',
|
||||
{
|
||||
parser: requirementParser,
|
||||
db: requirementDb,
|
||||
renderer: requirementRenderer,
|
||||
styles: requirementStyles,
|
||||
},
|
||||
requirementDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'sequence',
|
||||
{
|
||||
parser: sequenceParser,
|
||||
db: sequenceDb,
|
||||
renderer: sequenceRenderer,
|
||||
styles: sequenceStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.sequence) {
|
||||
cnf.sequence = {};
|
||||
}
|
||||
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
if ('sequenceDiagram' in cnf) {
|
||||
throw new Error(
|
||||
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
|
||||
);
|
||||
}
|
||||
sequenceDb.setWrap(cnf.wrap);
|
||||
sequenceRenderer.setConf(cnf.sequence);
|
||||
},
|
||||
},
|
||||
sequenceDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'state',
|
||||
{
|
||||
parser: stateParser,
|
||||
db: stateDb,
|
||||
renderer: stateRenderer,
|
||||
styles: stateStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.state) {
|
||||
cnf.state = {};
|
||||
}
|
||||
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
stateDb.clear();
|
||||
},
|
||||
},
|
||||
stateDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'stateDiagram',
|
||||
{
|
||||
parser: stateParser,
|
||||
db: stateDb,
|
||||
renderer: stateRendererV2,
|
||||
styles: stateStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.state) {
|
||||
cnf.state = {};
|
||||
}
|
||||
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
stateDb.clear();
|
||||
},
|
||||
},
|
||||
stateDetectorV2
|
||||
);
|
||||
registerDiagram(
|
||||
'journey',
|
||||
{
|
||||
parser: journeyParser,
|
||||
db: journeyDb,
|
||||
renderer: journeyRenderer,
|
||||
styles: journeyStyles,
|
||||
init: (cnf) => {
|
||||
journeyRenderer.setConf(cnf.journey);
|
||||
journeyDb.clear();
|
||||
},
|
||||
},
|
||||
journeyDetector
|
||||
);
|
||||
|
||||
registerDiagram(
|
||||
'flowchart',
|
||||
{
|
||||
parser: flowParser,
|
||||
db: flowDb,
|
||||
renderer: flowRendererV2,
|
||||
styles: flowStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.flowchart) {
|
||||
cnf.flowchart = {};
|
||||
}
|
||||
// TODO, broken as of 2022-09-14 (13809b50251845475e6dca65cc395761be38fbd2)
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
flowRenderer.setConf(cnf.flowchart);
|
||||
flowDb.clear();
|
||||
flowDb.setGen('gen-1');
|
||||
},
|
||||
},
|
||||
flowDetector
|
||||
);
|
||||
registerDiagram(
|
||||
'flowchart-v2',
|
||||
{
|
||||
parser: flowParser,
|
||||
db: flowDb,
|
||||
renderer: flowRendererV2,
|
||||
styles: flowStyles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.flowchart) {
|
||||
cnf.flowchart = {};
|
||||
}
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
// flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
|
||||
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
|
||||
flowRendererV2.setConf(cnf.flowchart);
|
||||
flowDb.clear();
|
||||
flowDb.setGen('gen-2');
|
||||
},
|
||||
},
|
||||
flowDetectorV2
|
||||
);
|
||||
registerDiagram(
|
||||
'gitGraph',
|
||||
{ parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer, styles: gitGraphStyles },
|
||||
gitGraphDetector
|
||||
// Ordering of detectors is important. The first one to return true will be used.
|
||||
registerLazyLoadedDiagrams(
|
||||
c4,
|
||||
classDiagramV2,
|
||||
classDiagram,
|
||||
er,
|
||||
gantt,
|
||||
info,
|
||||
pie,
|
||||
requirement,
|
||||
sequence,
|
||||
flowchartElk,
|
||||
flowchartV2,
|
||||
flowchart,
|
||||
mindmap,
|
||||
timeline,
|
||||
git,
|
||||
stateV2,
|
||||
state,
|
||||
journey
|
||||
);
|
||||
};
|
||||
|
@@ -2,8 +2,13 @@ import { detectType } from './detectType';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI';
|
||||
import { addDiagrams } from './diagram-orchestration';
|
||||
import { DiagramDetector } from './types';
|
||||
import { getDiagramFromText } from '../Diagram';
|
||||
import { it, describe, expect, beforeAll } from 'vitest';
|
||||
|
||||
addDiagrams();
|
||||
beforeAll(async () => {
|
||||
await getDiagramFromText('sequenceDiagram');
|
||||
});
|
||||
|
||||
describe('DiagramAPI', () => {
|
||||
it('should return default diagrams', () => {
|
||||
@@ -11,13 +16,17 @@ describe('DiagramAPI', () => {
|
||||
});
|
||||
|
||||
it('should throw error if diagram is not defined', () => {
|
||||
expect(() => getDiagram('loki')).toThrow();
|
||||
expect(() => getDiagram('loki')).toThrowErrorMatchingInlineSnapshot(
|
||||
'"Diagram loki not found."'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle diagram registrations', () => {
|
||||
expect(() => getDiagram('loki')).toThrow();
|
||||
expect(() => detectType('loki diagram')).toThrow(
|
||||
'No diagram type detected for text: loki diagram'
|
||||
expect(() => getDiagram('loki')).toThrowErrorMatchingInlineSnapshot(
|
||||
'"Diagram loki not found."'
|
||||
);
|
||||
expect(() => detectType('loki diagram')).toThrowErrorMatchingInlineSnapshot(
|
||||
'"No diagram type detected matching given configuration for text: loki diagram"'
|
||||
);
|
||||
const detector: DiagramDetector = (str: string) => {
|
||||
return str.match('loki') !== null;
|
||||
|
@@ -71,3 +71,9 @@ export const getDiagram = (name: string): DiagramDefinition => {
|
||||
}
|
||||
throw new Error(`Diagram ${name} not found.`);
|
||||
};
|
||||
|
||||
export class DiagramNotFoundError extends Error {
|
||||
constructor(message: string) {
|
||||
super(`Diagram ${message} not found.`);
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,8 @@ export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
||||
|
||||
type FrontMatterMetadata = {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,10 @@ export function extractFrontMatter(text: string, db: DiagramDb): string {
|
||||
db.setDiagramTitle?.(parsed.title);
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
db.setDisplayMode?.(parsed.displayMode);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
} else {
|
||||
return text;
|
||||
|
@@ -16,6 +16,7 @@ export interface InjectUtils {
|
||||
export interface DiagramDb {
|
||||
clear?: () => void;
|
||||
setDiagramTitle?: (title: string) => void;
|
||||
setDisplayMode?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
getAccDescription?: () => string;
|
||||
bindFunctions?: (element: Element) => void;
|
||||
|
@@ -61,8 +61,8 @@ Expecting 'TXT', got 'NEWLINE'"
|
||||
});
|
||||
|
||||
test('should throw the right error for unregistered diagrams', async () => {
|
||||
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowError(
|
||||
'No diagram type detected for text: thor TD; A-->B'
|
||||
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
'"No diagram type detected matching given configuration for text: thor TD; A-->B"'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -1,5 +1,20 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const c4Detector: DiagramDetector = (txt) => {
|
||||
const id = 'c4';
|
||||
|
||||
const detector = (txt: string) => {
|
||||
return txt.match(/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./c4Diagram');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
17
packages/mermaid/src/diagrams/c4/c4Diagram.ts
Normal file
17
packages/mermaid/src/diagrams/c4/c4Diagram.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import c4Parser from './parser/c4Diagram';
|
||||
import c4Db from './c4Db';
|
||||
import c4Renderer from './c4Renderer';
|
||||
import c4Styles from './styles';
|
||||
import { MermaidConfig } from '../../config.type';
|
||||
import { DiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser: c4Parser,
|
||||
db: c4Db,
|
||||
renderer: c4Renderer,
|
||||
styles: c4Styles,
|
||||
init: (cnf: MermaidConfig) => {
|
||||
c4Renderer.setConf(cnf.c4);
|
||||
},
|
||||
};
|
@@ -1,4 +1,5 @@
|
||||
import { select } from 'd3';
|
||||
// @ts-expect-error - d3 types issue
|
||||
import { select, Selection } from 'd3';
|
||||
import { log } from '../../logger';
|
||||
import * as configApi from '../../config';
|
||||
import common from '../common/common';
|
||||
@@ -13,44 +14,54 @@ import {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../../commonDb';
|
||||
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'classid-';
|
||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
||||
|
||||
let relations = [];
|
||||
let classes = {};
|
||||
let notes = [];
|
||||
let relations: ClassRelation[] = [];
|
||||
let classes: ClassMap = {};
|
||||
let notes: ClassNote[] = [];
|
||||
let classCounter = 0;
|
||||
|
||||
let funs = [];
|
||||
let functions: any[] = [];
|
||||
|
||||
const sanitizeText = (txt) => common.sanitizeText(txt, configApi.getConfig());
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig());
|
||||
|
||||
export const parseDirective = function (statement, context, type) {
|
||||
export const parseDirective = function (statement: string, context: string, type: string) {
|
||||
// @ts-ignore Don't wanna mess it up
|
||||
mermaidAPI.parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
const splitClassNameAndType = function (id) {
|
||||
const splitClassNameAndType = function (id: string) {
|
||||
let genericType = '';
|
||||
let className = id;
|
||||
|
||||
if (id.indexOf('~') > 0) {
|
||||
let split = id.split('~');
|
||||
className = split[0];
|
||||
|
||||
genericType = common.sanitizeText(split[1], configApi.getConfig());
|
||||
const split = id.split('~');
|
||||
className = sanitizeText(split[0]);
|
||||
genericType = sanitizeText(split[1]);
|
||||
}
|
||||
|
||||
return { className: className, type: genericType };
|
||||
};
|
||||
|
||||
export const setClassLabel = function (id: string, label: string) {
|
||||
if (label) {
|
||||
label = sanitizeText(label);
|
||||
}
|
||||
|
||||
const { className } = splitClassNameAndType(id);
|
||||
classes[className].label = label;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a node definition has been found.
|
||||
*
|
||||
* @param id
|
||||
* @param id - Id of the class to add
|
||||
* @public
|
||||
*/
|
||||
export const addClass = function (id) {
|
||||
let classId = splitClassNameAndType(id);
|
||||
export const addClass = function (id: string) {
|
||||
const classId = splitClassNameAndType(id);
|
||||
// Only add class if not exists
|
||||
if (classes[classId.className] !== undefined) {
|
||||
return;
|
||||
@@ -59,12 +70,13 @@ export const addClass = function (id) {
|
||||
classes[classId.className] = {
|
||||
id: classId.className,
|
||||
type: classId.type,
|
||||
label: classId.className,
|
||||
cssClasses: [],
|
||||
methods: [],
|
||||
members: [],
|
||||
annotations: [],
|
||||
domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter,
|
||||
};
|
||||
} as ClassNode;
|
||||
|
||||
classCounter++;
|
||||
};
|
||||
@@ -72,35 +84,33 @@ export const addClass = function (id) {
|
||||
/**
|
||||
* Function to lookup domId from id in the graph definition.
|
||||
*
|
||||
* @param id
|
||||
* @param id - class ID to lookup
|
||||
* @public
|
||||
*/
|
||||
export const lookUpDomId = function (id) {
|
||||
const classKeys = Object.keys(classes);
|
||||
for (const classKey of classKeys) {
|
||||
if (classes[classKey].id === id) {
|
||||
return classes[classKey].domId;
|
||||
}
|
||||
export const lookUpDomId = function (id: string): string {
|
||||
if (id in classes) {
|
||||
return classes[id].domId;
|
||||
}
|
||||
throw new Error('Class not found: ' + id);
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
relations = [];
|
||||
classes = {};
|
||||
notes = [];
|
||||
funs = [];
|
||||
funs.push(setupToolTips);
|
||||
functions = [];
|
||||
functions.push(setupToolTips);
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export const getClass = function (id) {
|
||||
export const getClass = function (id: string) {
|
||||
return classes[id];
|
||||
};
|
||||
export const getClasses = function () {
|
||||
return classes;
|
||||
};
|
||||
|
||||
export const getRelations = function () {
|
||||
export const getRelations = function (): ClassRelation[] {
|
||||
return relations;
|
||||
};
|
||||
|
||||
@@ -108,7 +118,7 @@ export const getNotes = function () {
|
||||
return notes;
|
||||
};
|
||||
|
||||
export const addRelation = function (relation) {
|
||||
export const addRelation = function (relation: ClassRelation) {
|
||||
log.debug('Adding relation: ' + JSON.stringify(relation));
|
||||
addClass(relation.id1);
|
||||
addClass(relation.id2);
|
||||
@@ -133,11 +143,11 @@ export const addRelation = function (relation) {
|
||||
* Adds an annotation to the specified class Annotations mark special properties of the given type
|
||||
* (like 'interface' or 'service')
|
||||
*
|
||||
* @param className The class name
|
||||
* @param annotation The name of the annotation without any brackets
|
||||
* @param className - The class name
|
||||
* @param annotation - The name of the annotation without any brackets
|
||||
* @public
|
||||
*/
|
||||
export const addAnnotation = function (className, annotation) {
|
||||
export const addAnnotation = function (className: string, annotation: string) {
|
||||
const validatedClassName = splitClassNameAndType(className).className;
|
||||
classes[validatedClassName].annotations.push(annotation);
|
||||
};
|
||||
@@ -145,13 +155,13 @@ export const addAnnotation = function (className, annotation) {
|
||||
/**
|
||||
* Adds a member to the specified class
|
||||
*
|
||||
* @param className The class name
|
||||
* @param member The full name of the member. If the member is enclosed in <<brackets>> it is
|
||||
* @param className - The class name
|
||||
* @param member - The full name of the member. If the member is enclosed in `<<brackets>>` it is
|
||||
* treated as an annotation If the member is ending with a closing bracket ) it is treated as a
|
||||
* method Otherwise the member will be treated as a normal property
|
||||
* @public
|
||||
*/
|
||||
export const addMember = function (className, member) {
|
||||
export const addMember = function (className: string, member: string) {
|
||||
const validatedClassName = splitClassNameAndType(className).className;
|
||||
const theClass = classes[validatedClassName];
|
||||
|
||||
@@ -161,7 +171,6 @@ export const addMember = function (className, member) {
|
||||
|
||||
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
|
||||
// Remove leading and trailing brackets
|
||||
// theClass.annotations.push(memberString.substring(2, memberString.length - 2));
|
||||
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
|
||||
} else if (memberString.indexOf(')') > 0) {
|
||||
theClass.methods.push(sanitizeText(memberString));
|
||||
@@ -171,14 +180,14 @@ export const addMember = function (className, member) {
|
||||
}
|
||||
};
|
||||
|
||||
export const addMembers = function (className, members) {
|
||||
export const addMembers = function (className: string, members: string[]) {
|
||||
if (Array.isArray(members)) {
|
||||
members.reverse();
|
||||
members.forEach((member) => addMember(className, member));
|
||||
}
|
||||
};
|
||||
|
||||
export const addNote = function (text, className) {
|
||||
export const addNote = function (text: string, className: string) {
|
||||
const note = {
|
||||
id: `note${notes.length}`,
|
||||
class: className,
|
||||
@@ -187,21 +196,20 @@ export const addNote = function (text, className) {
|
||||
notes.push(note);
|
||||
};
|
||||
|
||||
export const cleanupLabel = function (label) {
|
||||
if (label.substring(0, 1) === ':') {
|
||||
return common.sanitizeText(label.substr(1).trim(), configApi.getConfig());
|
||||
} else {
|
||||
return sanitizeText(label.trim());
|
||||
export const cleanupLabel = function (label: string) {
|
||||
if (label.startsWith(':')) {
|
||||
label = label.substring(1);
|
||||
}
|
||||
return sanitizeText(label.trim());
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a special node is found, e.g. a clickable element.
|
||||
*
|
||||
* @param ids Comma separated list of ids
|
||||
* @param className Class to add
|
||||
* @param ids - Comma separated list of ids
|
||||
* @param className - Class to add
|
||||
*/
|
||||
export const setCssClass = function (ids, className) {
|
||||
export const setCssClass = function (ids: string, className: string) {
|
||||
ids.split(',').forEach(function (_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) {
|
||||
@@ -216,28 +224,27 @@ export const setCssClass = function (ids, className) {
|
||||
/**
|
||||
* Called by parser when a tooltip is found, e.g. a clickable element.
|
||||
*
|
||||
* @param ids Comma separated list of ids
|
||||
* @param tooltip Tooltip to add
|
||||
* @param ids - Comma separated list of ids
|
||||
* @param tooltip - Tooltip to add
|
||||
*/
|
||||
const setTooltip = function (ids, tooltip) {
|
||||
const config = configApi.getConfig();
|
||||
const setTooltip = function (ids: string, tooltip?: string) {
|
||||
ids.split(',').forEach(function (id) {
|
||||
if (tooltip !== undefined) {
|
||||
classes[id].tooltip = common.sanitizeText(tooltip, config);
|
||||
classes[id].tooltip = sanitizeText(tooltip);
|
||||
}
|
||||
});
|
||||
};
|
||||
export const getTooltip = function (id) {
|
||||
export const getTooltip = function (id: string) {
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
*
|
||||
* @param ids Comma separated list of ids
|
||||
* @param linkStr URL to create a link for
|
||||
* @param target Target of the link, _blank by default as originally defined in the svgDraw.js file
|
||||
* @param ids - Comma separated list of ids
|
||||
* @param linkStr - URL to create a link for
|
||||
* @param target - Target of the link, _blank by default as originally defined in the svgDraw.js file
|
||||
*/
|
||||
export const setLink = function (ids, linkStr, target) {
|
||||
export const setLink = function (ids: string, linkStr: string, target: string) {
|
||||
const config = configApi.getConfig();
|
||||
ids.split(',').forEach(function (_id) {
|
||||
let id = _id;
|
||||
@@ -261,11 +268,11 @@ export const setLink = function (ids, linkStr, target) {
|
||||
/**
|
||||
* Called by parser when a click definition is found. Registers an event handler.
|
||||
*
|
||||
* @param ids Comma separated list of ids
|
||||
* @param functionName Function to be called on click
|
||||
* @param functionArgs Function args the function should be called with
|
||||
* @param ids - Comma separated list of ids
|
||||
* @param functionName - Function to be called on click
|
||||
* @param functionArgs - Function args the function should be called with
|
||||
*/
|
||||
export const setClickEvent = function (ids, functionName, functionArgs) {
|
||||
export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) {
|
||||
ids.split(',').forEach(function (id) {
|
||||
setClickFunc(id, functionName, functionArgs);
|
||||
classes[id].haveCallback = true;
|
||||
@@ -273,19 +280,19 @@ export const setClickEvent = function (ids, functionName, functionArgs) {
|
||||
setCssClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
const setClickFunc = function (domId, functionName, functionArgs) {
|
||||
const setClickFunc = function (domId: string, functionName: string, functionArgs: string) {
|
||||
const config = configApi.getConfig();
|
||||
let id = domId;
|
||||
let elemId = lookUpDomId(id);
|
||||
|
||||
if (config.securityLevel !== 'loose') {
|
||||
return;
|
||||
}
|
||||
if (functionName === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = domId;
|
||||
if (classes[id] !== undefined) {
|
||||
let argList = [];
|
||||
const elemId = lookUpDomId(id);
|
||||
let argList: string[] = [];
|
||||
if (typeof functionArgs === 'string') {
|
||||
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
|
||||
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
|
||||
@@ -305,7 +312,7 @@ const setClickFunc = function (domId, functionName, functionArgs) {
|
||||
argList.push(elemId);
|
||||
}
|
||||
|
||||
funs.push(function () {
|
||||
functions.push(function () {
|
||||
const elem = document.querySelector(`[id="${elemId}"]`);
|
||||
if (elem !== null) {
|
||||
elem.addEventListener(
|
||||
@@ -320,8 +327,8 @@ const setClickFunc = function (domId, functionName, functionArgs) {
|
||||
}
|
||||
};
|
||||
|
||||
export const bindFunctions = function (element) {
|
||||
funs.forEach(function (fun) {
|
||||
export const bindFunctions = function (element: Element) {
|
||||
functions.forEach(function (fun) {
|
||||
fun(element);
|
||||
});
|
||||
};
|
||||
@@ -339,8 +346,10 @@ export const relationType = {
|
||||
LOLLIPOP: 4,
|
||||
};
|
||||
|
||||
const setupToolTips = function (element) {
|
||||
let tooltipElem = select('.mermaidTooltip');
|
||||
const setupToolTips = function (element: Element) {
|
||||
let tooltipElem: Selection<HTMLDivElement, unknown, HTMLElement, unknown> =
|
||||
select('.mermaidTooltip');
|
||||
// @ts-ignore - _groups is a dynamic property
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0);
|
||||
}
|
||||
@@ -350,12 +359,14 @@ const setupToolTips = function (element) {
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function () {
|
||||
// @ts-expect-error - select is not part of the d3 type definition
|
||||
const el = select(this);
|
||||
const title = el.attr('title');
|
||||
// Dont try to draw a tooltip if no data is provided
|
||||
// Don't try to draw a tooltip if no data is provided
|
||||
if (title === null) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore - getBoundingClientRect is not part of the d3 type definition
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
tooltipElem.transition().duration(200).style('opacity', '.9');
|
||||
@@ -368,15 +379,16 @@ const setupToolTips = function (element) {
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
tooltipElem.transition().duration(500).style('opacity', 0);
|
||||
// @ts-expect-error - select is not part of the d3 type definition
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
};
|
||||
funs.push(setupToolTips);
|
||||
functions.push(setupToolTips);
|
||||
|
||||
let direction = 'TB';
|
||||
const getDirection = () => direction;
|
||||
const setDirection = (dir) => {
|
||||
const setDirection = (dir: string) => {
|
||||
direction = dir;
|
||||
};
|
||||
|
||||
@@ -412,4 +424,5 @@ export default {
|
||||
lookUpDomId,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setClassLabel,
|
||||
};
|
@@ -1,6 +1,8 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const classDetectorV2: DiagramDetector = (txt, config) => {
|
||||
const id = 'classDiagram';
|
||||
|
||||
const detector: DiagramDetector = (txt, config) => {
|
||||
// If we have configured to use dagre-wrapper then we should return true in this function for classDiagram code thus making it use the new class diagram
|
||||
if (
|
||||
txt.match(/^\s*classDiagram/) !== null &&
|
||||
@@ -11,3 +13,16 @@ export const classDetectorV2: DiagramDetector = (txt, config) => {
|
||||
// We have not opted to use the new renderer so we should return true if we detect a class diagram
|
||||
return txt.match(/^\s*classDiagram-v2/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./classDiagram-v2');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const classDetector: DiagramDetector = (txt, config) => {
|
||||
const id = 'class';
|
||||
|
||||
const detector: DiagramDetector = (txt, config) => {
|
||||
// If we have configured to use dagre-wrapper then we should never return true in this function
|
||||
if (config?.class?.defaultRenderer === 'dagre-wrapper') {
|
||||
return false;
|
||||
@@ -8,3 +10,16 @@ export const classDetector: DiagramDetector = (txt, config) => {
|
||||
// We have not opted to use the new renderer so we should return true if we detect a class diagram
|
||||
return txt.match(/^\s*classDiagram/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./classDiagram');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
20
packages/mermaid/src/diagrams/class/classDiagram-v2.ts
Normal file
20
packages/mermaid/src/diagrams/class/classDiagram-v2.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types';
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import parser from './parser/classDiagram';
|
||||
import db from './classDb';
|
||||
import styles from './styles';
|
||||
import renderer from './classRenderer-v2';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
db,
|
||||
renderer,
|
||||
styles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.class) {
|
||||
cnf.class = {};
|
||||
}
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
db.clear();
|
||||
},
|
||||
};
|
@@ -1,10 +1,11 @@
|
||||
// @ts-expect-error Jison doesn't export types
|
||||
import { parser } from './parser/classDiagram';
|
||||
import classDb from './classDb';
|
||||
import { vi } from 'vitest';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
const spyOn = vi.spyOn;
|
||||
|
||||
describe('class diagram, ', function () {
|
||||
describe('when parsing an info graph it', function () {
|
||||
describe('when parsing a class diagram', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
});
|
||||
@@ -189,6 +190,37 @@ describe('class diagram, ', function () {
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle cssClass shorthand with members', () => {
|
||||
parser.parse(`classDiagram-v2
|
||||
class Class10:::exClass2 {
|
||||
int[] id
|
||||
List~int~ ids
|
||||
test(List~int~ ids) List~bool~
|
||||
testArray() bool[]
|
||||
}`);
|
||||
|
||||
expect(classDb.getClass('Class10')).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [
|
||||
"exClass2",
|
||||
],
|
||||
"domId": "classId-Class10-27",
|
||||
"id": "Class10",
|
||||
"label": "Class10",
|
||||
"members": [
|
||||
"int[] id",
|
||||
"List~int~ ids",
|
||||
],
|
||||
"methods": [
|
||||
"test(List~int~ ids) List~bool~",
|
||||
"testArray() bool[]",
|
||||
],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle method statements', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
@@ -541,7 +573,7 @@ foo()
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fetching data from a classDiagram graph it', function () {
|
||||
describe('when fetching data from a classDiagram it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
@@ -946,4 +978,191 @@ foo()
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classDiagram with text labels', () => {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse a class with a text label', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
class C2["Class 2 with chars @?"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 with chars @?');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label and members', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label, members and annotation', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
<<interface>>
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.annotations.length).toBe(1);
|
||||
expect(c1.annotations[0]).toBe('interface');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class shorthand', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
cssClass "C1" styleClass
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css classes', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
class C2["Long long long long long long long long long long label"]
|
||||
C1 --> C2
|
||||
cssClass "C1,C2" styleClass
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Long long long long long long long long long long label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css class shorthands', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass1 {
|
||||
+member1
|
||||
}
|
||||
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
|
||||
C1 --> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass2');
|
||||
});
|
||||
|
||||
it('should parse multiple classes with same text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class with text label"]
|
||||
class C2["Class with text label"]
|
||||
class C3["Class with text label"]
|
||||
C1 --> C2
|
||||
C3 ..> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class with text label');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class with text label');
|
||||
|
||||
const c3 = classDb.getClass('C3');
|
||||
expect(c3.label).toBe('Class with text label');
|
||||
});
|
||||
|
||||
it('should parse classes with different text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["OneWord"]
|
||||
class C2["With, Comma"]
|
||||
class C3["With (Brackets)"]
|
||||
class C4["With [Brackets]"]
|
||||
class C5["With {Brackets}"]
|
||||
class C6[" "]
|
||||
class C7["With 1 number"]
|
||||
class C8["With . period..."]
|
||||
class C9["With - dash"]
|
||||
class C10["With _ underscore"]
|
||||
class C11["With ' single quote"]
|
||||
class C12["With ~!@#$%^&*()_+=-/?"]
|
||||
class C13["With Città foreign language"]
|
||||
`);
|
||||
expect(classDb.getClass('C1').label).toBe('OneWord');
|
||||
expect(classDb.getClass('C2').label).toBe('With, Comma');
|
||||
expect(classDb.getClass('C3').label).toBe('With (Brackets)');
|
||||
expect(classDb.getClass('C4').label).toBe('With [Brackets]');
|
||||
expect(classDb.getClass('C5').label).toBe('With {Brackets}');
|
||||
expect(classDb.getClass('C6').label).toBe(' ');
|
||||
expect(classDb.getClass('C7').label).toBe('With 1 number');
|
||||
expect(classDb.getClass('C8').label).toBe('With . period...');
|
||||
expect(classDb.getClass('C9').label).toBe('With - dash');
|
||||
expect(classDb.getClass('C10').label).toBe('With _ underscore');
|
||||
expect(classDb.getClass('C11').label).toBe("With ' single quote");
|
||||
expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?');
|
||||
expect(classDb.getClass('C13').label).toBe('With Città foreign language');
|
||||
});
|
||||
});
|
||||
});
|
20
packages/mermaid/src/diagrams/class/classDiagram.ts
Normal file
20
packages/mermaid/src/diagrams/class/classDiagram.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types';
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import parser from './parser/classDiagram';
|
||||
import db from './classDb';
|
||||
import styles from './styles';
|
||||
import renderer from './classRenderer';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
db,
|
||||
renderer,
|
||||
styles,
|
||||
init: (cnf) => {
|
||||
if (!cnf.class) {
|
||||
cnf.class = {};
|
||||
}
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
db.clear();
|
||||
},
|
||||
};
|
@@ -1,499 +0,0 @@
|
||||
import { select } from 'd3';
|
||||
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||
import { log } from '../../logger';
|
||||
import { getConfig } from '../../config';
|
||||
import { render } from '../../dagre-wrapper/index.js';
|
||||
import utils from '../../utils';
|
||||
import { curveLinear } from 'd3';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox';
|
||||
import common from '../common/common';
|
||||
|
||||
const sanitizeText = (txt) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
let conf = {
|
||||
dividerMargin: 10,
|
||||
padding: 5,
|
||||
textHeight: 10,
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param {Object<
|
||||
* string,
|
||||
* { cssClasses: string[]; text: string; id: string; type: string; domId: string }
|
||||
* >} classes
|
||||
* Object containing the vertices.
|
||||
* @param {SVGGElement} g The graph that is to be drawn.
|
||||
* @param _id
|
||||
* @param diagObj
|
||||
*/
|
||||
export const addClasses = function (classes, g, _id, diagObj) {
|
||||
// const svg = select(`[id="${svgId}"]`);
|
||||
const keys = Object.keys(classes);
|
||||
log.info('keys:', keys);
|
||||
log.info(classes);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = classes[id];
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
let cssClassStr = '';
|
||||
if (vertex.cssClasses.length > 0) {
|
||||
cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' ');
|
||||
}
|
||||
// if (vertex.classes.length > 0) {
|
||||
// classStr = vertex.classes.join(' ');
|
||||
// }
|
||||
|
||||
const styles = { labelStyle: '' }; //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)) {
|
||||
// const node = {
|
||||
// label: vertexText.replace(
|
||||
// eslint-disable-next-line @cspell/spellchecker
|
||||
// /fa[lrsb]?:fa-[\w-]+/g,
|
||||
// s => `<i class='${s.replace(':', ' ')}'></i>`
|
||||
// )
|
||||
// };
|
||||
// vertexNode = addHtmlLabel(svg, node).node();
|
||||
// vertexNode.parentNode.removeChild(vertexNode);
|
||||
// } else {
|
||||
// const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
// svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
|
||||
|
||||
// const rows = vertexText.split(common.lineBreakRegex);
|
||||
|
||||
// for (let j = 0; j < rows.length; j++) {
|
||||
// const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
// tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||
// tspan.setAttribute('dy', '1em');
|
||||
// tspan.setAttribute('x', '1');
|
||||
// tspan.textContent = rows[j];
|
||||
// svgLabel.appendChild(tspan);
|
||||
// }
|
||||
// vertexNode = svgLabel;
|
||||
// }
|
||||
|
||||
let radious = 0;
|
||||
let _shape = '';
|
||||
// Set the shape based parameters
|
||||
switch (vertex.type) {
|
||||
case 'class':
|
||||
_shape = 'class_box';
|
||||
break;
|
||||
default:
|
||||
_shape = 'class_box';
|
||||
}
|
||||
// Add the node
|
||||
g.setNode(vertex.id, {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: sanitizeText(vertexText),
|
||||
classData: vertex,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: cssClassStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
tooltip: diagObj.db.getTooltip(vertex.id) || '',
|
||||
haveCallback: vertex.haveCallback,
|
||||
link: vertex.link,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
type: vertex.type,
|
||||
padding: getConfig().flowchart.padding,
|
||||
});
|
||||
|
||||
log.info('setNode', {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: cssClassStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
type: vertex.type,
|
||||
padding: getConfig().flowchart.padding,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param {{text: string; class: string; placement: number}[]} notes
|
||||
* Object containing the additional vertices (notes).
|
||||
* @param {SVGGElement} g The graph that is to be drawn.
|
||||
* @param {number} startEdgeId starting index for note edge
|
||||
* @param classes
|
||||
*/
|
||||
export const addNotes = function (notes, g, startEdgeId, classes) {
|
||||
log.info(notes);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
notes.forEach(function (note, i) {
|
||||
const vertex = note;
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
let cssNoteStr = '';
|
||||
|
||||
const styles = { labelStyle: '', style: '' };
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
let vertexText = vertex.text;
|
||||
|
||||
let radious = 0;
|
||||
let _shape = 'note';
|
||||
// Add the node
|
||||
g.setNode(vertex.id, {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: sanitizeText(vertexText),
|
||||
noteData: vertex,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: cssNoteStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.id,
|
||||
tooltip: '',
|
||||
type: 'note',
|
||||
padding: getConfig().flowchart.padding,
|
||||
});
|
||||
|
||||
log.info('setNode', {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
type: 'note',
|
||||
padding: getConfig().flowchart.padding,
|
||||
});
|
||||
|
||||
if (!vertex.class || !(vertex.class in classes)) {
|
||||
return;
|
||||
}
|
||||
const edgeId = startEdgeId + i;
|
||||
const edgeData = {};
|
||||
//Set relationship style and line type
|
||||
edgeData.classes = 'relation';
|
||||
edgeData.pattern = 'dotted';
|
||||
|
||||
edgeData.id = `edgeNote${edgeId}`;
|
||||
// Set link type for rendering
|
||||
edgeData.arrowhead = 'none';
|
||||
|
||||
log.info(`Note edge: ${JSON.stringify(edgeData)}, ${JSON.stringify(vertex)}`);
|
||||
//Set edge extra labels
|
||||
edgeData.startLabelRight = '';
|
||||
edgeData.endLabelLeft = '';
|
||||
|
||||
//Set relation arrow types
|
||||
edgeData.arrowTypeStart = 'none';
|
||||
edgeData.arrowTypeEnd = 'none';
|
||||
let style = 'fill:none';
|
||||
let labelStyle = '';
|
||||
|
||||
edgeData.style = style;
|
||||
edgeData.labelStyle = labelStyle;
|
||||
|
||||
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
|
||||
|
||||
// Add the edge to the graph
|
||||
g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add edges to graph based on parsed graph definition
|
||||
*
|
||||
* @param relations
|
||||
* @param {object} g The graph object
|
||||
*/
|
||||
export const addRelations = function (relations, g) {
|
||||
const conf = getConfig().flowchart;
|
||||
let cnt = 0;
|
||||
|
||||
let defaultStyle;
|
||||
let defaultLabelStyle;
|
||||
|
||||
// if (typeof relations.defaultStyle !== 'undefined') {
|
||||
// const defaultStyles = getStylesFromArray(relations.defaultStyle);
|
||||
// defaultStyle = defaultStyles.style;
|
||||
// defaultLabelStyle = defaultStyles.labelStyle;
|
||||
// }
|
||||
|
||||
relations.forEach(function (edge) {
|
||||
cnt++;
|
||||
const edgeData = {};
|
||||
//Set relationship style and line type
|
||||
edgeData.classes = 'relation';
|
||||
edgeData.pattern = edge.relation.lineType == 1 ? 'dashed' : 'solid';
|
||||
|
||||
edgeData.id = 'id' + cnt;
|
||||
// Set link type for rendering
|
||||
if (edge.type === 'arrow_open') {
|
||||
edgeData.arrowhead = 'none';
|
||||
} else {
|
||||
edgeData.arrowhead = 'normal';
|
||||
}
|
||||
|
||||
log.info(edgeData, edge);
|
||||
//Set edge extra labels
|
||||
//edgeData.startLabelLeft = edge.relationTitle1;
|
||||
edgeData.startLabelRight = edge.relationTitle1 === 'none' ? '' : edge.relationTitle1;
|
||||
edgeData.endLabelLeft = edge.relationTitle2 === 'none' ? '' : edge.relationTitle2;
|
||||
//edgeData.endLabelRight = edge.relationTitle2;
|
||||
|
||||
//Set relation arrow types
|
||||
edgeData.arrowTypeStart = getArrowMarker(edge.relation.type1);
|
||||
edgeData.arrowTypeEnd = getArrowMarker(edge.relation.type2);
|
||||
let style = '';
|
||||
let labelStyle = '';
|
||||
|
||||
if (edge.style !== undefined) {
|
||||
const styles = getStylesFromArray(edge.style);
|
||||
style = styles.style;
|
||||
labelStyle = styles.labelStyle;
|
||||
} else {
|
||||
style = 'fill:none';
|
||||
if (defaultStyle !== undefined) {
|
||||
style = defaultStyle;
|
||||
}
|
||||
if (defaultLabelStyle !== undefined) {
|
||||
labelStyle = defaultLabelStyle;
|
||||
}
|
||||
}
|
||||
|
||||
edgeData.style = style;
|
||||
edgeData.labelStyle = labelStyle;
|
||||
|
||||
if (edge.interpolate !== undefined) {
|
||||
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
|
||||
} else if (relations.defaultInterpolate !== undefined) {
|
||||
edgeData.curve = interpolateToCurve(relations.defaultInterpolate, curveLinear);
|
||||
} else {
|
||||
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
|
||||
}
|
||||
|
||||
edge.text = edge.title;
|
||||
if (edge.text === undefined) {
|
||||
if (edge.style !== undefined) {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
}
|
||||
} else {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
edgeData.labelpos = 'c';
|
||||
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
edgeData.labelType = 'html';
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
|
||||
} else {
|
||||
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:');
|
||||
}
|
||||
}
|
||||
// Add the edge to the graph
|
||||
g.setEdge(edge.id1, edge.id2, edgeData, cnt);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the value of `conf` with the passed `cnf`
|
||||
*
|
||||
* @param {object} cnf Config to merge
|
||||
*/
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
*
|
||||
* @param {string} text
|
||||
* @param {string} id
|
||||
* @param _version
|
||||
* @param diagObj
|
||||
*/
|
||||
export const draw = function (text, id, _version, diagObj) {
|
||||
log.info('Drawing class - ', id);
|
||||
|
||||
const conf = getConfig().flowchart;
|
||||
const securityLevel = getConfig().securityLevel;
|
||||
log.info('config:', conf);
|
||||
const nodeSpacing = conf.nodeSpacing || 50;
|
||||
const rankSpacing = conf.rankSpacing || 50;
|
||||
|
||||
// Create the input mermaid.graph
|
||||
const g = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true,
|
||||
})
|
||||
.setGraph({
|
||||
rankdir: diagObj.db.getDirection(),
|
||||
nodesep: nodeSpacing,
|
||||
ranksep: rankSpacing,
|
||||
marginx: 8,
|
||||
marginy: 8,
|
||||
})
|
||||
.setDefaultEdgeLabel(function () {
|
||||
return {};
|
||||
});
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const classes = diagObj.db.getClasses();
|
||||
const relations = diagObj.db.getRelations();
|
||||
const notes = diagObj.db.getNotes();
|
||||
|
||||
log.info(relations);
|
||||
addClasses(classes, g, id, diagObj);
|
||||
addRelations(relations, g);
|
||||
addNotes(notes, g, relations.length + 1, classes);
|
||||
|
||||
// Add custom shapes
|
||||
// flowChartShapes.addToRenderV2(addShape);
|
||||
|
||||
// Set up an SVG group so that we can translate the final graph.
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: select('body');
|
||||
const svg = root.select(`[id="${id}"]`);
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
const element = root.select('#' + id + ' g');
|
||||
render(
|
||||
element,
|
||||
g,
|
||||
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'],
|
||||
'classDiagram',
|
||||
id
|
||||
);
|
||||
|
||||
utils.insertTitle(svg, 'classTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
|
||||
|
||||
setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
|
||||
|
||||
// Add label rects for non html labels
|
||||
if (!conf.htmlLabels) {
|
||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||
const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
|
||||
for (const label of labels) {
|
||||
// Get dimensions of label
|
||||
const dim = label.getBBox();
|
||||
|
||||
const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('rx', 0);
|
||||
rect.setAttribute('ry', 0);
|
||||
rect.setAttribute('width', dim.width);
|
||||
rect.setAttribute('height', dim.height);
|
||||
// rect.setAttribute('style', 'fill:#e8e8e8;');
|
||||
|
||||
label.insertBefore(rect, label.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// If node has a link, wrap it in an anchor SVG object.
|
||||
// const keys = Object.keys(classes);
|
||||
// keys.forEach(function(key) {
|
||||
// const vertex = classes[key];
|
||||
|
||||
// if (vertex.link) {
|
||||
// const node = select('#' + id + ' [id="' + key + '"]');
|
||||
// if (node) {
|
||||
// const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
|
||||
// link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
|
||||
// link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
|
||||
// link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
|
||||
|
||||
// const linkNode = node.insert(function() {
|
||||
// return link;
|
||||
// }, ':first-child');
|
||||
|
||||
// const shape = node.select('.label-container');
|
||||
// if (shape) {
|
||||
// linkNode.append(function() {
|
||||
// return shape.node();
|
||||
// });
|
||||
// }
|
||||
|
||||
// const label = node.select('.label');
|
||||
// if (label) {
|
||||
// linkNode.append(function() {
|
||||
// return label.node();
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the arrow marker for a type index
|
||||
*
|
||||
* @param {number} type The type to look for
|
||||
* @returns {'aggregation' | 'extension' | 'composition' | 'dependency'} The arrow marker
|
||||
*/
|
||||
function getArrowMarker(type) {
|
||||
let marker;
|
||||
switch (type) {
|
||||
case 0:
|
||||
marker = 'aggregation';
|
||||
break;
|
||||
case 1:
|
||||
marker = 'extension';
|
||||
break;
|
||||
case 2:
|
||||
marker = 'composition';
|
||||
break;
|
||||
case 3:
|
||||
marker = 'dependency';
|
||||
break;
|
||||
case 4:
|
||||
marker = 'lollipop';
|
||||
break;
|
||||
default:
|
||||
marker = 'none';
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw,
|
||||
};
|
368
packages/mermaid/src/diagrams/class/classRenderer-v2.ts
Normal file
368
packages/mermaid/src/diagrams/class/classRenderer-v2.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
// @ts-ignore d3 types are not available
|
||||
import { select, curveLinear } from 'd3';
|
||||
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||
import { log } from '../../logger';
|
||||
import { getConfig } from '../../config';
|
||||
import { render } from '../../dagre-wrapper/index.js';
|
||||
import utils from '../../utils';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox';
|
||||
import common from '../common/common';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes';
|
||||
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
let conf = {
|
||||
dividerMargin: 10,
|
||||
padding: 5,
|
||||
textHeight: 10,
|
||||
curve: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param classes - Object containing the vertices.
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
*/
|
||||
export const addClasses = function (
|
||||
classes: ClassMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
) {
|
||||
const keys = Object.keys(classes);
|
||||
log.info('keys:', keys);
|
||||
log.info(classes);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = classes[id];
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
*/
|
||||
let cssClassStr = '';
|
||||
if (vertex.cssClasses.length > 0) {
|
||||
cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' ');
|
||||
}
|
||||
|
||||
const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles);
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
const vertexText = vertex.label ?? vertex.id;
|
||||
const radius = 0;
|
||||
const shape = 'class_box';
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: shape,
|
||||
labelText: sanitizeText(vertexText),
|
||||
classData: vertex,
|
||||
rx: radius,
|
||||
ry: radius,
|
||||
class: cssClassStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
tooltip: diagObj.db.getTooltip(vertex.id) || '',
|
||||
haveCallback: vertex.haveCallback,
|
||||
link: vertex.link,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
type: vertex.type,
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
g.setNode(vertex.id, node);
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param notes - Object containing the additional vertices (notes).
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param startEdgeId - starting index for note edge
|
||||
* @param classes - Classes
|
||||
*/
|
||||
export const addNotes = function (
|
||||
notes: ClassNote[],
|
||||
g: graphlib.Graph,
|
||||
startEdgeId: number,
|
||||
classes: ClassMap
|
||||
) {
|
||||
log.info(notes);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
notes.forEach(function (note, i) {
|
||||
const vertex = note;
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
*
|
||||
*/
|
||||
const cssNoteStr = '';
|
||||
|
||||
const styles = { labelStyle: '', style: '' };
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
const vertexText = vertex.text;
|
||||
|
||||
const radius = 0;
|
||||
const shape = 'note';
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: shape,
|
||||
labelText: sanitizeText(vertexText),
|
||||
noteData: vertex,
|
||||
rx: radius,
|
||||
ry: radius,
|
||||
class: cssNoteStr,
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.id,
|
||||
tooltip: '',
|
||||
type: 'note',
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
g.setNode(vertex.id, node);
|
||||
log.info('setNode', node);
|
||||
|
||||
if (!vertex.class || !(vertex.class in classes)) {
|
||||
return;
|
||||
}
|
||||
const edgeId = startEdgeId + i;
|
||||
|
||||
const edgeData: EdgeData = {
|
||||
id: `edgeNote${edgeId}`,
|
||||
//Set relationship style and line type
|
||||
classes: 'relation',
|
||||
pattern: 'dotted',
|
||||
// Set link type for rendering
|
||||
arrowhead: 'none',
|
||||
//Set edge extra labels
|
||||
startLabelRight: '',
|
||||
endLabelLeft: '',
|
||||
//Set relation arrow types
|
||||
arrowTypeStart: 'none',
|
||||
arrowTypeEnd: 'none',
|
||||
style: 'fill:none',
|
||||
labelStyle: '',
|
||||
curve: interpolateToCurve(conf.curve, curveLinear),
|
||||
};
|
||||
|
||||
// Add the edge to the graph
|
||||
g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add edges to graph based on parsed graph definition
|
||||
*
|
||||
* @param relations -
|
||||
* @param g - The graph object
|
||||
*/
|
||||
export const addRelations = function (relations: ClassRelation[], g: graphlib.Graph) {
|
||||
const conf = getConfig().flowchart;
|
||||
let cnt = 0;
|
||||
|
||||
relations.forEach(function (edge) {
|
||||
cnt++;
|
||||
const edgeData: EdgeData = {
|
||||
//Set relationship style and line type
|
||||
classes: 'relation',
|
||||
pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid',
|
||||
id: 'id' + cnt,
|
||||
// Set link type for rendering
|
||||
arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal',
|
||||
//Set edge extra labels
|
||||
startLabelRight: edge.relationTitle1 === 'none' ? '' : edge.relationTitle1,
|
||||
endLabelLeft: edge.relationTitle2 === 'none' ? '' : edge.relationTitle2,
|
||||
//Set relation arrow types
|
||||
arrowTypeStart: getArrowMarker(edge.relation.type1),
|
||||
arrowTypeEnd: getArrowMarker(edge.relation.type2),
|
||||
style: 'fill:none',
|
||||
labelStyle: '',
|
||||
curve: interpolateToCurve(conf?.curve, curveLinear),
|
||||
};
|
||||
|
||||
log.info(edgeData, edge);
|
||||
|
||||
if (edge.style !== undefined) {
|
||||
const styles = getStylesFromArray(edge.style);
|
||||
edgeData.style = styles.style;
|
||||
edgeData.labelStyle = styles.labelStyle;
|
||||
}
|
||||
|
||||
edge.text = edge.title;
|
||||
if (edge.text === undefined) {
|
||||
if (edge.style !== undefined) {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
}
|
||||
} else {
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
edgeData.labelpos = 'c';
|
||||
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
if (getConfig().flowchart?.htmlLabels ?? getConfig().htmlLabels) {
|
||||
edgeData.labelType = 'html';
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
|
||||
} else {
|
||||
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:');
|
||||
}
|
||||
}
|
||||
// Add the edge to the graph
|
||||
g.setEdge(edge.id1, edge.id2, edgeData, cnt);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the value of `conf` with the passed `cnf`
|
||||
*
|
||||
* @param cnf - Config to merge
|
||||
*/
|
||||
export const setConf = function (cnf: any) {
|
||||
conf = {
|
||||
...conf,
|
||||
...cnf,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
*
|
||||
* @param text -
|
||||
* @param id -
|
||||
* @param _version -
|
||||
* @param diagObj -
|
||||
*/
|
||||
export const draw = function (text: string, id: string, _version: string, diagObj: any) {
|
||||
log.info('Drawing class - ', id);
|
||||
|
||||
// TODO V10: Why flowchart? Might be a mistake when copying.
|
||||
const conf = getConfig().flowchart ?? getConfig().class;
|
||||
const securityLevel = getConfig().securityLevel;
|
||||
log.info('config:', conf);
|
||||
const nodeSpacing = conf?.nodeSpacing ?? 50;
|
||||
const rankSpacing = conf?.rankSpacing ?? 50;
|
||||
|
||||
// Create the input mermaid.graph
|
||||
const g: graphlib.Graph = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true,
|
||||
})
|
||||
.setGraph({
|
||||
rankdir: diagObj.db.getDirection(),
|
||||
nodesep: nodeSpacing,
|
||||
ranksep: rankSpacing,
|
||||
marginx: 8,
|
||||
marginy: 8,
|
||||
})
|
||||
.setDefaultEdgeLabel(function () {
|
||||
return {};
|
||||
});
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const classes: ClassMap = diagObj.db.getClasses();
|
||||
const relations: ClassRelation[] = diagObj.db.getRelations();
|
||||
const notes: ClassNote[] = diagObj.db.getNotes();
|
||||
log.info(relations);
|
||||
addClasses(classes, g, id, diagObj);
|
||||
addRelations(relations, g);
|
||||
addNotes(notes, g, relations.length + 1, classes);
|
||||
|
||||
// Set up an SVG group so that we can translate the final graph.
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? // @ts-ignore Ignore type error for now
|
||||
|
||||
select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: select('body');
|
||||
// @ts-ignore Ignore type error for now
|
||||
const svg = root.select(`[id="${id}"]`);
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
// @ts-ignore Ignore type error for now
|
||||
const element = root.select('#' + id + ' g');
|
||||
render(
|
||||
element,
|
||||
g,
|
||||
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'],
|
||||
'classDiagram',
|
||||
id
|
||||
);
|
||||
|
||||
utils.insertTitle(svg, 'classTitleText', conf?.titleTopMargin ?? 5, diagObj.db.getDiagramTitle());
|
||||
|
||||
setupGraphViewbox(g, svg, conf?.diagramPadding, conf?.useMaxWidth);
|
||||
|
||||
// Add label rects for non html labels
|
||||
if (!conf?.htmlLabels) {
|
||||
// @ts-ignore Ignore type error for now
|
||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||
const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
|
||||
for (const label of labels) {
|
||||
// Get dimensions of label
|
||||
const dim = label.getBBox();
|
||||
|
||||
const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('rx', 0);
|
||||
rect.setAttribute('ry', 0);
|
||||
rect.setAttribute('width', dim.width);
|
||||
rect.setAttribute('height', dim.height);
|
||||
|
||||
label.insertBefore(rect, label.firstChild);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the arrow marker for a type index
|
||||
*
|
||||
* @param type - The type to look for
|
||||
* @returns The arrow marker
|
||||
*/
|
||||
function getArrowMarker(type: number) {
|
||||
let marker;
|
||||
switch (type) {
|
||||
case 0:
|
||||
marker = 'aggregation';
|
||||
break;
|
||||
case 1:
|
||||
marker = 'extension';
|
||||
break;
|
||||
case 2:
|
||||
marker = 'composition';
|
||||
break;
|
||||
case 3:
|
||||
marker = 'dependency';
|
||||
break;
|
||||
case 4:
|
||||
marker = 'lollipop';
|
||||
break;
|
||||
default:
|
||||
marker = 'none';
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw,
|
||||
};
|
55
packages/mermaid/src/diagrams/class/classTypes.ts
Normal file
55
packages/mermaid/src/diagrams/class/classTypes.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export interface ClassNode {
|
||||
id: string;
|
||||
type: string;
|
||||
label: string;
|
||||
cssClasses: string[];
|
||||
methods: string[];
|
||||
members: string[];
|
||||
annotations: string[];
|
||||
domId: string;
|
||||
link?: string;
|
||||
linkTarget?: string;
|
||||
haveCallback?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export interface ClassNote {
|
||||
id: string;
|
||||
class: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface EdgeData {
|
||||
arrowheadStyle?: string;
|
||||
labelpos?: string;
|
||||
labelType?: string;
|
||||
label?: string;
|
||||
classes: string;
|
||||
pattern: string;
|
||||
id: string;
|
||||
arrowhead: string;
|
||||
startLabelRight: string;
|
||||
endLabelLeft: string;
|
||||
arrowTypeStart: string;
|
||||
arrowTypeEnd: string;
|
||||
style: string;
|
||||
labelStyle: string;
|
||||
curve: any;
|
||||
}
|
||||
|
||||
export type ClassRelation = {
|
||||
id1: string;
|
||||
id2: string;
|
||||
relationTitle1: string;
|
||||
relationTitle2: string;
|
||||
type: string;
|
||||
title: string;
|
||||
text: string;
|
||||
style: string[];
|
||||
relation: {
|
||||
type1: number;
|
||||
type2: number;
|
||||
lineType: number;
|
||||
};
|
||||
};
|
||||
export type ClassMap = Record<string, ClassNode>;
|
@@ -119,6 +119,8 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
"=" return 'EQUALS';
|
||||
\= return 'EQUALS';
|
||||
\w+ return 'ALPHA';
|
||||
"[" return 'SQS';
|
||||
"]" return 'SQE';
|
||||
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
[0-9]+ return 'NUM';
|
||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
@@ -249,6 +251,10 @@ statements
|
||||
| statement NEWLINE statements
|
||||
;
|
||||
|
||||
classLabel
|
||||
: SQS STR SQE { $$=$2; }
|
||||
;
|
||||
|
||||
className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| classLiteralName { $$=$1; }
|
||||
@@ -274,10 +280,15 @@ statement
|
||||
;
|
||||
|
||||
classStatement
|
||||
: CLASS className {yy.addClass($2);}
|
||||
| CLASS className STYLE_SEPARATOR alphaNumToken {yy.addClass($2);yy.setCssClass($2, $4);}
|
||||
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);}
|
||||
| CLASS className STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.addClass($2);yy.setCssClass($2, $4);yy.addMembers($2,$6);}
|
||||
: classIdentifier
|
||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
|
||||
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
|
||||
;
|
||||
|
||||
classIdentifier
|
||||
: CLASS className {$$=$2; yy.addClass($2);}
|
||||
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
|
||||
;
|
||||
|
||||
annotationStatement
|
||||
|
@@ -1,5 +1,20 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const erDetector: DiagramDetector = (txt) => {
|
||||
const id = 'er';
|
||||
|
||||
const detector: DiagramDetector = (txt) => {
|
||||
return txt.match(/^\s*erDiagram/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./erDiagram');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
12
packages/mermaid/src/diagrams/er/erDiagram.ts
Normal file
12
packages/mermaid/src/diagrams/er/erDiagram.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import erParser from './parser/erDiagram';
|
||||
import erDb from './erDb';
|
||||
import erRenderer from './erRenderer';
|
||||
import erStyles from './styles';
|
||||
|
||||
export const diagram = {
|
||||
parser: erParser,
|
||||
db: erDb,
|
||||
renderer: erRenderer,
|
||||
styles: erStyles,
|
||||
};
|
@@ -19,8 +19,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
\%%(?!\{)[^\n]* /* skip comments */
|
||||
[^\}]\%\%[^\n]* /* skip comments */
|
||||
[\n]+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
[\s]+ return 'SPACE';
|
||||
@@ -81,7 +79,7 @@ start
|
||||
|
||||
document
|
||||
: /* empty */ { $$ = [] }
|
||||
| document line {$1.push($2);$$ = $1}
|
||||
| document line {$1.push($2);$$ = $1}
|
||||
;
|
||||
|
||||
line
|
||||
|
23
packages/mermaid/src/diagrams/error/errorDiagram.ts
Normal file
23
packages/mermaid/src/diagrams/error/errorDiagram.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types';
|
||||
import styles from './styles';
|
||||
import renderer from './errorRenderer';
|
||||
export const diagram: DiagramDefinition = {
|
||||
db: {
|
||||
clear: () => {
|
||||
// Quite ok, clear needs to be there for error to work as a regular diagram
|
||||
},
|
||||
},
|
||||
styles,
|
||||
renderer,
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
},
|
||||
init: () => {
|
||||
// no op
|
||||
},
|
||||
};
|
||||
|
||||
export default diagram;
|
@@ -4,15 +4,13 @@ import { select } from 'd3';
|
||||
import { log } from '../../logger';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
|
||||
let conf = {};
|
||||
|
||||
/**
|
||||
* Merges the value of `conf` with the passed `cnf`
|
||||
*
|
||||
* @param cnf - Config to merge
|
||||
*/
|
||||
export const setConf = function (cnf: any) {
|
||||
conf = { ...conf, ...cnf };
|
||||
export const setConf = function () {
|
||||
// no-op
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,7 +76,7 @@ export const draw = (_text: string, id: string, mermaidVersion: string) => {
|
||||
.attr('y', 250)
|
||||
.attr('font-size', '150px')
|
||||
.style('text-anchor', 'middle')
|
||||
.text('Syntax error in graph');
|
||||
.text('Syntax error in text');
|
||||
g.append('text') // text label for the x axis
|
||||
.attr('class', 'error-text')
|
||||
.attr('x', 1250)
|
||||
|
@@ -3,16 +3,17 @@ 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';
|
||||
import { labelHelper } from '../../../dagre-wrapper/shapes/util';
|
||||
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 ELK from 'elkjs/lib/elk.bundled.js';
|
||||
const elk = new ELK();
|
||||
|
||||
let elk;
|
||||
|
||||
const portPos = {};
|
||||
let portPos = {};
|
||||
|
||||
const conf = {};
|
||||
export const setConf = function (cnf) {
|
||||
@@ -52,7 +53,7 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
|
||||
if (vertex.classes.length > 0) {
|
||||
classStr = vertex.classes.join(' ');
|
||||
}
|
||||
|
||||
classStr = classStr + ' flowchart-label';
|
||||
const styles = getStylesFromArray(vertex.styles);
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
@@ -61,40 +62,6 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
|
||||
// We create a SVG label, either by delegating to addHtmlLabel or manually
|
||||
let vertexNode;
|
||||
const labelData = { width: 0, height: 0 };
|
||||
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();
|
||||
const bbox = vertexNode.getBBox();
|
||||
labelData.width = bbox.width;
|
||||
labelData.height = bbox.height;
|
||||
labelData.labelNode = vertexNode;
|
||||
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;
|
||||
const bbox = vertexNode.getBBox();
|
||||
labelData.width = bbox.width;
|
||||
labelData.height = bbox.height;
|
||||
labelData.labelNode = vertexNode;
|
||||
}
|
||||
|
||||
const ports = [
|
||||
{
|
||||
@@ -186,11 +153,13 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
|
||||
default:
|
||||
_shape = 'rect';
|
||||
}
|
||||
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
labelType: vertex.labelType,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
@@ -209,10 +178,33 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
|
||||
};
|
||||
let boundingBox;
|
||||
let nodeEl;
|
||||
|
||||
// Add the element to the DOM
|
||||
if (node.type !== 'group') {
|
||||
nodeEl = insertNode(nodes, node, vertex.dir);
|
||||
boundingBox = nodeEl.node().getBBox();
|
||||
} 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;
|
||||
// const bbox = vertexNode.getBBox();
|
||||
const { shapeSvg, bbox } = labelHelper(nodes, node, undefined, true);
|
||||
labelData.width = bbox.width;
|
||||
labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
|
||||
labelData.height = bbox.height;
|
||||
labelData.labelNode = shapeSvg.node();
|
||||
node.labelData = labelData;
|
||||
}
|
||||
// const { shapeSvg, bbox } = labelHelper(svg, node, undefined, true);
|
||||
|
||||
const data = {
|
||||
id: vertex.id,
|
||||
@@ -371,6 +363,10 @@ const getEdgeStartEndPoint = (edge, dir) => {
|
||||
let source = edge.start;
|
||||
let target = edge.end;
|
||||
|
||||
// Save the original source and target
|
||||
const sourceId = source;
|
||||
const targetId = target;
|
||||
|
||||
const startNode = nodeDb[source];
|
||||
const endNode = nodeDb[target];
|
||||
|
||||
@@ -387,7 +383,7 @@ const getEdgeStartEndPoint = (edge, dir) => {
|
||||
}
|
||||
|
||||
// Add the edge to the graph
|
||||
return { source, target };
|
||||
return { source, target, sourceId, targetId };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -516,7 +512,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
edgeData.labelpos = 'c';
|
||||
}
|
||||
|
||||
edgeData.labelType = 'text';
|
||||
edgeData.labelType = edge.labelType;
|
||||
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
|
||||
|
||||
if (edge.style === undefined) {
|
||||
@@ -530,14 +526,17 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
|
||||
const labelEl = insertEdgeLabel(labelsEl, edgeData);
|
||||
|
||||
// calculate start and end points of the edge
|
||||
const { source, target } = getEdgeStartEndPoint(edge, dir);
|
||||
// calculate start and end points of the edge, note that the source and target
|
||||
// can be modified for shapes that have ports
|
||||
const { source, target, sourceId, targetId } = 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: [source],
|
||||
targets: [target],
|
||||
sourceId,
|
||||
targetId,
|
||||
labelEl: labelEl,
|
||||
labels: [
|
||||
{
|
||||
@@ -698,7 +697,7 @@ const calcOffset = function (src, dest, parentLookupDb) {
|
||||
};
|
||||
|
||||
const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
|
||||
const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb);
|
||||
const offset = calcOffset(edge.sourceId, edge.targetId, parentLookupDb);
|
||||
|
||||
const src = edge.sections[0].startPoint;
|
||||
const dest = edge.sections[0].endPoint;
|
||||
@@ -765,13 +764,10 @@ const insertChildren = (nodeArray, parentLookupDb) => {
|
||||
*/
|
||||
|
||||
export const draw = async function (text, id, _version, diagObj) {
|
||||
if (!elk) {
|
||||
const ELK = (await import('elkjs/lib/elk.bundled.js')).default;
|
||||
elk = new ELK();
|
||||
}
|
||||
// Add temporary render element
|
||||
diagObj.db.clear();
|
||||
nodeDb = {};
|
||||
portPos = {};
|
||||
diagObj.db.setGen('gen-2');
|
||||
// Parse the graph definition
|
||||
diagObj.parser.parse(text);
|
||||
@@ -842,9 +838,17 @@ export const draw = async function (text, id, _version, diagObj) {
|
||||
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);
|
||||
diagObj.db.addVertex(
|
||||
subG.id,
|
||||
{ text: subG.title, type: subG.labelType },
|
||||
'group',
|
||||
undefined,
|
||||
subG.classes,
|
||||
subG.dir
|
||||
);
|
||||
}
|
||||
|
||||
// debugger;
|
||||
// Add an element in the svg to be used to hold the subgraphs container
|
||||
// elements
|
||||
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
|
||||
@@ -857,7 +861,7 @@ export const draw = async function (text, id, _version, diagObj) {
|
||||
// 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);
|
||||
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph, svg);
|
||||
|
||||
// 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');
|
||||
@@ -884,6 +888,8 @@ export const draw = async function (text, id, _version, diagObj) {
|
||||
},
|
||||
width: node.labelData.width,
|
||||
height: node.labelData.height,
|
||||
// width: 100,
|
||||
// height: 100,
|
||||
},
|
||||
];
|
||||
delete node.x;
|
||||
@@ -892,6 +898,7 @@ export const draw = async function (text, id, _version, diagObj) {
|
||||
delete node.height;
|
||||
}
|
||||
});
|
||||
|
||||
insertChildren(graph.children, parentLookupDb);
|
||||
log.info('after layout', JSON.stringify(graph, null, 2));
|
||||
const g = await elk.layout(graph);
|
||||
|
@@ -81,7 +81,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
.edgeLabel {
|
||||
background-color: ${options.edgeLabelBackground};
|
||||
rect {
|
||||
opacity: 0.5;
|
||||
opacity: 0.85;
|
||||
background-color: ${options.edgeLabelBackground};
|
||||
fill: ${options.edgeLabelBackground};
|
||||
}
|
||||
@@ -132,6 +132,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
// fill:#ccc;
|
||||
// // stroke:black;
|
||||
// }
|
||||
|
||||
.flowchart-label text {
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
${genSections(options)}
|
||||
`;
|
||||
|
||||
|
@@ -59,13 +59,14 @@ export const lookUpDomId = function (id) {
|
||||
*
|
||||
* @param _id
|
||||
* @param text
|
||||
* @param textObj
|
||||
* @param type
|
||||
* @param style
|
||||
* @param classes
|
||||
* @param dir
|
||||
* @param props
|
||||
*/
|
||||
export const addVertex = function (_id, text, type, style, classes, dir, props = {}) {
|
||||
export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) {
|
||||
let txt;
|
||||
let id = _id;
|
||||
if (id === undefined) {
|
||||
@@ -80,16 +81,17 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
|
||||
if (vertices[id] === undefined) {
|
||||
vertices[id] = {
|
||||
id: id,
|
||||
labelType: 'text',
|
||||
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter,
|
||||
styles: [],
|
||||
classes: [],
|
||||
};
|
||||
}
|
||||
vertexCounter++;
|
||||
if (text !== undefined) {
|
||||
if (textObj !== undefined) {
|
||||
config = configApi.getConfig();
|
||||
txt = sanitizeText(text.trim());
|
||||
|
||||
txt = sanitizeText(textObj.text.trim());
|
||||
vertices[id].labelType = textObj.type;
|
||||
// strip quotes if string starts and ends with a quote
|
||||
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
||||
txt = txt.substring(1, txt.length - 1);
|
||||
@@ -131,24 +133,27 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
|
||||
* @param _end
|
||||
* @param type
|
||||
* @param linkText
|
||||
* @param linkTextObj
|
||||
*/
|
||||
export const addSingleLink = function (_start, _end, type, linkText) {
|
||||
export const addSingleLink = function (_start, _end, type) {
|
||||
let start = _start;
|
||||
let end = _end;
|
||||
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
|
||||
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
|
||||
// log.info('Got edge...', start, end);
|
||||
|
||||
const edge = { start: start, end: end, type: undefined, text: '' };
|
||||
linkText = type.text;
|
||||
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
|
||||
log.info('abc78 Got edge...', edge);
|
||||
const linkTextObj = type.text;
|
||||
|
||||
if (linkText !== undefined) {
|
||||
edge.text = sanitizeText(linkText.trim());
|
||||
if (linkTextObj !== undefined) {
|
||||
edge.text = sanitizeText(linkTextObj.text.trim());
|
||||
|
||||
// strip quotes if string starts and ends with a quote
|
||||
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
||||
edge.text = edge.text.substring(1, edge.text.length - 1);
|
||||
}
|
||||
edge.labelType = linkTextObj.type;
|
||||
}
|
||||
|
||||
if (type !== undefined) {
|
||||
@@ -158,11 +163,12 @@ export const addSingleLink = function (_start, _end, type, linkText) {
|
||||
}
|
||||
edges.push(edge);
|
||||
};
|
||||
export const addLink = function (_start, _end, type, linktext) {
|
||||
export const addLink = function (_start, _end, type) {
|
||||
log.info('addLink (abc78)', _start, _end, type);
|
||||
let i, j;
|
||||
for (i = 0; i < _start.length; i++) {
|
||||
for (j = 0; j < _end.length; j++) {
|
||||
addSingleLink(_start[i], _end[j], type, linktext);
|
||||
addSingleLink(_start[i], _end[j], type);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -457,10 +463,9 @@ export const defaultStyle = function () {
|
||||
* @param _title
|
||||
*/
|
||||
export const addSubGraph = function (_id, list, _title) {
|
||||
// console.log('addSubGraph', _id, list, _title);
|
||||
let id = _id.trim();
|
||||
let title = _title;
|
||||
if (_id === _title && _title.match(/\s/)) {
|
||||
let id = _id.text.trim();
|
||||
let title = _title.text;
|
||||
if (_id === _title && _title.text.match(/\s/)) {
|
||||
id = undefined;
|
||||
}
|
||||
/** @param a */
|
||||
@@ -502,7 +507,14 @@ export const addSubGraph = function (_id, list, _title) {
|
||||
title = title || '';
|
||||
title = sanitizeText(title);
|
||||
subCount = subCount + 1;
|
||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [], dir };
|
||||
const subGraph = {
|
||||
id: id,
|
||||
nodes: nodeList,
|
||||
title: title.trim(),
|
||||
classes: [],
|
||||
dir,
|
||||
labelType: _title.type,
|
||||
};
|
||||
|
||||
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
|
||||
|
||||
|
@@ -1,16 +1,32 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const flowDetectorV2: DiagramDetector = (txt, config) => {
|
||||
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
|
||||
return false;
|
||||
}
|
||||
if (config?.flowchart?.defaultRenderer === 'elk') {
|
||||
const id = 'flowchart-v2';
|
||||
|
||||
const detector: DiagramDetector = (txt, config) => {
|
||||
if (
|
||||
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
|
||||
config?.flowchart?.defaultRenderer === 'elk'
|
||||
) {
|
||||
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) {
|
||||
if (txt.match(/^\s*graph/) !== null && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
||||
return true;
|
||||
}
|
||||
return txt.match(/^\s*flowchart/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./flowDiagram-v2');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
@@ -1,13 +1,28 @@
|
||||
import type { DiagramDetector } from '../../diagram-api/types';
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types';
|
||||
|
||||
export const flowDetector: DiagramDetector = (txt, config) => {
|
||||
const id = 'flowchart';
|
||||
|
||||
const detector: DiagramDetector = (txt, config) => {
|
||||
// If we have conferred to only use new flow charts this function should always return false
|
||||
// as in not signalling true for a legacy flowchart
|
||||
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
||||
return false;
|
||||
}
|
||||
if (config?.flowchart?.defaultRenderer === 'elk') {
|
||||
if (
|
||||
config?.flowchart?.defaultRenderer === 'dagre-wrapper' ||
|
||||
config?.flowchart?.defaultRenderer === 'elk'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return txt.match(/^\s*graph/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./flowDiagram');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
25
packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts
Normal file
25
packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import flowParser from './parser/flow';
|
||||
import flowDb from './flowDb';
|
||||
import flowRendererV2 from './flowRenderer-v2';
|
||||
import flowStyles from './styles';
|
||||
import { MermaidConfig } from '../../config.type';
|
||||
import { setConfig } from '../../config';
|
||||
|
||||
export const diagram = {
|
||||
parser: flowParser,
|
||||
db: flowDb,
|
||||
renderer: flowRendererV2,
|
||||
styles: flowStyles,
|
||||
init: (cnf: MermaidConfig) => {
|
||||
if (!cnf.flowchart) {
|
||||
cnf.flowchart = {};
|
||||
}
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
// flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
|
||||
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
|
||||
flowRendererV2.setConf(cnf.flowchart);
|
||||
flowDb.clear();
|
||||
flowDb.setGen('gen-2');
|
||||
},
|
||||
};
|
24
packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
Normal file
24
packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import flowParser from './parser/flow';
|
||||
import flowDb from './flowDb';
|
||||
import flowRenderer from './flowRenderer';
|
||||
import flowRendererV2 from './flowRenderer-v2';
|
||||
import flowStyles from './styles';
|
||||
import { MermaidConfig } from '../../config.type';
|
||||
|
||||
export const diagram = {
|
||||
parser: flowParser,
|
||||
db: flowDb,
|
||||
renderer: flowRendererV2,
|
||||
styles: flowStyles,
|
||||
init: (cnf: MermaidConfig) => {
|
||||
if (!cnf.flowchart) {
|
||||
cnf.flowchart = {};
|
||||
}
|
||||
// TODO, broken as of 2022-09-14 (13809b50251845475e6dca65cc395761be38fbd2)
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
flowRenderer.setConf(cnf.flowchart);
|
||||
flowDb.clear();
|
||||
flowDb.setGen('gen-1');
|
||||
},
|
||||
};
|
@@ -47,7 +47,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
|
||||
if (vertex.classes.length > 0) {
|
||||
classStr = vertex.classes.join(' ');
|
||||
}
|
||||
|
||||
classStr = classStr + ' flowchart-label';
|
||||
const styles = getStylesFromArray(vertex.styles);
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
@@ -55,31 +55,36 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
|
||||
|
||||
// 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);
|
||||
log.info('vertex', vertex, vertex.labelType);
|
||||
if (vertex.labelType === 'markdown') {
|
||||
log.info('vertex', vertex, vertex.labelType);
|
||||
} else {
|
||||
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
|
||||
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);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
vertexNode = svgLabel;
|
||||
}
|
||||
|
||||
let radious = 0;
|
||||
@@ -146,6 +151,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
labelType: vertex.labelType,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
@@ -165,6 +171,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
|
||||
|
||||
log.info('setNode', {
|
||||
labelStyle: styles.labelStyle,
|
||||
labelType: vertex.labelType,
|
||||
shape: _shape,
|
||||
labelText: vertexText,
|
||||
rx: radious,
|
||||
@@ -312,7 +319,7 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
edgeData.labelpos = 'c';
|
||||
}
|
||||
|
||||
edgeData.labelType = 'text';
|
||||
edgeData.labelType = edge.labelType;
|
||||
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
|
||||
|
||||
if (edge.style === undefined) {
|
||||
@@ -405,7 +412,14 @@ export const draw = function (text, id, _version, diagObj) {
|
||||
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);
|
||||
diagObj.db.addVertex(
|
||||
subG.id,
|
||||
{ text: subG.title, type: subG.labelType },
|
||||
'group',
|
||||
undefined,
|
||||
subG.classes,
|
||||
subG.dir
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
|
@@ -1,21 +1,24 @@
|
||||
import flowDb from './flowDb';
|
||||
import flowParser from './parser/flow';
|
||||
import { parser } from './parser/flow';
|
||||
import flowRenderer from './flowRenderer';
|
||||
import { Diagram } from '../../Diagram';
|
||||
import { addDiagrams } from '../../diagram-api/diagram-orchestration';
|
||||
|
||||
const diag = {
|
||||
db: flowDb,
|
||||
};
|
||||
addDiagrams();
|
||||
|
||||
describe('when using mermaid and ', function () {
|
||||
describe('when calling addEdges ', function () {
|
||||
beforeEach(function () {
|
||||
flowParser.parser.yy = flowDb;
|
||||
parser.yy = flowDb;
|
||||
flowDb.clear();
|
||||
flowDb.setGen('gen-2');
|
||||
});
|
||||
it('should handle edges with text', function () {
|
||||
const diag = new Diagram('graph TD;A-->|text ex|B;');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle edges with text', () => {
|
||||
parser.parse('graph TD;A-->|text ex|B;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -29,10 +32,10 @@ describe('when using mermaid and ', function () {
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
|
||||
it('should handle edges without text', function () {
|
||||
const diag = new Diagram('graph TD;A-->B;');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle edges without text', async function () {
|
||||
parser.parse('graph TD;A-->B;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -45,10 +48,10 @@ describe('when using mermaid and ', function () {
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
|
||||
it('should handle open-ended edges', function () {
|
||||
const diag = new Diagram('graph TD;A---B;');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle open-ended edges', () => {
|
||||
parser.parse('graph TD;A---B;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -61,10 +64,10 @@ describe('when using mermaid and ', function () {
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
|
||||
it('should handle edges with styles defined', function () {
|
||||
const diag = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle edges with styles defined', () => {
|
||||
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -77,10 +80,10 @@ describe('when using mermaid and ', function () {
|
||||
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
it('should handle edges with interpolation defined', function () {
|
||||
const diag = new Diagram('graph TD;A---B; linkStyle 0 interpolate basis');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle edges with interpolation defined', () => {
|
||||
parser.parse('graph TD;A---B; linkStyle 0 interpolate basis');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -93,12 +96,10 @@ describe('when using mermaid and ', function () {
|
||||
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
it('should handle edges with text and styles defined', function () {
|
||||
const diag = new Diagram(
|
||||
'graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;'
|
||||
);
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should handle edges with text and styles defined', () => {
|
||||
parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -113,10 +114,10 @@ describe('when using mermaid and ', function () {
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
|
||||
it('should set fill to "none" by default when handling edges', function () {
|
||||
const diag = new Diagram('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should set fill to "none" by default when handling edges', () => {
|
||||
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
@@ -130,12 +131,10 @@ describe('when using mermaid and ', function () {
|
||||
flowRenderer.addEdges(edges, mockG, diag);
|
||||
});
|
||||
|
||||
it('should not set fill to none if fill is set in linkStyle', function () {
|
||||
const diag = new Diagram(
|
||||
'graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;'
|
||||
);
|
||||
diag.db.getVertices();
|
||||
const edges = diag.db.getEdges();
|
||||
it('should not set fill to none if fill is set in linkStyle', () => {
|
||||
parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;');
|
||||
flowDb.getVertices();
|
||||
const edges = flowDb.getEdges();
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toContain('flowchart-A-');
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import flowDb from '../flowDb';
|
||||
import flow from './flow';
|
||||
import { setConfig } from '../../../config';
|
||||
import { cleanupComments } from '../../../diagram-api/comments';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
@@ -13,7 +14,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle comments', function () {
|
||||
const res = flow.parser.parse('graph TD;\n%% Comment\n A-->B;');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -28,7 +29,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle comments at the start', function () {
|
||||
const res = flow.parser.parse('%% Comment\ngraph TD;\n A-->B;');
|
||||
const res = flow.parser.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -43,7 +44,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle comments at the end', function () {
|
||||
const res = flow.parser.parse('graph TD;\n A-->B\n %% Comment at the end\n');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n %% Comment at the end\n'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -58,7 +59,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle comments at the end no trailing newline', function () {
|
||||
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -73,7 +74,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle comments at the end many trailing newlines', function () {
|
||||
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment\n\n\n');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -88,7 +89,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle no trailing newlines', function () {
|
||||
const res = flow.parser.parse('graph TD;\n A-->B');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -103,7 +104,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle many trailing newlines', function () {
|
||||
const res = flow.parser.parse('graph TD;\n A-->B\n\n');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n\n'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -118,7 +119,7 @@ describe('[Comments] when parsing', () => {
|
||||
});
|
||||
|
||||
it('should handle a comment with blank rows in-between', function () {
|
||||
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B;');
|
||||
const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
@@ -134,7 +135,9 @@ describe('[Comments] when parsing', () => {
|
||||
|
||||
it('should handle a comment with mermaid flowchart code in them', function () {
|
||||
const res = flow.parser.parse(
|
||||
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
|
||||
cleanupComments(
|
||||
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
|
||||
)
|
||||
);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
|
@@ -0,0 +1,64 @@
|
||||
import flowDb from '../flowDb';
|
||||
import flow from './flow';
|
||||
import { setConfig } from '../../../config';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('parsing a flow chart with markdown strings', function () {
|
||||
beforeEach(function () {
|
||||
flow.parser.yy = flowDb;
|
||||
flow.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('mardown formatting in nodes and labels', function () {
|
||||
const res = flow.parser.parse(`flowchart
|
||||
A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in the hog"] -- "The rat in the mat" -->C;`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert['A'].id).toBe('A');
|
||||
expect(vert['A'].text).toBe('The cat in **the** hat');
|
||||
expect(vert['A'].labelType).toBe('markdown');
|
||||
expect(vert['B'].id).toBe('B');
|
||||
expect(vert['B'].text).toBe('The dog in the hog');
|
||||
expect(vert['B'].labelType).toBe('text');
|
||||
expect(edges.length).toBe(2);
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe('arrow_point');
|
||||
expect(edges[0].text).toBe('The *bat* in the chat');
|
||||
expect(edges[0].labelType).toBe('markdown');
|
||||
expect(edges[1].start).toBe('B');
|
||||
expect(edges[1].end).toBe('C');
|
||||
expect(edges[1].type).toBe('arrow_point');
|
||||
expect(edges[1].text).toBe('The rat in the mat');
|
||||
expect(edges[1].labelType).toBe('text');
|
||||
});
|
||||
it('mardown formatting in subgraphs', function () {
|
||||
const res = flow.parser.parse(`flowchart LR
|
||||
subgraph "One"
|
||||
a("\`The **cat**
|
||||
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
|
||||
end
|
||||
subgraph "\`**Two**\`"
|
||||
c("\`The **cat**
|
||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||
end`);
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(2);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.nodes.length).toBe(2);
|
||||
expect(subgraph.title).toBe('One');
|
||||
expect(subgraph.labelType).toBe('text');
|
||||
|
||||
const subgraph2 = subgraphs[1];
|
||||
expect(subgraph2.nodes.length).toBe(2);
|
||||
expect(subgraph2.title).toBe('**Two**');
|
||||
expect(subgraph2.labelType).toBe('markdown');
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user