mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-26 19:56:45 +02:00
Compare commits
257 Commits
huynhicode
...
v10.2.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
75f9de3d4a | ||
![]() |
81e86bc87c | ||
![]() |
f464212dc0 | ||
![]() |
0ad084182a | ||
![]() |
e2e9d0f1f3 | ||
![]() |
ac488dd800 | ||
![]() |
d00d20fc37 | ||
![]() |
88b94dea66 | ||
![]() |
9896402be6 | ||
![]() |
e9b76bba9b | ||
![]() |
ad09d63f65 | ||
![]() |
af7531d08a | ||
![]() |
eef6745272 | ||
![]() |
0cb6df1ef8 | ||
![]() |
f20ff4d580 | ||
![]() |
2a6603b33a | ||
![]() |
6115f6976a | ||
![]() |
da1beb36bf | ||
![]() |
d2409dc163 | ||
![]() |
1b40f552b2 | ||
![]() |
5b696a72f8 | ||
![]() |
d148a65967 | ||
![]() |
74ccf6410d | ||
![]() |
7cdb15c526 | ||
![]() |
55092f532f | ||
![]() |
7b6cb4f974 | ||
![]() |
0cd1c5de97 | ||
![]() |
48e5d743b3 | ||
![]() |
98e75959cc | ||
![]() |
0cec854f3b | ||
![]() |
abcae14fa7 | ||
![]() |
641098e602 | ||
![]() |
c2c8024718 | ||
![]() |
5338d90077 | ||
![]() |
180c695f2f | ||
![]() |
ce9bdcc53f | ||
![]() |
38d9ebc5da | ||
![]() |
5fd5de10b5 | ||
![]() |
ca5d78098e | ||
![]() |
cb2bc2a587 | ||
![]() |
6093383d45 | ||
![]() |
bd1343648e | ||
![]() |
3517314390 | ||
![]() |
8d43b87200 | ||
![]() |
9c12c42a26 | ||
![]() |
5bfe32524d | ||
![]() |
bd616e0ef5 | ||
![]() |
9fb25310e8 | ||
![]() |
102b9bc8c3 | ||
![]() |
9cec5ae1c4 | ||
![]() |
ac23787084 | ||
![]() |
ad61f4c6b1 | ||
![]() |
496585b543 | ||
![]() |
9e6168111f | ||
![]() |
f535640c1e | ||
![]() |
acc19db5bb | ||
![]() |
6cce1b20fb | ||
![]() |
a00616863d | ||
![]() |
49c97dad4c | ||
![]() |
e984b34af4 | ||
![]() |
c255d95ae2 | ||
![]() |
27e59e5adc | ||
![]() |
507e24cc72 | ||
![]() |
bb400d53b9 | ||
![]() |
b1590c2211 | ||
![]() |
9eeeb97e15 | ||
![]() |
0ef115f69a | ||
![]() |
6a89ef94f7 | ||
![]() |
97be4983f0 | ||
![]() |
c7affbe33b | ||
![]() |
82eccb70fa | ||
![]() |
78efac2c51 | ||
![]() |
58ec2686da | ||
![]() |
3a5c4a149f | ||
![]() |
c889ef0c30 | ||
![]() |
cce5505662 | ||
![]() |
b925849893 | ||
![]() |
d86d1e7887 | ||
![]() |
132ce2fb4b | ||
![]() |
458ac84749 | ||
![]() |
fa3e800802 | ||
![]() |
f616663f2f | ||
![]() |
b563ddc19d | ||
![]() |
c755e45961 | ||
![]() |
de34a06718 | ||
![]() |
040827d95d | ||
![]() |
a991c32732 | ||
![]() |
c4aebe6d57 | ||
![]() |
0f354ff974 | ||
![]() |
b2a5ddcd85 | ||
![]() |
816800adcd | ||
![]() |
fe9c109837 | ||
![]() |
1253733962 | ||
![]() |
4cbcfa054e | ||
![]() |
9a7dbab5ed | ||
![]() |
6eef09661d | ||
![]() |
10a66030b9 | ||
![]() |
93370a8e11 | ||
![]() |
4e3c2adffc | ||
![]() |
fbd35ae95a | ||
![]() |
75e73a4f03 | ||
![]() |
17c798a1b3 | ||
![]() |
c0b7ba305f | ||
![]() |
bd53e9d449 | ||
![]() |
45139470a3 | ||
![]() |
3677d44a58 | ||
![]() |
272ccded93 | ||
![]() |
b27d018c09 | ||
![]() |
a1fe7222ac | ||
![]() |
1b48c19897 | ||
![]() |
2dc780a766 | ||
![]() |
0b73d40ca1 | ||
![]() |
500efda8d6 | ||
![]() |
8e9d1bfc35 | ||
![]() |
75337287ea | ||
![]() |
b04155178a | ||
![]() |
d7e140a829 | ||
![]() |
25a7164df2 | ||
![]() |
29c8f8c867 | ||
![]() |
a226b073a0 | ||
![]() |
0c18f26dc0 | ||
![]() |
4d0447574a | ||
![]() |
40176f9bef | ||
![]() |
97a34a0cfc | ||
![]() |
d1690a03db | ||
![]() |
d8c67cd2c9 | ||
![]() |
ccc87be462 | ||
![]() |
f8b029de0e | ||
![]() |
8573db5114 | ||
![]() |
c6c3b07c33 | ||
![]() |
7df21c01ec | ||
![]() |
a1e64c2102 | ||
![]() |
7f9ebcf2c6 | ||
![]() |
7e240dac22 | ||
![]() |
06fdb95389 | ||
![]() |
2216bbad25 | ||
![]() |
43819f1bd8 | ||
![]() |
221640aa25 | ||
![]() |
5f1a507820 | ||
![]() |
7fd4814abc | ||
![]() |
61e31b3fe6 | ||
![]() |
e903f06f75 | ||
![]() |
35051f1946 | ||
![]() |
bf37956528 | ||
![]() |
725b80831e | ||
![]() |
cb3982de7e | ||
![]() |
4c9e35e269 | ||
![]() |
18571052ad | ||
![]() |
0d373f3a6a | ||
![]() |
70a52da393 | ||
![]() |
3549ffc4ac | ||
![]() |
c79be5d829 | ||
![]() |
38efaf93c8 | ||
![]() |
dfa65c5fc8 | ||
![]() |
32928224e5 | ||
![]() |
4456c2a267 | ||
![]() |
9e0410e0d3 | ||
![]() |
cdc68d99fe | ||
![]() |
110cd61c64 | ||
![]() |
baf5e8f6b2 | ||
![]() |
99abd4c31b | ||
![]() |
9238deaf00 | ||
![]() |
a01343bf1b | ||
![]() |
88f9233aa8 | ||
![]() |
33c81d557e | ||
![]() |
91f1ef9c42 | ||
![]() |
eba3a7bdcc | ||
![]() |
5bbce45c56 | ||
![]() |
395a794758 | ||
![]() |
1f2af83a0c | ||
![]() |
4d3e5e42e1 | ||
![]() |
9b912c4a59 | ||
![]() |
c6fbabc70e | ||
![]() |
41969c434f | ||
![]() |
8cd48bf405 | ||
![]() |
8b18fd93f4 | ||
![]() |
ec62c2bf5b | ||
![]() |
b9e7ba023f | ||
![]() |
284d032d26 | ||
![]() |
9859491381 | ||
![]() |
95d8e3a5df | ||
![]() |
56ec1ab52c | ||
![]() |
597a162249 | ||
![]() |
24f5a15f20 | ||
![]() |
d125d22488 | ||
![]() |
d09151e870 | ||
![]() |
60db22886a | ||
![]() |
23bed7e653 | ||
![]() |
07dcb8cc44 | ||
![]() |
ba632ff65b | ||
![]() |
d79f946cbc | ||
![]() |
b0c58a9764 | ||
![]() |
94991520ea | ||
![]() |
05e7a6bd4b | ||
![]() |
9298f631f9 | ||
![]() |
04305bdddc | ||
![]() |
f018ca34da | ||
![]() |
bbe3909627 | ||
![]() |
642397b36c | ||
![]() |
562514964e | ||
![]() |
125be13df4 | ||
![]() |
f3f7460706 | ||
![]() |
9e58f7bd99 | ||
![]() |
5110967bcd | ||
![]() |
5c6773a47c | ||
![]() |
5634c6ca1a | ||
![]() |
1c17f4c770 | ||
![]() |
f17a325632 | ||
![]() |
9f2d14f676 | ||
![]() |
bed9e8daab | ||
![]() |
4a258a0f2c | ||
![]() |
e4bc3e2269 | ||
![]() |
a470407f02 | ||
![]() |
e6d11b9443 | ||
![]() |
c624cbb910 | ||
![]() |
b428c06bed | ||
![]() |
9cbc3d6fb9 | ||
![]() |
12ed81855a | ||
![]() |
f597f15f82 | ||
![]() |
909c063a67 | ||
![]() |
f93bac9dd4 | ||
![]() |
8cb3318d94 | ||
![]() |
cb1e8220a4 | ||
![]() |
49bb87e14a | ||
![]() |
ddde451915 | ||
![]() |
40d2682b14 | ||
![]() |
93eb72248a | ||
![]() |
6883a92672 | ||
![]() |
2fe0a1360f | ||
![]() |
8e0f947d6c | ||
![]() |
411f868f22 | ||
![]() |
39f9277a73 | ||
![]() |
ff3104ad9c | ||
![]() |
28155b0e0a | ||
![]() |
47edb2c0bb | ||
![]() |
642bc1a739 | ||
![]() |
5b0eb702e5 | ||
![]() |
62870597d6 | ||
![]() |
59a85a7dfd | ||
![]() |
204baa4586 | ||
![]() |
ac63788a9c | ||
![]() |
e3ca003127 | ||
![]() |
19363965ad | ||
![]() |
24c9506935 | ||
![]() |
ca8ce8201c | ||
![]() |
e357bbee42 | ||
![]() |
b725b69fda | ||
![]() |
b51b2c7b04 | ||
![]() |
434961b44a | ||
![]() |
75502d076e | ||
![]() |
4017bb3c49 | ||
![]() |
c1df62638d | ||
![]() |
533830172c | ||
![]() |
00a14ee811 | ||
![]() |
d688f1fe94 | ||
![]() |
8c2dbafdb5 | ||
![]() |
1495ae623a |
@@ -4,4 +4,5 @@ docs/Setup.md
|
||||
cypress.config.js
|
||||
cypress/plugins/index.js
|
||||
coverage
|
||||
*.json
|
||||
*.json
|
||||
node_modules
|
||||
|
26
.github/ISSUE_TEMPLATE/diagram_proposal.yml
vendored
26
.github/ISSUE_TEMPLATE/diagram_proposal.yml
vendored
@@ -3,6 +3,7 @@ description: Suggest a new Diagram Type to add to Mermaid.
|
||||
labels:
|
||||
- 'Status: Triage'
|
||||
- 'Type: Enhancement'
|
||||
- 'Type: New Diagram'
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -17,6 +18,14 @@ body:
|
||||
- Use a clear and concise title
|
||||
- Fill out the text fields with as much detail as possible.
|
||||
- Never be shy to give us screenshots and/or code samples. It will help!
|
||||
|
||||
## Example issues
|
||||
|
||||
Refer to the discussions here to get an idea of how the diagram syntax is created.
|
||||
|
||||
- https://github.com/mermaid-js/mermaid/issues/4269
|
||||
- https://github.com/mermaid-js/mermaid/issues/4282
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposal
|
||||
@@ -35,8 +44,17 @@ body:
|
||||
description: If applicable, add screenshots to show possible examples of how the diagram may look like.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Code Sample
|
||||
label: Syntax
|
||||
description: |-
|
||||
If applicable, add a code sample for how to implement this new diagram.
|
||||
The text will automatically be rendered as JavaScript code.
|
||||
render: javascript
|
||||
If possible, include a syntax which could be used to write the diagram.
|
||||
Try to add one or two examples of valid use-cases here.
|
||||
- type: dropdown
|
||||
id: implementation
|
||||
attributes:
|
||||
label: Implementation
|
||||
description: |-
|
||||
Would you like to implement this yourself, or is it a proposal for the community?
|
||||
If there is no corresponding PR from your side after 30 days, the diagram will be open for everyone to implement.
|
||||
options:
|
||||
- I will try and implement it myself.
|
||||
- This is a proposal which I'd love to see built into mermaid by the wonderful community.
|
||||
|
29
.github/workflows/build-docs.yml
vendored
Normal file
29
.github/workflows/build-docs.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Build Vitepress docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version: 18
|
||||
|
||||
- name: Install Packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm --filter mermaid run docs:build:vitepress
|
20
.github/workflows/e2e-applitools.yml
vendored
20
.github/workflows/e2e-applitools.yml
vendored
@@ -38,15 +38,8 @@ jobs:
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- if: ${{ env.USE_APPLI }}
|
||||
name: Notify applitools of new batch
|
||||
# Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
|
||||
@@ -54,19 +47,22 @@ jobs:
|
||||
env:
|
||||
# e.g. mermaid-js/mermaid/my-branch
|
||||
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
|
||||
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
|
||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: pnpm run e2e
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v4
|
||||
id: cypress
|
||||
with:
|
||||
start: pnpm run dev
|
||||
wait-on: 'http://localhost:9000'
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
# Mermaid applitools.config.js uses this to pick batch name.
|
||||
APPLI_BRANCH: ${{ github.ref_name }}
|
||||
APPLITOOLS_BATCH_ID: ${{ github.sha }}
|
||||
# e.g. mermaid-js/mermaid/my-branch
|
||||
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
|
||||
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
|
||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||
|
2
.github/workflows/link-checker.yml
vendored
2
.github/workflows/link-checker.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
uses: lycheeverse/lychee-action@v1.6.1
|
||||
uses: lycheeverse/lychee-action@v1.8.0
|
||||
with:
|
||||
args: >-
|
||||
--verbose
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
if ! pnpm run lint; then
|
||||
# print a nice error message on lint failure
|
||||
ERROR_MESSAGE='Running `pnpm run lint` failed.'
|
||||
ERROR_MESSAGE+=' Running `pnpm run lint:fix` may fix this issue. '
|
||||
ERROR_MESSAGE+=' Running `pnpm -w run lint:fix` may fix this issue. '
|
||||
ERROR_MESSAGE+=" If this error doesn't occur on your local machine,"
|
||||
ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.'
|
||||
ERROR_MESSAGE+=' You may also need to delete your prettier cache by running'
|
||||
|
7
.github/workflows/publish-docs.yml
vendored
7
.github/workflows/publish-docs.yml
vendored
@@ -5,10 +5,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
@@ -53,7 +49,6 @@ jobs:
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
environment:
|
||||
name: github-pages
|
||||
runs-on: ubuntu-latest
|
||||
@@ -61,4 +56,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
uses: actions/deploy-pages@v2
|
||||
|
2
.github/workflows/release-publish.yml
vendored
2
.github/workflows/release-publish.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: fregante/setup-git-user@v1
|
||||
- uses: fregante/setup-git-user@v2
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
|
||||
knsv*.html
|
||||
local*.html
|
||||
stats/
|
||||
|
||||
**/user-avatars/*
|
||||
**/contributor-names.json
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'!(docs/**/*)*.{ts,js,json,html,md,mts}': [
|
||||
'!(docs/**/*)*.{ts,js,html,md,mts}': [
|
||||
'eslint --cache --cache-strategy content --fix',
|
||||
// don't cache prettier yet, since we use `prettier-plugin-jsdoc`,
|
||||
// and prettier doesn't invalidate cache on plugin updates"
|
||||
|
@@ -4,4 +4,5 @@ cypress/platform/xss3.html
|
||||
coverage
|
||||
# Autogenerated by PNPM
|
||||
pnpm-lock.yaml
|
||||
stats
|
||||
stats
|
||||
packages/mermaid/src/docs/.vitepress/components.d.ts
|
||||
|
@@ -11,6 +11,7 @@ const visualize = process.argv.includes('--visualize');
|
||||
const watch = process.argv.includes('--watch');
|
||||
const mermaidOnly = process.argv.includes('--mermaid');
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const sourcemap = false;
|
||||
|
||||
type OutputOptions = Exclude<
|
||||
Exclude<InlineConfig['build'], undefined>['rollupOptions'],
|
||||
@@ -43,6 +44,11 @@ const packageOptions = {
|
||||
packageName: 'mermaid-example-diagram',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
'mermaid-zenuml': {
|
||||
name: 'mermaid-zenuml',
|
||||
packageName: 'mermaid-zenuml',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
};
|
||||
|
||||
interface BuildOptions {
|
||||
@@ -60,9 +66,15 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
{
|
||||
name,
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
sourcemap,
|
||||
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
|
||||
},
|
||||
{
|
||||
name,
|
||||
format: 'umd',
|
||||
sourcemap,
|
||||
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
|
||||
},
|
||||
];
|
||||
|
||||
if (core) {
|
||||
@@ -79,7 +91,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
{
|
||||
name,
|
||||
format: 'esm',
|
||||
sourcemap: true,
|
||||
sourcemap,
|
||||
entryFileNames: `${name}.core.mjs`,
|
||||
},
|
||||
];
|
||||
@@ -139,6 +151,7 @@ if (watch) {
|
||||
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
||||
if (!mermaidOnly) {
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
||||
}
|
||||
} else if (visualize) {
|
||||
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
||||
|
@@ -15,6 +15,7 @@ async function createServer() {
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.static('./packages/mermaid/dist'));
|
||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||
app.use(vite.middlewares);
|
||||
app.use(express.static('demos'));
|
||||
|
14
README.md
14
README.md
@@ -165,6 +165,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
@@ -184,6 +191,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State diagram [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]
|
||||
|
32
cSpell.json
32
cSpell.json
@@ -5,11 +5,14 @@
|
||||
"acyclicer",
|
||||
"adamiecki",
|
||||
"alois",
|
||||
"aloisklink",
|
||||
"antiscript",
|
||||
"antlr",
|
||||
"appli",
|
||||
"applitools",
|
||||
"asciidoctor",
|
||||
"ashish",
|
||||
"ashishjain",
|
||||
"astah",
|
||||
"bbox",
|
||||
"bilkent",
|
||||
@@ -23,6 +26,7 @@
|
||||
"classdef",
|
||||
"codedoc",
|
||||
"colour",
|
||||
"commitlint",
|
||||
"cpettitt",
|
||||
"customizability",
|
||||
"cuzon",
|
||||
@@ -48,30 +52,42 @@
|
||||
"grav",
|
||||
"greywolf",
|
||||
"huynh",
|
||||
"huynhicode",
|
||||
"inkdrop",
|
||||
"jaoude",
|
||||
"jgreywolf",
|
||||
"jison",
|
||||
"jiti",
|
||||
"kaufmann",
|
||||
"khroma",
|
||||
"klemm",
|
||||
"klink",
|
||||
"knsv",
|
||||
"knut",
|
||||
"knutsveidqvist",
|
||||
"laganeckas",
|
||||
"linetype",
|
||||
"lintstagedrc",
|
||||
"logmsg",
|
||||
"lucida",
|
||||
"markdownish",
|
||||
"matthieu",
|
||||
"matthieumorel",
|
||||
"mdast",
|
||||
"mdbook",
|
||||
"mermaidjs",
|
||||
"mermerd",
|
||||
"mindaugas",
|
||||
"mindmap",
|
||||
"mindmaps",
|
||||
"mitigations",
|
||||
"mkdocs",
|
||||
"mmorel",
|
||||
"mult",
|
||||
"nextra",
|
||||
"orlandoni",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
"phpbb",
|
||||
"plantuml",
|
||||
"playfair",
|
||||
@@ -83,6 +99,7 @@
|
||||
"rect",
|
||||
"rects",
|
||||
"redmine",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"sandboxed",
|
||||
"setupgraphviewbox",
|
||||
@@ -90,8 +107,12 @@
|
||||
"sidharth",
|
||||
"sidharthv",
|
||||
"sphinxcontrib",
|
||||
"startx",
|
||||
"starty",
|
||||
"statediagram",
|
||||
"steph",
|
||||
"stopx",
|
||||
"stopy",
|
||||
"stylis",
|
||||
"substate",
|
||||
"sveidqvist",
|
||||
@@ -101,16 +122,22 @@
|
||||
"textlength",
|
||||
"treemap",
|
||||
"ts-nocheck",
|
||||
"tsdoc",
|
||||
"tuleap",
|
||||
"tylerlong",
|
||||
"ugge",
|
||||
"unist",
|
||||
"unocss",
|
||||
"valign",
|
||||
"verdana",
|
||||
"viewports",
|
||||
"vinod",
|
||||
"visio",
|
||||
"vitepress",
|
||||
"vueuse",
|
||||
"xlink",
|
||||
"yash"
|
||||
"yash",
|
||||
"zenuml"
|
||||
],
|
||||
"patterns": [
|
||||
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
|
||||
@@ -150,6 +177,7 @@
|
||||
],
|
||||
"ignorePaths": [
|
||||
"packages/mermaid/src/docs/CHANGELOG.md",
|
||||
"packages/mermaid/src/docs/.vitepress/redirect.ts"
|
||||
"packages/mermaid/src/docs/.vitepress/redirect.ts",
|
||||
"packages/mermaid/src/docs/.vitepress/contributor-names.json"
|
||||
]
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ export const renderGraph = (graphStr, options, api) => {
|
||||
openURLAndVerifyRendering(url, options);
|
||||
};
|
||||
|
||||
const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||
export const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||
const useAppli = Cypress.env('useAppli');
|
||||
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { urlSnapshotTest } from '../../helpers/util.js';
|
||||
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.js';
|
||||
|
||||
describe('CSS injections', () => {
|
||||
it('should not allow CSS injections outside of the diagram', () => {
|
||||
@@ -13,4 +13,11 @@ describe('CSS injections', () => {
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
it('should not allow manipulating styletags using arrowheads', () => {
|
||||
openURLAndVerifyRendering('http://localhost:9000/xss23-css.html', {
|
||||
logLevel: 1,
|
||||
arrowMarkerAbsolute: false,
|
||||
flowchart: { htmlLabels: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -548,4 +548,18 @@ class C13["With Città foreign language"]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should add classes namespaces', function () {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
namespace Namespace1 {
|
||||
class C1
|
||||
class C2
|
||||
}
|
||||
C1 --> C2
|
||||
class C3
|
||||
class C4
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -286,7 +286,7 @@ describe('Class diagram', () => {
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
|
||||
it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
|
@@ -684,6 +684,20 @@ A --> B
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('elk: should include classes on the edges', () => {
|
||||
renderGraph(
|
||||
`flowchart-elk TD
|
||||
A --> B --> C --> D
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
const edges = svg.querySelectorAll('.edges > path');
|
||||
edges.forEach((edge) => {
|
||||
expect(edge).to.have.class('flowchart-link');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Markdown strings flowchart-elk (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
|
@@ -52,6 +52,17 @@ root[A root with a long text that wraps to keep the node size in check]
|
||||
);
|
||||
});
|
||||
|
||||
it('a root with wrapping text and long words that exceed width', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
root[A few smaller words but then averylongsetofcharacterswithoutwhitespacetoseparate that we expect to wrapontonextlinesandnotexceedwidthparameters]
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('a root with an icon', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
|
163
cypress/integration/rendering/quadrantChart.spec.js
Normal file
163
cypress/integration/rendering/quadrantChart.spec.js
Normal file
@@ -0,0 +1,163 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
|
||||
describe('Quadrant Chart', () => {
|
||||
it('should render if only chart type is provided', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
quadrantChart
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render a complete quadrant chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render without points', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should able to render y-axix on right side', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"quadrantChart": {"yAxisPosition": "right"}}}%%
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should able to render x-axix on bottom', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"quadrantChart": {"xAxisPosition": "bottom"}}}%%
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should able to render x-axix on bottom and y-axis on right', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"quadrantChart": {"xAxisPosition": "bottom", "yAxisPosition": "right"}}}%%
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should render without title', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
quadrantChart
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should use all the config', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600, "titlePadding": 20, "titleFontSize": 10, "quadrantPadding": 20, "quadrantTextTopPadding": 40, "quadrantLabelFontSize": 20, "quadrantInternalBorderStrokeWidth": 3, "quadrantExternalBorderStrokeWidth": 5, "xAxisLabelPadding": 20, "xAxisLabelFontSize": 20, "yAxisLabelPadding": 20, "yAxisLabelFontSize": 20, "pointTextPadding": 20, "pointLabelFontSize": 20, "pointRadius": 10 }}}%%
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('should use all the theme variable', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"themeVariables": {"quadrant1Fill": "#b4dcff","quadrant2Fill": "#fef0ff", "quadrant3Fill": "#fffaf0", "quadrant4Fill": "#f0fff2", "quadrant1TextFill": "#ff0000", "quadrant2TextFill": "#2d00df", "quadrant3TextFill": "#00ffda", "quadrant4TextFill": "#e68300", "quadrantPointFill": "#0149ff", "quadrantPointTextFill": "#dc00ff", "quadrantXAxisTextFill": "#ffb500", "quadrantYAxisTextFill": "#fae604", "quadrantInternalBorderStrokeFill": "#3636f2", "quadrantExternalBorderStrokeFill": "#ff1010", "quadrantTitleFill": "#00ea19"} }}%%
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
@@ -88,6 +88,16 @@ context('Sequence diagram', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle empty lines', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John<br/>
|
||||
John-->>Alice: Great<br/><br/>day!
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle line breaks and wrap annotations', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -123,6 +133,29 @@ context('Sequence diagram', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should render a sequence diagram with par_over', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
participant John
|
||||
par_over Section title
|
||||
Alice ->> Bob: Message 1<br>Second line
|
||||
Bob ->> John: Message 2
|
||||
end
|
||||
par_over Two line<br>section title
|
||||
Note over Alice: Alice note
|
||||
Note over Bob: Bob note<br>Second line
|
||||
Note over John: John note
|
||||
end
|
||||
par_over Mixed section
|
||||
Alice ->> Bob: Message 1
|
||||
Note left of Bob: Alice/Bob Note
|
||||
end
|
||||
`
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
it('should render different note fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
|
19
cypress/integration/rendering/zenuml.spec.js
Normal file
19
cypress/integration/rendering/zenuml.spec.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
|
||||
describe('Zen UML', () => {
|
||||
it('Basic Zen UML diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
zenuml
|
||||
A.method() {
|
||||
if(x) {
|
||||
B.method() {
|
||||
selfCall() { return X }
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
@@ -42,368 +42,86 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart LR
|
||||
subgraph one
|
||||
direction LR
|
||||
A[myClass1] --> B[default]
|
||||
subgraph two
|
||||
direction BT
|
||||
C[myClass2] --> D[default]
|
||||
end
|
||||
end
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
b("`The dog in **the** hog.(1).. a a a a *very long text* about it
|
||||
Word!
|
||||
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words.`") --apa--> c
|
||||
|
||||
</pre
|
||||
>
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
classDiagram-v2
|
||||
|
||||
classA -- classB : Inheritance
|
||||
classA -- classC : link
|
||||
classC -- classD : link
|
||||
classB -- classD
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart LR
|
||||
classDef aPID stroke:#4e4403,fill:#fdde29,color:#4e4403,rx:5px,ry:5px;
|
||||
classDef crm stroke:#333333,fill:#DCDCDC,color:#333333,rx:5px,ry:5px;
|
||||
classDef type stroke:#502604,fill:#FAB565,color:#502604,rx:20px,ry:20px;;
|
||||
|
||||
O0("Joe")
|
||||
class O0 aPID;
|
||||
|
||||
O1("Person")
|
||||
class O1 crm;
|
||||
O0 -- has type -->O1["Person"]
|
||||
|
||||
O2("aat:300411314")
|
||||
class O2 type;
|
||||
click O2 function "Sorry the newline html tags are not being processed correctly<br/> So all of this appears on the <br/> same line."
|
||||
O0 -- has type -->O2["Bug"]
|
||||
click O0 function "Lots of great info about Joe<br>Lots of great info about Joe<br>burt<br>fred";
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||
loop Loopy
|
||||
Bob->>Alice: Pasten
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart TD
|
||||
subgraph test
|
||||
direction TB
|
||||
subgraph test2
|
||||
direction LR
|
||||
F --> D
|
||||
end
|
||||
subgraph test3
|
||||
direction TB
|
||||
G --> H
|
||||
end
|
||||
end
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
b("`The dog in **the** hog.(1)
|
||||
NL`") --"`1o **bold**`"--> c[new strings svg labels]
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart TD
|
||||
|
||||
release-branch[Create Release Branch]:::relClass
|
||||
develop-branch[Update Develop Branch]:::relClass
|
||||
github-release-draft[GitHub Release Draft]:::relClass
|
||||
trigger-pipeline[Trigger Jenkins pipeline]:::fixClass
|
||||
github-release[GitHub Release]:::postClass
|
||||
|
||||
build-ready --> release-branch
|
||||
build-ready --> develop-branch
|
||||
release-branch --> jenkins-release-build
|
||||
jenkins-release-build --> github-release-draft
|
||||
jenkins-release-build --> install-release
|
||||
install-release --> verify-release
|
||||
jenkins-release-build --> announce
|
||||
github-release-draft --> github-release
|
||||
verify-release --> verify-check
|
||||
verify-check -- Yes --> github-release
|
||||
verify-check -- No --> release-fix
|
||||
release-fix --> release-branch-pr
|
||||
verify-check -- No --> delete-artifacts
|
||||
release-branch-pr --> trigger-pipeline
|
||||
delete-artifacts --> trigger-pipeline
|
||||
trigger-pipeline --> jenkins-release-build
|
||||
|
||||
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
b("`The dog in **the** hog.(1)
|
||||
NL`") --"`1o **bold**`"--> c[new strings html labels]
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart LR
|
||||
a["<strong>Haiya</strong>"]===>b
|
||||
<pre class="mermaid" style="width: 50%">
|
||||
%%{init: {"flowchart": {"htmlLabels": true}} }%%
|
||||
flowchart LR
|
||||
b("The dog in the hog.(1)\nNL") --"1o bold"--> c[old strings svg labels]
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart TD
|
||||
A --> B
|
||||
A --> C
|
||||
B --> C
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
flowchart TD
|
||||
A([stadium shape test])
|
||||
A -->|Get money| B([Go shopping])
|
||||
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
|
||||
C -->|One| D([Laptop])
|
||||
C -->|Two| E([iPhone])
|
||||
C -->|Three| F([Car<br/>wroom wroom])
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
sequenceDiagram
|
||||
title: My Sequence Diagram Title
|
||||
accTitle: My Acc Sequence Diagram
|
||||
accDescr: My Sequence Diagram Description
|
||||
|
||||
Alice->>John: Hello John, how are you?
|
||||
John-->>Alice: Great!
|
||||
Alice-)John: See you later!
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 50%">
|
||||
graph TD
|
||||
A -->|000| B
|
||||
B -->|111| C
|
||||
|
||||
linkStyle 1 stroke:#ff3,stroke-width:4px,color:red;
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
journey
|
||||
accTitle: My User Journey Diagram
|
||||
accDescr: My User Journey Diagram Description
|
||||
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
info
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
requirementDiagram
|
||||
accTitle: My req Diagram
|
||||
accDescr: My req Diagram Description
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
functionalRequirement test_req2 {
|
||||
id: 1.1
|
||||
text: the second test text.
|
||||
risk: low
|
||||
verifymethod: inspection
|
||||
}
|
||||
|
||||
performanceRequirement test_req3 {
|
||||
id: 1.2
|
||||
text: the third test text.
|
||||
risk: medium
|
||||
verifymethod: demonstration
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
element test_entity2 {
|
||||
type: word doc
|
||||
docRef: reqs/test_entity
|
||||
}
|
||||
|
||||
|
||||
test_entity - satisfies -> test_req2
|
||||
test_req - traces -> test_req2
|
||||
test_req - contains -> test_req3
|
||||
test_req <- copies - test_entity2
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
title Adding GANTT diagram functionality to mermaid
|
||||
excludes weekends
|
||||
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
|
||||
section A section
|
||||
Completed task :done, des1, 2014-01-06,2014-01-08
|
||||
Active task :active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
|
||||
section Critical tasks
|
||||
Completed task in the critical line :crit, done, 2014-01-06,24h
|
||||
Implement parser and jison :crit, done, after des1, 2d
|
||||
Create tests for parser :crit, active, 3d
|
||||
Future task in critical line :crit, 5d
|
||||
Create tests for renderer :2d
|
||||
Add to mermaid :1d
|
||||
Functionality added :milestone, 2014-01-25, 0d
|
||||
|
||||
section Documentation
|
||||
Describe gantt syntax :active, a1, after des1, 3d
|
||||
Add gantt diagram to demo page :after a1 , 20h
|
||||
Add another diagram to demo page :doc1, after a1 , 48h
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
Add gantt diagram to demo page :20h
|
||||
Add another diagram to demo page :48h
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
stateDiagram
|
||||
state Active {
|
||||
Idle
|
||||
}
|
||||
Inactive --> Idle: ACT
|
||||
Active --> Active: LOG
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
flowchart TB
|
||||
accTitle: My flowchart
|
||||
accDescr: My flowchart Description
|
||||
subgraph One
|
||||
a1-->a2-->a3
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
sequenceDiagram
|
||||
A ->> B: 1
|
||||
rect rgb(204, 0, 102)
|
||||
break yes
|
||||
rect rgb(0, 204, 204)
|
||||
C ->> C: 0
|
||||
end
|
||||
end
|
||||
end
|
||||
B ->> A: Return
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
classDiagram
|
||||
accTitle: My class diagram
|
||||
accDescr: My class diagram Description
|
||||
Class01 <|-- AveryLongClass : Cool
|
||||
Class09 --> C2 : Where am i?
|
||||
Class09 --* C3
|
||||
Class09 --|> Class07
|
||||
Class07 : equals()
|
||||
Class07 : Object[] elementData
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
%%{init: {'config': {'wrap': true }}}%%
|
||||
sequenceDiagram
|
||||
participant A as Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
gitGraph
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
branch featureA
|
||||
commit id:"FIX"
|
||||
commit id: "FIX-2"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
commit id:"THREE"
|
||||
cherry-pick id:"FIX"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
merge featureA
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
flowchart TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
classDiagram
|
||||
Animal "1" <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <--o Zebra
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class Duck{
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
</pre>
|
||||
<pre class="mermaid2" style="width: 100%">
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
</pre>
|
||||
|
||||
<!-- <script src="./mermaid.js"></script> -->
|
||||
<script src="./mermaid.js"></script>
|
||||
<script>
|
||||
<script type="module">
|
||||
// import mindmap from '../../packages/mermaid-mindmap/src/detector';
|
||||
// import example from '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs';
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
// await mermaid.registerExternalDiagrams([example]);
|
||||
mermaid.parseError = function (err, hash) {
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
mermaid.initialize({
|
||||
maxTextSize: 900000,
|
||||
// theme: 'forest',
|
||||
startOnLoad: true,
|
||||
securityLevel: 'loose',
|
||||
logLevel: 0,
|
||||
fontFamily: 'courier',
|
||||
flowchart: {
|
||||
// curve: 'curveLinear',
|
||||
useMaxWidth: true,
|
||||
htmlLabels: false,
|
||||
fontFamily: 'courier',
|
||||
// defaultRenderer: 'elk',
|
||||
useMaxWidth: false,
|
||||
// htmlLabels: false,
|
||||
htmlLabels: true,
|
||||
},
|
||||
lazyLoadedDiagrams: ['./mermaid-mindmap-detector.js'],
|
||||
// htmlLabels: false,
|
||||
gantt: {
|
||||
useMaxWidth: false,
|
||||
},
|
||||
sequence: {
|
||||
wrap: true,
|
||||
},
|
||||
useMaxWidth: false,
|
||||
});
|
||||
function callback() {
|
||||
alert('It worked');
|
||||
}
|
||||
|
||||
function clickByFlow(elemName) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'created-by-click';
|
||||
div.style = 'padding: 20px; background: green; color: white;';
|
||||
div.innerText = 'Clicked By Flow';
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('In parse error:');
|
||||
console.error(err);
|
||||
};
|
||||
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
|
||||
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
|
||||
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
|
||||
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import mermaid2 from './mermaid.esm.mjs';
|
||||
import externalExample from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs';
|
||||
import zenUml from '../../packages/mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
|
||||
|
||||
function b64ToUtf8(str) {
|
||||
return decodeURIComponent(escape(window.atob(str)));
|
||||
@@ -44,7 +45,7 @@ const contentLoaded = async function () {
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
||||
await mermaid2.registerExternalDiagrams([externalExample]);
|
||||
await mermaid2.registerExternalDiagrams([externalExample, zenUml]);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
await mermaid2.run();
|
||||
}
|
||||
|
85
cypress/platform/xss23-css.html
Normal file
85
cypress/platform/xss23-css.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
/* background: rgb(221, 208, 208); */
|
||||
/* background:#333; */
|
||||
font-family: 'Arial';
|
||||
/* font-size: 18px !important; */
|
||||
}
|
||||
h1 {
|
||||
color: grey;
|
||||
}
|
||||
.mermaid2 {
|
||||
display: none;
|
||||
}
|
||||
.mermaid svg {
|
||||
/* font-size: 18px !important; */
|
||||
}
|
||||
.malware {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 150px;
|
||||
background: red;
|
||||
color: black;
|
||||
display: flex;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: monospace;
|
||||
font-size: 72px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Security check</div>
|
||||
<div class="flex">
|
||||
<div id="diagram" class="mermaid"></div>
|
||||
<div id="graph-div"></div>
|
||||
<div id="res" class=""></div>
|
||||
</div>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.parseError = function (err, hash) {
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
mermaid.initialize({
|
||||
theme: 'base',
|
||||
startOnLoad: false,
|
||||
flowcharts: { htmlLabels: true },
|
||||
});
|
||||
function callback() {
|
||||
alert('It worked');
|
||||
}
|
||||
function xssAttack() {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'the-malware';
|
||||
div.className = 'malware';
|
||||
div.innerHTML = 'XSS Succeeded';
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
throw new Error('XSS Succeeded');
|
||||
}
|
||||
|
||||
let diagram = `graph TD
|
||||
A[["a marker-end=#quot;url(<s title='#<style>*{background:red}</style>'>b"]]
|
||||
`;
|
||||
const el = document.querySelector('#graph-div');
|
||||
console.log(diagram);
|
||||
const { svg } = await mermaid.render('graph-div', diagram);
|
||||
document.querySelector('#res').innerHTML = svg;
|
||||
window.rendered = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -54,6 +54,9 @@
|
||||
<li>
|
||||
<h2><a href="./pie.html">Pie</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a href="./quadrantchart.html">Quadrant charts</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a href="./requirements.html">Requirements</a></h2>
|
||||
</li>
|
||||
@@ -63,6 +66,9 @@
|
||||
<li>
|
||||
<h2><a href="./state.html">State</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a href="./zenuml.html">ZenUML</a></h2>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -38,7 +38,7 @@
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '../packages/mermaid/src/mermaid';
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
|
55
demos/quadrantchart.html
Normal file
55
demos/quadrantchart.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Quadrant chart demos</h1>
|
||||
<pre class="mermaid">
|
||||
%%{init: {"quadrantChart": {"quadrantPadding": 10}, "theme": "forest", "themeVariables": {"quadrant1TextFill": "blue"}} }%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> important
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
quadrant-4 Delete
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
|
||||
quadrantChart
|
||||
title Analytics and Business Intelligence Platforms
|
||||
x-axis "Completeness of Vision ❤" -->
|
||||
y-axis Ability to Execute
|
||||
quadrant-1 Leaders
|
||||
quadrant-2 Challengers
|
||||
quadrant-3 Niche
|
||||
quadrant-4 Visionaries
|
||||
Microsoft: [0.75, 0.75]
|
||||
Salesforce: [0.55, 0.60]
|
||||
IBM: [0.51, 0.40]
|
||||
Incorta: [0.20, 0.30]
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'default',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -144,6 +144,26 @@
|
||||
>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
participant John
|
||||
par_over Section title
|
||||
Alice ->> Bob: Message 1<br>Second line
|
||||
Bob ->> John: Message 2
|
||||
end
|
||||
par_over Two line<br>section title
|
||||
Note over Alice: Alice note
|
||||
Note over Bob: Bob note<br>Second line
|
||||
Note over John: John note
|
||||
end
|
||||
par_over Mixed section
|
||||
Alice ->> Bob: Message 1
|
||||
Note left of Bob: Alice/Bob Note
|
||||
end
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
|
53
demos/zenuml.html
Normal file
53
demos/zenuml.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Zenuml Test Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Zenuml demos</h1>
|
||||
<pre class="mermaid">
|
||||
zenuml
|
||||
title Sync Messages (Design Pattern: Adapter)
|
||||
@Starter(Client)
|
||||
Adapter.interfaceMethod() {
|
||||
translateParameter(parameter)
|
||||
|
||||
result = Implementation.implementationMethod()
|
||||
|
||||
translateResult()
|
||||
return translatedResult
|
||||
}
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
zenuml
|
||||
title Async Messages (SPA Authentication)
|
||||
// ```
|
||||
// GET https://${account.namespace}/authorize/?
|
||||
// response_type=token
|
||||
// &client_id=${account.clientId}
|
||||
// &redirect_url=YOUR_CALLBACK_URL
|
||||
// &state=VALUE_THAT_SURVIVES_REDIRECTS
|
||||
// &scope=openid
|
||||
// ```
|
||||
Browser->Auth0: 1. initiate the authentication
|
||||
Auth0->"Identity Provider": 2. OAuth2 / SAML, etc
|
||||
"Identity Provider"->"Identity Provider": 3. user gets authenticated
|
||||
Auth0->Browser: 4. redirect to ${YOUR_CALLBACK_URL}/#id_token=e68...
|
||||
Browser->Auth0: 5. validate id_token and get user profile
|
||||
Browser->"Your API": 6. call API sending JWT in Authorization header
|
||||
"Your API"->"Your API": 7. validate token
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import zenuml from './mermaid-zenuml.esm.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
mermaid.initialize({
|
||||
logLevel: 3,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
1054
docs/CHANGELOG.md
1054
docs/CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115)
|
||||
[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293)
|
||||
|
||||
---
|
||||
|
||||
|
@@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
|
||||
[mermaidAPI.ts:670](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L670)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -127,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312)
|
||||
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
|
||||
|
||||
---
|
||||
|
||||
@@ -153,7 +153,7 @@ the cleaned up svgCode
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
|
||||
[mermaidAPI.ts:257](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L257)
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +179,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192)
|
||||
[mermaidAPI.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L186)
|
||||
|
||||
---
|
||||
|
||||
@@ -202,7 +202,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
|
||||
[mermaidAPI.ts:234](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L234)
|
||||
|
||||
---
|
||||
|
||||
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176)
|
||||
[mermaidAPI.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L170)
|
||||
|
||||
---
|
||||
|
||||
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
|
||||
[mermaidAPI.ts:288](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L288)
|
||||
|
||||
---
|
||||
|
||||
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362)
|
||||
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
|
||||
|
@@ -112,10 +112,10 @@ A `securityLevel` configuration has to first be cleared. `securityLevel` sets th
|
||||
|
||||
Values:
|
||||
|
||||
- **strict**: (**default**) tags in text are encoded, click functionality is disabled
|
||||
- **loose**: tags in text are allowed, click functionality is enabled
|
||||
- **antiscript**: html tags in text are allowed, (only script element is removed), click functionality is enabled
|
||||
- **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This prevent any JavaScript running in the context. This may hinder interactive functionality of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc.
|
||||
- **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
|
||||
- **antiscript**: HTML tags in text are allowed (only script elements are removed) and click functionality is enabled.
|
||||
- **loose**: HTML tags in text are allowed and click functionality is enabled.
|
||||
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
|
||||
|
||||
> **Note**
|
||||
> This changes the default behaviour of mermaid so that after upgrade to 8.2, unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled.
|
||||
@@ -348,10 +348,10 @@ mermaid.parseError = function (err, hash) {
|
||||
displayErrorInGui(err);
|
||||
};
|
||||
|
||||
const textFieldUpdated = function () {
|
||||
const textFieldUpdated = async function () {
|
||||
const textStr = getTextFromFormField('code');
|
||||
|
||||
if (mermaid.parse(textStr)) {
|
||||
if (await mermaid.parse(textStr)) {
|
||||
reRender(textStr);
|
||||
}
|
||||
};
|
||||
|
@@ -16,6 +16,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Using code blocks](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) (**Native support**)
|
||||
- [GitHub action: Compile mermaid to image](https://github.com/neenjaw/compile-mermaid-markdown-action)
|
||||
- [svg-generator](https://github.com/SimonKenyonShepard/mermaidjs-github-svg-generator)
|
||||
- [GitHub Writer](https://github.com/ckeditor/github-writer)
|
||||
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) (**Native support**)
|
||||
- [Gitea](https://gitea.io) (**Native support**)
|
||||
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**)
|
||||
@@ -58,6 +59,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [hexo-filter-mermaid-diagrams](https://github.com/webappdevelp/hexo-filter-mermaid-diagrams)
|
||||
- [hexo-tag-mermaid](https://github.com/JameChou/hexo-tag-mermaid)
|
||||
- [hexo-mermaid-diagrams](https://github.com/mslxl/hexo-mermaid-diagrams)
|
||||
- [Nextra](https://nextra.site/)
|
||||
- [Mermaid](https://nextra.site/docs/guide/mermaid)
|
||||
|
||||
## CMS
|
||||
|
||||
@@ -142,13 +145,19 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Named block =Diagram](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
|
||||
- [GNU Nano](https://www.nano-editor.org/)
|
||||
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
|
||||
- [CKEditor](https://github.com/ckeditor/ckeditor5)
|
||||
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
|
||||
|
||||
## Document Generation
|
||||
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark.js](https://remark.js.org/)
|
||||
- [remark-mermaid](https://github.com/temando/remark-mermaid)
|
||||
- [remark](https://remark.js.org/)
|
||||
- [remark-mermaidjs](https://github.com/remcohaszing/remark-mermaidjs)
|
||||
- [rehype](https://github.com/rehypejs/rehype)
|
||||
- [rehype-mermaidjs](https://github.com/remcohaszing/rehype-mermaidjs)
|
||||
- [Gatsby](https://www.gatsbyjs.com/)
|
||||
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
|
||||
- [jSDoc](https://jsdoc.app/)
|
||||
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
|
||||
- [MkDocs](https://www.mkdocs.org)
|
||||
@@ -189,6 +198,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
|
||||
- [Reveal CK](https://github.com/jedcn/reveal-ck)
|
||||
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
|
||||
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
- [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
|
||||
|
@@ -235,6 +235,42 @@ journey
|
||||
Sit down: 5: Me
|
||||
```
|
||||
|
||||
### [Quadrant Chart](../syntax/quadrantChart.md)
|
||||
|
||||
```mermaid-example
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
**In depth guides and examples can be found at [Getting Started](./n00b-gettingStarted.md) and [Usage](../config/usage.md).**
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
# Announcements
|
||||
|
||||
## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here)
|
||||
## [Bad documentation is bad for developers](https://www.mermaidchart.com/blog/posts/bad-documentation-is-bad-for-developers)
|
||||
|
||||
3 April 2023 · 3 mins
|
||||
26 April 2023 · 11 mins
|
||||
|
||||
Markdown Strings reduce the hassle # Starting from v10.
|
||||
Documentation tends to be bad because companies and projects don’t fully realize the costs of bad documentation.
|
||||
|
@@ -6,6 +6,18 @@
|
||||
|
||||
# Blog
|
||||
|
||||
## [Bad documentation is bad for developers](https://www.mermaidchart.com/blog/posts/bad-documentation-is-bad-for-developers)
|
||||
|
||||
26 April 2023 · 11 mins
|
||||
|
||||
Documentation tends to be bad because companies and projects don’t fully realize the costs of bad documentation.
|
||||
|
||||
## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here/)
|
||||
|
||||
3 April 2023 · 3 mins
|
||||
|
||||
Markdown Strings reduce the hassle # Starting from v10.
|
||||
|
||||
## [Mermaid Chart officially launched with sharable diagram links and presentation mode](https://www.mermaidchart.com/blog/posts/mermaid-chart-officially-launched-with-sharable-diagram-links-and-presentation-mode/)
|
||||
|
||||
27 March 2023 · 2 mins
|
||||
|
@@ -128,7 +128,7 @@ classDiagram
|
||||
Vehicle <|-- Car
|
||||
```
|
||||
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
|
||||
|
||||
### Class labels
|
||||
|
||||
@@ -283,12 +283,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
|
||||
- `#` Protected
|
||||
- `~` Package/Internal
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
|
||||
>
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$`
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
|
||||
>
|
||||
> - `$` Static e.g.: `String someField$`
|
||||
|
||||
@@ -421,6 +421,34 @@ And `Link` can be one of:
|
||||
| -- | Solid |
|
||||
| .. | Dashed |
|
||||
|
||||
## Define Namespace
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cardinality / Multiplicity on relations
|
||||
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
|
||||
@@ -604,10 +632,26 @@ You would define these actions on a separate line after all classes have been de
|
||||
|
||||
## Notes
|
||||
|
||||
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
|
||||
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
|
||||
|
||||
### Examples
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
note "This is a general note"
|
||||
note for MyClass "This is a note for a class"
|
||||
class MyClass{
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
note "This is a general note"
|
||||
note for MyClass "This is a note for a class"
|
||||
class MyClass{
|
||||
}
|
||||
```
|
||||
|
||||
_URL Link:_
|
||||
|
||||
```mermaid-example
|
||||
|
@@ -54,7 +54,45 @@ flowchart LR
|
||||
id1[This is the text in the box]
|
||||
```
|
||||
|
||||
## Graph
|
||||
#### Unicode text
|
||||
|
||||
Use `"` to enclose the unicode text.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
id["This ❤ Unicode"]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id["This ❤ Unicode"]
|
||||
```
|
||||
|
||||
#### Markdown formatting
|
||||
|
||||
Use double quotes and backticks "\` text \`" to enclose the markdown text.
|
||||
|
||||
```mermaid-example
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
markdown["`This **is** _Markdown_`"]
|
||||
newLines["`Line1
|
||||
Line 2
|
||||
Line 3`"]
|
||||
markdown --> newLines
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
markdown["`This **is** _Markdown_`"]
|
||||
newLines["`Line1
|
||||
Line 2
|
||||
Line 3`"]
|
||||
markdown --> newLines
|
||||
```
|
||||
|
||||
### Direction
|
||||
|
||||
This statement declares the direction of the Flowchart.
|
||||
|
||||
@@ -82,15 +120,13 @@ flowchart LR
|
||||
Start --> Stop
|
||||
```
|
||||
|
||||
## Flowchart Orientation
|
||||
|
||||
Possible FlowChart orientations are:
|
||||
|
||||
- TB - top to bottom
|
||||
- TD - top-down/ same as top to bottom
|
||||
- BT - bottom to top
|
||||
- RL - right to left
|
||||
- LR - left to right
|
||||
- TB - Top to bottom
|
||||
- TD - Top-down/ same as top to bottom
|
||||
- BT - Bottom to top
|
||||
- RL - Right to left
|
||||
- LR - Left to right
|
||||
|
||||
## Node shapes
|
||||
|
||||
@@ -633,7 +669,7 @@ flowchart TB
|
||||
end
|
||||
```
|
||||
|
||||
## flowcharts
|
||||
### flowcharts
|
||||
|
||||
With the graphtype flowchart it is also possible to set edges to and from subgraphs as in the flowchart below.
|
||||
|
||||
@@ -671,7 +707,7 @@ flowchart TB
|
||||
two --> c2
|
||||
```
|
||||
|
||||
## Direction in subgraphs
|
||||
### Direction in subgraphs
|
||||
|
||||
With the graphtype flowcharts you can use the direction statement to set the direction which the subgraph will render like in this example.
|
||||
|
||||
@@ -1002,7 +1038,7 @@ flowchart TD
|
||||
B-->E(A fa:fa-camera-retro perhaps?)
|
||||
```
|
||||
|
||||
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
|
||||
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
|
||||
|
||||
## Graph declarations with spaces between vertices and link and without semicolon
|
||||
|
||||
|
BIN
docs/syntax/img/zenuml-participant-annotators.png
Normal file
BIN
docs/syntax/img/zenuml-participant-annotators.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 255 KiB |
@@ -182,7 +182,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
|
||||
|
||||
## Icons
|
||||
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
|
168
docs/syntax/quadrantChart.md
Normal file
168
docs/syntax/quadrantChart.md
Normal file
@@ -0,0 +1,168 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/quadrantChart.md](../../packages/mermaid/src/docs/syntax/quadrantChart.md).
|
||||
|
||||
# Quadrant Chart
|
||||
|
||||
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
|
||||
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
> **Note**
|
||||
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||
|
||||
> **Note**
|
||||
> For points x and y value min value is 0 and max value is 1.
|
||||
|
||||
### Title
|
||||
|
||||
The title is a short description of the chart and it will always render on top of the chart.
|
||||
|
||||
#### Example
|
||||
|
||||
quadrantChart
|
||||
title This is a sample example
|
||||
|
||||
### x-axis
|
||||
|
||||
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
|
||||
2. `x-axis <text>` only the left axis text will be rendered.
|
||||
|
||||
### y-axis
|
||||
|
||||
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
|
||||
2. `y-axis <text>` only the bottom axis text will be rendered.
|
||||
|
||||
### Quadrants text
|
||||
|
||||
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
|
||||
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
|
||||
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
|
||||
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
|
||||
|
||||
### Points
|
||||
|
||||
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
|
||||
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
|
||||
|
||||
## Chart Configurations
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| --------------------------------- | ------------------------------------------------------------------------------------------------- | :-----------: |
|
||||
| chartWidth | Width of the chart | 500 |
|
||||
| chartHeight | Height of the chart | 500 |
|
||||
| titlePadding | Top and Bottom padding of the title | 10 |
|
||||
| titleFontSize | Title font size | 20 |
|
||||
| quadrantPadding | Padding outside all the quadrants | 5 |
|
||||
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
|
||||
| quadrantLabelFontSize | Quadrant text font size | 16 |
|
||||
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
|
||||
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
|
||||
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
|
||||
| xAxisLabelFontSize | X-axis texts font size | 16 |
|
||||
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will alway be rendered in bottom | 'top' |
|
||||
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
|
||||
| yAxisLabelFontSize | Y-axis texts font size | 16 |
|
||||
| yAxisPosition | Position of y-axis (left , right) | 'left' |
|
||||
| pointTextPadding | Padding between point and the below text | 5 |
|
||||
| pointLabelFontSize | Point text font size | 12 |
|
||||
| pointRadius | Radius of the point to be drawn | 5 |
|
||||
|
||||
## Chart Theme Variables
|
||||
|
||||
| Parameter | Description |
|
||||
| -------------------------------- | --------------------------------------- |
|
||||
| quadrant1Fill | Fill color of the top right quadrant |
|
||||
| quadrant2Fill | Fill color of the top left quadrant |
|
||||
| quadrant3Fill | Fill color of the bottom left quadrant |
|
||||
| quadrant4Fill | Fill color of the bottom right quadrant |
|
||||
| quadrant1TextFill | Text color of the top right quadrant |
|
||||
| quadrant2TextFill | Text color of the top left quadrant |
|
||||
| quadrant3TextFill | Text color of the bottom left quadrant |
|
||||
| quadrant4TextFill | Text color of the bottom right quadrant |
|
||||
| quadrantPointFill | Points fill color |
|
||||
| quadrantPointTextFill | Points text color |
|
||||
| quadrantXAxisTextFill | X-axis text color |
|
||||
| quadrantYAxisTextFill | Y-axis text color |
|
||||
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
|
||||
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
|
||||
| quadrantTitleFill | Title color |
|
||||
|
||||
## Example on config and theme
|
||||
|
||||
```mermaid-example
|
||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
quadrant-4 Delete
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
quadrant-4 Delete
|
||||
```
|
472
docs/syntax/zenuml.md
Normal file
472
docs/syntax/zenuml.md
Normal file
@@ -0,0 +1,472 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/zenuml.md](../../packages/mermaid/src/docs/syntax/zenuml.md).
|
||||
|
||||
# ZenUML
|
||||
|
||||
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
|
||||
|
||||
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
|
||||
syntax than the original Sequence Diagram in mermaid.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
### Participants
|
||||
|
||||
The participants can be defined implicitly as in the first example on this page. The participants or actors are
|
||||
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
|
||||
different order than how they appear in the first message. It is possible to specify the actor's order of
|
||||
appearance by doing the following:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
### Annotators
|
||||
|
||||
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
Here are the available annotators:
|
||||

|
||||
|
||||
### Aliases
|
||||
|
||||
The participants can have a convenient identifier and a descriptive label.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
## Messages
|
||||
|
||||
Messages can be one of:
|
||||
|
||||
1. Sync message
|
||||
2. Async message
|
||||
3. Creation message
|
||||
4. Reply message
|
||||
|
||||
### Sync message
|
||||
|
||||
You can think of a sync (blocking) method in a programming language.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
### Async message
|
||||
|
||||
You can think of an async (non-blocking) method in a programming language.
|
||||
Fire an event and forget about it.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
### Creation message
|
||||
|
||||
We use `new` keyword to create an object.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
### Reply message
|
||||
|
||||
There are three ways to express a reply message:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting
|
||||
|
||||
Sync messages and Creation messages are naturally nestable with `{}`.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
It is possible to add comments to a sequence diagram with `// comment` syntax.
|
||||
Comments will be rendered above the messages or fragments. Comments on other places
|
||||
are ignored. Markdown is supported.
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
It is possible to express loops in a ZenUML diagram. This is done by any of the
|
||||
following notations:
|
||||
|
||||
1. while
|
||||
2. for
|
||||
3. forEach, foreach
|
||||
4. loop
|
||||
|
||||
```zenuml
|
||||
while(condition) {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
## Alt
|
||||
|
||||
It is possible to express alternative paths in a sequence diagram. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
if(condition1) {
|
||||
...statements...
|
||||
} else if(condition2) {
|
||||
...statements...
|
||||
} else {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
## Opt
|
||||
|
||||
It is possible to render an `opt` fragment. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
opt {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
## Parallel
|
||||
|
||||
It is possible to show actions that are happening in parallel.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```zenuml
|
||||
par {
|
||||
statement1
|
||||
statement2
|
||||
statement3
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
## Try/Catch/Finally (Break)
|
||||
|
||||
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
|
||||
|
||||
This is done by the notation
|
||||
|
||||
try {
|
||||
...statements...
|
||||
} catch {
|
||||
...statements...
|
||||
} finally {
|
||||
...statements...
|
||||
}
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
## Integrating with your library/website.
|
||||
|
||||
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
|
||||
|
||||
You can use this method to add mermaid including the zenuml diagram to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
</script>
|
||||
```
|
85
package.json
85
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "mermaid-monorepo",
|
||||
"private": true,
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.3",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@7.30.1",
|
||||
"packageManager": "pnpm@8.5.1",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@@ -18,7 +18,7 @@
|
||||
"build:vite": "ts-node-esm --transpileOnly .vite/build.ts",
|
||||
"build:mermaid": "pnpm build:vite --mermaid",
|
||||
"build:viz": "pnpm build:mermaid --visualize",
|
||||
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"build:watch": "pnpm build:vite --watch",
|
||||
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
|
||||
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
|
||||
@@ -26,6 +26,7 @@
|
||||
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
||||
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
|
||||
"contributors": "ts-node-esm scripts/updateContributors.ts",
|
||||
"cypress": "cypress run",
|
||||
"cypress:open": "cypress open",
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
@@ -54,65 +55,65 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-cypress": "^3.27.6",
|
||||
"@commitlint/cli": "^17.2.0",
|
||||
"@commitlint/config-conventional": "^17.2.0",
|
||||
"@cspell/eslint-plugin": "^6.14.2",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@applitools/eyes-cypress": "^3.32.0",
|
||||
"@commitlint/cli": "^17.6.1",
|
||||
"@commitlint/config-conventional": "^17.6.1",
|
||||
"@cspell/eslint-plugin": "^6.31.1",
|
||||
"@rollup/plugin-typescript": "^11.1.0",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/eslint": "^8.4.10",
|
||||
"@types/eslint": "^8.37.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/mdast": "^3.0.10",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/mdast": "^3.0.11",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"@vitest/coverage-c8": "^0.29.0",
|
||||
"@vitest/spy": "^0.29.0",
|
||||
"@vitest/ui": "^0.29.0",
|
||||
"concurrently": "^7.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-c8": "^0.31.0",
|
||||
"@vitest/spy": "^0.31.0",
|
||||
"@vitest/ui": "^0.31.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"coveralls": "^3.1.1",
|
||||
"cypress": "^12.0.0",
|
||||
"cypress": "^12.10.0",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"esbuild": "^0.17.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"esbuild": "^0.17.18",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.2",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-jest": "^27.1.5",
|
||||
"eslint-plugin-jsdoc": "^39.6.2",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-jsdoc": "^43.0.7",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-lodash": "^7.4.0",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.17",
|
||||
"eslint-plugin-unicorn": "^45.0.0",
|
||||
"eslint-plugin-unicorn": "^46.0.0",
|
||||
"express": "^4.18.2",
|
||||
"globby": "^13.1.2",
|
||||
"husky": "^8.0.2",
|
||||
"jest": "^29.3.1",
|
||||
"globby": "^13.1.4",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.5.0",
|
||||
"jison": "^0.4.18",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"jsdom": "^21.1.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pnpm": "^7.15.0",
|
||||
"prettier": "^2.7.1",
|
||||
"pnpm": "^8.3.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"rimraf": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.8.3",
|
||||
"start-server-and-test": "^1.15.4",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.29.0"
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.1",
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.15.0"
|
||||
"node": "18.16.0"
|
||||
}
|
||||
}
|
||||
|
@@ -48,8 +48,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"concurrently": "^7.5.0",
|
||||
"rimraf": "^4.0.0",
|
||||
"concurrently": "^8.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"mermaid": "workspace:*"
|
||||
},
|
||||
"resolutions": {
|
||||
|
1
packages/mermaid-zenuml/README.md
Symbolic link
1
packages/mermaid-zenuml/README.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../mermaid/src/docs/syntax/zenuml.md
|
47
packages/mermaid-zenuml/package.json
Normal file
47
packages/mermaid-zenuml/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@mermaid-js/mermaid-zenuml",
|
||||
"version": "0.1.0",
|
||||
"description": "MermaidJS plugin for ZenUML integration",
|
||||
"module": "dist/mermaid-zenuml.core.mjs",
|
||||
"types": "dist/detector.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-zenuml.core.mjs",
|
||||
"types": "./dist/detector.d.ts"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"zenuml",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid",
|
||||
"directory": "packages/mermaid-zenuml"
|
||||
},
|
||||
"contributors": [
|
||||
"Peng Xiao (https://github.com/MrCoder)",
|
||||
"Sidharth Vinod (https://sidharth.dev)",
|
||||
"Dong Cai (https://github.com/dontry)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zenuml/core": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "workspace:>=10.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
21
packages/mermaid-zenuml/src/detector.ts
Normal file
21
packages/mermaid-zenuml/src/detector.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { ExternalDiagramDefinition } from 'mermaid';
|
||||
|
||||
const id = 'zenuml';
|
||||
const regexp = /^\s*zenuml/;
|
||||
|
||||
const detector = (txt: string) => {
|
||||
return txt.match(regexp) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./zenuml-definition.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
58
packages/mermaid-zenuml/src/mermaidUtils.ts
Normal file
58
packages/mermaid-zenuml/src/mermaidUtils.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { MermaidConfig } from 'mermaid';
|
||||
|
||||
const warning = (s: string) => {
|
||||
// Todo remove debug code
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Log function was called before initialization', s);
|
||||
};
|
||||
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||
|
||||
export const LEVELS: Record<LogLevel, number> = {
|
||||
trace: 0,
|
||||
debug: 1,
|
||||
info: 2,
|
||||
warn: 3,
|
||||
error: 4,
|
||||
fatal: 5,
|
||||
};
|
||||
|
||||
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
|
||||
trace: warning,
|
||||
debug: warning,
|
||||
info: warning,
|
||||
warn: warning,
|
||||
error: warning,
|
||||
fatal: warning,
|
||||
};
|
||||
|
||||
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||
export let getConfig: () => MermaidConfig;
|
||||
export let sanitizeText: (str: string) => string;
|
||||
// eslint-disable @typescript-eslint/no-explicit-any
|
||||
export let setupGraphViewbox: (
|
||||
graph: any,
|
||||
svgElem: any,
|
||||
padding: any,
|
||||
useMaxWidth: boolean
|
||||
) => void;
|
||||
|
||||
export const injectUtils = (
|
||||
_log: Record<keyof typeof LEVELS, typeof console.log>,
|
||||
_setLogLevel: any,
|
||||
_getConfig: any,
|
||||
_sanitizeText: any,
|
||||
_setupGraphViewbox: any
|
||||
) => {
|
||||
_log.info('Mermaid utils injected');
|
||||
log.trace = _log.trace;
|
||||
log.debug = _log.debug;
|
||||
log.info = _log.info;
|
||||
log.warn = _log.warn;
|
||||
log.error = _log.error;
|
||||
log.fatal = _log.fatal;
|
||||
setLogLevel = _setLogLevel;
|
||||
getConfig = _getConfig;
|
||||
sanitizeText = _sanitizeText;
|
||||
setupGraphViewbox = _setupGraphViewbox;
|
||||
};
|
12
packages/mermaid-zenuml/src/parser.ts
Normal file
12
packages/mermaid-zenuml/src/parser.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* ZenUML manage parsing internally. It uses Antlr4 to parse the DSL.
|
||||
* The parser is defined in https://github.com/ZenUml/vue-sequence/blob/main/src/parser/index.js
|
||||
*
|
||||
* This is a dummy parser that satisfies the mermaid API logic.
|
||||
*/
|
||||
export default {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
};
|
17
packages/mermaid-zenuml/src/zenuml-definition.ts
Normal file
17
packages/mermaid-zenuml/src/zenuml-definition.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { injectUtils } from './mermaidUtils.js';
|
||||
import parser from './parser.js';
|
||||
import renderer from './zenumlRenderer.js';
|
||||
|
||||
export const diagram = {
|
||||
db: {
|
||||
clear: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer,
|
||||
parser,
|
||||
styles: () => {
|
||||
// no-op
|
||||
},
|
||||
injectUtils,
|
||||
};
|
68
packages/mermaid-zenuml/src/zenumlRenderer.ts
Normal file
68
packages/mermaid-zenuml/src/zenumlRenderer.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getConfig, log } from './mermaidUtils.js';
|
||||
import ZenUml from '@zenuml/core';
|
||||
|
||||
const regexp = /^\s*zenuml/;
|
||||
|
||||
// Create a Zen UML container outside the svg first for rendering, otherwise the Zen UML diagram cannot be rendered properly
|
||||
function createTemporaryZenumlContainer(id: string) {
|
||||
const container = document.createElement('div');
|
||||
container.id = `container-${id}`;
|
||||
container.style.display = 'flex';
|
||||
container.innerHTML = `<div id="zenUMLApp-${id}"></div>`;
|
||||
const app = container.querySelector(`#zenUMLApp-${id}`) as HTMLElement;
|
||||
return { container, app };
|
||||
}
|
||||
|
||||
// Create a foreignObject to wrap the Zen UML container in the svg
|
||||
function createForeignObject(id: string) {
|
||||
const foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
|
||||
foreignObject.setAttribute('x', '0');
|
||||
foreignObject.setAttribute('y', '0');
|
||||
foreignObject.setAttribute('width', '100%');
|
||||
foreignObject.setAttribute('height', '100%');
|
||||
const { container, app } = createTemporaryZenumlContainer(id);
|
||||
foreignObject.appendChild(container);
|
||||
return { foreignObject, container, app };
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a Zen UML in the tag with id: id based on the graph definition in text.
|
||||
*
|
||||
* @param text - The text of the diagram
|
||||
* @param id - The id of the diagram which will be used as a DOM element id¨
|
||||
*/
|
||||
export const draw = async function (text: string, id: string) {
|
||||
log.info('draw with Zen UML renderer', ZenUml);
|
||||
|
||||
text = text.replace(regexp, '');
|
||||
const { securityLevel } = getConfig();
|
||||
// Handle root and Document for when rendering in sandbox mode
|
||||
let sandboxElement: HTMLIFrameElement | null = null;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = document.getElementById('i' + id) as HTMLIFrameElement;
|
||||
}
|
||||
|
||||
const root = securityLevel === 'sandbox' ? sandboxElement?.contentWindow?.document : document;
|
||||
|
||||
const svgContainer = root?.querySelector(`svg#${id}`);
|
||||
|
||||
if (!root || !svgContainer) {
|
||||
log.error('Cannot find root or svgContainer');
|
||||
return;
|
||||
}
|
||||
|
||||
const { foreignObject, container, app } = createForeignObject(id);
|
||||
svgContainer.appendChild(foreignObject);
|
||||
// @ts-expect-error @zenuml/core@3.0.0 exports the wrong type for ZenUml
|
||||
const zenuml = new ZenUml(app);
|
||||
// default is a theme name. More themes to be added and will be configurable in the future
|
||||
await zenuml.render(text, 'theme-mermaid');
|
||||
|
||||
const { width, height } = window.getComputedStyle(container);
|
||||
log.debug('zenuml diagram size', width, height);
|
||||
svgContainer.setAttribute('style', `width: ${width}; height: ${height};`);
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
9
packages/mermaid-zenuml/tsconfig.json
Normal file
9
packages/mermaid-zenuml/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
5
packages/mermaid/.gitignore
vendored
5
packages/mermaid/.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
src/vitepress
|
||||
src/docs/config/setup
|
||||
README.*
|
||||
README.*
|
||||
src/docs/public/user-avatars/
|
||||
src/docs/.vitepress/cache
|
||||
src/docs/.vitepress/components.d.ts
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.1.0",
|
||||
"version": "10.2.3",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@@ -28,8 +28,8 @@
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"vitepress dev src/vitepress\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||
"release": "pnpm build",
|
||||
@@ -52,20 +52,20 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@khanacademy/simple-markdown": "^0.8.6",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "2.4.5",
|
||||
"dompurify": "3.0.3",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2",
|
||||
"stylis": "^4.1.2",
|
||||
"stylis": "^4.1.3",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^9.0.0",
|
||||
"web-worker": "^1.2.0"
|
||||
@@ -73,43 +73,46 @@
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/micromatch": "^4.0.2",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/stylis": "^4.0.2",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
||||
"@typescript-eslint/parser": "^5.42.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"concurrently": "^7.5.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"coveralls": "^3.1.1",
|
||||
"cpy-cli": "^4.2.0",
|
||||
"cspell": "^6.14.3",
|
||||
"cspell": "^6.31.1",
|
||||
"csstree-validator": "^3.0.0",
|
||||
"globby": "^13.1.2",
|
||||
"globby": "^13.1.4",
|
||||
"jison": "^0.4.18",
|
||||
"js-base64": "^3.7.2",
|
||||
"jsdom": "^21.0.0",
|
||||
"js-base64": "^3.7.5",
|
||||
"jsdom": "^21.1.1",
|
||||
"micromatch": "^4.0.5",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.8",
|
||||
"remark": "^14.0.2",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^4.0.0",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"typedoc": "^0.23.18",
|
||||
"typedoc-plugin-markdown": "^3.13.6",
|
||||
"typescript": "^4.8.4",
|
||||
"rimraf": "^5.0.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"typedoc": "^0.24.5",
|
||||
"typedoc-plugin-markdown": "^3.15.2",
|
||||
"typescript": "^5.0.4",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
"vitepress": "^1.0.0-alpha.46",
|
||||
"vitepress-plugin-search": "^1.0.4-alpha.19"
|
||||
"vitepress": "^1.0.0-alpha.72",
|
||||
"vitepress-plugin-search": "^1.0.4-alpha.20"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"dist/",
|
||||
"README.md"
|
||||
],
|
||||
"sideEffects": false
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ export interface MermaidConfig {
|
||||
state?: StateDiagramConfig;
|
||||
er?: ErDiagramConfig;
|
||||
pie?: PieDiagramConfig;
|
||||
quadrantChart?: QuadrantChartConfig;
|
||||
requirement?: RequirementDiagramConfig;
|
||||
mindmap?: MindmapDiagramConfig;
|
||||
gitGraph?: GitGraphDiagramConfig;
|
||||
@@ -226,6 +227,27 @@ export interface PieDiagramConfig extends BaseDiagramConfig {
|
||||
textPosition?: number;
|
||||
}
|
||||
|
||||
export interface QuadrantChartConfig extends BaseDiagramConfig {
|
||||
chartWidth: number;
|
||||
chartHeight: number;
|
||||
titleFontSize: number;
|
||||
titlePadding: number;
|
||||
quadrantPadding: number;
|
||||
xAxisLabelPadding: number;
|
||||
yAxisLabelPadding: number;
|
||||
xAxisLabelFontSize: number;
|
||||
yAxisLabelFontSize: number;
|
||||
quadrantLabelFontSize: number;
|
||||
quadrantTextTopPadding: number;
|
||||
pointTextPadding: number;
|
||||
pointLabelFontSize: number;
|
||||
pointRadius: number;
|
||||
xAxisPosition: 'top' | 'bottom';
|
||||
yAxisPosition: 'left' | 'right';
|
||||
quadrantInternalBorderStrokeWidth: number;
|
||||
quadrantExternalBorderStrokeWidth: number;
|
||||
}
|
||||
|
||||
export interface ErDiagramConfig extends BaseDiagramConfig {
|
||||
titleTopMargin?: number;
|
||||
diagramPadding?: number;
|
||||
|
@@ -88,13 +88,13 @@ const config: Partial<MermaidConfig> = {
|
||||
*
|
||||
* **Notes**:
|
||||
*
|
||||
* - **strict**: (**default**) tags in text are encoded, click functionality is disabled
|
||||
* - **loose**: tags in text are allowed, click functionality is enabled
|
||||
* - **antiscript**: html tags in text are allowed, (only script element is removed), click
|
||||
* functionality is enabled
|
||||
* - **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This
|
||||
* - **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
|
||||
* - **antiscript**: HTML tags in text are allowed (only script elements are removed), and click
|
||||
* functionality is enabled.
|
||||
* - **loose**: HTML tags in text are allowed and click functionality is enabled.
|
||||
* - **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This
|
||||
* prevent any JavaScript from running in the context. This may hinder interactive functionality
|
||||
* of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc.
|
||||
* of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
|
||||
*/
|
||||
securityLevel: 'strict',
|
||||
|
||||
@@ -1280,6 +1280,184 @@ const config: Partial<MermaidConfig> = {
|
||||
textPosition: 0.75,
|
||||
},
|
||||
|
||||
quadrantChart: {
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | chartWidth | Width of the chart | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 500
|
||||
*/
|
||||
chartWidth: 500,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | chartHeight | Height of the chart | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 500
|
||||
*/
|
||||
chartHeight: 500,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | titlePadding | Chart title top and bottom padding | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 10
|
||||
*/
|
||||
titlePadding: 10,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | titleFontSize | Chart title font size | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 20
|
||||
*/
|
||||
titleFontSize: 20,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | quadrantPadding | Padding around the quadrant square | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
quadrantPadding: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ---------------------- | -------------------------------------------------------------------------- | ------- | -------- | ------------------- |
|
||||
* | quadrantTextTopPadding | quadrant title padding from top if the quadrant is rendered on top | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
quadrantTextTopPadding: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | quadrantLabelFontSize | quadrant title font size | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 16
|
||||
*/
|
||||
quadrantLabelFontSize: 16,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------------------------- | ------------------------------------------------------------- | ------- | -------- | ------------------- |
|
||||
* | quadrantInternalBorderStrokeWidth | stroke width of edges of the box that are inside the quadrant | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 1
|
||||
*/
|
||||
quadrantInternalBorderStrokeWidth: 1,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------------------------- | -------------------------------------------------------------- | ------- | -------- | ------------------- |
|
||||
* | quadrantExternalBorderStrokeWidth | stroke width of edges of the box that are outside the quadrant | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 2
|
||||
*/
|
||||
quadrantExternalBorderStrokeWidth: 2,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | xAxisLabelPadding | Padding around x-axis labels | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
xAxisLabelPadding: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | xAxisLabelFontSize | x-axis label font size | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 16
|
||||
*/
|
||||
xAxisLabelFontSize: 16,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
|
||||
* | xAxisPosition | position of x-axis labels | string | Optional | 'top' or 'bottom' |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: top
|
||||
*/
|
||||
xAxisPosition: 'top',
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | yAxisLabelPadding | Padding around y-axis labels | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
yAxisLabelPadding: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
|
||||
* | yAxisLabelFontSize | y-axis label font size | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 16
|
||||
*/
|
||||
yAxisLabelFontSize: 16,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
|
||||
* | yAxisPosition | position of y-axis labels | string | Optional | 'left' or 'right' |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: left
|
||||
*/
|
||||
yAxisPosition: 'left',
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ---------------------- | -------------------------------------- | ------- | -------- | ------------------- |
|
||||
* | pointTextPadding | padding between point and point label | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
pointTextPadding: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ---------------------- | ---------------------- | ------- | -------- | ------------------- |
|
||||
* | pointTextPadding | point title font size | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 12
|
||||
*/
|
||||
pointLabelFontSize: 12,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
|
||||
* | pointRadius | radius of the point to be drawn | number | Optional | Any positive number |
|
||||
*
|
||||
* **Notes:**
|
||||
* Default value: 5
|
||||
*/
|
||||
pointRadius: 5,
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | ----------- | ------- | -------- | ----------- |
|
||||
* | useMaxWidth | See Notes | boolean | Required | true, false |
|
||||
*
|
||||
* **Notes:**
|
||||
*
|
||||
* When this flag is set to true, the diagram width is locked to 100% and scaled based on
|
||||
* available space. If set to false, the diagram reserves its absolute width.
|
||||
*
|
||||
* Default value: true
|
||||
*/
|
||||
useMaxWidth: true,
|
||||
},
|
||||
|
||||
/** The object containing configurations specific for req diagrams */
|
||||
requirement: {
|
||||
useWidth: undefined,
|
||||
|
@@ -6,6 +6,7 @@ import git from '../diagrams/git/gitGraphDetector.js';
|
||||
import gantt from '../diagrams/gantt/ganttDetector.js';
|
||||
import info from '../diagrams/info/infoDetector.js';
|
||||
import pie from '../diagrams/pie/pieDetector.js';
|
||||
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
|
||||
import requirement from '../diagrams/requirement/requirementDetector.js';
|
||||
import sequence from '../diagrams/sequence/sequenceDetector.js';
|
||||
import classDiagram from '../diagrams/class/classDetector.js';
|
||||
@@ -77,6 +78,7 @@ export const addDiagrams = () => {
|
||||
git,
|
||||
stateV2,
|
||||
state,
|
||||
journey
|
||||
journey,
|
||||
quadrantChart
|
||||
);
|
||||
};
|
||||
|
@@ -1,28 +1,9 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, width, height, x, y, link) {
|
||||
@@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) {
|
||||
|
||||
// <rect fill="#08427B" height="119.2188" rx="2.5" ry="2.5" stroke="#073B6F" stroke-width="0.5" width="110" x="120" y="7"/>
|
||||
// draw rect of c4Shape
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
|
||||
switch (c4Shape.typeC4Shape.text) {
|
||||
case 'person':
|
||||
case 'external_person':
|
||||
@@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const insertArrowEnd = function (elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
@@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) {
|
||||
// this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const getC4ShapeFont = (cnf, typeC4Shape) => {
|
||||
return {
|
||||
fontFamily: cnf[typeC4Shape + 'FontFamily'],
|
||||
@@ -714,6 +686,4 @@ export default {
|
||||
insertDatabaseIcon,
|
||||
insertComputerIcon,
|
||||
insertClockIcon,
|
||||
getNoteRect,
|
||||
sanitizeUrl, // TODO why is this exported?
|
||||
};
|
||||
|
@@ -14,7 +14,14 @@ import {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../../commonDb.js';
|
||||
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes.js';
|
||||
import {
|
||||
ClassRelation,
|
||||
ClassNode,
|
||||
ClassNote,
|
||||
ClassMap,
|
||||
NamespaceMap,
|
||||
NamespaceNode,
|
||||
} from './classTypes.js';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
||||
|
||||
@@ -22,6 +29,8 @@ let relations: ClassRelation[] = [];
|
||||
let classes: ClassMap = {};
|
||||
let notes: ClassNote[] = [];
|
||||
let classCounter = 0;
|
||||
let namespaces: NamespaceMap = {};
|
||||
let namespaceCounter = 0;
|
||||
|
||||
let functions: any[] = [];
|
||||
|
||||
@@ -100,12 +109,15 @@ export const clear = function () {
|
||||
notes = [];
|
||||
functions = [];
|
||||
functions.push(setupToolTips);
|
||||
namespaces = {};
|
||||
namespaceCounter = 0;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export const getClass = function (id: string) {
|
||||
return classes[id];
|
||||
};
|
||||
|
||||
export const getClasses = function () {
|
||||
return classes;
|
||||
};
|
||||
@@ -170,9 +182,10 @@ export const addMember = function (className: string, member: string) {
|
||||
const memberString = member.trim();
|
||||
|
||||
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
|
||||
// Remove leading and trailing brackets
|
||||
// its an annotation
|
||||
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
|
||||
} else if (memberString.indexOf(')') > 0) {
|
||||
//its a method
|
||||
theClass.methods.push(sanitizeText(memberString));
|
||||
} else if (memberString) {
|
||||
theClass.members.push(sanitizeText(memberString));
|
||||
@@ -234,7 +247,12 @@ const setTooltip = function (ids: string, tooltip?: string) {
|
||||
}
|
||||
});
|
||||
};
|
||||
export const getTooltip = function (id: string) {
|
||||
|
||||
export const getTooltip = function (id: string, namespace?: string) {
|
||||
if (namespace) {
|
||||
return namespaces[namespace].classes[id].tooltip;
|
||||
}
|
||||
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
/**
|
||||
@@ -392,6 +410,52 @@ const setDirection = (dir: string) => {
|
||||
direction = dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @public
|
||||
*/
|
||||
export const addNamespace = function (id: string) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
namespaces[id] = {
|
||||
id: id,
|
||||
classes: {},
|
||||
children: {},
|
||||
domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter,
|
||||
} as NamespaceNode;
|
||||
|
||||
namespaceCounter++;
|
||||
};
|
||||
|
||||
const getNamespace = function (name: string): NamespaceNode {
|
||||
return namespaces[name];
|
||||
};
|
||||
|
||||
const getNamespaces = function (): NamespaceMap {
|
||||
return namespaces;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @param classNames - Ids of the class to add
|
||||
* @public
|
||||
*/
|
||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
classNames.map((className) => {
|
||||
namespaces[id].classes[className] = classes[className];
|
||||
delete classes[className];
|
||||
classCounter--;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
parseDirective,
|
||||
setAccTitle,
|
||||
@@ -425,4 +489,8 @@ export default {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setClassLabel,
|
||||
addNamespace,
|
||||
addClassesToNamespace,
|
||||
getNamespace,
|
||||
getNamespaces,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs');
|
||||
|
||||
import { LALRGenerator } from 'jison';
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
it('should introduce no new conflicts', function () {
|
||||
const file = require.resolve('./parser/classDiagram.jison');
|
||||
const grammarSource = fs.readFileSync(file, 'utf8');
|
||||
const grammarParser = new LALRGenerator(grammarSource, {});
|
||||
expect(grammarParser.conflicts < 16).toBe(true);
|
||||
});
|
||||
});
|
@@ -0,0 +1,16 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
// @ts-ignore - no types
|
||||
import { LALRGenerator } from 'jison';
|
||||
|
||||
const getAbsolutePath = (relativePath: string) => {
|
||||
return fileURLToPath(new URL(relativePath, import.meta.url));
|
||||
};
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
it('should have no conflicts', async function () {
|
||||
const grammarSource = await readFile(getAbsolutePath('./parser/classDiagram.jison'), 'utf8');
|
||||
const grammarParser = new LALRGenerator(grammarSource, {});
|
||||
expect(grammarParser.conflicts).toBe(0);
|
||||
});
|
||||
});
|
78
packages/mermaid/src/diagrams/class/classParser.spec.ts
Normal file
78
packages/mermaid/src/diagrams/class/classParser.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { setConfig } from '../../config.js';
|
||||
import classDB from './classDb.js';
|
||||
// @ts-ignore - no types in jison
|
||||
import classDiagram from './parser/classDiagram.jison';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('when parsing class diagram', function () {
|
||||
beforeEach(function () {
|
||||
classDiagram.parser.yy = classDB;
|
||||
classDiagram.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse diagram with direction', () => {
|
||||
classDiagram.parser.parse(`classDiagram
|
||||
direction TB
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides`);
|
||||
|
||||
expect(Object.keys(classDB.getClasses()).length).toBe(3);
|
||||
expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [],
|
||||
"domId": "classId-Student-0",
|
||||
"id": "Student",
|
||||
"label": "Student",
|
||||
"members": [
|
||||
"-idCard : IdCard",
|
||||
],
|
||||
"methods": [],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
expect(classDB.getRelations().length).toBe(2);
|
||||
expect(classDB.getRelations()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "IdCard",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "carries",
|
||||
},
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "Bike",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "rides",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
@@ -8,7 +8,7 @@ import utils from '../../utils.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||
import common from '../common/common.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
|
||||
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
@@ -19,6 +19,59 @@ let conf = {
|
||||
curve: undefined,
|
||||
};
|
||||
|
||||
interface RectParameters {
|
||||
id: string;
|
||||
shape: 'rect';
|
||||
labelStyle: string;
|
||||
domId: string;
|
||||
labelText: string;
|
||||
padding: number | undefined;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param namespaces - Object containing the vertices.
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
*/
|
||||
export const addNamespaces = function (
|
||||
namespaces: NamespaceMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
) {
|
||||
const keys = Object.keys(namespaces);
|
||||
log.info('keys:', keys);
|
||||
log.info(namespaces);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = namespaces[id];
|
||||
|
||||
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||
const shape = 'rect';
|
||||
|
||||
const node: RectParameters = {
|
||||
shape: shape,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
labelText: sanitizeText(vertex.id),
|
||||
labelStyle: '',
|
||||
style: 'fill: none; stroke: black',
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
|
||||
g.setNode(vertex.id, node);
|
||||
addClasses(vertex.classes, g, _id, diagObj, vertex.id);
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
@@ -26,12 +79,14 @@ let conf = {
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
* @param parent - id of the parent namespace, if it exists
|
||||
*/
|
||||
export const addClasses = function (
|
||||
classes: ClassMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
diagObj: any,
|
||||
parent?: string
|
||||
) {
|
||||
const keys = Object.keys(classes);
|
||||
log.info('keys:', keys);
|
||||
@@ -55,6 +110,7 @@ export const addClasses = function (
|
||||
const vertexText = vertex.label ?? vertex.id;
|
||||
const radius = 0;
|
||||
const shape = 'class_box';
|
||||
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
@@ -67,7 +123,7 @@ export const addClasses = function (
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
tooltip: diagObj.db.getTooltip(vertex.id) || '',
|
||||
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
|
||||
haveCallback: vertex.haveCallback,
|
||||
link: vertex.link,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
@@ -76,6 +132,11 @@ export const addClasses = function (
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
g.setNode(vertex.id, node);
|
||||
|
||||
if (parent) {
|
||||
g.setParent(vertex.id, parent);
|
||||
}
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
@@ -275,10 +336,12 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
});
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const namespaces: NamespaceMap = diagObj.db.getNamespaces();
|
||||
const classes: ClassMap = diagObj.db.getClasses();
|
||||
const relations: ClassRelation[] = diagObj.db.getRelations();
|
||||
const notes: ClassNote[] = diagObj.db.getNotes();
|
||||
log.info(relations);
|
||||
addNamespaces(namespaces, g, id, diagObj);
|
||||
addClasses(classes, g, id, diagObj);
|
||||
addRelations(relations, g);
|
||||
addNotes(notes, g, relations.length + 1, classes);
|
||||
|
@@ -52,4 +52,13 @@ export type ClassRelation = {
|
||||
lineType: number;
|
||||
};
|
||||
};
|
||||
|
||||
export interface NamespaceNode {
|
||||
id: string;
|
||||
domId: string;
|
||||
classes: ClassMap;
|
||||
children: NamespaceMap;
|
||||
}
|
||||
|
||||
export type ClassMap = Record<string, ClassNode>;
|
||||
export type NamespaceMap = Record<string, NamespaceNode>;
|
||||
|
@@ -19,6 +19,10 @@
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%x class
|
||||
%x class-body
|
||||
%x namespace
|
||||
%x namespace-body
|
||||
%%
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
.*direction\s+TB[^\n]* return 'direction_tb';
|
||||
@@ -41,35 +45,41 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
|
||||
\s*(\r?\n)+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
|
||||
"classDiagram-v2" return 'CLASS_DIAGRAM';
|
||||
"classDiagram" return 'CLASS_DIAGRAM';
|
||||
[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
|
||||
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
|
||||
<struct><<EOF>> return "EOF_IN_STRUCT";
|
||||
<struct>[{] return "OPEN_IN_STRUCT";
|
||||
<struct>[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
|
||||
<struct>[\n] /* nothing */
|
||||
<struct>[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
|
||||
"[*]" return 'EDGE_STATE';
|
||||
|
||||
"class" return 'CLASS';
|
||||
"cssClass" return 'CSSCLASS';
|
||||
"callback" return 'CALLBACK';
|
||||
"link" return 'LINK';
|
||||
"click" return 'CLICK';
|
||||
"note for" return 'NOTE_FOR';
|
||||
"note" return 'NOTE';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
[~] this.begin("generic");
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
|
||||
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<namespace>\s+ /* skip whitespace */
|
||||
<namespace>[{] { this.begin("namespace-body"); return 'STRUCT_START';}
|
||||
<namespace-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<namespace-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<namespace-body>\s*(\r?\n)+ return 'NEWLINE';
|
||||
<namespace-body>\s+ /* skip whitespace */
|
||||
<namespace-body>"[*]" return 'EDGE_STATE';
|
||||
|
||||
[`] this.begin("bqstring");
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<INITIAL,namespace-body>"class" { this.begin('class'); return 'CLASS';}
|
||||
<class>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<class>\s+ /* skip whitespace */
|
||||
<class>[}] { this.popState(); this.popState(); return 'STRUCT_STOP';}
|
||||
<class>[{] { this.begin("class-body"); return 'STRUCT_START';}
|
||||
<class-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<class-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<class-body>"[*]" { return 'EDGE_STATE';}
|
||||
<class-body>[{] return "OPEN_IN_STRUCT";
|
||||
<class-body>[\n] /* nothing */
|
||||
<class-body>[^{}\n]* { return "MEMBER";}
|
||||
|
||||
<*>"cssClass" return 'CSSCLASS';
|
||||
<*>"callback" return 'CALLBACK';
|
||||
<*>"link" return 'LINK';
|
||||
<*>"click" return 'CLICK';
|
||||
<*>"note for" return 'NOTE_FOR';
|
||||
<*>"note" return 'NOTE';
|
||||
<*>"<<" return 'ANNOTATION_START';
|
||||
<*>">>" return 'ANNOTATION_END';
|
||||
|
||||
/*
|
||||
---interactivity command---
|
||||
@@ -77,7 +87,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
line was introduced with 'click'.
|
||||
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
|
||||
*/
|
||||
"href"[\s]+["] this.begin("href");
|
||||
<*>"href"[\s]+["] this.begin("href");
|
||||
<href>["] this.popState();
|
||||
<href>[^"]* return 'HREF';
|
||||
|
||||
@@ -89,41 +99,53 @@ the line was introduced with 'click'.
|
||||
arguments to the node that was specified by 'click'.
|
||||
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
|
||||
*/
|
||||
"call"[\s]+ this.begin("callback_name");
|
||||
<*>"call"[\s]+ this.begin("callback_name");
|
||||
<callback_name>\([\s]*\) this.popState();
|
||||
<callback_name>\( this.popState(); this.begin("callback_args");
|
||||
<callback_name>[^(]* return 'CALLBACK_NAME';
|
||||
<callback_args>\) this.popState();
|
||||
<callback_args>[^)]* return 'CALLBACK_ARGS';
|
||||
|
||||
"_self" return 'LINK_TARGET';
|
||||
"_blank" return 'LINK_TARGET';
|
||||
"_parent" return 'LINK_TARGET';
|
||||
"_top" return 'LINK_TARGET';
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
<*>[~] this.begin("generic");
|
||||
|
||||
\s*\<\| return 'EXTENSION';
|
||||
\s*\|\> return 'EXTENSION';
|
||||
\s*\> return 'DEPENDENCY';
|
||||
\s*\< return 'DEPENDENCY';
|
||||
\s*\* return 'COMPOSITION';
|
||||
\s*o return 'AGGREGATION';
|
||||
\s*\(\) return 'LOLLIPOP';
|
||||
\-\- return 'LINE';
|
||||
\.\. return 'DOTTED_LINE';
|
||||
":"{1}[^:\n;]+ return 'LABEL';
|
||||
":"{3} return 'STYLE_SEPARATOR';
|
||||
\- return 'MINUS';
|
||||
"." return 'DOT';
|
||||
\+ return 'PLUS';
|
||||
\% return 'PCT';
|
||||
"=" return 'EQUALS';
|
||||
\= return 'EQUALS';
|
||||
\w+ return 'ALPHA';
|
||||
"[" return 'SQS';
|
||||
"]" return 'SQE';
|
||||
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
[0-9]+ return 'NUM';
|
||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<*>["] this.begin("string");
|
||||
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<*>[`] this.begin("bqstring");
|
||||
|
||||
<*>"_self" return 'LINK_TARGET';
|
||||
<*>"_blank" return 'LINK_TARGET';
|
||||
<*>"_parent" return 'LINK_TARGET';
|
||||
<*>"_top" return 'LINK_TARGET';
|
||||
|
||||
<*>\s*\<\| return 'EXTENSION';
|
||||
<*>\s*\|\> return 'EXTENSION';
|
||||
<*>\s*\> return 'DEPENDENCY';
|
||||
<*>\s*\< return 'DEPENDENCY';
|
||||
<*>\s*\* return 'COMPOSITION';
|
||||
<*>\s*o return 'AGGREGATION';
|
||||
<*>\s*\(\) return 'LOLLIPOP';
|
||||
<*>\-\- return 'LINE';
|
||||
<*>\.\. return 'DOTTED_LINE';
|
||||
<*>":"{1}[^:\n;]+ return 'LABEL';
|
||||
<*>":"{3} return 'STYLE_SEPARATOR';
|
||||
<*>\- return 'MINUS';
|
||||
<*>"." return 'DOT';
|
||||
<*>\+ return 'PLUS';
|
||||
<*>\% return 'PCT';
|
||||
<*>"=" return 'EQUALS';
|
||||
<*>\= return 'EQUALS';
|
||||
<*>\w+ return 'ALPHA';
|
||||
<*>"[" return 'SQS';
|
||||
<*>"]" return 'SQE';
|
||||
<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
<*>[0-9]+ return 'NUM';
|
||||
<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
|
||||
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
|
||||
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
|
||||
@@ -184,9 +206,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
|
||||
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
|
||||
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
|
||||
return 'UNICODE_TEXT';
|
||||
\s return 'SPACE';
|
||||
<<EOF>> return 'EOF';
|
||||
return 'UNICODE_TEXT';
|
||||
<*>\s return 'SPACE';
|
||||
<*><<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
@@ -200,9 +222,8 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
|
||||
start
|
||||
: mermaidDoc
|
||||
| statments
|
||||
| direction
|
||||
| directive start
|
||||
| statements
|
||||
;
|
||||
|
||||
direction
|
||||
@@ -255,30 +276,50 @@ classLabel
|
||||
: SQS STR SQE { $$=$2; }
|
||||
;
|
||||
|
||||
namespaceName
|
||||
: alphaNumToken { $$=$1; }
|
||||
| alphaNumToken namespaceName { $$=$1+$2; }
|
||||
;
|
||||
|
||||
className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| classLiteralName { $$=$1; }
|
||||
| alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
|
||||
| classLiteralName GENERICTYPE { $$=$1+'~'+$2; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
|
||||
| classLiteralName GENERICTYPE { $$=$1+'~'+$2+'~'; }
|
||||
;
|
||||
|
||||
statement
|
||||
: relationStatement { yy.addRelation($1); }
|
||||
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
|
||||
| namespaceStatement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
| clickStatement
|
||||
| cssClassStatement
|
||||
| noteStatement
|
||||
| directive
|
||||
| direction
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
;
|
||||
|
||||
namespaceStatement
|
||||
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
|
||||
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
|
||||
;
|
||||
|
||||
namespaceIdentifier
|
||||
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
|
||||
;
|
||||
|
||||
classStatements
|
||||
: classStatement {$$=[$1]}
|
||||
| classStatement NEWLINE {$$=[$1]}
|
||||
| classStatement NEWLINE classStatements {$3.unshift($1); $$=$3}
|
||||
;
|
||||
|
||||
classStatement
|
||||
: classIdentifier
|
||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||
@@ -366,7 +407,7 @@ textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFA
|
||||
|
||||
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
|
||||
|
||||
alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
|
||||
alphaNumToken : UNICODE_TEXT | NUM | ALPHA | MINUS;
|
||||
|
||||
classLiteralName : BQUOTE_STR;
|
||||
|
||||
|
@@ -199,11 +199,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type !== undefined && classDef.type !== '') {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
let classTitleString = getClassTitleString(classDef);
|
||||
|
||||
const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title');
|
||||
|
||||
@@ -291,6 +287,16 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
return classInfo;
|
||||
};
|
||||
|
||||
export const getClassTitleString = function (classDef) {
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type) {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
|
||||
return classTitleString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a note diagram
|
||||
*
|
||||
@@ -355,106 +361,59 @@ export const drawNote = function (elem, note, conf, diagObj) {
|
||||
};
|
||||
|
||||
export const parseMember = function (text) {
|
||||
const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/;
|
||||
const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/;
|
||||
|
||||
let fieldMatch = text.match(fieldRegEx);
|
||||
let methodMatch = text.match(methodRegEx);
|
||||
|
||||
if (fieldMatch && !methodMatch) {
|
||||
return buildFieldDisplay(fieldMatch);
|
||||
} else if (methodMatch) {
|
||||
return buildMethodDisplay(methodMatch);
|
||||
} else {
|
||||
return buildLegacyDisplay(text);
|
||||
}
|
||||
};
|
||||
|
||||
const buildFieldDisplay = function (parsedText) {
|
||||
let cssStyle = '';
|
||||
let displayText = '';
|
||||
|
||||
try {
|
||||
let visibility = parsedText[1] ? parsedText[1].trim() : '';
|
||||
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
|
||||
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
|
||||
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
|
||||
let classifier = parsedText[5] ? parsedText[5].trim() : '';
|
||||
|
||||
displayText = visibility + fieldType + genericType + ' ' + fieldName;
|
||||
cssStyle = parseClassifier(classifier);
|
||||
} catch (err) {
|
||||
displayText = parsedText;
|
||||
}
|
||||
|
||||
return {
|
||||
displayText: displayText,
|
||||
cssStyle: cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
const buildMethodDisplay = function (parsedText) {
|
||||
let cssStyle = '';
|
||||
let displayText = '';
|
||||
|
||||
try {
|
||||
let visibility = parsedText[1] ? parsedText[1].trim() : '';
|
||||
let methodName = parsedText[2] ? parsedText[2].trim() : '';
|
||||
let parameters = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
|
||||
let classifier = parsedText[4] ? parsedText[4].trim() : '';
|
||||
let returnType = parsedText[5] ? ' : ' + parseGenericTypes(parsedText[5]).trim() : '';
|
||||
|
||||
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
|
||||
cssStyle = parseClassifier(classifier);
|
||||
} catch (err) {
|
||||
displayText = parsedText;
|
||||
}
|
||||
|
||||
return {
|
||||
displayText: displayText,
|
||||
cssStyle: cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
const buildLegacyDisplay = function (text) {
|
||||
// if for some reason we don't have any match, use old format to parse text
|
||||
let displayText = '';
|
||||
let cssStyle = '';
|
||||
let returnType = '';
|
||||
let methodStart = text.indexOf('(');
|
||||
let methodEnd = text.indexOf(')');
|
||||
|
||||
if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
|
||||
let visibility = '';
|
||||
let methodName = '';
|
||||
let visibility = '';
|
||||
let firstChar = text.substring(0, 1);
|
||||
let lastChar = text.substring(text.length - 1, text.length);
|
||||
|
||||
let firstChar = text.substring(0, 1);
|
||||
if (firstChar.match(/\w/)) {
|
||||
methodName = text.substring(0, methodStart).trim();
|
||||
} else {
|
||||
if (firstChar.match(/[#+~-]/)) {
|
||||
visibility = firstChar;
|
||||
}
|
||||
if (firstChar.match(/[#+~-]/)) {
|
||||
visibility = firstChar;
|
||||
}
|
||||
|
||||
methodName = text.substring(1, methodStart).trim();
|
||||
}
|
||||
let noClassifierRe = /[\s\w)~]/;
|
||||
if (!lastChar.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(lastChar);
|
||||
}
|
||||
|
||||
const startIndex = visibility === '' ? 0 : 1;
|
||||
let endIndex = cssStyle === '' ? text.length : text.length - 1;
|
||||
text = text.substring(startIndex, endIndex);
|
||||
|
||||
const methodStart = text.indexOf('(');
|
||||
const methodEnd = text.indexOf(')');
|
||||
const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length;
|
||||
|
||||
if (isMethod) {
|
||||
let methodName = text.substring(0, methodStart).trim();
|
||||
|
||||
const parameters = text.substring(methodStart + 1, methodEnd);
|
||||
const classifier = text.substring(methodEnd + 1, 1);
|
||||
cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2));
|
||||
|
||||
displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
|
||||
|
||||
if (methodEnd < text.length) {
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
// special case: classifier after the closing parenthesis
|
||||
let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
|
||||
if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(potentialClassifier);
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
} else {
|
||||
returnType = text.substring(methodEnd + 1).trim();
|
||||
}
|
||||
|
||||
if (returnType !== '') {
|
||||
if (returnType.charAt(0) === ':') {
|
||||
returnType = returnType.substring(1).trim();
|
||||
}
|
||||
returnType = ' : ' + parseGenericTypes(returnType);
|
||||
displayText += returnType;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
|
||||
displayText = parseGenericTypes(text);
|
||||
displayText = visibility + parseGenericTypes(text);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -462,6 +421,7 @@ const buildLegacyDisplay = function (text) {
|
||||
cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a <tspan> for a member in a diagram
|
||||
*
|
||||
@@ -502,6 +462,7 @@ const parseClassifier = function (classifier) {
|
||||
};
|
||||
|
||||
export default {
|
||||
getClassTitleString,
|
||||
drawClass,
|
||||
drawEdge,
|
||||
drawNote,
|
||||
|
@@ -1,8 +1,19 @@
|
||||
import svgDraw from './svgDraw.js';
|
||||
|
||||
describe('class member Renderer, ', function () {
|
||||
describe('when parsing text to build method display string', function () {
|
||||
it('should handle simple method declaration', function () {
|
||||
describe('given a string representing class method, ', function () {
|
||||
it('should handle class names with generics', function () {
|
||||
const classDef = {
|
||||
id: 'Car',
|
||||
type: 'T',
|
||||
label: 'Car',
|
||||
};
|
||||
|
||||
let actual = svgDraw.getClassTitleString(classDef);
|
||||
expect(actual).toBe('Car<T>');
|
||||
});
|
||||
|
||||
describe('when parsing base method declaration', function () {
|
||||
it('should handle simple declaration', function () {
|
||||
const str = 'foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -10,71 +21,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = '+foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = '-foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('-foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = '#foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('#foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle package/internal visibility', function () {
|
||||
const str = '~foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('~foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for visibility', function () {
|
||||
const str = '!foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle abstract method classifier', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for classifier', function () {
|
||||
const str = 'foo()!';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with parameters', function () {
|
||||
it('should handle declaration with parameters', function () {
|
||||
const str = 'foo(int id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -82,7 +29,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with multiple parameters', function () {
|
||||
it('should handle declaration with multiple parameters', function () {
|
||||
const str = 'foo(int id, object thing)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -90,7 +37,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters', function () {
|
||||
it('should handle declaration with single item in parameters', function () {
|
||||
const str = 'foo(id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -98,7 +45,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters with extra spaces', function () {
|
||||
it('should handle declaration with single item in parameters with extra spaces', function () {
|
||||
const str = ' foo ( id) ';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -106,22 +53,6 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic parameter', function () {
|
||||
const str = 'foo(List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
@@ -130,6 +61,46 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with normal and generic parameter', function () {
|
||||
const str = 'foo(int, List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int, List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon return value', function () {
|
||||
const str = 'foo(id) : int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon generic return value', function () {
|
||||
const str = 'foo(id) : List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with all possible markup', function () {
|
||||
const str = '+foo ( List~int~ ids )* List~Item~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
@@ -138,7 +109,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle method declaration with nested markup', function () {
|
||||
it('should handle method declaration with nested generics', function () {
|
||||
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -147,8 +118,134 @@ describe('class member Renderer, ', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing text to build field display string', function () {
|
||||
it('should handle simple field declaration', function () {
|
||||
describe('when parsing method visibility', function () {
|
||||
it('should correctly handle public', function () {
|
||||
const str = '+foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle private', function () {
|
||||
const str = '-foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('-foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle protected', function () {
|
||||
const str = '#foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('#foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle package/internal', function () {
|
||||
const str = '~foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('~foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing method classifier', function () {
|
||||
it('should handle abstract method', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method with return type', function () {
|
||||
const str = 'foo(name: String) int*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)* int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with return type', function () {
|
||||
const str = 'foo(name: String) int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with colon and return type', function () {
|
||||
const str = 'foo(name: String): int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)$ int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for classifier', function () {
|
||||
const str = 'foo()!';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a string representing class member, ', function () {
|
||||
describe('when parsing member declaration', function () {
|
||||
it('should handle simple field', function () {
|
||||
const str = 'id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type', function () {
|
||||
const str = 'int id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('int id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type (name first)', function () {
|
||||
const str = 'id: int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id: int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle array field', function () {
|
||||
const str = 'int[] ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -156,7 +253,15 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field declaration with generic type', function () {
|
||||
it('should handle array field (name first)', function () {
|
||||
const str = 'ids: int[]';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: int[]');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with generic type', function () {
|
||||
const str = 'List~int~ ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -164,12 +269,62 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle static field classifier', function () {
|
||||
it('should handle field with generic type (name first)', function () {
|
||||
const str = 'ids: List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classifiers', function () {
|
||||
it('should handle static field', function () {
|
||||
const str = 'String foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('String foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field (name first)', function () {
|
||||
const str = 'foo: String$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: String');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type', function () {
|
||||
const str = 'List~String~ foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<String> foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type (name first)', function () {
|
||||
const str = 'foo: List~String~$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: List<String>');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type', function () {
|
||||
const str = 'List~List~int~~ idLists';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<List<int>> idLists');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type (name first)', function () {
|
||||
const str = 'idLists: List~List~int~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('idLists: List<List<int>>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { MermaidConfig } from '../../config.type.js';
|
||||
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
* Gets the rows of lines in a string
|
||||
*
|
||||
@@ -65,8 +67,6 @@ export const sanitizeTextOrArray = (
|
||||
return a.flat().map((x: string) => sanitizeText(x, config));
|
||||
};
|
||||
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
* Whether or not a text has any line breaks
|
||||
*
|
||||
@@ -138,6 +138,32 @@ const getUrl = (useAbsolute: boolean): string => {
|
||||
export const evaluate = (val?: string | boolean): boolean =>
|
||||
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;
|
||||
|
||||
/**
|
||||
* Wrapper around Math.max which removes non-numeric values
|
||||
* Returns the larger of a set of supplied numeric expressions.
|
||||
* @param values - Numeric expressions to be evaluated
|
||||
* @returns The smaller value
|
||||
*/
|
||||
export const getMax = function (...values: number[]): number {
|
||||
const newValues: number[] = values.filter((value) => {
|
||||
return !isNaN(value);
|
||||
});
|
||||
return Math.max(...newValues);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper around Math.min which removes non-numeric values
|
||||
* Returns the smaller of a set of supplied numeric expressions.
|
||||
* @param values - Numeric expressions to be evaluated
|
||||
* @returns The smaller value
|
||||
*/
|
||||
export const getMin = function (...values: number[]): number {
|
||||
const newValues: number[] = values.filter((value) => {
|
||||
return !isNaN(value);
|
||||
});
|
||||
return Math.min(...newValues);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes generics in typescript syntax
|
||||
*
|
||||
@@ -180,4 +206,6 @@ export default {
|
||||
removeScript,
|
||||
getUrl,
|
||||
evaluate,
|
||||
getMax,
|
||||
getMin,
|
||||
};
|
||||
|
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
*
|
||||
* @param {any} elem Diagram (reference for bounds)
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
const sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
anchor: 'start',
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
@@ -20,6 +20,7 @@ const Cardinality = {
|
||||
ZERO_OR_MORE: 'ZERO_OR_MORE',
|
||||
ONE_OR_MORE: 'ONE_OR_MORE',
|
||||
ONLY_ONE: 'ONLY_ONE',
|
||||
MD_PARENT: 'MD_PARENT',
|
||||
};
|
||||
|
||||
const Identification = {
|
||||
|
@@ -7,6 +7,8 @@ const ERMarkers = {
|
||||
ONE_OR_MORE_END: 'ONE_OR_MORE_END',
|
||||
ZERO_OR_MORE_START: 'ZERO_OR_MORE_START',
|
||||
ZERO_OR_MORE_END: 'ZERO_OR_MORE_END',
|
||||
MD_PARENT_END: 'MD_PARENT_END',
|
||||
MD_PARENT_START: 'MD_PARENT_START',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,30 @@ const ERMarkers = {
|
||||
const insertMarkers = function (elem, conf) {
|
||||
let marker;
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.MD_PARENT_START)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.MD_PARENT_END)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
|
@@ -478,6 +478,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
case diagObj.db.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
|
||||
break;
|
||||
case diagObj.db.Cardinality.MD_PARENT:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_END + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rel.relSpec.cardB) {
|
||||
@@ -502,6 +505,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
case diagObj.db.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
|
||||
break;
|
||||
case diagObj.db.Cardinality.MD_PARENT:
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_START + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
// Now label the relationship
|
||||
|
@@ -57,6 +57,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
o\| return 'ZERO_OR_ONE';
|
||||
o\{ return 'ZERO_OR_MORE';
|
||||
\|\{ return 'ONE_OR_MORE';
|
||||
\s*u return 'MD_PARENT';
|
||||
\.\. return 'NON_IDENTIFYING';
|
||||
\-\- return 'IDENTIFYING';
|
||||
"to" return 'IDENTIFYING';
|
||||
@@ -170,6 +171,7 @@ cardinality
|
||||
| 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
|
||||
| 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
|
||||
| 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
|
||||
| 'MD_PARENT' { $$ = yy.Cardinality.MD_PARENT; }
|
||||
;
|
||||
|
||||
relType
|
||||
|
@@ -718,5 +718,14 @@ describe('when parsing ER diagram it...', function () {
|
||||
const rels = erDb.getRelationships();
|
||||
expect(rels[0].roleA).toBe('places');
|
||||
});
|
||||
|
||||
it('should represent parent-child relationship correctly', function () {
|
||||
erDiagram.parser.parse('erDiagram\nPROJECT u--o{ TEAM_MEMBER : "parent"');
|
||||
const rels = erDb.getRelationships();
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.MD_PARENT);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,6 +33,17 @@ const getStyles = (options) =>
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
#MD_PARENT_START {
|
||||
fill: #f5f5f5 !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
#MD_PARENT_END {
|
||||
fill: #f5f5f5 !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default getStyles;
|
||||
|
@@ -414,7 +414,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
|
||||
edges.forEach(function (edge) {
|
||||
// Identify Link
|
||||
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
// count the links from+to the same node to give unique id
|
||||
if (linkIdCnt[linkIdBase] === undefined) {
|
||||
linkIdCnt[linkIdBase] = 0;
|
||||
@@ -425,8 +425,8 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
}
|
||||
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
|
||||
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = { style: '', labelStyle: '' };
|
||||
edgeData.minlen = edge.length || 1;
|
||||
@@ -717,7 +717,7 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
|
||||
const edgePath = edgesEl
|
||||
.insert('path')
|
||||
.attr('d', curve(points))
|
||||
.attr('class', 'path')
|
||||
.attr('class', 'path ' + edgeData.classes)
|
||||
.attr('fill', 'none');
|
||||
const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel');
|
||||
const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl));
|
||||
|
@@ -214,7 +214,7 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
cnt++;
|
||||
|
||||
// Identify Link
|
||||
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
// count the links from+to the same node to give unique id
|
||||
if (linkIdCnt[linkIdBase] === undefined) {
|
||||
linkIdCnt[linkIdBase] = 0;
|
||||
@@ -225,8 +225,8 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
}
|
||||
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
|
||||
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = { style: '', labelStyle: '' };
|
||||
edgeData.minlen = edge.length || 1;
|
||||
|
@@ -176,9 +176,9 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
cnt++;
|
||||
|
||||
// Identify Link
|
||||
var linkId = 'L-' + edge.start + '-' + edge.end;
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkId = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = {};
|
||||
|
||||
|
@@ -1,3 +1,6 @@
|
||||
// import khroma from 'khroma';
|
||||
import * as khroma from 'khroma';
|
||||
|
||||
/** Returns the styles given options */
|
||||
export interface FlowChartStyleOptions {
|
||||
arrowheadColor: string;
|
||||
@@ -15,6 +18,18 @@ export interface FlowChartStyleOptions {
|
||||
titleColor: string;
|
||||
}
|
||||
|
||||
const fade = (color: string, opacity: number) => {
|
||||
// @ts-ignore TODO: incorrect types from khroma
|
||||
const channel = khroma.channel;
|
||||
|
||||
const r = channel(color, 'r');
|
||||
const g = channel(color, 'g');
|
||||
const b = channel(color, 'b');
|
||||
|
||||
// @ts-ignore incorrect types from khroma
|
||||
return khroma.rgba(r, g, b, opacity);
|
||||
};
|
||||
|
||||
const getStyles = (options: FlowChartStyleOptions) =>
|
||||
`.label {
|
||||
font-family: ${options.fontFamily};
|
||||
@@ -82,6 +97,12 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* For html labels only */
|
||||
.labelBkg {
|
||||
background-color: ${fade(options.edgeLabelBackground, 0.5)};
|
||||
// background-color:
|
||||
}
|
||||
|
||||
.cluster rect {
|
||||
fill: ${options.clusterBkg};
|
||||
stroke: ${options.clusterBorder};
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjsIsoWeek from 'dayjs/esm/plugin/isoWeek/index.js';
|
||||
import dayjsCustomParseFormat from 'dayjs/esm/plugin/customParseFormat/index.js';
|
||||
import dayjsAdvancedFormat from 'dayjs/esm/plugin/advancedFormat/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import dayjsIsoWeek from 'dayjs/plugin/isoWeek.js';
|
||||
import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js';
|
||||
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js';
|
||||
import { log } from '../../logger.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import utils from '../../utils.js';
|
||||
@@ -287,7 +287,17 @@ const getStartDate = function (prevTime, dateFormat, str) {
|
||||
log.debug('Invalid date:' + str);
|
||||
log.debug('With date format:' + dateFormat.trim());
|
||||
const d = new Date(str);
|
||||
if (d === undefined || isNaN(d.getTime())) {
|
||||
if (
|
||||
d === undefined ||
|
||||
isNaN(d.getTime()) ||
|
||||
// WebKit browsers can mis-parse invalid dates to be ridiculously
|
||||
// huge numbers, e.g. new Date('202304') gets parsed as January 1, 202304.
|
||||
// This can cause virtually infinite loops while rendering, so for the
|
||||
// purposes of Gantt charts we'll just treat any date beyond 10,000 AD/BC as
|
||||
// invalid.
|
||||
d.getFullYear() < -10000 ||
|
||||
d.getFullYear() > 10000
|
||||
) {
|
||||
throw new Error('Invalid date:' + str);
|
||||
}
|
||||
return d;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck TODO: Fix TS
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import ganttDb from './ganttDb.js';
|
||||
import { convert } from '../../tests/util.js';
|
||||
|
||||
@@ -432,4 +432,10 @@ describe('when using the ganttDb', function () {
|
||||
ganttDb.setTodayMarker(expected);
|
||||
expect(ganttDb.getTodayMarker()).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should reject dates with ridiculous years', function () {
|
||||
ganttDb.setDateFormat('YYYYMMDD');
|
||||
ganttDb.addTask('test1', 'id1,202304,1d');
|
||||
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { log } from '../../logger.js';
|
||||
import {
|
||||
select,
|
||||
|
@@ -70,6 +70,7 @@ const defaultBkg = function (elem, node, section) {
|
||||
.attr('x2', node.width)
|
||||
.attr('y2', node.height);
|
||||
};
|
||||
|
||||
const rectBkg = function (elem, node) {
|
||||
elem
|
||||
.append('rect')
|
||||
@@ -78,6 +79,7 @@ const rectBkg = function (elem, node) {
|
||||
.attr('height', node.height)
|
||||
.attr('width', node.width);
|
||||
};
|
||||
|
||||
const cloudBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@@ -108,6 +110,7 @@ const cloudBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const bangBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@@ -139,6 +142,7 @@ const bangBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const circleBkg = function (elem, node) {
|
||||
elem
|
||||
.append('circle')
|
||||
|
@@ -0,0 +1,185 @@
|
||||
%lex
|
||||
%options case-insensitive
|
||||
|
||||
%x string
|
||||
%x string
|
||||
%x md_string
|
||||
%x title
|
||||
%x open_directive
|
||||
%x type_directive
|
||||
%x arg_directive
|
||||
%x close_directive
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%x point_start
|
||||
%x point_x
|
||||
%x point_y
|
||||
%%
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
\%\%(?!\{)[^\n]* /* skip comments */
|
||||
[^\}]\%\%[^\n]* /* skip comments */
|
||||
[\n\r]+ return 'NEWLINE';
|
||||
\%\%[^\n]* /* do nothing */
|
||||
|
||||
title { this.begin("title");return 'title'; }
|
||||
<title>(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
|
||||
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
|
||||
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
|
||||
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
<acc_descr_multiline>[\}] { this.popState(); }
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
|
||||
" "*"x-axis"" "* return 'X-AXIS';
|
||||
" "*"y-axis"" "* return 'Y-AXIS';
|
||||
" "*\-\-+\>" "* return 'AXIS-TEXT-DELIMITER'
|
||||
" "*"quadrant-1"" "* return 'QUADRANT_1';
|
||||
" "*"quadrant-2"" "* return 'QUADRANT_2';
|
||||
" "*"quadrant-3"" "* return 'QUADRANT_3';
|
||||
" "*"quadrant-4"" "* return 'QUADRANT_4';
|
||||
|
||||
["][`] { this.begin("md_string");}
|
||||
<md_string>[^`"]+ { return "MD_STR";}
|
||||
<md_string>[`]["] { this.popState();}
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
|
||||
\s*\:\s*\[\s* {this.begin("point_start"); return 'point_start';}
|
||||
<point_start>(1)|(0(.\d+)?) {this.begin('point_x'); return 'point_x';}
|
||||
<point_start>\s*\]" "* {this.popState();}
|
||||
<point_x>\s*\,\s* {this.popState(); this.begin('point_y');}
|
||||
<point_y>(1)|(0(.\d+)?) {this.popState(); return 'point_y';}
|
||||
|
||||
" "*"quadrantChart"" "* return 'QUADRANT';
|
||||
|
||||
[A-Za-z]+ return 'ALPHA';
|
||||
":" return 'COLON';
|
||||
\+ return 'PLUS';
|
||||
"," return 'COMMA';
|
||||
"=" return 'EQUALS';
|
||||
\= return 'EQUALS';
|
||||
"*" return 'MULT';
|
||||
\# return 'BRKT';
|
||||
[\_] return 'UNDERSCORE';
|
||||
"." return 'DOT';
|
||||
"&" return 'AMP';
|
||||
\- return 'MINUS';
|
||||
[0-9]+ return 'NUM';
|
||||
\s return 'SPACE';
|
||||
";" return 'SEMI';
|
||||
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
|
||||
<<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
: eol start
|
||||
| SPACE start
|
||||
| directive start
|
||||
| QUADRANT document
|
||||
;
|
||||
|
||||
document
|
||||
: /* empty */
|
||||
| document line
|
||||
;
|
||||
|
||||
line
|
||||
: statement eol
|
||||
;
|
||||
|
||||
statement
|
||||
:
|
||||
| SPACE statement
|
||||
| axisDetails
|
||||
| quadrantDetails
|
||||
| points
|
||||
| title title_value { $$=$2.trim();yy.setDiagramTitle($$); }
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
|
||||
| directive
|
||||
;
|
||||
|
||||
points
|
||||
: text point_start point_x point_y {yy.addPoint($1, $3, $4);}
|
||||
;
|
||||
|
||||
axisDetails
|
||||
: X-AXIS text AXIS-TEXT-DELIMITER text {yy.setXAxisLeftText($2); yy.setXAxisRightText($4);}
|
||||
| X-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setXAxisLeftText($2);}
|
||||
| X-AXIS text {yy.setXAxisLeftText($2);}
|
||||
| Y-AXIS text AXIS-TEXT-DELIMITER text {yy.setYAxisBottomText($2); yy.setYAxisTopText($4);}
|
||||
| Y-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setYAxisBottomText($2);}
|
||||
| Y-AXIS text {yy.setYAxisBottomText($2);}
|
||||
;
|
||||
|
||||
quadrantDetails
|
||||
: QUADRANT_1 text {yy.setQuadrant1Text($2)}
|
||||
| QUADRANT_2 text {yy.setQuadrant2Text($2)}
|
||||
| QUADRANT_3 text {yy.setQuadrant3Text($2)}
|
||||
| QUADRANT_4 text {yy.setQuadrant4Text($2)}
|
||||
;
|
||||
|
||||
directive
|
||||
: openDirective typeDirective closeDirective
|
||||
| openDirective typeDirective ':' argDirective closeDirective
|
||||
;
|
||||
|
||||
eol
|
||||
: NEWLINE
|
||||
| SEMI
|
||||
| EOF
|
||||
;
|
||||
|
||||
openDirective
|
||||
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
|
||||
;
|
||||
|
||||
typeDirective
|
||||
: type_directive { yy.parseDirective($1, 'type_directive'); }
|
||||
;
|
||||
|
||||
argDirective
|
||||
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
|
||||
;
|
||||
|
||||
closeDirective
|
||||
: close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); }
|
||||
;
|
||||
|
||||
text: alphaNumToken
|
||||
{ $$={text:$1, type: 'text'};}
|
||||
| text textNoTagsToken
|
||||
{ $$={text:$1.text+''+$2, type: $1.type};}
|
||||
| STR
|
||||
{ $$={text: $1, type: 'text'};}
|
||||
| MD_STR
|
||||
{ $$={text: $1, type: 'markdown'};}
|
||||
;
|
||||
|
||||
alphaNum
|
||||
: alphaNumToken
|
||||
{$$=$1;}
|
||||
| alphaNum alphaNumToken
|
||||
{$$=$1+''+$2;}
|
||||
;
|
||||
|
||||
|
||||
alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
|
||||
|
||||
textNoTagsToken: alphaNumToken | SPACE | MINUS;
|
||||
|
||||
%%
|
@@ -0,0 +1,298 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import { parser } from './quadrant.jison';
|
||||
import { Mock, vi } from 'vitest';
|
||||
|
||||
const parserFnConstructor = (str: string) => {
|
||||
return () => {
|
||||
parser.parse(str);
|
||||
};
|
||||
};
|
||||
|
||||
const mockDB: Record<string, Mock<any, any>> = {
|
||||
setQuadrant1Text: vi.fn(),
|
||||
setQuadrant2Text: vi.fn(),
|
||||
setQuadrant3Text: vi.fn(),
|
||||
setQuadrant4Text: vi.fn(),
|
||||
setXAxisLeftText: vi.fn(),
|
||||
setXAxisRightText: vi.fn(),
|
||||
setYAxisTopText: vi.fn(),
|
||||
setYAxisBottomText: vi.fn(),
|
||||
setDiagramTitle: vi.fn(),
|
||||
parseDirective: vi.fn(),
|
||||
addPoint: vi.fn(),
|
||||
};
|
||||
|
||||
function clearMocks() {
|
||||
for (const key in mockDB) {
|
||||
mockDB[key].mockRestore();
|
||||
}
|
||||
}
|
||||
|
||||
describe('Testing quadrantChart jison file', () => {
|
||||
beforeEach(() => {
|
||||
parser.yy = mockDB;
|
||||
clearMocks();
|
||||
});
|
||||
|
||||
it('should throw error if quadrantChart text is not there', () => {
|
||||
const str = 'quadrant-1 do';
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error if only quadrantChart is there', () => {
|
||||
const str = 'quadrantChart';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should be able to parse directive', () => {
|
||||
const str =
|
||||
'%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
|
||||
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
|
||||
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
|
||||
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
|
||||
'arg_directive',
|
||||
]);
|
||||
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
|
||||
'}%%',
|
||||
'close_directive',
|
||||
'quadrantChart',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to parse xAxis text', () => {
|
||||
let str = 'quadrantChart\nx-axis urgent --> not urgent';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
|
||||
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n x-AxIs Urgent --> Not Urgent \n';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
|
||||
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str =
|
||||
'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
|
||||
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
|
||||
text: 'Not Urgent (* +=[❤',
|
||||
type: 'text',
|
||||
});
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
|
||||
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
|
||||
text: 'Urgent(* +=[❤ ⟶ ',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be able to parse yAxis text', () => {
|
||||
let str = 'quadrantChart\ny-axis urgent --> not urgent';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
|
||||
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n y-AxIs Urgent --> Not Urgent \n';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
|
||||
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str =
|
||||
'quadrantChart\n Y-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
|
||||
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({
|
||||
text: 'Not Urgent (* +=[❤',
|
||||
type: 'text',
|
||||
});
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
|
||||
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
|
||||
|
||||
clearMocks();
|
||||
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤" --> ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
|
||||
text: 'Urgent(* +=[❤ ⟶ ',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be able to parse quadrant1 text', () => {
|
||||
let str = 'quadrantChart\nquadrant-1 Plan';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-1 Plan ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-1 "Plan(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan(* +=[❤', type: 'text' });
|
||||
});
|
||||
|
||||
it('should be able to parse quadrant2 text', () => {
|
||||
let str = 'quadrantChart\nquadrant-2 do';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'do', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-2 Do ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-2 "Do(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do(* +=[❤', type: 'text' });
|
||||
});
|
||||
|
||||
it('should be able to parse quadrant3 text', () => {
|
||||
let str = 'quadrantChart\nquadrant-3 deligate';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'deligate', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-3 Deligate ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-3 "Deligate(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate(* +=[❤', type: 'text' });
|
||||
});
|
||||
|
||||
it('should be able to parse quadrant4 text', () => {
|
||||
let str = 'quadrantChart\nquadrant-4 delete';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'delete', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-4 Delete ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete ', type: 'text' });
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n QuaDrant-4 "Delete(* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete(* +=[❤', type: 'text' });
|
||||
});
|
||||
|
||||
it('should be able to parse title', () => {
|
||||
let str = 'quadrantChart\ntitle this is title';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this is title');
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n TiTle this Is title ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this Is title');
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n title "this is title (* +=[❤"';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('"this is title (* +=[❤"');
|
||||
});
|
||||
|
||||
it('should be able to parse points', () => {
|
||||
let str = 'quadrantChart\npoint1: [0.1, 0.4]';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'point1', type: 'text' }, '0.1', '0.4');
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n Point1 : [0.1, 0.4] ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Point1', type: 'text' }, '0.1', '0.4');
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n "Point1 : (* +=[❤": [1, 0] ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith(
|
||||
{ text: 'Point1 : (* +=[❤', type: 'text' },
|
||||
'1',
|
||||
'0'
|
||||
);
|
||||
|
||||
clearMocks();
|
||||
str = 'QuadRantChart \n Point1 : [1.2, 0.4] ';
|
||||
expect(parserFnConstructor(str)).toThrow();
|
||||
});
|
||||
|
||||
it('should be able to parse the whole chart', () => {
|
||||
const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
|
||||
quadrantChart
|
||||
title Analytics and Business Intelligence Platforms
|
||||
x-axis "Completeness of Vision ❤" --> "x-axis-2"
|
||||
y-axis Ability to Execute --> "y-axis-2"
|
||||
quadrant-1 Leaders
|
||||
quadrant-2 Challengers
|
||||
quadrant-3 Niche
|
||||
quadrant-4 Visionaries
|
||||
Microsoft: [0.75, 0.75]
|
||||
Salesforce: [0.55, 0.60]
|
||||
IBM: [0.51, 0.40]
|
||||
Incorta: [0.20, 0.30]`;
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
|
||||
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
|
||||
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
|
||||
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
|
||||
'arg_directive',
|
||||
]);
|
||||
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
|
||||
'}%%',
|
||||
'close_directive',
|
||||
'quadrantChart',
|
||||
]);
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
|
||||
text: 'Completeness of Vision ❤',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'x-axis-2', type: 'text' });
|
||||
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'y-axis-2', type: 'text' });
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
|
||||
text: 'Ability to Execute',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Leaders', type: 'text' });
|
||||
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Challengers', type: 'text' });
|
||||
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Niche', type: 'text' });
|
||||
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith(
|
||||
{ text: 'Microsoft', type: 'text' },
|
||||
'0.75',
|
||||
'0.75'
|
||||
);
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith(
|
||||
{ text: 'Salesforce', type: 'text' },
|
||||
'0.55',
|
||||
'0.60'
|
||||
);
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'IBM', type: 'text' }, '0.51', '0.40');
|
||||
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Incorta', type: 'text' }, '0.20', '0.30');
|
||||
});
|
||||
});
|
601
packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts
Normal file
601
packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts
Normal file
@@ -0,0 +1,601 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import { scaleLinear } from 'd3';
|
||||
import { log } from '../../logger.js';
|
||||
import { QuadrantChartConfig } from '../../config.type.js';
|
||||
import defaultConfig from '../../defaultConfig.js';
|
||||
import { getThemeVariables } from '../../themes/theme-default.js';
|
||||
|
||||
const defaultThemeVariables = getThemeVariables();
|
||||
|
||||
export type TextVerticalPos = 'left' | 'center' | 'right';
|
||||
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface QuadrantPointInputType extends Point {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface QuadrantTextType extends Point {
|
||||
text: string;
|
||||
fill: string;
|
||||
verticalPos: TextVerticalPos;
|
||||
horizontalPos: TextHorizontalPos;
|
||||
fontSize: number;
|
||||
rotation: number;
|
||||
}
|
||||
|
||||
export interface QuadrantPointType extends Point {
|
||||
fill: string;
|
||||
radius: number;
|
||||
text: QuadrantTextType;
|
||||
}
|
||||
|
||||
export interface QuadrantQuadrantsType extends Point {
|
||||
text: QuadrantTextType;
|
||||
width: number;
|
||||
height: number;
|
||||
fill: string;
|
||||
}
|
||||
|
||||
export interface QuadrantLineType {
|
||||
strokeWidth: number;
|
||||
strokeFill: string;
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
}
|
||||
|
||||
export interface QuadrantBuildType {
|
||||
points: QuadrantPointType[];
|
||||
quadrants: QuadrantQuadrantsType[];
|
||||
axisLabels: QuadrantTextType[];
|
||||
title?: QuadrantTextType;
|
||||
borderLines?: QuadrantLineType[];
|
||||
}
|
||||
|
||||
export interface quadrantBuilderData {
|
||||
titleText: string;
|
||||
quadrant1Text: string;
|
||||
quadrant2Text: string;
|
||||
quadrant3Text: string;
|
||||
quadrant4Text: string;
|
||||
xAxisLeftText: string;
|
||||
xAxisRightText: string;
|
||||
yAxisBottomText: string;
|
||||
yAxisTopText: string;
|
||||
points: QuadrantPointInputType[];
|
||||
}
|
||||
|
||||
export interface QuadrantBuilderConfig extends QuadrantChartConfig {
|
||||
showXAxis: boolean;
|
||||
showYAxis: boolean;
|
||||
showTitle: boolean;
|
||||
}
|
||||
|
||||
export interface QuadrantBuilderThemeConfig {
|
||||
quadrantTitleFill: string;
|
||||
quadrant1Fill: string;
|
||||
quadrant2Fill: string;
|
||||
quadrant3Fill: string;
|
||||
quadrant4Fill: string;
|
||||
quadrant1TextFill: string;
|
||||
quadrant2TextFill: string;
|
||||
quadrant3TextFill: string;
|
||||
quadrant4TextFill: string;
|
||||
quadrantPointFill: string;
|
||||
quadrantPointTextFill: string;
|
||||
quadrantXAxisTextFill: string;
|
||||
quadrantYAxisTextFill: string;
|
||||
quadrantInternalBorderStrokeFill: string;
|
||||
quadrantExternalBorderStrokeFill: string;
|
||||
}
|
||||
|
||||
interface CalculateSpaceData {
|
||||
xAxisSpace: {
|
||||
top: number;
|
||||
bottom: number;
|
||||
};
|
||||
yAxisSpace: {
|
||||
left: number;
|
||||
right: number;
|
||||
};
|
||||
titleSpace: {
|
||||
top: number;
|
||||
};
|
||||
quadrantSpace: {
|
||||
quadrantLeft: number;
|
||||
quadrantTop: number;
|
||||
quadrantWidth: number;
|
||||
quadrantHalfWidth: number;
|
||||
quadrantHeight: number;
|
||||
quadrantHalfHeight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export class QuadrantBuilder {
|
||||
private config: QuadrantBuilderConfig;
|
||||
private themeConfig: QuadrantBuilderThemeConfig;
|
||||
private data: quadrantBuilderData;
|
||||
|
||||
constructor() {
|
||||
this.config = this.getDefaultConfig();
|
||||
this.themeConfig = this.getDefaultThemeConfig();
|
||||
this.data = this.getDefaultData();
|
||||
}
|
||||
|
||||
getDefaultData(): quadrantBuilderData {
|
||||
return {
|
||||
titleText: '',
|
||||
quadrant1Text: '',
|
||||
quadrant2Text: '',
|
||||
quadrant3Text: '',
|
||||
quadrant4Text: '',
|
||||
xAxisLeftText: '',
|
||||
xAxisRightText: '',
|
||||
yAxisBottomText: '',
|
||||
yAxisTopText: '',
|
||||
points: [],
|
||||
};
|
||||
}
|
||||
|
||||
getDefaultConfig(): QuadrantBuilderConfig {
|
||||
return {
|
||||
showXAxis: true,
|
||||
showYAxis: true,
|
||||
showTitle: true,
|
||||
chartHeight: defaultConfig.quadrantChart?.chartWidth || 500,
|
||||
chartWidth: defaultConfig.quadrantChart?.chartHeight || 500,
|
||||
titlePadding: defaultConfig.quadrantChart?.titlePadding || 10,
|
||||
titleFontSize: defaultConfig.quadrantChart?.titleFontSize || 20,
|
||||
quadrantPadding: defaultConfig.quadrantChart?.quadrantPadding || 5,
|
||||
xAxisLabelPadding: defaultConfig.quadrantChart?.xAxisLabelPadding || 5,
|
||||
yAxisLabelPadding: defaultConfig.quadrantChart?.yAxisLabelPadding || 5,
|
||||
xAxisLabelFontSize: defaultConfig.quadrantChart?.xAxisLabelFontSize || 16,
|
||||
yAxisLabelFontSize: defaultConfig.quadrantChart?.yAxisLabelFontSize || 16,
|
||||
quadrantLabelFontSize: defaultConfig.quadrantChart?.quadrantLabelFontSize || 16,
|
||||
quadrantTextTopPadding: defaultConfig.quadrantChart?.quadrantTextTopPadding || 5,
|
||||
pointTextPadding: defaultConfig.quadrantChart?.pointTextPadding || 5,
|
||||
pointLabelFontSize: defaultConfig.quadrantChart?.pointLabelFontSize || 12,
|
||||
pointRadius: defaultConfig.quadrantChart?.pointRadius || 5,
|
||||
xAxisPosition: defaultConfig.quadrantChart?.xAxisPosition || 'top',
|
||||
yAxisPosition: defaultConfig.quadrantChart?.yAxisPosition || 'left',
|
||||
quadrantInternalBorderStrokeWidth:
|
||||
defaultConfig.quadrantChart?.quadrantInternalBorderStrokeWidth || 1,
|
||||
quadrantExternalBorderStrokeWidth:
|
||||
defaultConfig.quadrantChart?.quadrantExternalBorderStrokeWidth || 2,
|
||||
};
|
||||
}
|
||||
|
||||
getDefaultThemeConfig(): QuadrantBuilderThemeConfig {
|
||||
return {
|
||||
quadrant1Fill: defaultThemeVariables.quadrant1Fill,
|
||||
quadrant2Fill: defaultThemeVariables.quadrant2Fill,
|
||||
quadrant3Fill: defaultThemeVariables.quadrant3Fill,
|
||||
quadrant4Fill: defaultThemeVariables.quadrant4Fill,
|
||||
quadrant1TextFill: defaultThemeVariables.quadrant1TextFill,
|
||||
quadrant2TextFill: defaultThemeVariables.quadrant2TextFill,
|
||||
quadrant3TextFill: defaultThemeVariables.quadrant3TextFill,
|
||||
quadrant4TextFill: defaultThemeVariables.quadrant4TextFill,
|
||||
quadrantPointFill: defaultThemeVariables.quadrantPointFill,
|
||||
quadrantPointTextFill: defaultThemeVariables.quadrantPointTextFill,
|
||||
quadrantXAxisTextFill: defaultThemeVariables.quadrantXAxisTextFill,
|
||||
quadrantYAxisTextFill: defaultThemeVariables.quadrantYAxisTextFill,
|
||||
quadrantTitleFill: defaultThemeVariables.quadrantTitleFill,
|
||||
quadrantInternalBorderStrokeFill: defaultThemeVariables.quadrantInternalBorderStrokeFill,
|
||||
quadrantExternalBorderStrokeFill: defaultThemeVariables.quadrantExternalBorderStrokeFill,
|
||||
};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.config = this.getDefaultConfig();
|
||||
this.themeConfig = this.getDefaultThemeConfig();
|
||||
this.data = this.getDefaultData();
|
||||
log.info('clear called');
|
||||
}
|
||||
|
||||
setData(data: Partial<quadrantBuilderData>) {
|
||||
this.data = { ...this.data, ...data };
|
||||
}
|
||||
|
||||
addPoints(points: QuadrantPointInputType[]) {
|
||||
this.data.points = [...points, ...this.data.points];
|
||||
}
|
||||
|
||||
setConfig(config: Partial<QuadrantBuilderConfig>) {
|
||||
log.trace('setConfig called with: ', config);
|
||||
this.config = { ...this.config, ...config };
|
||||
}
|
||||
|
||||
setThemeConfig(themeConfig: Partial<QuadrantBuilderThemeConfig>) {
|
||||
log.trace('setThemeConfig called with: ', themeConfig);
|
||||
this.themeConfig = { ...this.themeConfig, ...themeConfig };
|
||||
}
|
||||
|
||||
calculateSpace(
|
||||
xAxisPosition: typeof this.config.xAxisPosition,
|
||||
showXAxis: boolean,
|
||||
showYAxis: boolean,
|
||||
showTitle: boolean
|
||||
): CalculateSpaceData {
|
||||
const xAxisSpaceCalculation =
|
||||
this.config.xAxisLabelPadding * 2 + this.config.xAxisLabelFontSize;
|
||||
const xAxisSpace = {
|
||||
top: xAxisPosition === 'top' && showXAxis ? xAxisSpaceCalculation : 0,
|
||||
bottom: xAxisPosition === 'bottom' && showXAxis ? xAxisSpaceCalculation : 0,
|
||||
};
|
||||
|
||||
const yAxisSpaceCalculation =
|
||||
this.config.yAxisLabelPadding * 2 + this.config.yAxisLabelFontSize;
|
||||
const yAxisSpace = {
|
||||
left: this.config.yAxisPosition === 'left' && showYAxis ? yAxisSpaceCalculation : 0,
|
||||
right: this.config.yAxisPosition === 'right' && showYAxis ? yAxisSpaceCalculation : 0,
|
||||
};
|
||||
|
||||
const titleSpaceCalculation = this.config.titleFontSize + this.config.titlePadding * 2;
|
||||
const titleSpace = {
|
||||
top: showTitle ? titleSpaceCalculation : 0,
|
||||
};
|
||||
|
||||
const quadrantLeft = this.config.quadrantPadding + yAxisSpace.left;
|
||||
const quadrantTop = this.config.quadrantPadding + xAxisSpace.top + titleSpace.top;
|
||||
const quadrantWidth =
|
||||
this.config.chartWidth - this.config.quadrantPadding * 2 - yAxisSpace.left - yAxisSpace.right;
|
||||
const quadrantHeight =
|
||||
this.config.chartHeight -
|
||||
this.config.quadrantPadding * 2 -
|
||||
xAxisSpace.top -
|
||||
xAxisSpace.bottom -
|
||||
titleSpace.top;
|
||||
|
||||
const quadrantHalfWidth = quadrantWidth / 2;
|
||||
const quadrantHalfHeight = quadrantHeight / 2;
|
||||
const quadrantSpace = {
|
||||
quadrantLeft,
|
||||
quadrantTop,
|
||||
quadrantWidth,
|
||||
quadrantHalfWidth,
|
||||
quadrantHeight,
|
||||
quadrantHalfHeight,
|
||||
};
|
||||
|
||||
return {
|
||||
xAxisSpace,
|
||||
yAxisSpace,
|
||||
titleSpace,
|
||||
quadrantSpace,
|
||||
};
|
||||
}
|
||||
|
||||
getAxisLabels(
|
||||
xAxisPosition: typeof this.config.xAxisPosition,
|
||||
showXAxis: boolean,
|
||||
showYAxis: boolean,
|
||||
spaceData: CalculateSpaceData
|
||||
): QuadrantTextType[] {
|
||||
const { quadrantSpace, titleSpace } = spaceData;
|
||||
const {
|
||||
quadrantHalfHeight,
|
||||
quadrantHeight,
|
||||
quadrantLeft,
|
||||
quadrantHalfWidth,
|
||||
quadrantTop,
|
||||
quadrantWidth,
|
||||
} = quadrantSpace;
|
||||
const drawAxisLabelInMiddle = this.data.points.length === 0;
|
||||
const axisLabels: QuadrantTextType[] = [];
|
||||
|
||||
if (this.data.xAxisLeftText && showXAxis) {
|
||||
axisLabels.push({
|
||||
text: this.data.xAxisLeftText,
|
||||
fill: this.themeConfig.quadrantXAxisTextFill,
|
||||
x: quadrantLeft + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
|
||||
y:
|
||||
xAxisPosition === 'top'
|
||||
? this.config.xAxisLabelPadding + titleSpace.top
|
||||
: this.config.xAxisLabelPadding +
|
||||
quadrantTop +
|
||||
quadrantHeight +
|
||||
this.config.quadrantPadding,
|
||||
fontSize: this.config.xAxisLabelFontSize,
|
||||
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
|
||||
horizontalPos: 'top',
|
||||
rotation: 0,
|
||||
});
|
||||
}
|
||||
if (this.data.xAxisRightText && showXAxis) {
|
||||
axisLabels.push({
|
||||
text: this.data.xAxisRightText,
|
||||
fill: this.themeConfig.quadrantXAxisTextFill,
|
||||
x: quadrantLeft + quadrantHalfWidth + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
|
||||
y:
|
||||
xAxisPosition === 'top'
|
||||
? this.config.xAxisLabelPadding + titleSpace.top
|
||||
: this.config.xAxisLabelPadding +
|
||||
quadrantTop +
|
||||
quadrantHeight +
|
||||
this.config.quadrantPadding,
|
||||
fontSize: this.config.xAxisLabelFontSize,
|
||||
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
|
||||
horizontalPos: 'top',
|
||||
rotation: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.data.yAxisBottomText && showYAxis) {
|
||||
axisLabels.push({
|
||||
text: this.data.yAxisBottomText,
|
||||
fill: this.themeConfig.quadrantYAxisTextFill,
|
||||
x:
|
||||
this.config.yAxisPosition === 'left'
|
||||
? this.config.yAxisLabelPadding
|
||||
: this.config.yAxisLabelPadding +
|
||||
quadrantLeft +
|
||||
quadrantWidth +
|
||||
this.config.quadrantPadding,
|
||||
y: quadrantTop + quadrantHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
|
||||
fontSize: this.config.yAxisLabelFontSize,
|
||||
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
|
||||
horizontalPos: 'top',
|
||||
rotation: -90,
|
||||
});
|
||||
}
|
||||
if (this.data.yAxisTopText && showYAxis) {
|
||||
axisLabels.push({
|
||||
text: this.data.yAxisTopText,
|
||||
fill: this.themeConfig.quadrantYAxisTextFill,
|
||||
x:
|
||||
this.config.yAxisPosition === 'left'
|
||||
? this.config.yAxisLabelPadding
|
||||
: this.config.yAxisLabelPadding +
|
||||
quadrantLeft +
|
||||
quadrantWidth +
|
||||
this.config.quadrantPadding,
|
||||
y: quadrantTop + quadrantHalfHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
|
||||
fontSize: this.config.yAxisLabelFontSize,
|
||||
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
|
||||
horizontalPos: 'top',
|
||||
rotation: -90,
|
||||
});
|
||||
}
|
||||
return axisLabels;
|
||||
}
|
||||
|
||||
getQuadrants(spaceData: CalculateSpaceData): QuadrantQuadrantsType[] {
|
||||
const { quadrantSpace } = spaceData;
|
||||
|
||||
const { quadrantHalfHeight, quadrantLeft, quadrantHalfWidth, quadrantTop } = quadrantSpace;
|
||||
|
||||
const quadrants: QuadrantQuadrantsType[] = [
|
||||
{
|
||||
text: {
|
||||
text: this.data.quadrant1Text,
|
||||
fill: this.themeConfig.quadrant1TextFill,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fontSize: this.config.quadrantLabelFontSize,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'middle',
|
||||
rotation: 0,
|
||||
},
|
||||
x: quadrantLeft + quadrantHalfWidth,
|
||||
y: quadrantTop,
|
||||
width: quadrantHalfWidth,
|
||||
height: quadrantHalfHeight,
|
||||
fill: this.themeConfig.quadrant1Fill,
|
||||
},
|
||||
{
|
||||
text: {
|
||||
text: this.data.quadrant2Text,
|
||||
fill: this.themeConfig.quadrant2TextFill,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fontSize: this.config.quadrantLabelFontSize,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'middle',
|
||||
rotation: 0,
|
||||
},
|
||||
x: quadrantLeft,
|
||||
y: quadrantTop,
|
||||
width: quadrantHalfWidth,
|
||||
height: quadrantHalfHeight,
|
||||
fill: this.themeConfig.quadrant2Fill,
|
||||
},
|
||||
{
|
||||
text: {
|
||||
text: this.data.quadrant3Text,
|
||||
fill: this.themeConfig.quadrant3TextFill,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fontSize: this.config.quadrantLabelFontSize,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'middle',
|
||||
rotation: 0,
|
||||
},
|
||||
x: quadrantLeft,
|
||||
y: quadrantTop + quadrantHalfHeight,
|
||||
width: quadrantHalfWidth,
|
||||
height: quadrantHalfHeight,
|
||||
fill: this.themeConfig.quadrant3Fill,
|
||||
},
|
||||
{
|
||||
text: {
|
||||
text: this.data.quadrant4Text,
|
||||
fill: this.themeConfig.quadrant4TextFill,
|
||||
x: 0,
|
||||
y: 0,
|
||||
fontSize: this.config.quadrantLabelFontSize,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'middle',
|
||||
rotation: 0,
|
||||
},
|
||||
x: quadrantLeft + quadrantHalfWidth,
|
||||
y: quadrantTop + quadrantHalfHeight,
|
||||
width: quadrantHalfWidth,
|
||||
height: quadrantHalfHeight,
|
||||
fill: this.themeConfig.quadrant4Fill,
|
||||
},
|
||||
];
|
||||
for (const quadrant of quadrants) {
|
||||
quadrant.text.x = quadrant.x + quadrant.width / 2;
|
||||
// place the text in the center of the box
|
||||
if (this.data.points.length === 0) {
|
||||
quadrant.text.y = quadrant.y + quadrant.height / 2;
|
||||
quadrant.text.horizontalPos = 'middle';
|
||||
// place the text top of the quadrant square
|
||||
} else {
|
||||
quadrant.text.y = quadrant.y + this.config.quadrantTextTopPadding;
|
||||
quadrant.text.horizontalPos = 'top';
|
||||
}
|
||||
}
|
||||
|
||||
return quadrants;
|
||||
}
|
||||
|
||||
getQuadrantPoints(spaceData: CalculateSpaceData): QuadrantPointType[] {
|
||||
const { quadrantSpace } = spaceData;
|
||||
|
||||
const { quadrantHeight, quadrantLeft, quadrantTop, quadrantWidth } = quadrantSpace;
|
||||
|
||||
const xAxis = scaleLinear()
|
||||
.domain([0, 1])
|
||||
.range([quadrantLeft, quadrantWidth + quadrantLeft]);
|
||||
|
||||
const yAxis = scaleLinear()
|
||||
.domain([0, 1])
|
||||
.range([quadrantHeight + quadrantTop, quadrantTop]);
|
||||
|
||||
const points: QuadrantPointType[] = this.data.points.map((point) => {
|
||||
const props: QuadrantPointType = {
|
||||
x: xAxis(point.x),
|
||||
y: yAxis(point.y),
|
||||
fill: this.themeConfig.quadrantPointFill,
|
||||
radius: this.config.pointRadius,
|
||||
text: {
|
||||
text: point.text,
|
||||
fill: this.themeConfig.quadrantPointTextFill,
|
||||
x: xAxis(point.x),
|
||||
y: yAxis(point.y) + this.config.pointTextPadding,
|
||||
verticalPos: 'center',
|
||||
horizontalPos: 'top',
|
||||
fontSize: this.config.pointLabelFontSize,
|
||||
rotation: 0,
|
||||
},
|
||||
};
|
||||
return props;
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
getBorders(spaceData: CalculateSpaceData): QuadrantLineType[] {
|
||||
const halfExternalBorderWidth = this.config.quadrantExternalBorderStrokeWidth / 2;
|
||||
const { quadrantSpace } = spaceData;
|
||||
|
||||
const {
|
||||
quadrantHalfHeight,
|
||||
quadrantHeight,
|
||||
quadrantLeft,
|
||||
quadrantHalfWidth,
|
||||
quadrantTop,
|
||||
quadrantWidth,
|
||||
} = quadrantSpace;
|
||||
|
||||
const borderLines: QuadrantLineType[] = [
|
||||
// top border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
|
||||
x1: quadrantLeft - halfExternalBorderWidth,
|
||||
y1: quadrantTop,
|
||||
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
|
||||
y2: quadrantTop,
|
||||
},
|
||||
// right border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
|
||||
x1: quadrantLeft + quadrantWidth,
|
||||
y1: quadrantTop + halfExternalBorderWidth,
|
||||
x2: quadrantLeft + quadrantWidth,
|
||||
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
|
||||
},
|
||||
// bottom border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
|
||||
x1: quadrantLeft - halfExternalBorderWidth,
|
||||
y1: quadrantTop + quadrantHeight,
|
||||
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
|
||||
y2: quadrantTop + quadrantHeight,
|
||||
},
|
||||
// left border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
|
||||
x1: quadrantLeft,
|
||||
y1: quadrantTop + halfExternalBorderWidth,
|
||||
x2: quadrantLeft,
|
||||
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
|
||||
},
|
||||
// vertical inner border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
|
||||
x1: quadrantLeft + quadrantHalfWidth,
|
||||
y1: quadrantTop + halfExternalBorderWidth,
|
||||
x2: quadrantLeft + quadrantHalfWidth,
|
||||
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
|
||||
},
|
||||
// horizontal inner border
|
||||
{
|
||||
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
|
||||
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
|
||||
x1: quadrantLeft + halfExternalBorderWidth,
|
||||
y1: quadrantTop + quadrantHalfHeight,
|
||||
x2: quadrantLeft + quadrantWidth - halfExternalBorderWidth,
|
||||
y2: quadrantTop + quadrantHalfHeight,
|
||||
},
|
||||
];
|
||||
return borderLines;
|
||||
}
|
||||
|
||||
getTitle(showTitle: boolean): QuadrantTextType | undefined {
|
||||
if (showTitle) {
|
||||
return {
|
||||
text: this.data.titleText,
|
||||
fill: this.themeConfig.quadrantTitleFill,
|
||||
fontSize: this.config.titleFontSize,
|
||||
horizontalPos: 'top',
|
||||
verticalPos: 'center',
|
||||
rotation: 0,
|
||||
y: this.config.titlePadding,
|
||||
x: this.config.chartWidth / 2,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
build(): QuadrantBuildType {
|
||||
const showXAxis =
|
||||
this.config.showXAxis && !!(this.data.xAxisLeftText || this.data.xAxisRightText);
|
||||
const showYAxis =
|
||||
this.config.showYAxis && !!(this.data.yAxisTopText || this.data.yAxisBottomText);
|
||||
const showTitle = this.config.showTitle && !!this.data.titleText;
|
||||
|
||||
const xAxisPosition = this.data.points.length > 0 ? 'bottom' : this.config.xAxisPosition;
|
||||
|
||||
const calculatedSpace = this.calculateSpace(xAxisPosition, showXAxis, showYAxis, showTitle);
|
||||
|
||||
return {
|
||||
points: this.getQuadrantPoints(calculatedSpace),
|
||||
quadrants: this.getQuadrants(calculatedSpace),
|
||||
axisLabels: this.getAxisLabels(xAxisPosition, showXAxis, showYAxis, calculatedSpace),
|
||||
borderLines: this.getBorders(calculatedSpace),
|
||||
title: this.getTitle(showTitle),
|
||||
};
|
||||
}
|
||||
}
|
128
packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts
Normal file
128
packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { log } from '../../logger.js';
|
||||
import mermaidAPI from '../../mermaidAPI.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import { sanitizeText } from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
} from '../../commonDb.js';
|
||||
import { QuadrantBuilder } from './quadrantBuilder.js';
|
||||
|
||||
const config = configApi.getConfig();
|
||||
|
||||
function textSanitizer(text: string) {
|
||||
return sanitizeText(text.trim(), config);
|
||||
}
|
||||
|
||||
type LexTextObj = { text: string; type: 'text' | 'markdown' };
|
||||
|
||||
const quadrantBuilder = new QuadrantBuilder();
|
||||
|
||||
function setQuadrant1Text(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ quadrant1Text: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setQuadrant2Text(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ quadrant2Text: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setQuadrant3Text(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ quadrant3Text: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setQuadrant4Text(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ quadrant4Text: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setXAxisLeftText(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ xAxisLeftText: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setXAxisRightText(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ xAxisRightText: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setYAxisTopText(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ yAxisTopText: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function setYAxisBottomText(textObj: LexTextObj) {
|
||||
quadrantBuilder.setData({ yAxisBottomText: textSanitizer(textObj.text) });
|
||||
}
|
||||
|
||||
function addPoint(textObj: LexTextObj, x: number, y: number) {
|
||||
quadrantBuilder.addPoints([{ x, y, text: textSanitizer(textObj.text) }]);
|
||||
}
|
||||
|
||||
function setWidth(width: number) {
|
||||
quadrantBuilder.setConfig({ chartWidth: width });
|
||||
}
|
||||
|
||||
function setHeight(height: number) {
|
||||
quadrantBuilder.setConfig({ chartHeight: height });
|
||||
}
|
||||
|
||||
function getQuadrantData() {
|
||||
const config = configApi.getConfig();
|
||||
const { themeVariables, quadrantChart: quadrantChartConfig } = config;
|
||||
if (quadrantChartConfig) {
|
||||
quadrantBuilder.setConfig(quadrantChartConfig);
|
||||
}
|
||||
quadrantBuilder.setThemeConfig({
|
||||
quadrant1Fill: themeVariables.quadrant1Fill,
|
||||
quadrant2Fill: themeVariables.quadrant2Fill,
|
||||
quadrant3Fill: themeVariables.quadrant3Fill,
|
||||
quadrant4Fill: themeVariables.quadrant4Fill,
|
||||
quadrant1TextFill: themeVariables.quadrant1TextFill,
|
||||
quadrant2TextFill: themeVariables.quadrant2TextFill,
|
||||
quadrant3TextFill: themeVariables.quadrant3TextFill,
|
||||
quadrant4TextFill: themeVariables.quadrant4TextFill,
|
||||
quadrantPointFill: themeVariables.quadrantPointFill,
|
||||
quadrantPointTextFill: themeVariables.quadrantPointTextFill,
|
||||
quadrantXAxisTextFill: themeVariables.quadrantXAxisTextFill,
|
||||
quadrantYAxisTextFill: themeVariables.quadrantYAxisTextFill,
|
||||
quadrantExternalBorderStrokeFill: themeVariables.quadrantExternalBorderStrokeFill,
|
||||
quadrantInternalBorderStrokeFill: themeVariables.quadrantInternalBorderStrokeFill,
|
||||
quadrantTitleFill: themeVariables.quadrantTitleFill,
|
||||
});
|
||||
quadrantBuilder.setData({ titleText: getDiagramTitle() });
|
||||
return quadrantBuilder.build();
|
||||
}
|
||||
|
||||
export const parseDirective = function (statement: string, context: string, type: string) {
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
mermaidAPI.parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
const clear = function () {
|
||||
quadrantBuilder.clear();
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export default {
|
||||
setWidth,
|
||||
setHeight,
|
||||
setQuadrant1Text,
|
||||
setQuadrant2Text,
|
||||
setQuadrant3Text,
|
||||
setQuadrant4Text,
|
||||
setXAxisLeftText,
|
||||
setXAxisRightText,
|
||||
setYAxisTopText,
|
||||
setYAxisBottomText,
|
||||
addPoint,
|
||||
getQuadrantData,
|
||||
parseDirective,
|
||||
clear,
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
};
|
@@ -0,0 +1,20 @@
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
const id = 'quadrantChart';
|
||||
|
||||
const detector: DiagramDetector = (txt) => {
|
||||
return txt.match(/^\s*quadrantChart/) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./quadrantDiagram.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
@@ -0,0 +1,12 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import parser from './parser/quadrant.jison';
|
||||
import db from './quadrantDb.js';
|
||||
import renderer from './quadrantRenderer.js';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
db,
|
||||
renderer,
|
||||
styles: () => '',
|
||||
};
|
173
packages/mermaid/src/diagrams/quadrant-chart/quadrantRenderer.ts
Normal file
173
packages/mermaid/src/diagrams/quadrant-chart/quadrantRenderer.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
import { select } from 'd3';
|
||||
import * as configApi from '../../config.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { Diagram } from '../../Diagram.js';
|
||||
import {
|
||||
QuadrantBuildType,
|
||||
QuadrantLineType,
|
||||
QuadrantPointType,
|
||||
QuadrantQuadrantsType,
|
||||
QuadrantTextType,
|
||||
TextHorizontalPos,
|
||||
TextVerticalPos,
|
||||
} from './quadrantBuilder.js';
|
||||
|
||||
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
|
||||
function getDominantBaseLine(horizontalPos: TextHorizontalPos) {
|
||||
return horizontalPos === 'top' ? 'hanging' : 'middle';
|
||||
}
|
||||
|
||||
function getTextAnchor(verticalPos: TextVerticalPos) {
|
||||
return verticalPos === 'left' ? 'start' : 'middle';
|
||||
}
|
||||
|
||||
function getTransformation(data: { x: number; y: number; rotation: number }) {
|
||||
return `translate(${data.x}, ${data.y}) rotate(${data.rotation || 0})`;
|
||||
}
|
||||
|
||||
const conf = configApi.getConfig();
|
||||
|
||||
log.debug('Rendering quadrant chart\n' + txt);
|
||||
|
||||
const securityLevel = conf.securityLevel;
|
||||
// Handle root and Document for when rendering in sandbox mode
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: select('body');
|
||||
|
||||
const svg = root.select(`[id="${id}"]`);
|
||||
|
||||
const group = svg.append('g').attr('class', 'main');
|
||||
|
||||
const width = conf.quadrantChart?.chartWidth || 500;
|
||||
const height = conf.quadrantChart?.chartHeight || 500;
|
||||
|
||||
configureSvgSize(svg, height, width, conf.quadrantChart?.useMaxWidth || true);
|
||||
|
||||
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
diagObj.db.setHeight(height);
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
diagObj.db.setWidth(width);
|
||||
|
||||
// @ts-ignore: TODO Fix ts errors
|
||||
const quadrantData: QuadrantBuildType = diagObj.db.getQuadrantData();
|
||||
|
||||
const quadrantsGroup = group.append('g').attr('class', 'quadrants');
|
||||
const borderGroup = group.append('g').attr('class', 'border');
|
||||
const dataPointGroup = group.append('g').attr('class', 'data-points');
|
||||
const labelGroup = group.append('g').attr('class', 'labels');
|
||||
const titleGroup = group.append('g').attr('class', 'title');
|
||||
|
||||
if (quadrantData.title) {
|
||||
titleGroup
|
||||
.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('fill', quadrantData.title.fill)
|
||||
.attr('font-size', quadrantData.title.fontSize)
|
||||
.attr('dominant-baseline', getDominantBaseLine(quadrantData.title.horizontalPos))
|
||||
.attr('text-anchor', getTextAnchor(quadrantData.title.verticalPos))
|
||||
.attr('transform', getTransformation(quadrantData.title))
|
||||
.text(quadrantData.title.text);
|
||||
}
|
||||
|
||||
if (quadrantData.borderLines) {
|
||||
borderGroup
|
||||
.selectAll('line')
|
||||
.data(quadrantData.borderLines)
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('x1', (data: QuadrantLineType) => data.x1)
|
||||
.attr('y1', (data: QuadrantLineType) => data.y1)
|
||||
.attr('x2', (data: QuadrantLineType) => data.x2)
|
||||
.attr('y2', (data: QuadrantLineType) => data.y2)
|
||||
.style('stroke', (data: QuadrantLineType) => data.strokeFill)
|
||||
.style('stroke-width', (data: QuadrantLineType) => data.strokeWidth);
|
||||
}
|
||||
|
||||
const quadrants = quadrantsGroup
|
||||
.selectAll('g.quadrant')
|
||||
.data(quadrantData.quadrants)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'quadrant');
|
||||
|
||||
quadrants
|
||||
.append('rect')
|
||||
.attr('x', (data: QuadrantQuadrantsType) => data.x)
|
||||
.attr('y', (data: QuadrantQuadrantsType) => data.y)
|
||||
.attr('width', (data: QuadrantQuadrantsType) => data.width)
|
||||
.attr('height', (data: QuadrantQuadrantsType) => data.height)
|
||||
.attr('fill', (data: QuadrantQuadrantsType) => data.fill);
|
||||
|
||||
quadrants
|
||||
.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('fill', (data: QuadrantQuadrantsType) => data.text.fill)
|
||||
.attr('font-size', (data: QuadrantQuadrantsType) => data.text.fontSize)
|
||||
.attr('dominant-baseline', (data: QuadrantQuadrantsType) =>
|
||||
getDominantBaseLine(data.text.horizontalPos)
|
||||
)
|
||||
.attr('text-anchor', (data: QuadrantQuadrantsType) => getTextAnchor(data.text.verticalPos))
|
||||
.attr('transform', (data: QuadrantQuadrantsType) => getTransformation(data.text))
|
||||
.text((data: QuadrantQuadrantsType) => data.text.text);
|
||||
|
||||
const labels = labelGroup
|
||||
.selectAll('g.label')
|
||||
.data(quadrantData.axisLabels)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'label');
|
||||
|
||||
labels
|
||||
.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.text((data: QuadrantTextType) => data.text)
|
||||
.attr('fill', (data: QuadrantTextType) => data.fill)
|
||||
.attr('font-size', (data: QuadrantTextType) => data.fontSize)
|
||||
.attr('dominant-baseline', (data: QuadrantTextType) => getDominantBaseLine(data.horizontalPos))
|
||||
.attr('text-anchor', (data: QuadrantTextType) => getTextAnchor(data.verticalPos))
|
||||
.attr('transform', (data: QuadrantTextType) => getTransformation(data));
|
||||
|
||||
const dataPoints = dataPointGroup
|
||||
.selectAll('g.data-point')
|
||||
.data(quadrantData.points)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'data-point');
|
||||
|
||||
dataPoints
|
||||
.append('circle')
|
||||
.attr('cx', (data: QuadrantPointType) => data.x)
|
||||
.attr('cy', (data: QuadrantPointType) => data.y)
|
||||
.attr('r', (data: QuadrantPointType) => data.radius)
|
||||
.attr('fill', (data: QuadrantPointType) => data.fill);
|
||||
|
||||
dataPoints
|
||||
.append('text')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.text((data: QuadrantPointType) => data.text.text)
|
||||
.attr('fill', (data: QuadrantPointType) => data.text.fill)
|
||||
.attr('font-size', (data: QuadrantPointType) => data.text.fontSize)
|
||||
.attr('dominant-baseline', (data: QuadrantPointType) =>
|
||||
getDominantBaseLine(data.text.horizontalPos)
|
||||
)
|
||||
.attr('text-anchor', (data: QuadrantPointType) => getTextAnchor(data.text.verticalPos))
|
||||
.attr('transform', (data: QuadrantPointType) => getTransformation(data.text));
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@@ -47,6 +47,7 @@
|
||||
"alt" { this.begin('LINE'); return 'alt'; }
|
||||
"else" { this.begin('LINE'); return 'else'; }
|
||||
"par" { this.begin('LINE'); return 'par'; }
|
||||
"par_over" { this.begin('LINE'); return 'par_over'; }
|
||||
"and" { this.begin('LINE'); return 'and'; }
|
||||
"critical" { this.begin('LINE'); return 'critical'; }
|
||||
"option" { this.begin('LINE'); return 'option'; }
|
||||
@@ -190,6 +191,14 @@ statement
|
||||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| par_over restOfLine par_sections end
|
||||
{
|
||||
// Parallel (overlapped) start
|
||||
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_OVER_START});
|
||||
// Content in par is already in $3
|
||||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| critical restOfLine option_sections end
|
||||
{
|
||||
// critical start
|
||||
|
@@ -286,6 +286,7 @@ export const LINETYPE = {
|
||||
CRITICAL_END: 29,
|
||||
BREAK_START: 30,
|
||||
BREAK_END: 31,
|
||||
PAR_OVER_START: 32,
|
||||
};
|
||||
|
||||
export const ARROWTYPE = {
|
||||
|
@@ -1126,6 +1126,29 @@ end`;
|
||||
expect(messages[1].from).toBe('Alice');
|
||||
expect(messages[2].from).toBe('Bob');
|
||||
});
|
||||
it('it should handle par_over statements', async () => {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
par_over Parallel overlap
|
||||
Alice ->> Bob: Message
|
||||
Note left of Alice: Alice note
|
||||
Note right of Bob: Bob note
|
||||
end`;
|
||||
|
||||
await mermaidAPI.parse(str);
|
||||
const actors = diagram.db.getActors();
|
||||
|
||||
expect(actors.Alice.description).toBe('Alice');
|
||||
expect(actors.Bob.description).toBe('Bob');
|
||||
|
||||
const messages = diagram.db.getMessages();
|
||||
|
||||
expect(messages.length).toBe(5);
|
||||
expect(messages[0].message).toBe('Parallel overlap');
|
||||
expect(messages[1].from).toBe('Alice');
|
||||
expect(messages[2].from).toBe('Alice');
|
||||
expect(messages[3].from).toBe('Bob');
|
||||
});
|
||||
it('should handle special characters in signals', async () => {
|
||||
const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment';
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { select, selectAll } from 'd3';
|
||||
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
|
||||
import { log } from '../../logger.js';
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import * as configApi from '../../config.js';
|
||||
import assignWithDepth from '../../assignWithDepth.js';
|
||||
import utils from '../../utils.js';
|
||||
@@ -130,10 +131,10 @@ export const bounds = {
|
||||
this.activations.forEach(updateFn('activation'));
|
||||
},
|
||||
insert: function (startx, starty, stopx, stopy) {
|
||||
const _startx = Math.min(startx, stopx);
|
||||
const _stopx = Math.max(startx, stopx);
|
||||
const _starty = Math.min(starty, stopy);
|
||||
const _stopy = Math.max(starty, stopy);
|
||||
const _startx = common.getMin(startx, stopx);
|
||||
const _stopx = common.getMax(startx, stopx);
|
||||
const _starty = common.getMin(starty, stopy);
|
||||
const _stopy = common.getMax(starty, stopy);
|
||||
|
||||
this.updateVal(bounds.data, 'startx', _startx, Math.min);
|
||||
this.updateVal(bounds.data, 'starty', _starty, Math.min);
|
||||
@@ -183,6 +184,11 @@ export const bounds = {
|
||||
endLoop: function () {
|
||||
return this.sequenceItems.pop();
|
||||
},
|
||||
isLoopOverlap: function () {
|
||||
return this.sequenceItems.length
|
||||
? this.sequenceItems[this.sequenceItems.length - 1].overlap
|
||||
: false;
|
||||
},
|
||||
addSectionToLoop: function (message) {
|
||||
const loop = this.sequenceItems.pop();
|
||||
loop.sections = loop.sections || [];
|
||||
@@ -191,9 +197,19 @@ export const bounds = {
|
||||
loop.sectionTitles.push(message);
|
||||
this.sequenceItems.push(loop);
|
||||
},
|
||||
saveVerticalPos: function () {
|
||||
if (this.isLoopOverlap()) {
|
||||
this.savedVerticalPos = this.verticalPos;
|
||||
}
|
||||
},
|
||||
resetVerticalPos: function () {
|
||||
if (this.isLoopOverlap()) {
|
||||
this.verticalPos = this.savedVerticalPos;
|
||||
}
|
||||
},
|
||||
bumpVerticalPos: function (bump) {
|
||||
this.verticalPos = this.verticalPos + bump;
|
||||
this.data.stopy = this.verticalPos;
|
||||
this.data.stopy = common.getMax(this.data.stopy, this.verticalPos);
|
||||
},
|
||||
getVerticalPos: function () {
|
||||
return this.verticalPos;
|
||||
@@ -225,7 +241,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
noteModel.height = conf.boxMargin;
|
||||
noteModel.starty = bounds.getVerticalPos();
|
||||
const rect = svgDraw.getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = noteModel.startx;
|
||||
rect.y = noteModel.starty;
|
||||
rect.width = noteModel.width || conf.width;
|
||||
@@ -233,7 +249,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
|
||||
const g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = noteModel.startx;
|
||||
textObj.y = noteModel.starty;
|
||||
textObj.width = rect.width;
|
||||
@@ -316,7 +332,7 @@ function boundMessage(_diagram, msgModel): number {
|
||||
lineStartY = bounds.getVerticalPos() + totalOffset;
|
||||
}
|
||||
totalOffset += 30;
|
||||
const dx = Math.max(textWidth / 2, conf.width / 2);
|
||||
const dx = common.getMax(textWidth / 2, conf.width / 2);
|
||||
bounds.insert(
|
||||
startx - dx,
|
||||
bounds.getVerticalPos() - 10 + totalOffset,
|
||||
@@ -347,7 +363,7 @@ function boundMessage(_diagram, msgModel): number {
|
||||
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
|
||||
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
|
||||
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = startx;
|
||||
textObj.y = starty + 10;
|
||||
textObj.width = stopx - startx;
|
||||
@@ -373,9 +389,9 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
|
||||
.append('path')
|
||||
.attr(
|
||||
'd',
|
||||
`M ${startx},${lineStartY} H ${startx + Math.max(conf.width / 2, textWidth / 2)} V ${
|
||||
lineStartY + 25
|
||||
} H ${startx}`
|
||||
`M ${startx},${lineStartY} H ${
|
||||
startx + common.getMax(conf.width / 2, textWidth / 2)
|
||||
} V ${lineStartY + 25} H ${startx}`
|
||||
);
|
||||
} else {
|
||||
line = diagram
|
||||
@@ -509,7 +525,7 @@ export const drawActors = function (
|
||||
|
||||
// Add some rendering data to the object
|
||||
actor.width = actor.width || conf.width;
|
||||
actor.height = Math.max(actor.height || conf.height, conf.height);
|
||||
actor.height = common.getMax(actor.height || conf.height, conf.height);
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
@@ -517,7 +533,7 @@ export const drawActors = function (
|
||||
|
||||
// Draw the box with the attached line
|
||||
const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
maxHeight = common.getMax(maxHeight, height);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
||||
|
||||
prevWidth += actor.width + prevMargin;
|
||||
@@ -589,10 +605,10 @@ const activationBounds = function (actor, actors) {
|
||||
const activations = actorActivations(actor);
|
||||
|
||||
const left = activations.reduce(function (acc, activation) {
|
||||
return Math.min(acc, activation.startx);
|
||||
return common.getMin(acc, activation.startx);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
const right = activations.reduce(function (acc, activation) {
|
||||
return Math.max(acc, activation.stopx);
|
||||
return common.getMax(acc, activation.stopx);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
return [left, right];
|
||||
};
|
||||
@@ -609,7 +625,7 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
|
||||
|
||||
// const lines = common.splitBreaks(msg.message).length;
|
||||
const textDims = utils.calculateTextDimensions(msg.message, textConf);
|
||||
const totalOffset = Math.max(textDims.height, conf.labelBoxHeight);
|
||||
const totalOffset = common.getMax(textDims.height, conf.labelBoxHeight);
|
||||
heightAdjust = postMargin + totalOffset;
|
||||
log.debug(`${totalOffset} - ${msg.message}`);
|
||||
}
|
||||
@@ -709,6 +725,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
|
||||
switch (msg.type) {
|
||||
case diagObj.db.LINETYPE.NOTE:
|
||||
bounds.resetVerticalPos();
|
||||
noteModel = msg.noteModel;
|
||||
drawNote(diagram, noteModel);
|
||||
break;
|
||||
@@ -784,6 +801,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
bounds.models.addLoop(loopModel);
|
||||
break;
|
||||
case diagObj.db.LINETYPE.PAR_START:
|
||||
case diagObj.db.LINETYPE.PAR_OVER_START:
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
@@ -791,6 +809,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
(message) => bounds.newLoop(message)
|
||||
);
|
||||
bounds.saveVerticalPos();
|
||||
break;
|
||||
case diagObj.db.LINETYPE.PAR_AND:
|
||||
adjustLoopHeightForWrap(
|
||||
@@ -858,6 +877,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
default:
|
||||
try {
|
||||
// lastMsg = msg
|
||||
bounds.resetVerticalPos();
|
||||
msgModel = msg.msgModel;
|
||||
msgModel.starty = bounds.getVerticalPos();
|
||||
msgModel.sequenceIndex = sequenceIndex;
|
||||
@@ -1025,45 +1045,45 @@ function getMaxMessageWidthPerActor(
|
||||
* margin
|
||||
*/
|
||||
if (isMessage && msg.from === actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.to] = Math.max(
|
||||
maxMessageWidthPerActor[msg.to] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.to] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (isMessage && msg.from === actor.prevActor) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (isMessage && msg.from === msg.to) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
|
||||
maxMessageWidthPerActor[msg.to] = Math.max(
|
||||
maxMessageWidthPerActor[msg.to] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.to] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.OVER) {
|
||||
if (actor.prevActor) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
}
|
||||
|
||||
if (actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
@@ -1119,10 +1139,10 @@ function calculateActorMargins(
|
||||
const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf));
|
||||
actor.width = actor.wrap
|
||||
? conf.width
|
||||
: Math.max(conf.width, actDims.width + 2 * conf.wrapPadding);
|
||||
: common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding);
|
||||
|
||||
actor.height = actor.wrap ? Math.max(actDims.height, conf.height) : conf.height;
|
||||
maxHeight = Math.max(maxHeight, actor.height);
|
||||
actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height;
|
||||
maxHeight = common.getMax(maxHeight, actor.height);
|
||||
});
|
||||
|
||||
for (const actorKey in actorToMessageWidth) {
|
||||
@@ -1138,14 +1158,14 @@ function calculateActorMargins(
|
||||
if (!nextActor) {
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2;
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
actor.margin = common.getMax(actorWidth, conf.actorMargin);
|
||||
continue;
|
||||
}
|
||||
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
|
||||
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
actor.margin = common.getMax(actorWidth, conf.actorMargin);
|
||||
}
|
||||
|
||||
let maxBoxHeight = 0;
|
||||
@@ -1161,8 +1181,8 @@ function calculateActorMargins(
|
||||
}
|
||||
|
||||
const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont);
|
||||
maxBoxHeight = Math.max(boxMsgDimensions.height, maxBoxHeight);
|
||||
const minWidth = Math.max(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
|
||||
maxBoxHeight = common.getMax(boxMsgDimensions.height, maxBoxHeight);
|
||||
const minWidth = common.getMax(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
|
||||
box.margin = conf.boxTextMargin;
|
||||
if (totalWidth < minWidth) {
|
||||
const missing = (minWidth - totalWidth) / 2;
|
||||
@@ -1171,7 +1191,7 @@ function calculateActorMargins(
|
||||
});
|
||||
boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight));
|
||||
|
||||
return Math.max(maxHeight, conf.height);
|
||||
return common.getMax(maxHeight, conf.height);
|
||||
}
|
||||
|
||||
const buildNoteModel = function (msg, actors, diagObj) {
|
||||
@@ -1186,7 +1206,7 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
const noteModel = {
|
||||
width: shouldWrap
|
||||
? conf.width
|
||||
: Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin),
|
||||
: common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin),
|
||||
height: 0,
|
||||
startx: actors[msg.from].x,
|
||||
stopx: 0,
|
||||
@@ -1196,16 +1216,16 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
};
|
||||
if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, textDimensions.width)
|
||||
: Math.max(
|
||||
? common.getMax(conf.width, textDimensions.width)
|
||||
: common.getMax(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2;
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin)
|
||||
: Math.max(
|
||||
? common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin)
|
||||
: common.getMax(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
@@ -1213,13 +1233,21 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
} else if (msg.to === msg.from) {
|
||||
textDimensions = utils.calculateTextDimensions(
|
||||
shouldWrap
|
||||
? utils.wrapLabel(msg.message, Math.max(conf.width, actors[msg.from].width), noteFont(conf))
|
||||
? utils.wrapLabel(
|
||||
msg.message,
|
||||
common.getMax(conf.width, actors[msg.from].width),
|
||||
noteFont(conf)
|
||||
)
|
||||
: msg.message,
|
||||
noteFont(conf)
|
||||
);
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, actors[msg.from].width)
|
||||
: Math.max(actors[msg.from].width, conf.width, textDimensions.width + 2 * conf.noteMargin);
|
||||
? common.getMax(conf.width, actors[msg.from].width)
|
||||
: common.getMax(
|
||||
actors[msg.from].width,
|
||||
conf.width,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2;
|
||||
} else {
|
||||
noteModel.width =
|
||||
@@ -1271,14 +1299,14 @@ const buildMessageModel = function (msg, actors, diagObj) {
|
||||
if (msg.wrap && msg.message) {
|
||||
msg.message = utils.wrapLabel(
|
||||
msg.message,
|
||||
Math.max(boundedWidth + 2 * conf.wrapPadding, conf.width),
|
||||
common.getMax(boundedWidth + 2 * conf.wrapPadding, conf.width),
|
||||
messageFont(conf)
|
||||
);
|
||||
}
|
||||
const msgDims = utils.calculateTextDimensions(msg.message, messageFont(conf));
|
||||
|
||||
return {
|
||||
width: Math.max(
|
||||
width: common.getMax(
|
||||
msg.wrap ? 0 : msgDims.width + 2 * conf.wrapPadding,
|
||||
boundedWidth + 2 * conf.wrapPadding,
|
||||
conf.width
|
||||
@@ -1308,6 +1336,7 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
case diagObj.db.LINETYPE.ALT_START:
|
||||
case diagObj.db.LINETYPE.OPT_START:
|
||||
case diagObj.db.LINETYPE.PAR_START:
|
||||
case diagObj.db.LINETYPE.PAR_OVER_START:
|
||||
case diagObj.db.LINETYPE.CRITICAL_START:
|
||||
case diagObj.db.LINETYPE.BREAK_START:
|
||||
stack.push({
|
||||
@@ -1367,10 +1396,10 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
msg.noteModel = noteModel;
|
||||
stack.forEach((stk) => {
|
||||
current = stk;
|
||||
current.from = Math.min(current.from, noteModel.startx);
|
||||
current.to = Math.max(current.to, noteModel.startx + noteModel.width);
|
||||
current.from = common.getMin(current.from, noteModel.startx);
|
||||
current.to = common.getMax(current.to, noteModel.startx + noteModel.width);
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
|
||||
common.getMax(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
|
||||
});
|
||||
} else {
|
||||
msgModel = buildMessageModel(msg, actors, diagObj);
|
||||
@@ -1381,18 +1410,23 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
if (msgModel.startx === msgModel.stopx) {
|
||||
const from = actors[msg.from];
|
||||
const to = actors[msg.to];
|
||||
current.from = Math.min(
|
||||
current.from = common.getMin(
|
||||
from.x - msgModel.width / 2,
|
||||
from.x - from.width / 2,
|
||||
current.from
|
||||
);
|
||||
current.to = Math.max(to.x + msgModel.width / 2, to.x + from.width / 2, current.to);
|
||||
current.to = common.getMax(
|
||||
to.x + msgModel.width / 2,
|
||||
to.x + from.width / 2,
|
||||
current.to
|
||||
);
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.to - current.from)) - conf.labelBoxWidth;
|
||||
common.getMax(current.width, Math.abs(current.to - current.from)) -
|
||||
conf.labelBoxWidth;
|
||||
} else {
|
||||
current.from = Math.min(msgModel.startx, current.from);
|
||||
current.to = Math.max(msgModel.stopx, current.to);
|
||||
current.width = Math.max(current.width, msgModel.width) - conf.labelBoxWidth;
|
||||
current.from = common.getMin(msgModel.startx, current.from);
|
||||
current.to = common.getMax(msgModel.stopx, current.to);
|
||||
current.width = common.getMax(current.width, msgModel.width) - conf.labelBoxWidth;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -1,33 +1,13 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { addFunction } from '../../interactionDb.js';
|
||||
import { parseFontSize } from '../../utils.js';
|
||||
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.class !== undefined) {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
// const sanitizeUrl = function (s) {
|
||||
// return s
|
||||
// .replace(/&/g, '&')
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/javascript:/g, '');
|
||||
// };
|
||||
|
||||
const addPopupInteraction = (id, actorCnt) => {
|
||||
addFunction(() => {
|
||||
const arr = document.querySelectorAll(id);
|
||||
@@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
|
||||
if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
|
||||
return { height: 0, width: 0 };
|
||||
@@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
|
||||
return { height: rectData.height + linkY, width: menuWidth };
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const popupMenu = function (popid) {
|
||||
return (
|
||||
"var pu = document.getElementById('" +
|
||||
@@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) {
|
||||
pu.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
let prevTextHeight = 0,
|
||||
textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
let textHeight = 0;
|
||||
const lines = textData.text.split(common.lineBreakRegex);
|
||||
|
||||
const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize);
|
||||
@@ -188,6 +154,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
textData.anchor !== undefined &&
|
||||
textData.textMargin !== undefined &&
|
||||
@@ -217,6 +184,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let [i, line] of lines.entries()) {
|
||||
if (
|
||||
textData.textMargin !== undefined &&
|
||||
@@ -256,15 +224,16 @@ export const drawText = function (elem, textData) {
|
||||
textElem.attr('dy', dy);
|
||||
}
|
||||
|
||||
const text = line || ZERO_WIDTH_SPACE;
|
||||
if (textData.tspan) {
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x);
|
||||
if (textData.fill !== undefined) {
|
||||
span.attr('fill', textData.fill);
|
||||
}
|
||||
span.text(line);
|
||||
span.text(text);
|
||||
} else {
|
||||
textElem.text(line);
|
||||
textElem.text(text);
|
||||
}
|
||||
if (
|
||||
textData.valign !== undefined &&
|
||||
@@ -371,7 +340,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
}
|
||||
}
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
var cssclass = 'actor';
|
||||
if (actor.properties != null && actor.properties['class']) {
|
||||
cssclass = actor.properties['class'];
|
||||
@@ -391,9 +360,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
if (actor.properties != null && actor.properties['icon']) {
|
||||
const iconSrc = actor.properties['icon'].trim();
|
||||
if (iconSrc.charAt(0) === '@') {
|
||||
drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
} else {
|
||||
drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +407,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
const actElem = elem.append('g');
|
||||
actElem.attr('class', 'actor-man');
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = actor.x;
|
||||
rect.y = actor.y;
|
||||
rect.fill = '#eaeaea';
|
||||
@@ -447,7 +416,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
rect.class = 'actor';
|
||||
rect.rx = 3;
|
||||
rect.ry = 3;
|
||||
// drawRect(actElem, rect);
|
||||
|
||||
actElem
|
||||
.append('line')
|
||||
@@ -532,6 +500,7 @@ export const drawBox = function (elem, box, conf) {
|
||||
export const anchorElement = function (elem) {
|
||||
return elem.append('g');
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an activation in the diagram
|
||||
*
|
||||
@@ -542,7 +511,7 @@ export const anchorElement = function (elem) {
|
||||
* @param {any} actorActivations - Number of activations on the actor.
|
||||
*/
|
||||
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
const g = bounds.anchored;
|
||||
rect.x = bounds.startx;
|
||||
rect.y = bounds.starty;
|
||||
@@ -594,7 +563,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
});
|
||||
}
|
||||
|
||||
let txt = getTextObj();
|
||||
let txt = svgDrawCommon.getTextObj();
|
||||
txt.text = labelText;
|
||||
txt.x = loopModel.startx;
|
||||
txt.y = loopModel.starty;
|
||||
@@ -661,16 +630,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
svgDrawCommon.drawBackgroundRect(elem, bounds);
|
||||
};
|
||||
|
||||
export const insertDatabaseIcon = function (elem) {
|
||||
@@ -737,6 +697,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -755,6 +716,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@@ -776,6 +738,7 @@ export const insertSequenceNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup cross head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@@ -1062,8 +1025,6 @@ export default {
|
||||
drawActor,
|
||||
drawBox,
|
||||
drawPopup,
|
||||
drawImage,
|
||||
drawEmbeddedImage,
|
||||
anchorElement,
|
||||
drawActivation,
|
||||
drawLoop,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user