Compare commits

..

1 Commits

Author SHA1 Message Date
Sidharth Vinod
9491e7035e docs: Release process 2023-02-24 16:46:58 +05:30
132 changed files with 2242 additions and 4211 deletions

View File

@@ -51,10 +51,18 @@ body:
label: Setup
description: |-
Please fill out the below info.
Note that you only need to fill out the relevant section
Note that you only need to fill out one and not both sections.
value: |-
- Mermaid version:
**Desktop**
- OS and Version: [Windows, Linux, Mac, ...]
- Browser and Version: [Chrome, Edge, Firefox]
**Smartphone**
- Device: [Samsung, iPhone, ...]
- OS and Version: [Android, iOS, ...]
- Browser and Version: [Chrome, Safari, ...]
- type: textarea
attributes:
label: Additional Context

View File

@@ -1,18 +1,18 @@
## :bookmark_tabs: Summary
Brief description about the content of your PR.
Resolves #<your issue id here>
## :straight_ruler: Design Decisions
Describe the way your implementation works or what design decisions you made if applicable.
### :clipboard: Tasks
Make sure you
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
- [ ] :computer: have added unit/e2e tests (if appropriate)
- [ ] :notebook: have added documentation (if appropriate)
- [ ] :bookmark: targeted `develop` branch
## :bookmark_tabs: Summary
Brief description about the content of your PR.
Resolves #<your issue id here>
## :straight_ruler: Design Decisions
Describe the way your implementation works or what design decisions you made if applicable.
### :clipboard: Tasks
Make sure you
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
- [ ] :computer: have added unit/e2e tests (if appropriate)
- [ ] :notebook: have added documentation (if appropriate)
- [ ] :bookmark: targeted `develop` branch

View File

@@ -0,0 +1,19 @@
# Release vX.X.X
## Release checklist
- [ ] Forked from `develop` branch
- [ ] Draft PR created, targeting `master` branch
- [ ] Ready for review
- [ ] Ready for testing
- [ ] Tested by -
- [ ] Ready for merge
## Release process
- The PR is marked as `Ready for review` by the author
- The PR is reviewed by at least one other person
- The PR is marked as `Ready for testing` by the reviewer once the review and necessary changes are complete
- The PR is tested by the someone other than the author
- The PR is marked as `Tested by - @testerName` by the tester once the testing is complete
- The PR is marked as `Ready for merge` by the author once the testing is complete and CI is green.

View File

@@ -21,7 +21,11 @@ jobs:
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
# Need to skip setup if Cypress run is skipped, otherwise an error
# is thrown since the pnpm cache step fails
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with:
cache: pnpm
node-version: ${{ matrix.node-version }}
# Install NPM dependencies, cache them correctly

View File

@@ -14,7 +14,6 @@ on:
pull_request:
branches:
- master
workflow_dispatch:
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '30 8 * * *'
@@ -36,16 +35,9 @@ jobs:
restore-keys: cache-lychee-
- name: Link Checker
uses: lycheeverse/lychee-action@v1.6.1
uses: lycheeverse/lychee-action@v1.5.4
with:
args: >-
--verbose
--no-progress
--cache
--max-cache-age 1d
packages/mermaid/src/docs/**/*.md
README.md
README.zh-CN.md
args: --verbose --no-progress --cache --max-cache-age 1d packages/mermaid/src/docs/**/*.md README.md README.zh-CN.md
fail: true
jobSummary: true
env:

View File

@@ -33,14 +33,6 @@ jobs:
run: |
pnpm run ci --coverage
- name: Run ganttDb tests using California timezone
env:
# Makes sure that gantt db works even in a timezone that has daylight savings
# since some days have 25 hours instead of 24.
TZ: America/Los_Angeles
run: |
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
- name: Upload Coverage to Coveralls
# it feels a bit weird to use @master, but that's what the docs use
# (coveralls also doesn't publish a @v1 we can use)

View File

@@ -4,7 +4,12 @@
# Network error: Forbidden
https://codepen.io
# Timeout error, maybe Twitter has anti-bot defenses against GitHub's CI servers?
# Network error: The certificate was not trusted
https://mkdocs.org/
https://osawards.com/javascript/#nominees
https://osawards.com/javascript/2019
# Timeout error, maybe Twitter has anti-bot defences against GitHub's CI servers?
https://twitter.com/mermaidjs_
# Don't check files that are generated during the build via `pnpm docs:code`

1
.npmrc
View File

@@ -1,2 +1,3 @@
auto-install-peers=true
strict-peer-dependencies=false
use-inline-specifiers-lockfile-format=true

View File

@@ -10,7 +10,7 @@ async function createServer() {
configFile: './vite.config.ts',
mode: 'production',
server: { middlewareMode: true },
appType: 'custom', // don't include Vite's default HTML handling middleware
appType: 'custom', // don't include Vite's default HTML handling middlewares
});
app.use(cors());

View File

@@ -55,8 +55,6 @@ The documentation is written in **Markdown**. For more information about Markdow
The source files for the project documentation are located in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory. This is where you should make changes.
The files under `/packages/mermaid/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory.
After editing files in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory, be sure to run `pnpm install` and `pnpm run --filter mermaid docs:build` locally to build the `/docs` directory.
```mermaid
flowchart LR
classDef default fill:#fff,color:black,stroke:black

View File

@@ -19,7 +19,6 @@
"brkt",
"brolin",
"brotli",
"città",
"classdef",
"codedoc",
"colour",
@@ -47,7 +46,6 @@
"graphviz",
"grav",
"greywolf",
"huynh",
"inkdrop",
"jaoude",
"jison",
@@ -91,7 +89,6 @@
"sidharthv",
"sphinxcontrib",
"statediagram",
"steph",
"stylis",
"substate",
"sveidqvist",

View File

@@ -22,7 +22,7 @@ export const mermaidUrl = (graphStr, options, api) => {
return url;
};
export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation = undefined) => {
export const imgSnapshotTest = (graphStr, _options, api = false, validation) => {
cy.log(_options);
const options = Object.assign(_options);
if (!options.fontFamily) {

View File

@@ -13,6 +13,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('1: should render a simple class diagram', () => {
@@ -46,6 +47,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('2: should render a simple class diagrams with cardinality', () => {
@@ -74,6 +76,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('should render a simple class diagram with different visibilities', () => {
@@ -91,6 +94,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('should render multiple class diagrams', () => {
@@ -143,6 +147,7 @@ describe('Class diagram V2', () => {
],
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('4: should render a simple class diagram with comments', () => {
@@ -172,6 +177,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('5: should render a simple class diagram with abstract method', () => {
@@ -183,6 +189,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('6: should render a simple class diagram with static method', () => {
@@ -194,6 +201,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('7: should render a simple class diagram with Generic class', () => {
@@ -213,6 +221,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('8: should render a simple class diagram with Generic class and relations', () => {
@@ -233,6 +242,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('9: should render a simple class diagram with clickable link', () => {
@@ -254,6 +264,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('10: should render a simple class diagram with clickable callback', () => {
@@ -275,6 +286,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('11: should render a simple class diagram with return type on method', () => {
@@ -289,6 +301,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('12: should render a simple class diagram with generic types', () => {
@@ -304,6 +317,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('13: should render a simple class diagram with css classes applied', () => {
@@ -321,6 +335,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('14: should render a simple class diagram with css classes applied directly', () => {
@@ -336,6 +351,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
@@ -349,6 +365,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('16a: should render a simple class diagram with static field', () => {
@@ -361,6 +378,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('16b: should handle the direction statement with TB', () => {
@@ -385,6 +403,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('18: should handle the direction statement with LR', () => {
@@ -409,6 +428,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('17a: should handle the direction statement with BT', () => {
imgSnapshotTest(
@@ -432,6 +452,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('17b: should handle the direction statement with RL', () => {
imgSnapshotTest(
@@ -455,6 +476,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('18: should render a simple class diagram with notes', () => {
@@ -471,6 +493,7 @@ describe('Class diagram V2', () => {
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
it('1433: should render a simple class with a title', () => {
@@ -480,72 +503,8 @@ title: simple class diagram
---
classDiagram-v2
class Class10
`
);
});
it('should render a class with text label', () => {
imgSnapshotTest(
`classDiagram
class C1["Class 1 with text label"]
C1 --> C2`
);
});
it('should render two classes with text labels', () => {
imgSnapshotTest(
`classDiagram
class C1["Class 1 with text label"]
class C2["Class 2 with chars @?"]
C1 --> C2`
);
});
it('should render a class with a text label, members and annotation', () => {
imgSnapshotTest(
`classDiagram
class C1["Class 1 with text label"] {
&lt;&lt;interface&gt;&gt;
+member1
}
C1 --> C2`
);
});
it('should render multiple classes with same text labels', () => {
imgSnapshotTest(
`classDiagram
class C1["Class with text label"]
class C2["Class with text label"]
class C3["Class with text label"]
C1 --> C2
C3 ..> C2
`
);
});
it('should render classes with different text labels', () => {
imgSnapshotTest(
`classDiagram
class C1["OneWord"]
class C2["With, Comma"]
class C3["With (Brackets)"]
class C4["With [Brackets]"]
class C5["With {Brackets}"]
class C7["With 1 number"]
class C8["With . period..."]
class C9["With - dash"]
class C10["With _ underscore"]
class C11["With ' single quote"]
class C12["With ~!@#$%^&*()_+=-/?"]
class C13["With Città foreign language"]
`
);
});
it('should render classLabel if class has already been defined earlier', () => {
imgSnapshotTest(
`classDiagram
Animal <|-- Duck
class Duck["Duck with text label"]
`
`,
{}
);
});
});

View File

@@ -10,6 +10,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render an ER diagram with a recursive relationship', () => {
@@ -22,6 +23,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render an ER diagram with multiple relationships between the same two entities', () => {
@@ -33,6 +35,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render a cyclical ER diagram', () => {
@@ -45,6 +48,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render a not-so-simple ER diagram', () => {
@@ -62,6 +66,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render multiple ER diagrams', () => {
@@ -80,6 +85,7 @@ describe('Entity Relationship Diagram', () => {
],
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render an ER diagram with blank or empty labels', () => {
@@ -92,6 +98,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render an ER diagrams when useMaxWidth is true (default)', () => {
@@ -144,6 +151,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ er: { useMaxWidth: false } }
);
cy.get('svg');
});
it('should render entities with and without attributes', () => {
@@ -156,6 +164,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with generic and array attributes', () => {
@@ -170,6 +179,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with length in attributes type', () => {
@@ -183,6 +193,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities and attributes with big and small entity names', () => {
@@ -198,6 +209,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with keys', () => {
@@ -216,6 +228,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with comments', () => {
@@ -234,6 +247,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with keys and comments', () => {
@@ -253,6 +267,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('should render entities with aliases', () => {
@@ -270,6 +285,7 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
cy.get('svg');
});
it('1433: should render a simple ER diagram with a title', () => {

View File

@@ -1,45 +0,0 @@
import { imgSnapshotTest } from '../../helpers/util';
describe('Error Diagrams', () => {
beforeEach(() => {
cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('Parse error');
// return false to prevent the error from
// failing this test
return false;
});
});
it('should render a simple ER diagram', () => {
imgSnapshotTest(
`
error
`,
{ logLevel: 1 }
);
});
it('should render error diagram for actual errors', () => {
imgSnapshotTest(
`
flowchart TD
A[Christmas] --|Get money| B(Go shopping)
`,
{ logLevel: 1 }
);
});
it('should render error for wrong ER diagram', () => {
imgSnapshotTest(
`
erDiagram
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
MONGODB-CLUSTERS ||..|{
ATLAS-TEAMS ||..|{
`,
{ logLevel: 1 }
);
});
});

View File

@@ -684,149 +684,4 @@ A --> B
{ titleTopMargin: 0 }
);
});
describe('Markdown strings flowchart-elk (#4220)', () => {
describe('html labels', () => {
it('With styling and classes', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
classDef someclass fill:#f96
`,
{ titleTopMargin: 0 }
);
});
it('With formatting in a node', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
a{"\`The **cat** in the hat\`"} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
`,
{ titleTopMargin: 0 }
);
});
it('New line in node and formatted edge label', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
b("\`The dog in **the** hog.(1)
NL\`") --"\`1o **bold**\`"--> c
`,
{ titleTopMargin: 0 }
);
});
it('Wrapping long text with a new line', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
b(\`The dog in **the** hog.(1).. a a a a *very long text* about it
Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`) --> c
`,
{ titleTopMargin: 0 }
);
});
it('Sub graphs and markdown strings', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
end
subgraph "\`**Two**\`"
c("\`The **cat**
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
`,
{ titleTopMargin: 0 }
);
});
});
describe('svg text labels', () => {
it('With styling and classes', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart-elk LR
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
classDef someclass fill:#f96
`,
{ titleTopMargin: 0 }
);
});
it('With formatting in a node', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart-elk LR
a{"\`The **cat** in the hat\`"} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
`,
{ titleTopMargin: 0 }
);
});
it('New line in node and formatted edge label', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart-elk LR
b("\`The dog in **the** hog.(1)
NL\`") --"\`1o **bold**\`"--> c
`,
{ titleTopMargin: 0 }
);
});
it('Wrapping long text with a new line', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart-elk LR
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
`,
{ titleTopMargin: 0 }
);
});
it('Sub graphs and markdown strings', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart-elk LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
end
subgraph "\`**Two**\`"
c("\`The **cat**
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
`,
{ titleTopMargin: 0 }
);
});
});
});
});

View File

@@ -685,149 +685,4 @@ A ~~~ B
{ titleTopMargin: 0 }
);
});
describe('Markdown strings flowchart (#4220)', () => {
describe('html labels', () => {
it('With styling and classes', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
classDef someclass fill:#f96
`,
{ titleTopMargin: 0 }
);
});
it('With formatting in a node', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
a{"\`The **cat** in the hat\`"} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
`,
{ titleTopMargin: 0 }
);
});
it('New line in node and formatted edge label', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
b("\`The dog in **the** hog.(1)
NL\`") --"\`1o **bold**\`"--> c
`,
{ titleTopMargin: 0 }
);
});
it('Wrapping long text with a new line', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
`,
{ titleTopMargin: 0 }
);
});
it('Sub graphs and markdown strings', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
end
subgraph "\`**Two**\`"
c("\`The **cat**
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
`,
{ titleTopMargin: 0 }
);
});
});
describe('svg text labels', () => {
it('With styling and classes', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
classDef someclass fill:#f96
`,
{ titleTopMargin: 0 }
);
});
it('With formatting in a node', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
a{"\`The **cat** in the hat\`"} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
`,
{ titleTopMargin: 0 }
);
});
it('New line in node and formatted edge label', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
b("\`The dog in **the** hog.(1)
NL\`") --"\`1o **bold**\`"--> c
`,
{ titleTopMargin: 0 }
);
});
it('Wrapping long text with a new line', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
Word!
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
`,
{ titleTopMargin: 0 }
);
});
it('Sub graphs and markdown strings', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
end
subgraph "\`**Two**\`"
c("\`The **cat**
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
`,
{ titleTopMargin: 0 }
);
});
});
});
});

View File

@@ -133,24 +133,6 @@ describe('Gantt diagram', () => {
);
});
it('should default to showing today marker', () => {
// This test only works if the environment thinks today is 1010-10-10
imgSnapshotTest(
`
gantt
title Show today marker (vertical line should be visible)
dateFormat YYYY-MM-DD
axisFormat %d
%% Should default to being on
%% todayMarker on
section Section1
Yesterday: 1010-10-09, 1d
Today: 1010-10-10, 1d
`,
{}
);
});
it('should hide today marker', () => {
imgSnapshotTest(
`
@@ -160,8 +142,7 @@ describe('Gantt diagram', () => {
axisFormat %d
todayMarker off
section Section1
Yesterday: 1010-10-09, 1d
Today: 1010-10-10, 1d
Today: 1, -1h
`,
{}
);
@@ -176,8 +157,7 @@ describe('Gantt diagram', () => {
axisFormat %d
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Yesterday: 1010-10-09, 1d
Today: 1010-10-10, 1d
Today: 1, -1h
`,
{}
);
@@ -455,39 +435,4 @@ describe('Gantt diagram', () => {
{ gantt: { topAxis: true } }
);
});
it('should render when compact is true', () => {
imgSnapshotTest(
`
---
displayMode: compact
---
gantt
title GANTT compact
dateFormat HH:mm:ss
axisFormat %Hh%M
section DB Clean
Clean: 12:00:00, 10m
Clean: 12:30:00, 12m
Clean: 13:00:00, 8m
Clean: 13:30:00, 9m
Clean: 14:00:00, 13m
Clean: 14:30:00, 10m
Clean: 15:00:00, 11m
section Sessions
A: 12:00:00, 63m
B: 12:30:00, 12m
C: 13:05:00, 12m
D: 13:06:00, 33m
E: 13:15:00, 55m
F: 13:20:00, 12m
G: 13:32:00, 18m
H: 13:50:00, 20m
I: 14:10:00, 10m
`,
{}
);
});
});

View File

@@ -223,18 +223,5 @@ mindmap
shouldHaveRoot
);
});
describe('Markdown strings mindmaps (#4220)', () => {
it('Formatted label with linebreak and a wrapping label and emojis', () => {
imgSnapshotTest(
`mindmap
id1[\`**Start** with
a second line 😎\`]
id2[\`The dog in **the** hog... a *very long text* about it
Word!\`]
`,
{ titleTopMargin: 0 }
);
});
});
/* The end */
});

View File

@@ -75,15 +75,4 @@ describe('Pie Chart', () => {
expect(svg).to.not.have.attr('style');
});
});
it('should render a pie diagram when textPosition is set', () => {
imgSnapshotTest(
`
pie
"Dogs": 50
"Cats": 25
`,
{ logLevel: 1, pie: { textPosition: 0.9 } }
);
cy.get('svg');
});
});

View File

@@ -12,6 +12,7 @@
<style>
body {
background: rgb(221, 208, 208);
/*background:#333;*/
font-family: 'Arial';
}
h1 {
@@ -119,9 +120,17 @@ classE o-- classF : aggregation
};
mermaid.initialize({
theme: 'default',
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,
flowchart: { curve: 'linear', htmlLabels: true },
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50, showSequenceNumbers: true },
// sequenceDiagram: { actorMargin: 300 } // deprecated
// fontFamily: '"arial", sans-serif',
// themeVariables: {
// fontFamily: '"arial", sans-serif',
// },
curve: 'linear',
securityLevel: 'loose',
});

View File

@@ -29,9 +29,9 @@
}
.mermaid svg {
/* font-size: 18px !important; */
background-color: #efefef;
background-image: radial-gradient(#fff 51%, transparent 91%),
radial-gradient(#fff 51%, transparent 91%);
background-color: #eee;
background-image: radial-gradient(#fff 1%, transparent 11%),
radial-gradient(#fff 1%, transparent 11%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
@@ -51,103 +51,29 @@
font-family: monospace;
font-size: 72px;
}
/* tspan {
font-size: 6px !important;
} */
</style>
</head>
<body>
<pre id="diagram" class="mermaid">
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*] </pre
>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
</pre>
<pre id="diagram" class="mermaid">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
graph BT
a{The cat in the hat} -- 1o --> b
a -- 2o --> c
a -- 3o --> d
g --2i--> a
d --1i--> a
h --3i -->a
b --> d(The dog in the hog)
c --> d
</pre>
<pre id="diagram" class="mermaid2">
flowchart
id["`A root with a long text that wraps to keep the node size in check. A root with a long text that wraps to keep the node size in check`"]</pre
>
<pre id="diagram" class="mermaid2">
flowchart LR
A[A text that needs to be wrapped wraps to another line]
B[A text that needs to be<br/>wrapped wraps to another line]
C["`A text that needs to be wrapped to another line`"]</pre>
<pre id="diagram" class="mermaid2">
flowchart LR
C["`A text
that needs
to be wrapped
in another
way`"]
</pre
>
<pre id="diagram" class="mermaid">
classDiagram-v2
note "I love this diagram!\nDo you love it?"
</pre>
<pre id="diagram" class="mermaid">
stateDiagram-v2
State1: The state with a note with minus - and plus + in it
note left of State1
Important information! You can write
notes with . and in them.
end note </pre
>
<pre id="diagram" class="mermaid2">
mindmap
root
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"theme": "forest"} }%%
mindmap
id1[**Start2**<br/>end]
id2[**Start2**<br />end]
%% Another comment
id3[**Start2**<br>end] %% Comment
id4[**Start2**<br >end<br >the very end]
flowchart-elk TB
a --> b
a --> c
b --> d
c --> d
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1["`**Start2**
second line 😎 with long text that is wrapping to the next line`"]
id2["`Child **with bold** text`"]
id3["`Children of which some
is using *italic type of* text`"]
id4[Child]
id5["`Child
Row
and another
`"]
</pre>
<pre id="diagram" class="mermaid2">
mindmap
id1("`**Root**`"]
id2["`A formatted text... with **bold** and *italics*`"]
id3[Regular labels works as usual]
id4["`Emojis and unicode works too: 🤓
शान्तिः سلام 和平 `"]
</pre>
<pre id="diagram" class="mermaid">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
@@ -163,7 +89,7 @@ flowchart TB
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
subgraph amd[AMD Latte GPU]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
@@ -202,62 +128,6 @@ flowchart TB
rtc{{rtc}}
</pre
>
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk", "htmlLabels": false}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
subgraph ibm[IBM Espresso CPU]
core0[IBM PowerPC Broadway Core 0]
core1[IBM PowerPC Broadway Core 1]
core2[IBM PowerPC Broadway Core 2]
rom[16 KB ROM]
core0 --- core2
rom --> core2
end
subgraph amd["`**AMD** Latte GPU`"]
mem[Memory & I/O Bridge]
dram[DRAM Controller]
edram[32 MB EDRAM MEM1]
rom[512 B SEEPROM]
sata[SATA IF]
exi[EXI]
subgraph gx[GX]
sram[3 MB 1T-SRAM]
end
radeon[AMD Radeon R7xx GX2]
mem --- gx
mem --- radeon
rom --- mem
mem --- sata
mem --- exi
dram --- sata
dram --- exi
end
ddr3[2 GB DDR3 RAM MEM2]
mem --- ddr3
dram --- ddr3
edram --- ddr3
core1 --- mem
exi --- rtc
rtc{{rtc}}
</pre
>
<br />
<pre id="diagram" class="mermaid2">
flowchart TB
@@ -400,16 +270,14 @@ mindmap
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
// theme: 'forest',
theme: 'forest',
startOnLoad: true,
logLevel: 0,
logLevel: 5,
flowchart: {
// defaultRenderer: 'elk',
useMaxWidth: false,
// htmlLabels: false,
htmlLabels: true,
},
// htmlLabels: false,
gantt: {
useMaxWidth: false,
},

View File

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

View File

@@ -47,6 +47,7 @@ const contentLoaded = async function () {
await mermaid2.registerExternalDiagrams([externalExample]);
mermaid2.initialize(graphObj.mermaid);
await mermaid2.run();
markRendered();
}
};
@@ -122,6 +123,7 @@ const contentLoadedApi = async function () {
bindFunctions(div);
}
}
markRendered();
};
if (typeof document !== 'undefined') {
@@ -133,10 +135,10 @@ if (typeof document !== 'undefined') {
function () {
if (this.location.href.match('xss.html')) {
this.console.log('Using api');
void contentLoadedApi().finally(markRendered);
void contentLoadedApi();
} else {
this.console.log('Not using api');
void contentLoaded().finally(markRendered);
void contentLoaded();
}
},
false

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Error | Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
</head>
<body>
<pre class="mermaid">
erDiagram
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
</pre>
<pre class="mermaid">
erDiagram
ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
MONGODB-CLUSTERS ||..|{
ATLAS-TEAMS ||..|{
</pre>
<hr />
<pre class="mermaid">
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
</pre>
<pre class="mermaid">
flowchart TD
A[Christmas] --|Get money| B(Go shopping)
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
</script>
</body>
</html>

View File

@@ -78,7 +78,7 @@
axisFormat %d/%m
todayMarker off
section Section1
Today: 1, 08-08-09-01:00, 5min
Today: 1, -01:00, 5min
</pre>
<hr />
@@ -89,7 +89,7 @@
axisFormat %d/%m
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Today: 1, 08-08-09-01:00, 5min
Today: 1, -01:00, 5min
</pre>
<hr />
@@ -166,37 +166,6 @@
</pre>
<hr />
<pre class="mermaid">
---
displayMode: compact
---
gantt
title GANTT compact
dateFormat HH:mm:ss
axisFormat %Hh%M
section DB Clean
Clean: 12:00:00, 10m
Clean: 12:30:00, 12m
Clean: 13:00:00, 8m
Clean: 13:30:00, 9m
Clean: 14:00:00, 13m
Clean: 14:30:00, 10m
Clean: 15:00:00, 11m
section Sessions
A: 12:00:00, 63m
B: 12:30:00, 12m
C: 13:05:00, 12m
D: 13:06:00, 33m
E: 13:15:00, 55m
F: 13:20:00, 12m
G: 13:32:00, 18m
H: 13:50:00, 20m
I: 14:10:00, 10m
</pre>
<hr />
<script>
function ganttTestClick(a, b, c) {
console.log('a:', a);

View File

@@ -81,6 +81,7 @@
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
await mermaid.registerExternalDiagrams([mermaidMindmap]);
mermaid.initialize({
theme: 'base',
startOnLoad: true,

View File

@@ -26,7 +26,6 @@
<hr />
<pre class="mermaid">
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
pie
title Key elements in Product X
accTitle: Key elements in Product X
@@ -38,7 +37,7 @@
</pre>
<script type="module">
import mermaid from '../packages/mermaid/src/mermaid';
import mermaid from '../packages/mermaid';
mermaid.initialize({
theme: 'forest',
// themeCSS: '.node rect { fill: red; }',

View File

@@ -6,7 +6,7 @@
# Tutorials
This is a list of publicly available Tutorials for using Mermaid.JS and is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
This is list of publicly available Tutorials for using Mermaid.JS . This is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
**Note that these tutorials might display an older interface, but the usage of the live-editor will largely be the same.**

View File

@@ -16,4 +16,4 @@
#### Defined in
[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70)

View File

@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
[mermaidAPI.ts:91](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L91)
---
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
#### Defined in
[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)

View File

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

View File

@@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in
[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75)
## Variables
### mermaidAPI
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean` | `void`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
## mermaidAPI configuration defaults
@@ -88,7 +88,6 @@ const config = {
numberSectionStyles: 4,
axisFormat: '%Y-%m-%d',
topAxis: false,
displayMode: '',
},
};
mermaid.initialize(config);
@@ -96,7 +95,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
[mermaidAPI.ts:680](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L680)
## Functions
@@ -127,7 +126,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:308](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L308)
---
@@ -153,7 +152,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:259](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L259)
---
@@ -179,7 +178,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:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L188)
---
@@ -202,7 +201,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:236](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L236)
---
@@ -229,7 +228,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:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L172)
---
@@ -249,7 +248,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156)
[mermaidAPI.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L152)
---
@@ -269,7 +268,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L127)
[mermaidAPI.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L123)
---
@@ -295,7 +294,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:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
---
@@ -320,4 +319,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:358](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L358)

View File

@@ -261,34 +261,6 @@ The theming engine will only recognize hex colors and not color names. So, the v
| activationBkgColor | secondaryColor | Activation Background Color |
| sequenceNumberColor | calculated from lineColor | Sequence Number Color |
## Pie Diagram Variables
| Variable | Default value | Description |
| ------------------- | ------------------------------ | ------------------------------------------ |
| pie1 | primaryColor | Fill for 1st section in pie diagram |
| pie2 | secondaryColor | Fill for 2nd section in pie diagram |
| pie3 | calculated from tertiary | Fill for 3rd section in pie diagram |
| pie4 | calculated from primaryColor | Fill for 4th section in pie diagram |
| pie5 | calculated from secondaryColor | Fill for 5th section in pie diagram |
| pie6 | calculated from tertiaryColor | Fill for 6th section in pie diagram |
| pie7 | calculated from primaryColor | Fill for 7th section in pie diagram |
| pie8 | calculated from primaryColor | Fill for 8th section in pie diagram |
| pie9 | calculated from primaryColor | Fill for 9th section in pie diagram |
| pie10 | calculated from primaryColor | Fill for 10th section in pie diagram |
| pie11 | calculated from primaryColor | Fill for 11th section in pie diagram |
| pie12 | calculated from primaryColor | Fill for 12th section in pie diagram |
| pieTitleTextSize | 25px | Title text size |
| pieTitleTextColor | taskTextDarkColor | Title text color |
| pieSectionTextSize | 17px | Text size of individual section labels |
| pieSectionTextColor | textColor | Text color of individual section labels |
| pieLegendTextSize | 17px | Text size of labels in diagram legend |
| pieLegendTextColor | taskTextDarkColor | Text color of labels in diagram legend |
| pieStrokeColor | black | Border color of individual pie sections |
| pieStrokeWidth | 2px | Border width of individual pie sections |
| pieOuterStrokeWidth | 2px | Border width of pie diagram's outer circle |
| pieOuterStrokeColor | black | Border color of pie diagram's outer circle |
| pieOpacity | 0.7 | Opacity of individual pie sections |
## State Colors
| Variable | Default value | Description |

View File

@@ -247,23 +247,6 @@ The example below show an outline of how this could be used. The example just lo
</script>
```
To determine the type of diagram present in a given text, you can utilize the `mermaid.detectType` function, as demonstrated in the example below.
```html
<script type="module">
import mermaid from './mermaid.esm.mjs';
const graphDefinition = `sequenceDiagram
Pumbaa->>Timon:I ate like a pig.
Timon->>Pumbaa:Pumbaa, you ARE a pig.`;
try {
const type = mermaid.detectType(graphDefinition);
console.log(type); // 'sequence'
} catch (error) {
// UnknownDiagramError
}
</script>
```
### Binding events
Sometimes the generated graph also has defined interactions like tooltip and click events. When using the API one must

View File

@@ -20,7 +20,6 @@ They also serve as proof of concept, for the variety of things that can be built
- [Gitea](https://gitea.io) (**Native support**)
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**)
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**)
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) (**Native support**)
- [Deepdwn](https://billiam.itch.io/deepdwn) (**Native support**)
- [Joplin](https://joplinapp.org) (**Native support**)
- [Swimm](https://swimm.io) (**Native support**)
@@ -89,7 +88,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [FosWiki](https://foswiki.org)
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
- [DokuWiki](https://dokuwiki.org)
- [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
- [Flowcharts](https://www.dokuwiki.org/plugin:flowcharts?s[]=mermaid)
- [ComboStrap](https://combostrap.com/mermaid)
- [TiddlyWiki](https://tiddlywiki.com/)
- [mermaid-tw5: full js library](https://github.com/efurlanm/mermaid-tw5)
@@ -150,7 +149,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [remark-mermaid](https://github.com/temando/remark-mermaid)
- [jSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
- [MkDocs](https://www.mkdocs.org)
- [MkDocs](https://mkdocs.org)
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Type Doc](https://typedoc.org/)

View File

@@ -1,9 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/news/announcements.md](../../packages/mermaid/src/docs/news/announcements.md).
# Announcements
Stay tuned for some exciting announcements!

View File

@@ -1,37 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/news/blog.md](../../packages/mermaid/src/docs/news/blog.md).
# Blog
## [Automatic text wrapping in flowcharts is here!](https://wwww.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
Exciting news for all Mermaid OSS fans: Mermaid Chart has officially launched with Mermaid Chart!
## [If you're not excited about ChatGPT, then you're not being creative](https://www.mermaidchart.com/blog/posts/if-youre-not-excited-about-chatgpt-then-youre-not-being-creative-enough/)
8 March 2023 · 9 mins
The hype around AI in general and ChatGPT, in particular, is so intense that its very understandable to assume the hype train is driving straight toward the trough of disillusionment.
## [Flow charts are O(n)2 complex, so don't go over 100 connections](https://www.mermaidchart.com/blog/posts/flow-charts-are-on2-complex-so-dont-go-over-100-connections/)
1 March 2023 · 12 mins
Flowchart design is a game of balance: Read about the importance of dialling in the right level of detail and how to manage complexity in large flowcharts.
## [Busting the myth that developers can't write](https://www.mermaidchart.com/blog/posts/busting-the-myth-that-developers-cant-write/)
10 February 2023 · 10 mins
Busting the myth that developers cant write # Its an annoying stereotype that developers dont know how to write, speak, and otherwise communicate.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -130,40 +130,6 @@ classDiagram
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
### Class labels
In case you need to provide a label for a class, you can use the following syntax:
```mermaid-example
classDiagram
class Animal["Animal with a label"]
class Car["Car with *! symbols"]
Animal --> Car
```
```mermaid
classDiagram
class Animal["Animal with a label"]
class Car["Car with *! symbols"]
Animal --> Car
```
You can also use backticks to escape special characters in the label:
```mermaid-example
classDiagram
class `Animal Class!`
class `Car Class`
`Animal Class!` --> `Car Class`
```
```mermaid
classDiagram
class `Animal Class!`
class `Car Class`
`Animal Class!` --> `Car Class`
```
## Defining Members of a class
UML provides mechanisms to represent class members such as attributes and methods, as well as additional information about them.
@@ -726,11 +692,11 @@ Beginner's tip—a full example using interactive links in an HTML page:
### Styling a node
It is possible to apply specific styles such as a thicker border or a different background color to individual nodes. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
It is possible to apply specific styles such as a thicker border or a different background color to individual nodes. This is done by predefining classes in css styles that can be applied from the graph definition:
```html
<style>
.styleClass > rect {
.cssClass > rect {
fill: #ff0000;
stroke: #ffff00;
stroke-width: 4px;
@@ -740,29 +706,29 @@ It is possible to apply specific styles such as a thicker border or a different
Then attaching that class to a specific node:
cssClass "nodeId1" styleClass;
cssClass "nodeId1" cssClass;
It is also possible to attach a class to a list of nodes in one statement:
cssClass "nodeId1,nodeId2" styleClass;
cssClass "nodeId1,nodeId2" cssClass;
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
```mermaid-example
classDiagram
class Animal:::styleClass
class Animal:::cssClass
```
```mermaid
classDiagram
class Animal:::styleClass
class Animal:::cssClass
```
Or:
```mermaid-example
classDiagram
class Animal:::styleClass {
class Animal:::cssClass {
-int sizeInFeet
-canEat()
}
@@ -770,7 +736,7 @@ classDiagram
```mermaid
classDiagram
class Animal:::styleClass {
class Animal:::cssClass {
-int sizeInFeet
-canEat()
}

View File

@@ -183,6 +183,20 @@ flowchart LR
### A hexagon node
Code:
```mermaid-example
flowchart LR
id1{{This is the text in the box}}
```
```mermaid
flowchart LR
id1{{This is the text in the box}}
```
Render:
```mermaid-example
flowchart LR
id1{{This is the text in the box}}
@@ -710,44 +724,6 @@ flowchart LR
B1 --> B2
```
## Markdown Strings
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
```mermaid-example
%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
subgraph "One"
a("`The **cat**
in the hat`") -- "edge label" --> b{{"`The **dog** in the hog`"}}
end
subgraph "`**Two**`"
c("`The **cat**
in the hat`") -- "`Bold **edge label**`" --> d("The dog in the hog")
end
```
```mermaid
%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
subgraph "One"
a("`The **cat**
in the hat`") -- "edge label" --> b{{"`The **dog** in the hog`"}}
end
subgraph "`**Two**`"
c("`The **cat**
in the hat`") -- "`Bold **edge label**`" --> d("The dog in the hog")
end
```
Formatting:
- For bold text, use double asterisks \*\* before and after the text.
- For italics, use single asterisks \* before and after the text.
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
This feature is applicable to node labels, edge labels, and subgraph labels.
## Interaction
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.

View File

@@ -184,7 +184,7 @@ The following formatting options are supported:
| `YY` | 14 | 2 digit year |
| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. |
| `M MM` | 1..12 | Month number |
| `MMM MMMM` | January..Dec | Month name in locale set by `dayjs.locale()` |
| `MMM MMMM` | January..Dec | Month name in locale set by `moment.locale()` |
| `D DD` | 1..31 | Day of month |
| `Do` | 1st..31st | Day of month with ordinal |
| `DDD DDDD` | 1..365 | Day of year |
@@ -200,7 +200,7 @@ The following formatting options are supported:
| `SSS` | 0..999 | Thousandths of a second |
| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z |
More info in: <https://day.js.org/docs/en/parse/string-format/>
More info in: <https://momentjs.com/docs/#/parsing/string-format/>
### Output date format on the axis
@@ -257,41 +257,9 @@ The pattern is:
More info in: <https://github.com/d3/d3-time#interval_every>
## Output in compact mode
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceeding YAML settings.
```mermaid-example
---
displayMode: compact
---
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :a2, 2014-01-20, 25d
Another one :a3, 2014-02-10, 20d
```
```mermaid
---
displayMode: compact
---
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :a2, 2014-01-20, 25d
Another one :a3, 2014-02-10, 20d
```
## Comments
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
```mermaid-example
gantt

View File

@@ -224,7 +224,7 @@ mindmap
C
```
_These classes need to be supplied by the site administrator._
_These classes needs top be supplied by the site administrator._
## Unclear indentation
@@ -254,34 +254,6 @@ Root
C
```
## Markdown Strings
The "Markdown Strings" feature enhances mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
```mermaid-example
mindmap
id1["`**Root** with
a second line
Unicode works too: 🤓`"]
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
id3[Regular labels still works]
```
```mermaid
mindmap
id1["`**Root** with
a second line
Unicode works too: 🤓`"]
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
id3[Regular labels still works]
```
Formatting:
- For bold text, use double asterisks \*\* before and after the text.
- For italics, use single asterisks \* before and after the text.
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
## Integrating with your library/website.
Mindmap uses the experimental lazy loading & async rendering features which could change in the future. From version 9.4.0 this diagram is included in mermaid but use lazy loading in order to keep the size of mermaid down. This is important in order to be able to add additional diagrams going forward.

View File

@@ -48,7 +48,6 @@ Drawing a pie chart is really simple in mermaid.
## Example
```mermaid-example
%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
pie showData
title Key elements in Product X
"Calcium" : 42.96
@@ -58,7 +57,6 @@ pie showData
```
```mermaid
%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
pie showData
title Key elements in Product X
"Calcium" : 42.96
@@ -66,11 +64,3 @@ pie showData
"Magnesium" : 10.01
"Iron" : 5
```
## Configuration
Possible pie diagram configuration parameters:
| Parameter | Description | Default value |
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
| `textPosition` | The axial position of the pie slice labels, from 0.0 at the center to 1.0 at the outside edge of the circle. | `0.75` |

View File

@@ -8,7 +8,7 @@
> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia
"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia
### An example of a timeline.
@@ -213,7 +213,7 @@ However, if there is no section defined, then we have two possibilities:
```
Note that there are no sections defined, and each time period and its corresponding events will have its own color scheme.
Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme.
2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme.
@@ -257,7 +257,7 @@ let us look at same example, where we have disabled the multiColor option.
### Customizing Color scheme
You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
In case you have more than 12 sections, the color scheme will start to repeat.
NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.

View File

@@ -1,10 +1,10 @@
{
"name": "mermaid-monorepo",
"private": true,
"version": "10.0.2",
"version": "9.4.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@7.30.1",
"packageManager": "pnpm@7.27.0",
"keywords": [
"diagram",
"markdown",
@@ -70,9 +70,9 @@
"@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"@vitest/coverage-c8": "^0.29.0",
"@vitest/spy": "^0.29.0",
"@vitest/ui": "^0.29.0",
"@vitest/coverage-c8": "^0.28.4",
"@vitest/spy": "^0.28.4",
"@vitest/ui": "^0.28.4",
"concurrently": "^7.5.0",
"cors": "^2.8.5",
"coveralls": "^3.1.1",
@@ -109,9 +109,9 @@
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^4.1.1",
"vitest": "^0.29.0"
"vitest": "^0.28.5"
},
"volta": {
"node": "18.15.0"
"node": "18.14.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "10.1.0-rc.1",
"version": "10.0.0",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -8,8 +8,7 @@
"exports": {
".": {
"types": "./dist/mermaid.d.ts",
"import": "./dist/mermaid.core.mjs",
"default": "./dist/mermaid.core.mjs"
"import": "./dist/mermaid.core.mjs"
},
"./*": "./*"
},
@@ -53,17 +52,16 @@
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"@khanacademy/simple-markdown": "^0.8.6",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
"d3": "^7.4.0",
"dagre-d3-es": "7.0.10",
"dayjs": "^1.11.7",
"dompurify": "2.4.5",
"dagre-d3-es": "7.0.8",
"dompurify": "2.4.3",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"moment-mini": "^2.29.4",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2",
"ts-dedent": "^2.2.0",
@@ -75,7 +73,7 @@
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.4.0",
"@types/jsdom": "^21.0.0",
"@types/lodash-es": "^4.17.7",
"@types/lodash-es": "^4.17.6",
"@types/micromatch": "^4.0.2",
"@types/prettier": "^2.7.1",
"@types/stylis": "^4.0.2",

View File

@@ -5,14 +5,8 @@ import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { extractFrontMatter } from './diagram-api/frontmatter';
import { UnknownDiagramError } from './errors';
import { DetailedError } from './utils';
import { cleanupComments } from './diagram-api/comments';
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
/**
* An object representing a parsed mermaid diagram definition.
* @privateRemarks This is exported as part of the public mermaidAPI.
*/
export class Diagram {
type = 'graph';
parser;
@@ -44,10 +38,7 @@ export class Diagram {
// Similarly, we can't do this in getDiagramFromText() because some code
// calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter().
this.parser.parse = (text: string) =>
originalParse(cleanupComments(extractFrontMatter(text, this.db)));
this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
this.parser.parser.yy = this.db;
if (diagram.init) {
diagram.init(cnf);
@@ -77,15 +68,6 @@ export class Diagram {
}
}
/**
* Parse the text asynchronously and generate a Diagram object asynchronously.
* **Warning:** This function may be changed in the future.
* @alpha
* @param text - The mermaid diagram definition.
* @returns A the Promise of a Diagram object.
* @throws {@link UnknownDiagramError} if the diagram type can not be found.
* @privateRemarks This is exported as part of the public mermaidAPI.
*/
export const getDiagramFromText = async (text: string): Promise<Diagram> => {
const type = detectType(text, configApi.getConfig());
try {

View File

@@ -3,7 +3,6 @@ import { getConfig } from './config';
let title = '';
let diagramTitle = '';
let description = '';
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
export const clear = function (): void {
@@ -37,10 +36,10 @@ export const getDiagramTitle = function (): string {
};
export default {
getAccTitle,
setAccTitle,
getDiagramTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle: getDiagramTitle,
getAccDescription,
setAccDescription,
clear,

View File

@@ -222,9 +222,7 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
maxNodeWidth: number;
}
export interface PieDiagramConfig extends BaseDiagramConfig {
textPosition?: number;
}
export type PieDiagramConfig = BaseDiagramConfig;
export interface ErDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
@@ -266,10 +264,6 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
padding?: number;
textHeight?: number;
defaultRenderer?: string;
nodeSpacing?: number;
rankSpacing?: number;
diagramPadding?: number;
htmlLabels?: boolean;
}
export interface JourneyDiagramConfig extends BaseDiagramConfig {
@@ -301,7 +295,6 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
leftMargin?: number;
width?: number;
height?: number;
padding?: number;
boxMargin?: number;
boxTextMargin?: number;
noteMargin?: number;
@@ -318,7 +311,6 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
sectionFills?: string[];
sectionColours?: string[];
disableMulticolor?: boolean;
useMaxWidth?: boolean;
}
export interface GanttDiagramConfig extends BaseDiagramConfig {
@@ -335,7 +327,6 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
axisFormat?: string;
tickInterval?: string;
topAxis?: boolean;
displayMode?: string;
}
export interface SequenceDiagramConfig extends BaseDiagramConfig {
@@ -386,7 +377,6 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
curve?: string;
padding?: number;
defaultRenderer?: string;
wrappingWidth?: number;
}
export interface FontConfig {

View File

@@ -1,13 +1,12 @@
import intersectRect from './intersect/intersect-rect';
import { log } from '../logger';
import createLabel from './createLabel';
import { createText } from '../rendering-util/createText';
import { select } from 'd3';
import { getConfig } from '../config';
import { evaluate } from '../diagrams/common/common';
const rect = (parent, node) => {
log.info('Creating subgraph rect for ', node.id, node);
log.trace('Creating subgraph rect for ', node.id, node);
// Add outer g element
const shapeSvg = parent
@@ -18,18 +17,12 @@ const rect = (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
// const text = label
// .node()
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
const text =
node.labelType === 'markdown'
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@@ -63,20 +56,13 @@ const rect = (parent, node) => {
.attr('width', width)
.attr('height', node.height + padding);
if (useHtmlLabels) {
label.attr(
'transform',
// This puts the labal on top of the box instead of inside it
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
);
} else {
label.attr(
'transform',
// This puts the labal on top of the box instead of inside it
'translate(' + node.x + ', ' + (node.y - node.height / 2) + ')'
);
}
// Center the label
label.attr(
'transform',
// This puts the labal on top of the box instead of inside it
// 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2 - bbox.height) + ')'
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
);
const rectBox = rect.node().getBBox();
node.width = rectBox.width;

View File

@@ -41,13 +41,7 @@ function addHtmlLabel(node) {
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
return fo.node();
}
/**
* @param _vertexText
* @param style
* @param isTitle
* @param isNode
* @deprecated svg-util/createText instead
*/
const createLabel = (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') {

View File

@@ -1,6 +1,5 @@
import { log } from '../logger';
import createLabel from './createLabel';
import { createText } from '../rendering-util/createText';
import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config';
import utils from '../utils';
@@ -15,17 +14,8 @@ export const clear = () => {
};
export const insertEdgeLabel = (elem, edge) => {
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the actual text element
const labelElement =
edge.labelType === 'markdown'
? createText(elem, edge.label, {
style: edge.labelStyle,
useHtmlLabels,
addSvgBackground: true,
})
: createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType);
const labelElement = createLabel(edge.label, edge.labelStyle);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@@ -36,7 +26,7 @@ export const insertEdgeLabel = (elem, edge) => {
// Center the label
let bbox = labelElement.getBBox();
if (useHtmlLabels) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = labelElement.children[0];
const dv = select(labelElement);
bbox = div.getBoundingClientRect();

View File

@@ -313,11 +313,10 @@ const cylinder = (parent, node) => {
const rect = (parent, node) => {
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
log.trace('Classes = ', node.classes);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
// const totalWidth = bbox.width + node.padding * 2;
// const totalHeight = bbox.height + node.padding * 2;
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
rect
@@ -325,8 +324,6 @@ const rect = (parent, node) => {
.attr('style', node.style)
.attr('rx', node.rx)
.attr('ry', node.ry)
// .attr('x', -bbox.width / 2 - node.padding)
// .attr('y', -bbox.height / 2 - node.padding)
.attr('x', -bbox.width / 2 - halfPadding)
.attr('y', -bbox.height / 2 - halfPadding)
.attr('width', totalWidth)
@@ -775,7 +772,7 @@ const class_box = (parent, node) => {
maxWidth += interfaceBBox.width;
}
let classTitleString = node.classData.label;
let classTitleString = node.classData.id;
if (node.classData.type !== undefined && node.classData.type !== '') {
if (getConfig().flowchart.htmlLabels) {
@@ -930,6 +927,61 @@ const class_box = (parent, node) => {
);
verticalPos += classTitleBBox.height + rowPadding;
});
//
// let bbox;
// if (evaluate(getConfig().flowchart.htmlLabels)) {
// const div = interfaceLabel.children[0];
// const dv = select(interfaceLabel);
// bbox = div.getBoundingClientRect();
// dv.attr('width', bbox.width);
// dv.attr('height', bbox.height);
// }
// bbox = labelContainer.getBBox();
// log.info('Text 2', text2);
// const textRows = text2.slice(1, text2.length);
// let titleBox = text.getBBox();
// const descr = label
// .node()
// .appendChild(createLabel(textRows.join('<br/>'), node.labelStyle, true, true));
// if (evaluate(getConfig().flowchart.htmlLabels)) {
// const div = descr.children[0];
// const dv = select(descr);
// bbox = div.getBoundingClientRect();
// dv.attr('width', bbox.width);
// dv.attr('height', bbox.height);
// }
// // bbox = label.getBBox();
// // log.info(descr);
// select(descr).attr(
// 'transform',
// 'translate( ' +
// // (titleBox.width - bbox.width) / 2 +
// (bbox.width > titleBox.width ? 0 : (titleBox.width - bbox.width) / 2) +
// ', ' +
// (titleBox.height + halfPadding + 5) +
// ')'
// );
// select(text).attr(
// 'transform',
// 'translate( ' +
// // (titleBox.width - bbox.width) / 2 +
// (bbox.width < titleBox.width ? 0 : -(titleBox.width - bbox.width) / 2) +
// ', ' +
// 0 +
// ')'
// );
// // Get the size of the label
// // Bounding box for title and text
// bbox = label.node().getBBox();
// // Center the label
// label.attr(
// 'transform',
// 'translate(' + -bbox.width / 2 + ', ' + (-bbox.height / 2 - halfPadding + 3) + ')'
// );
rect
.attr('class', 'outer title-state')
@@ -938,6 +990,13 @@ const class_box = (parent, node) => {
.attr('width', maxWidth + node.padding)
.attr('height', maxHeight + node.padding);
// innerLine
// .attr('class', 'divider')
// .attr('x1', -bbox.width / 2 - halfPadding)
// .attr('x2', bbox.width / 2 + halfPadding)
// .attr('y1', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding)
// .attr('y2', -bbox.height / 2 - halfPadding + titleBox.height + halfPadding);
updateNodeBounds(node, rect);
node.intersect = function (point) {

View File

@@ -1,13 +1,8 @@
import { updateNodeBounds, labelHelper } from './util';
import { log } from '../../logger';
import { getConfig } from '../../config';
import intersect from '../intersect/index.js';
const note = (parent, node) => {
const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart.htmlLabels;
if (!useHtmlLabels) {
node.centerLabel = true;
}
const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
log.info('Classes = ', node.classes);

View File

@@ -1,12 +1,10 @@
import createLabel from '../createLabel';
import { createText } from '../../rendering-util/createText';
import { getConfig } from '../../config';
import { decodeEntities } from '../../mermaidAPI';
import { select } from 'd3';
import { evaluate, sanitizeText } from '../../diagrams/common/common';
export const labelHelper = (parent, node, _classes, isNode) => {
let classes;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
if (!_classes) {
classes = 'node default';
} else {
@@ -29,17 +27,9 @@ export const labelHelper = (parent, node, _classes, isNode) => {
labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0];
}
const textNode = label.node();
let text;
if (node.labelType === 'markdown') {
// text = textNode;
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
useHtmlLabels,
width: node.width || getConfig().flowchart.wrappingWidth,
classes: 'markdown-node-label',
});
} else {
text = textNode.appendChild(
const text = label
.node()
.appendChild(
createLabel(
sanitizeText(decodeEntities(labelText), getConfig()),
node.labelStyle,
@@ -47,7 +37,6 @@ export const labelHelper = (parent, node, _classes, isNode) => {
isNode
)
);
}
// Get the size of the label
let bbox = text.getBBox();
@@ -63,15 +52,8 @@ export const labelHelper = (parent, node, _classes, isNode) => {
const halfPadding = node.padding / 2;
// Center the label
if (useHtmlLabels) {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
} else {
label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
}
if (node.centerLabel) {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
}
label.insert('rect', ':first-child');
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
return { shapeSvg, bbox, halfPadding, label };
};

View File

@@ -258,18 +258,6 @@ const config: Partial<MermaidConfig> = {
* Default value: 'dagre-wrapper'
*/
defaultRenderer: 'dagre-wrapper',
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ----------- | ------- | -------- | ----------------------- |
* | wrappingWidth | See notes | number | 4 | width of nodes where text is wrapped |
*
* **Notes:**
*
* When using markdown strings the text ius wrapped automatically, this
* value sets the max width of a text before it continues on a new line.
* Default value: 'dagre-wrapper'
*/
wrappingWidth: 200,
},
/** The object containing configurations specific for sequence diagrams */
@@ -671,17 +659,6 @@ const config: Partial<MermaidConfig> = {
*/
numberSectionStyles: 4,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ------------------------- | ------ | -------- | --------- |
* | displayMode | Controls the display mode | string | 4 | 'compact' |
*
* **Notes**:
*
* - **compact**: Enables displaying multiple tasks on the same row.
*/
displayMode: '',
/**
* | Parameter | Description | Type | Required | Values |
* | ---------- | ---------------------------- | ---- | -------- | ---------------- |
@@ -707,6 +684,7 @@ const config: Partial<MermaidConfig> = {
* Default value: undefined
*/
tickInterval: undefined,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
@@ -1269,15 +1247,6 @@ const config: Partial<MermaidConfig> = {
* Default value: true
*/
useMaxWidth: true,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------ | -------------------------------------------------------------------------------- | ------- | -------- | ------------------- |
* | textPosition | Axial position of slice's label from zero at the center to 1 at the outside edge | Number | Optional | Decimal from 0 to 1 |
*
* **Notes:** Default value: 0.75
*/
textPosition: 0.75,
},
/** The object containing configurations specific for req diagrams */

View File

@@ -1,94 +0,0 @@
// tests to check that comments are removed
import { cleanupComments } from './comments';
import { describe, it, expect } from 'vitest';
describe('comments', () => {
it('should remove comments', () => {
const text = `
%% This is a comment
%% This is another comment
graph TD
A-->B
%% This is a comment
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"graph TD
A-->B
"
`);
});
it('should keep init statements when removing comments', () => {
const text = `
%% This is a comment
%% This is another comment
%%{init: {'theme': 'forest'}}%%
%%{ init: {'theme': 'space before init'}}%%
%%{init: {'theme': 'space after ending'}}%%
graph TD
A-->B
B-->C
%% This is a comment
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"%%{init: {'theme': 'forest'}}%%
%%{ init: {'theme': 'space before init'}}%%
%%{init: {'theme': 'space after ending'}}%%
graph TD
A-->B
B-->C
"
`);
});
it('should remove indented comments', () => {
const text = `
%% This is a comment
graph TD
A-->B
%% This is a comment
C-->D
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"graph TD
A-->B
C-->D
"
`);
});
it('should remove empty newlines from start', () => {
const text = `
%% This is a comment
graph TD
A-->B
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"graph TD
A-->B
"
`);
});
it('should remove comments at end of text with no newline', () => {
const text = `
graph TD
A-->B
%% This is a comment`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"graph TD
A-->B
"
`);
});
});

View File

@@ -1,8 +0,0 @@
/**
* Remove all lines starting with `%%` from the text that don't contain a `%%{`
* @param text - The text to remove comments from
* @returns cleaned text
*/
export const cleanupComments = (text: string): string => {
return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
};

View File

@@ -46,24 +46,9 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
}
}
throw new UnknownDiagramError(
`No diagram type detected matching given configuration for text: ${text}`
);
throw new UnknownDiagramError(`No diagram type detected for text: ${text}`);
};
/**
* Registers lazy-loaded diagrams to Mermaid.
*
* The diagram function is loaded asynchronously, so that diagrams are only loaded
* if the diagram is detected.
*
* @remarks
* Please note that the order of diagram detectors is important.
* The first detector to return `true` is the diagram that will be loaded
* and used, so put more specific detectors at the beginning!
*
* @param diagrams - Diagrams to lazy load, and their detectors, in order of importance.
*/
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
for (const { id, detector, loader } of diagrams) {
addDetector(id, detector, loader);
@@ -112,6 +97,4 @@ export const addDetector = (key: string, detector: DiagramDetector, loader?: Dia
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
};
export const getDiagramLoader = (key: string) => {
return detectors[key].loader;
};
export const getDiagramLoader = (key: string) => detectors[key].loader;

View File

@@ -1,82 +0,0 @@
import { it, describe, expect } from 'vitest';
import { detectType } from './detectType';
import { addDiagrams } from './diagram-orchestration';
describe('diagram-orchestration', () => {
it('should register diagrams', () => {
expect(() => detectType('graph TD; A-->B')).toThrow();
addDiagrams();
expect(detectType('graph TD; A-->B')).toBe('flowchart');
});
describe('proper diagram types should be detetced', () => {
beforeAll(() => {
addDiagrams();
});
it.each([
{ text: 'graph TD;', expected: 'flowchart' },
{ text: 'flowchart TD;', expected: 'flowchart-v2' },
{ text: 'flowchart-v2 TD;', expected: 'flowchart-v2' },
{ text: 'flowchart-elk TD;', expected: 'flowchart-elk' },
{ text: 'error', expected: 'error' },
{ text: 'C4Context;', expected: 'c4' },
{ text: 'classDiagram', expected: 'class' },
{ text: 'classDiagram-v2', expected: 'classDiagram' },
{ text: 'erDiagram', expected: 'er' },
{ text: 'journey', expected: 'journey' },
{ text: 'gantt', expected: 'gantt' },
{ text: 'pie', expected: 'pie' },
{ text: 'requirementDiagram', expected: 'requirement' },
{ text: 'info', expected: 'info' },
{ text: 'sequenceDiagram', expected: 'sequence' },
{ text: 'mindmap', expected: 'mindmap' },
{ text: 'timeline', expected: 'timeline' },
{ text: 'gitGraph', expected: 'gitGraph' },
{ text: 'stateDiagram', expected: 'state' },
{ text: 'stateDiagram-v2', expected: 'stateDiagram' },
])(
'should $text be detected as $expected',
({ text, expected }: { text: string; expected: string }) => {
expect(detectType(text)).toBe(expected);
}
);
it('should detect proper flowchart type based on config', () => {
// graph & dagre-d3 => flowchart
expect(detectType('graph TD; A-->B')).toBe('flowchart');
// graph & dagre-d3 => flowchart
expect(detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })).toBe(
'flowchart'
);
// flowchart & dagre-d3 => error
expect(() =>
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })
).toThrowErrorMatchingInlineSnapshot(
'"No diagram type detected matching given configuration for text: flowchart TD; A-->B"'
);
// graph & dagre-wrapper => flowchart-v2
expect(
detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
).toBe('flowchart-v2');
// flowchart ==> flowchart-v2
expect(detectType('flowchart TD; A-->B')).toBe('flowchart-v2');
// flowchart && dagre-wrapper ==> flowchart-v2
expect(
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
).toBe('flowchart-v2');
// flowchart && elk ==> flowchart-elk
expect(detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'elk' } })).toBe(
'flowchart-elk'
);
});
it('should not detect flowchart if pie contains flowchart', () => {
expect(
detectType(`pie title: "flowchart"
flowchart: 1 "pie" pie: 2 "pie"`)
).toBe('pie');
});
});
});

View File

@@ -13,7 +13,7 @@ import classDiagramV2 from '../diagrams/class/classDetector-V2';
import state from '../diagrams/state/stateDetector';
import stateV2 from '../diagrams/state/stateDetector-V2';
import journey from '../diagrams/user-journey/journeyDetector';
import errorDiagram from '../diagrams/error/errorDiagram';
import error from '../diagrams/error/errorDetector';
import flowchartElk from '../diagrams/flowchart/elk/detector';
import timeline from '../diagrams/timeline/detector';
import mindmap from '../diagrams/mindmap/detector';
@@ -28,9 +28,6 @@ export const addDiagrams = () => {
// This is added here to avoid race-conditions.
// We could optimize the loading logic somehow.
hasLoadedDiagrams = true;
registerDiagram('error', errorDiagram, (text) => {
return text.toLowerCase().trim() === 'error';
});
registerDiagram(
'---',
// --- diagram type may appear if YAML front-matter is not parsed correctly
@@ -48,7 +45,7 @@ export const addDiagrams = () => {
throw new Error(
'Diagrams beginning with --- are not valid. ' +
'If you were trying to use a YAML front-matter, please ensure that ' +
"you've correctly opened and closed the YAML front-matter with un-indented `---` blocks"
"you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
);
},
},
@@ -58,25 +55,25 @@ export const addDiagrams = () => {
return text.toLowerCase().trimStart().startsWith('---');
}
);
// Ordering of detectors is important. The first one to return true will be used.
registerLazyLoadedDiagrams(
error,
c4,
classDiagramV2,
classDiagram,
classDiagramV2,
er,
gantt,
info,
pie,
requirement,
sequence,
flowchartElk,
flowchartV2,
flowchart,
flowchartV2,
flowchartElk,
mindmap,
timeline,
git,
stateV2,
state,
stateV2,
journey
);
};

View File

@@ -3,7 +3,6 @@ import { getDiagram, registerDiagram } from './diagramAPI';
import { addDiagrams } from './diagram-orchestration';
import { DiagramDetector } from './types';
import { getDiagramFromText } from '../Diagram';
import { it, describe, expect, beforeAll } from 'vitest';
addDiagrams();
beforeAll(async () => {
@@ -16,17 +15,13 @@ describe('DiagramAPI', () => {
});
it('should throw error if diagram is not defined', () => {
expect(() => getDiagram('loki')).toThrowErrorMatchingInlineSnapshot(
'"Diagram loki not found."'
);
expect(() => getDiagram('loki')).toThrow();
});
it('should handle diagram registrations', () => {
expect(() => getDiagram('loki')).toThrowErrorMatchingInlineSnapshot(
'"Diagram loki not found."'
);
expect(() => detectType('loki diagram')).toThrowErrorMatchingInlineSnapshot(
'"No diagram type detected matching given configuration for text: loki diagram"'
expect(() => getDiagram('loki')).toThrow();
expect(() => detectType('loki diagram')).toThrow(
'No diagram type detected for text: loki diagram'
);
const detector: DiagramDetector = (str: string) => {
return str.match('loki') !== null;

View File

@@ -11,8 +11,6 @@ export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
type FrontMatterMetadata = {
title?: string;
// Allows custom display modes. Currently used for compact mode in gantt charts.
displayMode?: string;
};
/**
@@ -35,10 +33,6 @@ export function extractFrontMatter(text: string, db: DiagramDb): string {
db.setDiagramTitle?.(parsed.title);
}
if (parsed?.displayMode) {
db.setDisplayMode?.(parsed.displayMode);
}
return text.slice(matches[0].length);
} else {
return text;

View File

@@ -16,7 +16,6 @@ export interface InjectUtils {
export interface DiagramDb {
clear?: () => void;
setDiagramTitle?: (title: string) => void;
setDisplayMode?: (title: string) => void;
getAccTitle?: () => string;
getAccDescription?: () => string;
bindFunctions?: (element: Element) => void;

View File

@@ -61,8 +61,8 @@ Expecting 'TXT', got 'NEWLINE'"
});
test('should throw the right error for unregistered diagrams', async () => {
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot(
'"No diagram type detected matching given configuration for text: thor TD; A-->B"'
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowError(
'No diagram type detected for text: thor TD; A-->B'
);
});
});

View File

@@ -1,5 +1,4 @@
// @ts-expect-error - d3 types issue
import { select, Selection } from 'd3';
import { select } from 'd3';
import { log } from '../../logger';
import * as configApi from '../../config';
import common from '../common/common';
@@ -14,54 +13,44 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../../commonDb';
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes';
const MERMAID_DOM_ID_PREFIX = 'classId-';
const MERMAID_DOM_ID_PREFIX = 'classid-';
let relations: ClassRelation[] = [];
let classes: ClassMap = {};
let notes: ClassNote[] = [];
let relations = [];
let classes = {};
let notes = [];
let classCounter = 0;
let functions: any[] = [];
let funs = [];
const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig());
const sanitizeText = (txt) => common.sanitizeText(txt, configApi.getConfig());
export const parseDirective = function (statement: string, context: string, type: string) {
// @ts-ignore Don't wanna mess it up
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const splitClassNameAndType = function (id: string) {
const splitClassNameAndType = function (id) {
let genericType = '';
let className = id;
if (id.indexOf('~') > 0) {
const split = id.split('~');
className = sanitizeText(split[0]);
genericType = sanitizeText(split[1]);
let split = id.split('~');
className = split[0];
genericType = common.sanitizeText(split[1], configApi.getConfig());
}
return { className: className, type: genericType };
};
export const setClassLabel = function (id: string, label: string) {
if (label) {
label = sanitizeText(label);
}
const { className } = splitClassNameAndType(id);
classes[className].label = label;
};
/**
* Function called by parser when a node definition has been found.
*
* @param id - Id of the class to add
* @param id
* @public
*/
export const addClass = function (id: string) {
const classId = splitClassNameAndType(id);
export const addClass = function (id) {
let classId = splitClassNameAndType(id);
// Only add class if not exists
if (classes[classId.className] !== undefined) {
return;
@@ -70,13 +59,12 @@ export const addClass = function (id: string) {
classes[classId.className] = {
id: classId.className,
type: classId.type,
label: classId.className,
cssClasses: [],
methods: [],
members: [],
annotations: [],
domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter,
} as ClassNode;
};
classCounter++;
};
@@ -84,33 +72,35 @@ export const addClass = function (id: string) {
/**
* Function to lookup domId from id in the graph definition.
*
* @param id - class ID to lookup
* @param id
* @public
*/
export const lookUpDomId = function (id: string): string {
if (id in classes) {
return classes[id].domId;
export const lookUpDomId = function (id) {
const classKeys = Object.keys(classes);
for (const classKey of classKeys) {
if (classes[classKey].id === id) {
return classes[classKey].domId;
}
}
throw new Error('Class not found: ' + id);
};
export const clear = function () {
relations = [];
classes = {};
notes = [];
functions = [];
functions.push(setupToolTips);
funs = [];
funs.push(setupToolTips);
commonClear();
};
export const getClass = function (id: string) {
export const getClass = function (id) {
return classes[id];
};
export const getClasses = function () {
return classes;
};
export const getRelations = function (): ClassRelation[] {
export const getRelations = function () {
return relations;
};
@@ -118,7 +108,7 @@ export const getNotes = function () {
return notes;
};
export const addRelation = function (relation: ClassRelation) {
export const addRelation = function (relation) {
log.debug('Adding relation: ' + JSON.stringify(relation));
addClass(relation.id1);
addClass(relation.id2);
@@ -143,11 +133,11 @@ export const addRelation = function (relation: ClassRelation) {
* Adds an annotation to the specified class Annotations mark special properties of the given type
* (like 'interface' or 'service')
*
* @param className - The class name
* @param annotation - The name of the annotation without any brackets
* @param className The class name
* @param annotation The name of the annotation without any brackets
* @public
*/
export const addAnnotation = function (className: string, annotation: string) {
export const addAnnotation = function (className, annotation) {
const validatedClassName = splitClassNameAndType(className).className;
classes[validatedClassName].annotations.push(annotation);
};
@@ -155,13 +145,13 @@ export const addAnnotation = function (className: string, annotation: string) {
/**
* Adds a member to the specified class
*
* @param className - The class name
* @param member - The full name of the member. If the member is enclosed in `<<brackets>>` it is
* @param className The class name
* @param member The full name of the member. If the member is enclosed in <<brackets>> it is
* treated as an annotation If the member is ending with a closing bracket ) it is treated as a
* method Otherwise the member will be treated as a normal property
* @public
*/
export const addMember = function (className: string, member: string) {
export const addMember = function (className, member) {
const validatedClassName = splitClassNameAndType(className).className;
const theClass = classes[validatedClassName];
@@ -171,6 +161,7 @@ export const addMember = function (className: string, member: string) {
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
// Remove leading and trailing brackets
// theClass.annotations.push(memberString.substring(2, memberString.length - 2));
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
} else if (memberString.indexOf(')') > 0) {
theClass.methods.push(sanitizeText(memberString));
@@ -180,14 +171,14 @@ export const addMember = function (className: string, member: string) {
}
};
export const addMembers = function (className: string, members: string[]) {
export const addMembers = function (className, members) {
if (Array.isArray(members)) {
members.reverse();
members.forEach((member) => addMember(className, member));
}
};
export const addNote = function (text: string, className: string) {
export const addNote = function (text, className) {
const note = {
id: `note${notes.length}`,
class: className,
@@ -196,20 +187,21 @@ export const addNote = function (text: string, className: string) {
notes.push(note);
};
export const cleanupLabel = function (label: string) {
if (label.startsWith(':')) {
label = label.substring(1);
export const cleanupLabel = function (label) {
if (label.substring(0, 1) === ':') {
return common.sanitizeText(label.substr(1).trim(), configApi.getConfig());
} else {
return sanitizeText(label.trim());
}
return sanitizeText(label.trim());
};
/**
* Called by parser when a special node is found, e.g. a clickable element.
*
* @param ids - Comma separated list of ids
* @param className - Class to add
* @param ids Comma separated list of ids
* @param className Class to add
*/
export const setCssClass = function (ids: string, className: string) {
export const setCssClass = function (ids, className) {
ids.split(',').forEach(function (_id) {
let id = _id;
if (_id[0].match(/\d/)) {
@@ -224,27 +216,28 @@ export const setCssClass = function (ids: string, className: string) {
/**
* Called by parser when a tooltip is found, e.g. a clickable element.
*
* @param ids - Comma separated list of ids
* @param tooltip - Tooltip to add
* @param ids Comma separated list of ids
* @param tooltip Tooltip to add
*/
const setTooltip = function (ids: string, tooltip?: string) {
const setTooltip = function (ids, tooltip) {
const config = configApi.getConfig();
ids.split(',').forEach(function (id) {
if (tooltip !== undefined) {
classes[id].tooltip = sanitizeText(tooltip);
classes[id].tooltip = common.sanitizeText(tooltip, config);
}
});
};
export const getTooltip = function (id: string) {
export const getTooltip = function (id) {
return classes[id].tooltip;
};
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
*
* @param ids - Comma separated list of ids
* @param linkStr - URL to create a link for
* @param target - Target of the link, _blank by default as originally defined in the svgDraw.js file
* @param ids Comma separated list of ids
* @param linkStr URL to create a link for
* @param target Target of the link, _blank by default as originally defined in the svgDraw.js file
*/
export const setLink = function (ids: string, linkStr: string, target: string) {
export const setLink = function (ids, linkStr, target) {
const config = configApi.getConfig();
ids.split(',').forEach(function (_id) {
let id = _id;
@@ -268,11 +261,11 @@ export const setLink = function (ids: string, linkStr: string, target: string) {
/**
* Called by parser when a click definition is found. Registers an event handler.
*
* @param ids - Comma separated list of ids
* @param functionName - Function to be called on click
* @param functionArgs - Function args the function should be called with
* @param ids Comma separated list of ids
* @param functionName Function to be called on click
* @param functionArgs Function args the function should be called with
*/
export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) {
export const setClickEvent = function (ids, functionName, functionArgs) {
ids.split(',').forEach(function (id) {
setClickFunc(id, functionName, functionArgs);
classes[id].haveCallback = true;
@@ -280,19 +273,19 @@ export const setClickEvent = function (ids: string, functionName: string, functi
setCssClass(ids, 'clickable');
};
const setClickFunc = function (domId: string, functionName: string, functionArgs: string) {
const setClickFunc = function (domId, functionName, functionArgs) {
const config = configApi.getConfig();
let id = domId;
let elemId = lookUpDomId(id);
if (config.securityLevel !== 'loose') {
return;
}
if (functionName === undefined) {
return;
}
const id = domId;
if (classes[id] !== undefined) {
const elemId = lookUpDomId(id);
let argList: string[] = [];
let argList = [];
if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
@@ -312,7 +305,7 @@ const setClickFunc = function (domId: string, functionName: string, functionArgs
argList.push(elemId);
}
functions.push(function () {
funs.push(function () {
const elem = document.querySelector(`[id="${elemId}"]`);
if (elem !== null) {
elem.addEventListener(
@@ -327,8 +320,8 @@ const setClickFunc = function (domId: string, functionName: string, functionArgs
}
};
export const bindFunctions = function (element: Element) {
functions.forEach(function (fun) {
export const bindFunctions = function (element) {
funs.forEach(function (fun) {
fun(element);
});
};
@@ -346,10 +339,8 @@ export const relationType = {
LOLLIPOP: 4,
};
const setupToolTips = function (element: Element) {
let tooltipElem: Selection<HTMLDivElement, unknown, HTMLElement, unknown> =
select('.mermaidTooltip');
// @ts-ignore - _groups is a dynamic property
const setupToolTips = function (element) {
let tooltipElem = select('.mermaidTooltip');
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0);
}
@@ -359,14 +350,12 @@ const setupToolTips = function (element: Element) {
const nodes = svg.selectAll('g.node');
nodes
.on('mouseover', function () {
// @ts-expect-error - select is not part of the d3 type definition
const el = select(this);
const title = el.attr('title');
// Don't try to draw a tooltip if no data is provided
// Dont try to draw a tooltip if no data is provided
if (title === null) {
return;
}
// @ts-ignore - getBoundingClientRect is not part of the d3 type definition
const rect = this.getBoundingClientRect();
tooltipElem.transition().duration(200).style('opacity', '.9');
@@ -379,16 +368,15 @@ const setupToolTips = function (element: Element) {
})
.on('mouseout', function () {
tooltipElem.transition().duration(500).style('opacity', 0);
// @ts-expect-error - select is not part of the d3 type definition
const el = select(this);
el.classed('hover', false);
});
};
functions.push(setupToolTips);
funs.push(setupToolTips);
let direction = 'TB';
const getDirection = () => direction;
const setDirection = (dir: string) => {
const setDirection = (dir) => {
direction = dir;
};
@@ -424,5 +412,4 @@ export default {
lookUpDomId,
setDiagramTitle,
getDiagramTitle,
setClassLabel,
};

View File

@@ -1,11 +1,10 @@
// @ts-expect-error Jison doesn't export types
import { parser } from './parser/classDiagram';
import classDb from './classDb';
import { vi, describe, it, expect } from 'vitest';
import { vi } from 'vitest';
const spyOn = vi.spyOn;
describe('class diagram, ', function () {
describe('when parsing a class diagram', function () {
describe('when parsing an info graph it', function () {
beforeEach(function () {
parser.yy = classDb;
});
@@ -190,37 +189,6 @@ describe('class diagram, ', function () {
parser.parse(str);
});
it('should handle cssClass shorthand with members', () => {
parser.parse(`classDiagram-v2
class Class10:::exClass2 {
int[] id
List~int~ ids
test(List~int~ ids) List~bool~
testArray() bool[]
}`);
expect(classDb.getClass('Class10')).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [
"exClass2",
],
"domId": "classId-Class10-27",
"id": "Class10",
"label": "Class10",
"members": [
"int[] id",
"List~int~ ids",
],
"methods": [
"test(List~int~ ids) List~bool~",
"testArray() bool[]",
],
"type": "",
}
`);
});
it('should handle method statements', function () {
const str =
'classDiagram\n' +
@@ -573,7 +541,7 @@ foo()
});
});
describe('when fetching data from a classDiagram it', function () {
describe('when fetching data from a classDiagram graph it', function () {
beforeEach(function () {
parser.yy = classDb;
parser.yy.clear();
@@ -978,191 +946,4 @@ foo()
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
});
});
describe('when parsing classDiagram with text labels', () => {
beforeEach(function () {
parser.yy = classDb;
parser.yy.clear();
});
it('should parse a class with a text label', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"]
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('C2');
});
it('should parse two classes with text labels', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"]
class C2["Class 2 with chars @?"]
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Class 2 with chars @?');
});
it('should parse a class with a text label and members', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"] {
+member1
}
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('C2');
});
it('should parse a class with a text label, members and annotation', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"] {
<<interface>>
+member1
}
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('C2');
});
it('should parse a class with text label and css class shorthand', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"]:::styleClass {
+member1
}
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass');
});
it('should parse a class with text label and css class', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"] {
+member1
}
C1 --> C2
cssClass "C1" styleClass
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass');
});
it('should parse two classes with text labels and css classes', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"] {
+member1
}
class C2["Long long long long long long long long long long label"]
C1 --> C2
cssClass "C1,C2" styleClass
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Long long long long long long long long long long label');
expect(c2.cssClasses.length).toBe(1);
expect(c2.cssClasses[0]).toBe('styleClass');
});
it('should parse two classes with text labels and css class shorthands', () => {
parser.parse(`classDiagram
class C1["Class 1 with text label"]:::styleClass1 {
+member1
}
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
C1 --> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
expect(c2.cssClasses.length).toBe(1);
expect(c2.cssClasses[0]).toBe('styleClass2');
});
it('should parse multiple classes with same text labels', () => {
parser.parse(`classDiagram
class C1["Class with text label"]
class C2["Class with text label"]
class C3["Class with text label"]
C1 --> C2
C3 ..> C2
`);
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class with text label');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Class with text label');
const c3 = classDb.getClass('C3');
expect(c3.label).toBe('Class with text label');
});
it('should parse classes with different text labels', () => {
parser.parse(`classDiagram
class C1["OneWord"]
class C2["With, Comma"]
class C3["With (Brackets)"]
class C4["With [Brackets]"]
class C5["With {Brackets}"]
class C6[" "]
class C7["With 1 number"]
class C8["With . period..."]
class C9["With - dash"]
class C10["With _ underscore"]
class C11["With ' single quote"]
class C12["With ~!@#$%^&*()_+=-/?"]
class C13["With Città foreign language"]
`);
expect(classDb.getClass('C1').label).toBe('OneWord');
expect(classDb.getClass('C2').label).toBe('With, Comma');
expect(classDb.getClass('C3').label).toBe('With (Brackets)');
expect(classDb.getClass('C4').label).toBe('With [Brackets]');
expect(classDb.getClass('C5').label).toBe('With {Brackets}');
expect(classDb.getClass('C6').label).toBe(' ');
expect(classDb.getClass('C7').label).toBe('With 1 number');
expect(classDb.getClass('C8').label).toBe('With . period...');
expect(classDb.getClass('C9').label).toBe('With - dash');
expect(classDb.getClass('C10').label).toBe('With _ underscore');
expect(classDb.getClass('C11').label).toBe("With ' single quote");
expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?');
expect(classDb.getClass('C13').label).toBe('With Città foreign language');
});
});
});

View File

@@ -0,0 +1,499 @@
import { select } from 'd3';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
import utils from '../../utils';
import { curveLinear } from 'd3';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import common from '../common/common';
const sanitizeText = (txt) => common.sanitizeText(txt, getConfig());
let conf = {
dividerMargin: 10,
padding: 5,
textHeight: 10,
};
/**
* Function that adds the vertices found during parsing to the graph to be rendered.
*
* @param {Object<
* string,
* { cssClasses: string[]; text: string; id: string; type: string; domId: string }
* >} classes
* Object containing the vertices.
* @param {SVGGElement} g The graph that is to be drawn.
* @param _id
* @param diagObj
*/
export const addClasses = function (classes, g, _id, diagObj) {
// const svg = select(`[id="${svgId}"]`);
const keys = Object.keys(classes);
log.info('keys:', keys);
log.info(classes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
const vertex = classes[id];
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let cssClassStr = '';
if (vertex.cssClasses.length > 0) {
cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' ');
}
// if (vertex.classes.length > 0) {
// classStr = vertex.classes.join(' ');
// }
const styles = { labelStyle: '' }; //getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
// We create a SVG label, either by delegating to addHtmlLabel or manually
// let vertexNode;
// if (evaluate(getConfig().flowchart.htmlLabels)) {
// const node = {
// label: vertexText.replace(
// eslint-disable-next-line @cspell/spellchecker
// /fa[lrsb]?:fa-[\w-]+/g,
// s => `<i class='${s.replace(':', ' ')}'></i>`
// )
// };
// vertexNode = addHtmlLabel(svg, node).node();
// vertexNode.parentNode.removeChild(vertexNode);
// } else {
// const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
// svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
// const rows = vertexText.split(common.lineBreakRegex);
// for (let j = 0; j < rows.length; j++) {
// const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
// tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
// tspan.setAttribute('dy', '1em');
// tspan.setAttribute('x', '1');
// tspan.textContent = rows[j];
// svgLabel.appendChild(tspan);
// }
// vertexNode = svgLabel;
// }
let radious = 0;
let _shape = '';
// Set the shape based parameters
switch (vertex.type) {
case 'class':
_shape = 'class_box';
break;
default:
_shape = 'class_box';
}
// Add the node
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: sanitizeText(vertexText),
classData: vertex,
rx: radious,
ry: radious,
class: cssClassStr,
style: styles.style,
id: vertex.id,
domId: vertex.domId,
tooltip: diagObj.db.getTooltip(vertex.id) || '',
haveCallback: vertex.haveCallback,
link: vertex.link,
width: vertex.type === 'group' ? 500 : undefined,
type: vertex.type,
padding: getConfig().flowchart.padding,
});
log.info('setNode', {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
class: cssClassStr,
style: styles.style,
id: vertex.id,
width: vertex.type === 'group' ? 500 : undefined,
type: vertex.type,
padding: getConfig().flowchart.padding,
});
});
};
/**
* Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
*
* @param {{text: string; class: string; placement: number}[]} notes
* Object containing the additional vertices (notes).
* @param {SVGGElement} g The graph that is to be drawn.
* @param {number} startEdgeId starting index for note edge
* @param classes
*/
export const addNotes = function (notes, g, startEdgeId, classes) {
log.info(notes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
notes.forEach(function (note, i) {
const vertex = note;
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let cssNoteStr = '';
const styles = { labelStyle: '', style: '' };
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text;
let radious = 0;
let _shape = 'note';
// Add the node
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: sanitizeText(vertexText),
noteData: vertex,
rx: radious,
ry: radious,
class: cssNoteStr,
style: styles.style,
id: vertex.id,
domId: vertex.id,
tooltip: '',
type: 'note',
padding: getConfig().flowchart.padding,
});
log.info('setNode', {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
style: styles.style,
id: vertex.id,
type: 'note',
padding: getConfig().flowchart.padding,
});
if (!vertex.class || !(vertex.class in classes)) {
return;
}
const edgeId = startEdgeId + i;
const edgeData = {};
//Set relationship style and line type
edgeData.classes = 'relation';
edgeData.pattern = 'dotted';
edgeData.id = `edgeNote${edgeId}`;
// Set link type for rendering
edgeData.arrowhead = 'none';
log.info(`Note edge: ${JSON.stringify(edgeData)}, ${JSON.stringify(vertex)}`);
//Set edge extra labels
edgeData.startLabelRight = '';
edgeData.endLabelLeft = '';
//Set relation arrow types
edgeData.arrowTypeStart = 'none';
edgeData.arrowTypeEnd = 'none';
let style = 'fill:none';
let labelStyle = '';
edgeData.style = style;
edgeData.labelStyle = labelStyle;
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
// Add the edge to the graph
g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
});
};
/**
* Add edges to graph based on parsed graph definition
*
* @param relations
* @param {object} g The graph object
*/
export const addRelations = function (relations, g) {
const conf = getConfig().flowchart;
let cnt = 0;
let defaultStyle;
let defaultLabelStyle;
// if (typeof relations.defaultStyle !== 'undefined') {
// const defaultStyles = getStylesFromArray(relations.defaultStyle);
// defaultStyle = defaultStyles.style;
// defaultLabelStyle = defaultStyles.labelStyle;
// }
relations.forEach(function (edge) {
cnt++;
const edgeData = {};
//Set relationship style and line type
edgeData.classes = 'relation';
edgeData.pattern = edge.relation.lineType == 1 ? 'dashed' : 'solid';
edgeData.id = 'id' + cnt;
// Set link type for rendering
if (edge.type === 'arrow_open') {
edgeData.arrowhead = 'none';
} else {
edgeData.arrowhead = 'normal';
}
log.info(edgeData, edge);
//Set edge extra labels
//edgeData.startLabelLeft = edge.relationTitle1;
edgeData.startLabelRight = edge.relationTitle1 === 'none' ? '' : edge.relationTitle1;
edgeData.endLabelLeft = edge.relationTitle2 === 'none' ? '' : edge.relationTitle2;
//edgeData.endLabelRight = edge.relationTitle2;
//Set relation arrow types
edgeData.arrowTypeStart = getArrowMarker(edge.relation.type1);
edgeData.arrowTypeEnd = getArrowMarker(edge.relation.type2);
let style = '';
let labelStyle = '';
if (edge.style !== undefined) {
const styles = getStylesFromArray(edge.style);
style = styles.style;
labelStyle = styles.labelStyle;
} else {
style = 'fill:none';
if (defaultStyle !== undefined) {
style = defaultStyle;
}
if (defaultLabelStyle !== undefined) {
labelStyle = defaultLabelStyle;
}
}
edgeData.style = style;
edgeData.labelStyle = labelStyle;
if (edge.interpolate !== undefined) {
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
} else if (relations.defaultInterpolate !== undefined) {
edgeData.curve = interpolateToCurve(relations.defaultInterpolate, curveLinear);
} else {
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
}
edge.text = edge.title;
if (edge.text === undefined) {
if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
if (getConfig().flowchart.htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else {
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
}
}
// Add the edge to the graph
g.setEdge(edge.id1, edge.id2, edgeData, cnt);
});
};
/**
* Merges the value of `conf` with the passed `cnf`
*
* @param {object} cnf Config to merge
*/
export const setConf = function (cnf) {
const keys = Object.keys(cnf);
keys.forEach(function (key) {
conf[key] = cnf[key];
});
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
*
* @param {string} text
* @param {string} id
* @param _version
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {
log.info('Drawing class - ', id);
const conf = getConfig().flowchart;
const securityLevel = getConfig().securityLevel;
log.info('config:', conf);
const nodeSpacing = conf.nodeSpacing || 50;
const rankSpacing = conf.rankSpacing || 50;
// Create the input mermaid.graph
const g = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: diagObj.db.getDirection(),
nodesep: nodeSpacing,
ranksep: rankSpacing,
marginx: 8,
marginy: 8,
})
.setDefaultEdgeLabel(function () {
return {};
});
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const classes = diagObj.db.getClasses();
const relations = diagObj.db.getRelations();
const notes = diagObj.db.getNotes();
log.info(relations);
addClasses(classes, g, id, diagObj);
addRelations(relations, g);
addNotes(notes, g, relations.length + 1, classes);
// Add custom shapes
// flowChartShapes.addToRenderV2(addShape);
// Set up an SVG group so that we can translate the final graph.
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const svg = root.select(`[id="${id}"]`);
// Run the renderer. This is what draws the final graph.
const element = root.select('#' + id + ' g');
render(
element,
g,
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'],
'classDiagram',
id
);
utils.insertTitle(svg, 'classTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
// Add label rects for non html labels
if (!conf.htmlLabels) {
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
for (const label of labels) {
// Get dimensions of label
const dim = label.getBBox();
const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('rx', 0);
rect.setAttribute('ry', 0);
rect.setAttribute('width', dim.width);
rect.setAttribute('height', dim.height);
// rect.setAttribute('style', 'fill:#e8e8e8;');
label.insertBefore(rect, label.firstChild);
}
}
// If node has a link, wrap it in an anchor SVG object.
// const keys = Object.keys(classes);
// keys.forEach(function(key) {
// const vertex = classes[key];
// if (vertex.link) {
// const node = select('#' + id + ' [id="' + key + '"]');
// if (node) {
// const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
// link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
// link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
// link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
// const linkNode = node.insert(function() {
// return link;
// }, ':first-child');
// const shape = node.select('.label-container');
// if (shape) {
// linkNode.append(function() {
// return shape.node();
// });
// }
// const label = node.select('.label');
// if (label) {
// linkNode.append(function() {
// return label.node();
// });
// }
// }
// }
// });
};
/**
* Gets the arrow marker for a type index
*
* @param {number} type The type to look for
* @returns {'aggregation' | 'extension' | 'composition' | 'dependency'} The arrow marker
*/
function getArrowMarker(type) {
let marker;
switch (type) {
case 0:
marker = 'aggregation';
break;
case 1:
marker = 'extension';
break;
case 2:
marker = 'composition';
break;
case 3:
marker = 'dependency';
break;
case 4:
marker = 'lollipop';
break;
default:
marker = 'none';
}
return marker;
}
export default {
setConf,
draw,
};

View File

@@ -1,368 +0,0 @@
// @ts-ignore d3 types are not available
import { select, curveLinear } from 'd3';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
import utils from '../../utils';
import { interpolateToCurve, getStylesFromArray } from '../../utils';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import common from '../common/common';
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes';
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
let conf = {
dividerMargin: 10,
padding: 5,
textHeight: 10,
curve: undefined,
};
/**
* Function that adds the vertices found during parsing to the graph to be rendered.
*
* @param classes - Object containing the vertices.
* @param g - The graph that is to be drawn.
* @param _id - id of the graph
* @param diagObj - The diagram object
*/
export const addClasses = function (
classes: ClassMap,
g: graphlib.Graph,
_id: string,
diagObj: any
) {
const keys = Object.keys(classes);
log.info('keys:', keys);
log.info(classes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
keys.forEach(function (id) {
const vertex = classes[id];
/**
* Variable for storing the classes for the vertex
*/
let cssClassStr = '';
if (vertex.cssClasses.length > 0) {
cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' ');
}
const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.label ?? vertex.id;
const radius = 0;
const shape = 'class_box';
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: shape,
labelText: sanitizeText(vertexText),
classData: vertex,
rx: radius,
ry: radius,
class: cssClassStr,
style: styles.style,
id: vertex.id,
domId: vertex.domId,
tooltip: diagObj.db.getTooltip(vertex.id) || '',
haveCallback: vertex.haveCallback,
link: vertex.link,
width: vertex.type === 'group' ? 500 : undefined,
type: vertex.type,
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
};
g.setNode(vertex.id, node);
log.info('setNode', node);
});
};
/**
* Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
*
* @param notes - Object containing the additional vertices (notes).
* @param g - The graph that is to be drawn.
* @param startEdgeId - starting index for note edge
* @param classes - Classes
*/
export const addNotes = function (
notes: ClassNote[],
g: graphlib.Graph,
startEdgeId: number,
classes: ClassMap
) {
log.info(notes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
notes.forEach(function (note, i) {
const vertex = note;
/**
* Variable for storing the classes for the vertex
*
*/
const cssNoteStr = '';
const styles = { labelStyle: '', style: '' };
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.text;
const radius = 0;
const shape = 'note';
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: shape,
labelText: sanitizeText(vertexText),
noteData: vertex,
rx: radius,
ry: radius,
class: cssNoteStr,
style: styles.style,
id: vertex.id,
domId: vertex.id,
tooltip: '',
type: 'note',
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
};
g.setNode(vertex.id, node);
log.info('setNode', node);
if (!vertex.class || !(vertex.class in classes)) {
return;
}
const edgeId = startEdgeId + i;
const edgeData: EdgeData = {
id: `edgeNote${edgeId}`,
//Set relationship style and line type
classes: 'relation',
pattern: 'dotted',
// Set link type for rendering
arrowhead: 'none',
//Set edge extra labels
startLabelRight: '',
endLabelLeft: '',
//Set relation arrow types
arrowTypeStart: 'none',
arrowTypeEnd: 'none',
style: 'fill:none',
labelStyle: '',
curve: interpolateToCurve(conf.curve, curveLinear),
};
// Add the edge to the graph
g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
});
};
/**
* Add edges to graph based on parsed graph definition
*
* @param relations -
* @param g - The graph object
*/
export const addRelations = function (relations: ClassRelation[], g: graphlib.Graph) {
const conf = getConfig().flowchart;
let cnt = 0;
relations.forEach(function (edge) {
cnt++;
const edgeData: EdgeData = {
//Set relationship style and line type
classes: 'relation',
pattern: edge.relation.lineType == 1 ? 'dashed' : 'solid',
id: 'id' + cnt,
// Set link type for rendering
arrowhead: edge.type === 'arrow_open' ? 'none' : 'normal',
//Set edge extra labels
startLabelRight: edge.relationTitle1 === 'none' ? '' : edge.relationTitle1,
endLabelLeft: edge.relationTitle2 === 'none' ? '' : edge.relationTitle2,
//Set relation arrow types
arrowTypeStart: getArrowMarker(edge.relation.type1),
arrowTypeEnd: getArrowMarker(edge.relation.type2),
style: 'fill:none',
labelStyle: '',
curve: interpolateToCurve(conf?.curve, curveLinear),
};
log.info(edgeData, edge);
if (edge.style !== undefined) {
const styles = getStylesFromArray(edge.style);
edgeData.style = styles.style;
edgeData.labelStyle = styles.labelStyle;
}
edge.text = edge.title;
if (edge.text === undefined) {
if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
if (getConfig().flowchart?.htmlLabels ?? getConfig().htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else {
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
}
}
// Add the edge to the graph
g.setEdge(edge.id1, edge.id2, edgeData, cnt);
});
};
/**
* Merges the value of `conf` with the passed `cnf`
*
* @param cnf - Config to merge
*/
export const setConf = function (cnf: any) {
conf = {
...conf,
...cnf,
};
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
*
* @param text -
* @param id -
* @param _version -
* @param diagObj -
*/
export const draw = function (text: string, id: string, _version: string, diagObj: any) {
log.info('Drawing class - ', id);
// TODO V10: Why flowchart? Might be a mistake when copying.
const conf = getConfig().flowchart ?? getConfig().class;
const securityLevel = getConfig().securityLevel;
log.info('config:', conf);
const nodeSpacing = conf?.nodeSpacing ?? 50;
const rankSpacing = conf?.rankSpacing ?? 50;
// Create the input mermaid.graph
const g: graphlib.Graph = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: diagObj.db.getDirection(),
nodesep: nodeSpacing,
ranksep: rankSpacing,
marginx: 8,
marginy: 8,
})
.setDefaultEdgeLabel(function () {
return {};
});
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const classes: ClassMap = diagObj.db.getClasses();
const relations: ClassRelation[] = diagObj.db.getRelations();
const notes: ClassNote[] = diagObj.db.getNotes();
log.info(relations);
addClasses(classes, g, id, diagObj);
addRelations(relations, g);
addNotes(notes, g, relations.length + 1, classes);
// Set up an SVG group so that we can translate the final graph.
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? // @ts-ignore Ignore type error for now
select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// @ts-ignore Ignore type error for now
const svg = root.select(`[id="${id}"]`);
// Run the renderer. This is what draws the final graph.
// @ts-ignore Ignore type error for now
const element = root.select('#' + id + ' g');
render(
element,
g,
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'],
'classDiagram',
id
);
utils.insertTitle(svg, 'classTitleText', conf?.titleTopMargin ?? 5, diagObj.db.getDiagramTitle());
setupGraphViewbox(g, svg, conf?.diagramPadding, conf?.useMaxWidth);
// Add label rects for non html labels
if (!conf?.htmlLabels) {
// @ts-ignore Ignore type error for now
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
for (const label of labels) {
// Get dimensions of label
const dim = label.getBBox();
const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('rx', 0);
rect.setAttribute('ry', 0);
rect.setAttribute('width', dim.width);
rect.setAttribute('height', dim.height);
label.insertBefore(rect, label.firstChild);
}
}
};
/**
* Gets the arrow marker for a type index
*
* @param type - The type to look for
* @returns The arrow marker
*/
function getArrowMarker(type: number) {
let marker;
switch (type) {
case 0:
marker = 'aggregation';
break;
case 1:
marker = 'extension';
break;
case 2:
marker = 'composition';
break;
case 3:
marker = 'dependency';
break;
case 4:
marker = 'lollipop';
break;
default:
marker = 'none';
}
return marker;
}
export default {
setConf,
draw,
};

View File

@@ -1,55 +0,0 @@
export interface ClassNode {
id: string;
type: string;
label: string;
cssClasses: string[];
methods: string[];
members: string[];
annotations: string[];
domId: string;
link?: string;
linkTarget?: string;
haveCallback?: boolean;
tooltip?: string;
}
export interface ClassNote {
id: string;
class: string;
text: string;
}
export interface EdgeData {
arrowheadStyle?: string;
labelpos?: string;
labelType?: string;
label?: string;
classes: string;
pattern: string;
id: string;
arrowhead: string;
startLabelRight: string;
endLabelLeft: string;
arrowTypeStart: string;
arrowTypeEnd: string;
style: string;
labelStyle: string;
curve: any;
}
export type ClassRelation = {
id1: string;
id2: string;
relationTitle1: string;
relationTitle2: string;
type: string;
title: string;
text: string;
style: string[];
relation: {
type1: number;
type2: number;
lineType: number;
};
};
export type ClassMap = Record<string, ClassNode>;

View File

@@ -119,8 +119,6 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
"=" return 'EQUALS';
\= return 'EQUALS';
\w+ return 'ALPHA';
"[" return 'SQS';
"]" return 'SQE';
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
[0-9]+ return 'NUM';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
@@ -251,10 +249,6 @@ statements
| statement NEWLINE statements
;
classLabel
: SQS STR SQE { $$=$2; }
;
className
: alphaNumToken { $$=$1; }
| classLiteralName { $$=$1; }
@@ -280,15 +274,10 @@ statement
;
classStatement
: classIdentifier
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
;
classIdentifier
: CLASS className {$$=$2; yy.addClass($2);}
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
: CLASS className {yy.addClass($2);}
| CLASS className STYLE_SEPARATOR alphaNumToken {yy.addClass($2);yy.setCssClass($2, $4);}
| CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addClass($2);yy.addMembers($2,$4);}
| CLASS className STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.addClass($2);yy.setCssClass($2, $4);yy.addMembers($2,$6);}
;
annotationStatement

View File

@@ -19,6 +19,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
[\s]+ return 'SPACE';

View File

@@ -0,0 +1,20 @@
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types';
const id = 'error';
const detector: DiagramDetector = (text) => {
return text.toLowerCase().trim() === 'error';
};
const loader = async () => {
const { diagram } = await import('./errorDiagram');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -19,5 +19,3 @@ export const diagram: DiagramDefinition = {
// no op
},
};
export default diagram;

View File

@@ -4,13 +4,15 @@ import { select } from 'd3';
import { log } from '../../logger';
import { getErrorMessage } from '../../utils';
let conf = {};
/**
* Merges the value of `conf` with the passed `cnf`
*
* @param cnf - Config to merge
*/
export const setConf = function () {
// no-op
export const setConf = function (cnf: any) {
conf = { ...conf, ...cnf };
};
/**
@@ -76,7 +78,7 @@ export const draw = (_text: string, id: string, mermaidVersion: string) => {
.attr('y', 250)
.attr('font-size', '150px')
.style('text-anchor', 'middle')
.text('Syntax error in text');
.text('Syntax error in graph');
g.append('text') // text label for the x axis
.attr('class', 'error-text')
.attr('x', 1250)

View File

@@ -3,17 +3,16 @@ import { insertNode } from '../../../dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils';
import { labelHelper } from '../../../dagre-wrapper/shapes/util';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { getConfig } from '../../../config';
import { log } from '../../../logger';
import { setupGraphViewbox } from '../../../setupGraphViewbox';
import common, { evaluate } from '../../common/common';
import { interpolateToCurve, getStylesFromArray } from '../../../utils';
import ELK from 'elkjs/lib/elk.bundled.js';
const elk = new ELK();
let portPos = {};
let elk;
const portPos = {};
const conf = {};
export const setConf = function (cnf) {
@@ -53,7 +52,7 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ');
}
classStr = classStr + ' flowchart-label';
const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
@@ -62,6 +61,40 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
const labelData = { width: 0, height: 0 };
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(svg, node).node();
const bbox = vertexNode.getBBox();
labelData.width = bbox.width;
labelData.height = bbox.height;
labelData.labelNode = vertexNode;
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(common.lineBreakRegex);
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
const bbox = vertexNode.getBBox();
labelData.width = bbox.width;
labelData.height = bbox.height;
labelData.labelNode = vertexNode;
}
const ports = [
{
@@ -153,13 +186,11 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
default:
_shape = 'rect';
}
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
@@ -178,33 +209,10 @@ export const addVertices = function (vert, svgId, root, doc, diagObj, parentLook
};
let boundingBox;
let nodeEl;
// Add the element to the DOM
if (node.type !== 'group') {
nodeEl = insertNode(nodes, node, vertex.dir);
boundingBox = nodeEl.node().getBBox();
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
// svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
// const rows = vertexText.split(common.lineBreakRegex);
// for (const row of rows) {
// const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
// tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
// tspan.setAttribute('dy', '1em');
// tspan.setAttribute('x', '1');
// tspan.textContent = row;
// svgLabel.appendChild(tspan);
// }
// vertexNode = svgLabel;
// const bbox = vertexNode.getBBox();
const { shapeSvg, bbox } = labelHelper(nodes, node, undefined, true);
labelData.width = bbox.width;
labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
labelData.height = bbox.height;
labelData.labelNode = shapeSvg.node();
node.labelData = labelData;
}
// const { shapeSvg, bbox } = labelHelper(svg, node, undefined, true);
const data = {
id: vertex.id,
@@ -363,10 +371,6 @@ const getEdgeStartEndPoint = (edge, dir) => {
let source = edge.start;
let target = edge.end;
// Save the original source and target
const sourceId = source;
const targetId = target;
const startNode = nodeDb[source];
const endNode = nodeDb[target];
@@ -383,7 +387,7 @@ const getEdgeStartEndPoint = (edge, dir) => {
}
// Add the edge to the graph
return { source, target, sourceId, targetId };
return { source, target };
};
/**
@@ -512,7 +516,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
@@ -526,17 +530,14 @@ export const addEdges = function (edges, diagObj, graph, svg) {
const labelEl = insertEdgeLabel(labelsEl, edgeData);
// calculate start and end points of the edge, note that the source and target
// can be modified for shapes that have ports
const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
// calculate start and end points of the edge
const { source, target } = getEdgeStartEndPoint(edge, dir);
log.debug('abc78 source and target', source, target);
// Add the edge to the graph
graph.edges.push({
id: 'e' + edge.start + edge.end,
sources: [source],
targets: [target],
sourceId,
targetId,
labelEl: labelEl,
labels: [
{
@@ -697,7 +698,7 @@ const calcOffset = function (src, dest, parentLookupDb) {
};
const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
const offset = calcOffset(edge.sourceId, edge.targetId, parentLookupDb);
const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb);
const src = edge.sections[0].startPoint;
const dest = edge.sections[0].endPoint;
@@ -764,10 +765,13 @@ const insertChildren = (nodeArray, parentLookupDb) => {
*/
export const draw = async function (text, id, _version, diagObj) {
if (!elk) {
const ELK = (await import('elkjs/lib/elk.bundled.js')).default;
elk = new ELK();
}
// Add temporary render element
diagObj.db.clear();
nodeDb = {};
portPos = {};
diagObj.db.setGen('gen-2');
// Parse the graph definition
diagObj.parser.parse(text);
@@ -838,17 +842,9 @@ export const draw = async function (text, id, _version, diagObj) {
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
diagObj.db.addVertex(
subG.id,
{ text: subG.title, type: subG.labelType },
'group',
undefined,
subG.classes,
subG.dir
);
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// debugger;
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
@@ -861,7 +857,7 @@ export const draw = async function (text, id, _version, diagObj) {
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph, svg);
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
@@ -888,8 +884,6 @@ export const draw = async function (text, id, _version, diagObj) {
},
width: node.labelData.width,
height: node.labelData.height,
// width: 100,
// height: 100,
},
];
delete node.x;
@@ -898,7 +892,6 @@ export const draw = async function (text, id, _version, diagObj) {
delete node.height;
}
});
insertChildren(graph.children, parentLookupDb);
log.info('after layout', JSON.stringify(graph, null, 2));
const g = await elk.layout(graph);
@@ -934,12 +927,9 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {
.attr('width', node.width)
.attr('height', node.height);
const label = subgraphEl.insert('g').attr('class', 'label');
const labelCentering = getConfig().flowchart.htmlLabels ? node.labelData.width / 2 : 0;
label.attr(
'transform',
`translate(${node.labels[0].x + relX + node.x + labelCentering}, ${
node.labels[0].y + relY + node.y + 3
})`
`translate(${node.labels[0].x + relX + node.x}, ${node.labels[0].y + relY + node.y})`
);
label.node().appendChild(node.labelData.labelNode);

View File

@@ -81,7 +81,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
.edgeLabel {
background-color: ${options.edgeLabelBackground};
rect {
opacity: 0.85;
opacity: 0.5;
background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground};
}
@@ -132,11 +132,6 @@ const getStyles = (options: FlowChartStyleOptions) =>
// fill:#ccc;
// // stroke:black;
// }
.flowchart-label text {
text-anchor: middle;
}
${genSections(options)}
`;

View File

@@ -59,14 +59,13 @@ export const lookUpDomId = function (id) {
*
* @param _id
* @param text
* @param textObj
* @param type
* @param style
* @param classes
* @param dir
* @param props
*/
export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) {
export const addVertex = function (_id, text, type, style, classes, dir, props = {}) {
let txt;
let id = _id;
if (id === undefined) {
@@ -81,17 +80,16 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
if (vertices[id] === undefined) {
vertices[id] = {
id: id,
labelType: 'text',
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter,
styles: [],
classes: [],
};
}
vertexCounter++;
if (textObj !== undefined) {
if (text !== undefined) {
config = configApi.getConfig();
txt = sanitizeText(textObj.text.trim());
vertices[id].labelType = textObj.type;
txt = sanitizeText(text.trim());
// strip quotes if string starts and ends with a quote
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
txt = txt.substring(1, txt.length - 1);
@@ -133,27 +131,24 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
* @param _end
* @param type
* @param linkText
* @param linkTextObj
*/
export const addSingleLink = function (_start, _end, type) {
export const addSingleLink = function (_start, _end, type, linkText) {
let start = _start;
let end = _end;
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
// log.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
log.info('abc78 Got edge...', edge);
const linkTextObj = type.text;
const edge = { start: start, end: end, type: undefined, text: '' };
linkText = type.text;
if (linkTextObj !== undefined) {
edge.text = sanitizeText(linkTextObj.text.trim());
if (linkText !== undefined) {
edge.text = sanitizeText(linkText.trim());
// strip quotes if string starts and ends with a quote
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
edge.text = edge.text.substring(1, edge.text.length - 1);
}
edge.labelType = linkTextObj.type;
}
if (type !== undefined) {
@@ -163,12 +158,11 @@ export const addSingleLink = function (_start, _end, type) {
}
edges.push(edge);
};
export const addLink = function (_start, _end, type) {
log.info('addLink (abc78)', _start, _end, type);
export const addLink = function (_start, _end, type, linktext) {
let i, j;
for (i = 0; i < _start.length; i++) {
for (j = 0; j < _end.length; j++) {
addSingleLink(_start[i], _end[j], type);
addSingleLink(_start[i], _end[j], type, linktext);
}
}
};
@@ -463,9 +457,10 @@ export const defaultStyle = function () {
* @param _title
*/
export const addSubGraph = function (_id, list, _title) {
let id = _id.text.trim();
let title = _title.text;
if (_id === _title && _title.text.match(/\s/)) {
// console.log('addSubGraph', _id, list, _title);
let id = _id.trim();
let title = _title;
if (_id === _title && _title.match(/\s/)) {
id = undefined;
}
/** @param a */
@@ -507,14 +502,7 @@ export const addSubGraph = function (_id, list, _title) {
title = title || '';
title = sanitizeText(title);
subCount = subCount + 1;
const subGraph = {
id: id,
nodes: nodeList,
title: title.trim(),
classes: [],
dir,
labelType: _title.type,
};
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [], dir };
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);

View File

@@ -4,15 +4,15 @@ import type { ExternalDiagramDefinition } from '../../diagram-api/types';
const id = 'flowchart-v2';
const detector: DiagramDetector = (txt, config) => {
if (
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
config?.flowchart?.defaultRenderer === 'elk'
) {
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
return false;
}
if (config?.flowchart?.defaultRenderer === 'elk') {
return false;
}
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
if (txt.match(/^\s*graph/) !== null && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
if (txt.match(/^\s*graph/) !== null) {
return true;
}
return txt.match(/^\s*flowchart/) !== null;

View File

@@ -5,10 +5,10 @@ const id = 'flowchart';
const detector: DiagramDetector = (txt, config) => {
// If we have conferred to only use new flow charts this function should always return false
// as in not signalling true for a legacy flowchart
if (
config?.flowchart?.defaultRenderer === 'dagre-wrapper' ||
config?.flowchart?.defaultRenderer === 'elk'
) {
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
return false;
}
if (config?.flowchart?.defaultRenderer === 'elk') {
return false;
}
return txt.match(/^\s*graph/) !== null;

View File

@@ -47,7 +47,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ');
}
classStr = classStr + ' flowchart-label';
const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
@@ -55,36 +55,31 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
log.info('vertex', vertex, vertex.labelType);
if (vertex.labelType === 'markdown') {
log.info('vertex', vertex, vertex.labelType);
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
} else {
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(svg, node).node();
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(common.lineBreakRegex);
const rows = vertexText.split(common.lineBreakRegex);
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
}
let radious = 0;
@@ -151,7 +146,6 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
@@ -171,7 +165,6 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
log.info('setNode', {
labelStyle: styles.labelStyle,
labelType: vertex.labelType,
shape: _shape,
labelText: vertexText,
rx: radious,
@@ -319,7 +312,7 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
@@ -412,14 +405,7 @@ export const draw = function (text, id, _version, diagObj) {
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
log.info('Subgraph - ', subG);
diagObj.db.addVertex(
subG.id,
{ text: subG.title, type: subG.labelType },
'group',
undefined,
subG.classes,
subG.dir
);
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// Fetch the vertices/nodes and edges/links from the parsed graph definition

View File

@@ -1,7 +1,6 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
import { cleanupComments } from '../../../diagram-api/comments';
setConfig({
securityLevel: 'strict',
@@ -14,7 +13,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));
const res = flow.parser.parse('graph TD;\n%% Comment\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -29,7 +28,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the start', function () {
const res = flow.parser.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));
const res = flow.parser.parse('%% Comment\ngraph TD;\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -44,7 +43,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n %% Comment at the end\n'));
const res = flow.parser.parse('graph TD;\n A-->B\n %% Comment at the end\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -59,7 +58,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end no trailing newline', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -74,7 +73,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end many trailing newlines', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment\n\n\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -89,7 +88,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle no trailing newlines', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B'));
const res = flow.parser.parse('graph TD;\n A-->B');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -104,7 +103,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle many trailing newlines', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n\n'));
const res = flow.parser.parse('graph TD;\n A-->B\n\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -119,7 +118,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle a comment with blank rows in-between', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -135,9 +134,7 @@ describe('[Comments] when parsing', () => {
it('should handle a comment with mermaid flowchart code in them', function () {
const res = flow.parser.parse(
cleanupComments(
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
)
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
);
const vert = flow.parser.yy.getVertices();

View File

@@ -1,64 +0,0 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe('parsing a flow chart with markdown strings', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});
it('mardown formatting in nodes and labels', function () {
const res = flow.parser.parse(`flowchart
A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in the hog"] -- "The rat in the mat" -->C;`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['A'].text).toBe('The cat in **the** hat');
expect(vert['A'].labelType).toBe('markdown');
expect(vert['B'].id).toBe('B');
expect(vert['B'].text).toBe('The dog in the hog');
expect(vert['B'].labelType).toBe('text');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow_point');
expect(edges[0].text).toBe('The *bat* in the chat');
expect(edges[0].labelType).toBe('markdown');
expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow_point');
expect(edges[1].text).toBe('The rat in the mat');
expect(edges[1].labelType).toBe('text');
});
it('mardown formatting in subgraphs', function () {
const res = flow.parser.parse(`flowchart LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
end
subgraph "\`**Two**\`"
c("\`The **cat**
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end`);
const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
expect(subgraph.title).toBe('One');
expect(subgraph.labelType).toBe('text');
const subgraph2 = subgraphs[1];
expect(subgraph2.nodes.length).toBe(2);
expect(subgraph2.title).toBe('**Two**');
expect(subgraph2.labelType).toBe('markdown');
});
});

View File

@@ -7,7 +7,6 @@
/* lexical grammar */
%lex
%x string
%x md_string
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -28,6 +27,8 @@
<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 */
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'; }
@@ -36,9 +37,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
// <acc_descr_multiline>.*[^\n]* { return "acc_descr_line"}
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
@@ -436,13 +434,11 @@ arrowText:
;
text: textToken
{ $$={text:$1, type: 'text'};}
{$$=$1;}
| text textToken
{ $$={text:$1.text+''+$2, type: $1.type};}
{$$=$1+''+$2;}
| STR
{ $$={text: $1, type: 'text'};}
| MD_STR
{ $$={text: $1, type: 'markdown'};}
{$$=$1;}
;

View File

@@ -1,7 +1,6 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
import { cleanupComments } from '../../../diagram-api/comments';
setConfig({
securityLevel: 'strict',
@@ -14,7 +13,7 @@ describe('parsing a flow chart', function () {
});
it('should handle a trailing whitespaces after statements', function () {
const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;'));
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();

View File

@@ -23,11 +23,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
.cluster-label text {
fill: ${options.titleColor};
}
.cluster-label span,p {
.cluster-label span {
color: ${options.titleColor};
}
.label text,span,p {
.label text,span {
fill: ${options.nodeTextColor || options.textColor};
color: ${options.nodeTextColor || options.textColor};
}
@@ -41,15 +41,6 @@ const getStyles = (options: FlowChartStyleOptions) =>
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.flowchart-label text {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
// text-anchor: middle;
// }
// .flowchart-label .text-inner-tspan {
// text-anchor: start;
// }
.node .label {
text-align: center;
@@ -92,7 +83,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
fill: ${options.titleColor};
}
.cluster span,p {
.cluster span {
color: ${options.titleColor};
}
/* .cluster div {

View File

@@ -1,8 +1,5 @@
import moment from 'moment-mini';
import { sanitizeUrl } from '@braintree/sanitize-url';
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';
import * as configApi from '../../config';
import utils from '../../utils';
@@ -18,10 +15,6 @@ import {
getDiagramTitle,
} from '../../commonDb';
dayjs.extend(dayjsIsoWeek);
dayjs.extend(dayjsCustomParseFormat);
dayjs.extend(dayjsAdvancedFormat);
let dateFormat = '';
let axisFormat = '';
let tickInterval = undefined;
@@ -32,7 +25,6 @@ let links = {};
let sections = [];
let tasks = [];
let currentSection = '';
let displayMode = '';
const tags = ['active', 'done', 'crit', 'milestone'];
let funs = [];
let inclusiveEndDates = false;
@@ -56,7 +48,6 @@ export const clear = function () {
rawTasks = [];
dateFormat = '';
axisFormat = '';
displayMode = '';
tickInterval = undefined;
todayMarker = '';
includes = [];
@@ -112,14 +103,6 @@ export const topAxisEnabled = function () {
return topAxis;
};
export const setDisplayMode = function (txt) {
displayMode = txt;
};
export const getDisplayMode = function () {
return displayMode;
};
export const getDateFormat = function () {
return dateFormat;
};
@@ -153,11 +136,11 @@ export const getSections = function () {
};
export const getTasks = function () {
let allItemsProcessed = compileTasks();
let allItemsPricessed = compileTasks();
const maxDepth = 10;
let iterationCount = 0;
while (!allItemsProcessed && iterationCount < maxDepth) {
allItemsProcessed = compileTasks();
while (!allItemsPricessed && iterationCount < maxDepth) {
allItemsPricessed = compileTasks();
iterationCount++;
}
@@ -179,58 +162,18 @@ export const isInvalidDate = function (date, dateFormat, excludes, includes) {
return excludes.includes(date.format(dateFormat.trim()));
};
/**
* TODO: fully document what this function does and what types it accepts
*
* @param {object} task - The task to check.
* @param {string | Date} task.startTime - Might be a `Date` or a `string`.
* TODO: is this always a Date?
* @param {string | Date} task.endTime - Might be a `Date` or a `string`.
* TODO: is this always a Date?
* @param {string} dateFormat - Dayjs date format string.
* @param {*} excludes
* @param {*} includes
*/
const checkTaskDates = function (task, dateFormat, excludes, includes) {
if (!excludes.length || task.manualEndTime) {
return;
}
let startTime;
if (task.startTime instanceof Date) {
startTime = dayjs(task.startTime);
} else {
startTime = dayjs(task.startTime, dateFormat, true);
}
startTime = startTime.add(1, 'd');
let originalEndTime;
if (task.endTime instanceof Date) {
originalEndTime = dayjs(task.endTime);
} else {
originalEndTime = dayjs(task.endTime, dateFormat, true);
}
const [fixedEndTime, renderEndTime] = fixTaskDates(
startTime,
originalEndTime,
dateFormat,
excludes,
includes
);
task.endTime = fixedEndTime.toDate();
let startTime = moment(task.startTime, dateFormat, true);
startTime.add(1, 'd');
let endTime = moment(task.endTime, dateFormat, true);
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes, includes);
task.endTime = endTime.toDate();
task.renderEndTime = renderEndTime;
};
/**
* TODO: what does this function do?
*
* @param {dayjs.Dayjs} startTime - The start time.
* @param {dayjs.Dayjs} endTime - The original end time (will return a different end time if it's invalid).
* @param {string} dateFormat - Dayjs date format string.
* @param {*} excludes
* @param {*} includes
* @returns {[endTime: dayjs.Dayjs, renderEndTime: Date | null]} The new `endTime`, and the end time to render.
* `renderEndTime` may be `null` if `startTime` is newer than `endTime`.
*/
const fixTaskDates = function (startTime, endTime, dateFormat, excludes, includes) {
let invalid = false;
let renderEndTime = null;
@@ -240,11 +183,11 @@ const fixTaskDates = function (startTime, endTime, dateFormat, excludes, include
}
invalid = isInvalidDate(startTime, dateFormat, excludes, includes);
if (invalid) {
endTime = endTime.add(1, 'd');
endTime.add(1, 'd');
}
startTime = startTime.add(1, 'd');
startTime.add(1, 'd');
}
return [endTime, renderEndTime];
return renderEndTime;
};
const getStartDate = function (prevTime, dateFormat, str) {
@@ -280,7 +223,7 @@ const getStartDate = function (prevTime, dateFormat, str) {
}
// Check for actual date set
let mDate = dayjs(str, dateFormat.trim(), true);
let mDate = moment(str, dateFormat.trim(), true);
if (mDate.isValid()) {
return mDate.toDate();
} else {
@@ -295,14 +238,11 @@ const getStartDate = function (prevTime, dateFormat, str) {
};
/**
* Parse a string into the args for `dayjs.add()`.
* Parse a string as a moment duration.
*
* The string have to be compound by a value and a shorthand duration unit. For example `5d`
* represents 5 days.
*
* Please be aware that 1 day may be 23 or 25 hours, if the user lives in an area
* that has daylight savings time (or even 23.5/24.5 hours in Lord Howe Island!)
*
* Shorthand unit supported are:
*
* - `y` for years
@@ -314,36 +254,33 @@ const getStartDate = function (prevTime, dateFormat, str) {
* - `ms` for milliseconds
*
* @param {string} str - A string representing the duration.
* @returns {[value: number, unit: dayjs.ManipulateType]} Arguments to pass to `dayjs.add()`
* @returns {moment.Duration} A moment duration, including an invalid moment for invalid input
* string.
*/
const parseDuration = function (str) {
const statement = /^(\d+(?:\.\d+)?)([Mdhmswy]|ms)$/.exec(str.trim());
if (statement !== null) {
return [Number.parseFloat(statement[1]), statement[2]];
return moment.duration(Number.parseFloat(statement[1]), statement[2]);
}
// NaN means an invalid duration
return [NaN, 'ms'];
return moment.duration.invalid();
};
const getEndDate = function (prevTime, dateFormat, str, inclusive = false) {
str = str.trim();
// Check for actual date
let mDate = dayjs(str, dateFormat.trim(), true);
let mDate = moment(str, dateFormat.trim(), true);
if (mDate.isValid()) {
if (inclusive) {
mDate = mDate.add(1, 'd');
mDate.add(1, 'd');
}
return mDate.toDate();
}
let endTime = dayjs(prevTime);
const [durationValue, durationUnit] = parseDuration(str);
if (!Number.isNaN(durationValue)) {
const newEndTime = endTime.add(durationValue, durationUnit);
if (newEndTime.isValid()) {
endTime = newEndTime;
}
const endTime = moment(prevTime);
const duration = parseDuration(str);
if (duration.isValid()) {
endTime.add(duration);
}
return endTime.toDate();
};
@@ -409,7 +346,7 @@ const compileData = function (prevTask, dataStr) {
if (endTimeData) {
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates);
task.manualEndTime = dayjs(endTimeData, 'YYYY-MM-DD', true).isValid();
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid();
checkTaskDates(task, dateFormat, excludes, includes);
}
@@ -559,7 +496,7 @@ const compileTasks = function () {
);
if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true;
rawTasks[pos].manualEndTime = dayjs(
rawTasks[pos].manualEndTime = moment(
rawTasks[pos].raw.endTime.data,
'YYYY-MM-DD',
true
@@ -729,8 +666,6 @@ export default {
getAccTitle,
setDiagramTitle,
getDiagramTitle,
setDisplayMode,
getDisplayMode,
setAccDescription,
getAccDescription,
addSection,

View File

@@ -1,5 +1,5 @@
// @ts-nocheck TODO: Fix TS
import dayjs from 'dayjs';
import moment from 'moment-mini';
import ganttDb from './ganttDb';
import { convert } from '../../tests/util';
@@ -9,7 +9,7 @@ describe('when using the ganttDb', function () {
});
describe('when using duration', function () {
it.each([{ str: '1d', expected: [1, 'd'] }])(
it.each([{ str: '1d', expected: moment.duration(1, 'd') }])(
'should %s resulting in $o duration',
({ str, expected }) => {
expect(ganttDb.parseDuration(str)).toEqual(expected);
@@ -19,11 +19,11 @@ describe('when using the ganttDb', function () {
it.each(
convert`
str | expected
${'1d'} | ${[1, 'd']}
${'2w'} | ${[2, 'w']}
${'1ms'} | ${[1, 'ms']}
${'0.1s'} | ${[0.1, 's']}
${'1f'} | ${[NaN, 'ms']}
${'1d'} | ${moment.duration(1, 'd')}
${'2w'} | ${moment.duration(2, 'w')}
${'1ms'} | ${moment.duration(1, 'ms')}
${'0.1s'} | ${moment.duration(100, 'ms')}
${'1f'} | ${moment.duration.invalid()}
`
)('should $str resulting in $expected duration', ({ str, expected }) => {
expect(ganttDb.parseDuration(str)).toEqual(expected);
@@ -34,7 +34,6 @@ describe('when using the ganttDb', function () {
beforeEach(function () {
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.enableInclusiveEndDates();
ganttDb.setDisplayMode('compact');
ganttDb.setTodayMarker('off');
ganttDb.setExcludes('weekends 2019-02-06,friday');
ganttDb.addSection('weekends skip test');
@@ -54,7 +53,6 @@ describe('when using the ganttDb', function () {
${'getExcludes'} | ${[]}
${'getSections'} | ${[]}
${'endDatesAreInclusive'} | ${false}
${'getDisplayMode'} | ${''}
`)('should clear $fn', ({ fn, expected }) => {
expect(ganttDb[fn]()).toEqual(expected);
});
@@ -173,44 +171,44 @@ describe('when using the ganttDb', function () {
const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(dayjs('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(dayjs('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[0].renderEndTime).toEqual(dayjs('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(dayjs('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(dayjs('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toEqual(dayjs('2019-02-06', 'YYYY-MM-DD').toDate());
expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate());
expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2');
expect(tasks[2].startTime).toEqual(dayjs('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(dayjs('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].renderEndTime).toEqual(dayjs('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].id).toEqual('id3');
expect(tasks[2].task).toEqual('test3');
expect(tasks[3].startTime).toEqual(dayjs('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(dayjs('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[3].renderEndTime).toBeNull(); // Fixed end
expect(tasks[3].id).toEqual('id4');
expect(tasks[3].task).toEqual('test4');
expect(tasks[4].startTime).toEqual(dayjs('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(dayjs('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].renderEndTime).toEqual(dayjs('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].id).toEqual('id5');
expect(tasks[4].task).toEqual('test5');
expect(tasks[5].startTime).toEqual(dayjs('2019-02-13', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(dayjs('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[5].renderEndTime).toEqual(dayjs('2019-02-15', 'YYYY-MM-DD').toDate());
expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate());
expect(tasks[5].id).toEqual('id6');
expect(tasks[5].task).toEqual('test6');
expect(tasks[6].startTime).toEqual(dayjs('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(dayjs('2019-02-19', 'YYYY-MM-DD').toDate());
expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate());
expect(tasks[6].id).toEqual('id7');
expect(tasks[6].task).toEqual('test7');
});
@@ -245,103 +243,109 @@ describe('when using the ganttDb', function () {
const tasks = ganttDb.getTasks();
// Section - A section
expect(tasks[0].startTime).toEqual(dayjs('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(dayjs('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[0].startTime).toEqual(moment('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[0].order).toEqual(0);
expect(tasks[0].id).toEqual('des1');
expect(tasks[0].task).toEqual('Completed task');
expect(tasks[1].startTime).toEqual(dayjs('2014-01-09', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(dayjs('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[1].startTime).toEqual(moment('2014-01-09', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[1].order).toEqual(1);
expect(tasks[1].id).toEqual('des2');
expect(tasks[1].task).toEqual('Active task');
expect(tasks[2].startTime).toEqual(dayjs('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(dayjs('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[2].startTime).toEqual(moment('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(moment('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[2].order).toEqual(2);
expect(tasks[2].id).toEqual('des3');
expect(tasks[2].task).toEqual('Future task');
expect(tasks[3].startTime).toEqual(dayjs('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(dayjs('2014-01-22', 'YYYY-MM-DD').toDate());
expect(tasks[3].startTime).toEqual(moment('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(moment('2014-01-22', 'YYYY-MM-DD').toDate());
expect(tasks[3].order).toEqual(3);
expect(tasks[3].id).toEqual('des4');
expect(tasks[3].task).toEqual('Future task2');
// Section - Critical tasks
expect(tasks[4].startTime).toEqual(dayjs('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(dayjs('2014-01-07', 'YYYY-MM-DD').toDate());
expect(tasks[4].startTime).toEqual(moment('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(moment('2014-01-07', 'YYYY-MM-DD').toDate());
expect(tasks[4].order).toEqual(4);
expect(tasks[4].id).toEqual('task1');
expect(tasks[4].task).toEqual('Completed task in the critical line');
expect(tasks[5].startTime).toEqual(dayjs('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(dayjs('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[5].startTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(moment('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[5].order).toEqual(5);
expect(tasks[5].id).toEqual('task2');
expect(tasks[5].task).toEqual('Implement parser and jison');
expect(tasks[6].startTime).toEqual(dayjs('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(dayjs('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[6].startTime).toEqual(moment('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[6].order).toEqual(6);
expect(tasks[6].id).toEqual('task3');
expect(tasks[6].task).toEqual('Create tests for parser');
expect(tasks[7].startTime).toEqual(dayjs('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[7].endTime).toEqual(dayjs('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[7].startTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[7].endTime).toEqual(moment('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[7].order).toEqual(7);
expect(tasks[7].id).toEqual('task4');
expect(tasks[7].task).toEqual('Future task in critical line');
expect(tasks[8].startTime).toEqual(dayjs('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[8].endTime).toEqual(dayjs('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[8].startTime).toEqual(moment('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[8].endTime).toEqual(moment('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[8].order).toEqual(8);
expect(tasks[8].id).toEqual('task5');
expect(tasks[8].task).toEqual('Create tests for renderer');
expect(tasks[9].startTime).toEqual(dayjs('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[9].endTime).toEqual(dayjs('2014-01-21', 'YYYY-MM-DD').toDate());
expect(tasks[9].startTime).toEqual(moment('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[9].endTime).toEqual(moment('2014-01-21', 'YYYY-MM-DD').toDate());
expect(tasks[9].order).toEqual(9);
expect(tasks[9].id).toEqual('task6');
expect(tasks[9].task).toEqual('Add to mermaid');
// Section - Documentation
expect(tasks[10].startTime).toEqual(dayjs('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[10].endTime).toEqual(dayjs('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[10].startTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[10].endTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[10].order).toEqual(10);
expect(tasks[10].id).toEqual('a1');
expect(tasks[10].task).toEqual('Describe gantt syntax');
expect(tasks[11].startTime).toEqual(dayjs('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[11].endTime).toEqual(dayjs('2014-01-11 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[11].startTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[11].endTime).toEqual(
moment('2014-01-11 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate()
);
expect(tasks[11].order).toEqual(11);
expect(tasks[11].id).toEqual('task7');
expect(tasks[11].task).toEqual('Add gantt diagram to demo page');
expect(tasks[12].startTime).toEqual(dayjs('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[12].endTime).toEqual(dayjs('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[12].startTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[12].endTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[12].order).toEqual(12);
expect(tasks[12].id).toEqual('doc1');
expect(tasks[12].task).toEqual('Add another diagram to demo page');
// Section - Last section
expect(tasks[13].startTime).toEqual(dayjs('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[13].endTime).toEqual(dayjs('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[13].startTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[13].endTime).toEqual(moment('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[13].order).toEqual(13);
expect(tasks[13].id).toEqual('task8');
expect(tasks[13].task).toEqual('Describe gantt syntax');
expect(tasks[14].startTime).toEqual(dayjs('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[14].endTime).toEqual(dayjs('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[14].startTime).toEqual(moment('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[14].endTime).toEqual(
moment('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate()
);
expect(tasks[14].order).toEqual(14);
expect(tasks[14].id).toEqual('task9');
expect(tasks[14].task).toEqual('Add gantt diagram to demo page');
expect(tasks[15].startTime).toEqual(
dayjs('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate()
moment('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate()
);
expect(tasks[15].endTime).toEqual(
moment('2014-01-18 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate()
);
expect(tasks[15].endTime).toEqual(dayjs('2014-01-18 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[15].order).toEqual(15);
expect(tasks[15].id).toEqual('task10');
expect(tasks[15].task).toEqual('Add another diagram to demo page');
@@ -354,53 +358,19 @@ describe('when using the ganttDb', function () {
ganttDb.addTask('test2', 'id2,after id1,20d');
const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(dayjs('2019-09-30', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(dayjs('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[0].startTime).toEqual(moment('2019-09-30', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(dayjs('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(dayjs('2019-10-31', 'YYYY-MM-DD').toDate());
expect(tasks[1].startTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-10-31', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2');
});
/**
* Unfortunately, Vitest has no way of modifying the timezone at runtime, so
* in order to test this, please run this test with
*
* ```bash
* TZ='America/Los_Angeles' pnpm exec vitest run ganttDb
* ```
*/
/* c8 ignore start */ // tell code-coverage to ignore this block of code
describe.skipIf(process.env.TZ != 'America/Los_Angeles')(
'when using a timezone with daylight savings (only run if TZ="America/Los_Angeles")',
() => {
it('should add 1 day even on days with 25 hours', function () {
const startTime = new Date(2020, 10, 1);
expect(startTime.toISOString()).toBe('2020-11-01T07:00:00.000Z');
const endTime = new Date(2020, 10, 2);
expect(endTime.toISOString()).toBe('2020-11-02T08:00:00.000Z');
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection('Task handles 25 hour day');
ganttDb.addTask('daylight savings day', 'id1,2020-11-01,1d');
const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(startTime);
expect(tasks[0].endTime).toEqual(endTime);
// In USA states that use daylight savings, 2020-11-01 had 25 hours
const millisecondsIn25Hours = 25 * 60 * 60 * 1000;
expect(endTime - startTime).toEqual(millisecondsIn25Hours);
});
}
);
/* c8 ignore stop */
describe('when setting inclusive end dates', function () {
beforeEach(function () {
ganttDb.setDateFormat('YYYY-MM-DD');
@@ -410,13 +380,13 @@ describe('when using the ganttDb', function () {
});
it('should automatically add one day to all end dates', function () {
const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(dayjs('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(dayjs('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(dayjs('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(dayjs('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[1].manualEndTime).toBeTruthy();
expect(tasks[1].id).toEqual('id2');

View File

@@ -1,4 +1,4 @@
import dayjs from 'dayjs';
import moment from 'moment-mini';
import { log } from '../../logger';
import {
select,
@@ -24,43 +24,12 @@ export const setConf = function () {
log.debug('Something is calling, setConf, remove the call');
};
/**
* For this issue:
* https://github.com/mermaid-js/mermaid/issues/1618
*
* Finds the number of intersections between tasks that happen at any point in time.
* Used to figure out how many rows are needed to display the tasks when the display
* mode is set to 'compact'.
*
* @param tasks
* @param orderOffset
*/
const getMaxIntersections = (tasks, orderOffset) => {
let timeline = [...tasks].map(() => -Infinity);
let sorted = [...tasks].sort((a, b) => a.startTime - b.startTime || a.order - b.order);
let maxIntersections = 0;
for (const element of sorted) {
for (let j = 0; j < timeline.length; j++) {
if (element.startTime >= timeline[j]) {
timeline[j] = element.endTime;
element.order = j + orderOffset;
if (j > maxIntersections) {
maxIntersections = j;
}
break;
}
}
}
return maxIntersections;
};
let w;
export const draw = function (text, id, version, diagObj) {
const conf = getConfig().gantt;
// diagObj.db.clear();
// parser.parse(text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
@@ -87,40 +56,7 @@ export const draw = function (text, id, version, diagObj) {
const taskArray = diagObj.db.getTasks();
// Set height based on number of tasks
let categories = [];
for (const element of taskArray) {
categories.push(element.type);
}
categories = checkUnique(categories);
const categoryHeights = {};
let h = 2 * conf.topPadding;
if (diagObj.db.getDisplayMode() === 'compact' || conf.displayMode === 'compact') {
const categoryElements = {};
for (const element of taskArray) {
if (categoryElements[element.section] === undefined) {
categoryElements[element.section] = [element];
} else {
categoryElements[element.section].push(element);
}
}
let intersections = 0;
for (const category of Object.keys(categoryElements)) {
const categoryHeight = getMaxIntersections(categoryElements[category], intersections) + 1;
intersections += categoryHeight;
h += categoryHeight * (conf.barHeight + conf.barGap);
categoryHeights[category] = categoryHeight;
}
} else {
h += taskArray.length * (conf.barHeight + conf.barGap);
for (const category of categories) {
categoryHeights[category] = taskArray.filter((task) => task.type === category).length;
}
}
const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
// Set viewBox
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
@@ -138,6 +74,16 @@ export const draw = function (text, id, version, diagObj) {
])
.rangeRound([0, w - conf.leftPadding - conf.rightPadding]);
let categories = [];
for (const element of taskArray) {
categories.push(element.type);
}
const catsUnfiltered = categories; // for vert labels
categories = checkUnique(categories);
/**
* @param a
* @param b
@@ -211,15 +157,11 @@ export const draw = function (text, id, version, diagObj) {
* @param w
*/
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) {
// Get unique task orders. Required to draw the background rects when display mode is compact.
const uniqueTaskOrderIds = [...new Set(theArray.map((item) => item.order))];
const uniqueTasks = uniqueTaskOrderIds.map((id) => theArray.find((item) => item.order === id));
// Draw background rects covering the entire width of the graph, these form the section rows.
svg
.append('g')
.selectAll('rect')
.data(uniqueTasks)
.data(theArray)
.enter()
.append('rect')
.attr('x', 0)
@@ -493,16 +435,16 @@ export const draw = function (text, id, version, diagObj) {
const excludeRanges = [];
let range = null;
let d = dayjs(minTime);
let d = moment(minTime);
while (d.valueOf() <= maxTime) {
if (diagObj.db.isInvalidDate(d, dateFormat, excludes, includes)) {
if (!range) {
range = {
start: d,
end: d,
start: d.clone(),
end: d.clone(),
};
} else {
range.end = d;
range.end = d.clone();
}
} else {
if (range) {
@@ -510,7 +452,7 @@ export const draw = function (text, id, version, diagObj) {
range = null;
}
}
d = d.add(1, 'd');
d.add(1, 'd');
}
const rectangles = svg.append('g').selectAll('rect').data(excludeRanges).enter();
@@ -525,7 +467,7 @@ export const draw = function (text, id, version, diagObj) {
})
.attr('y', conf.gridLineStartPadding)
.attr('width', function (d) {
const renderEnd = d.end.add(1, 'day');
const renderEnd = d.end.clone().add(1, 'day');
return timeScale(renderEnd) - timeScale(d.start);
})
.attr('height', h - theTopPad - conf.gridLineStartPadding)
@@ -640,9 +582,12 @@ export const draw = function (text, id, version, diagObj) {
* @param theTopPad
*/
function vertLabels(theGap, theTopPad) {
const numOccurances = [];
let prevGap = 0;
const numOccurances = Object.keys(categoryHeights).map((d) => [d, categoryHeights[d]]);
for (const [i, category] of categories.entries()) {
numOccurances[i] = [category, getCount(category, catsUnfiltered)];
}
svg
.append('g') // without doing this, impossible to put grid lines behind text
@@ -680,6 +625,7 @@ export const draw = function (text, id, version, diagObj) {
}
})
.attr('font-size', conf.sectionFontSize)
.attr('font-size', conf.sectionFontSize)
.attr('class', function (d) {
for (const [i, category] of categories.entries()) {
if (d[0] === category) {
@@ -736,6 +682,31 @@ export const draw = function (text, id, version, diagObj) {
}
return result;
}
/**
* From this stack exchange question:
* http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
*
* @param arr
*/
function getCounts(arr) {
let i = arr.length; // const to loop over
const obj = {}; // obj to store results
while (i) {
obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
}
return obj;
}
/**
* Get specific from everything
*
* @param word
* @param arr
*/
function getCount(word, arr) {
return getCounts(arr)[word] || 0;
}
};
export default {

View File

@@ -131,10 +131,9 @@ statement
| includes {yy.setIncludes($1.substr(9));$$=$1.substr(9);}
| todayMarker {yy.setTodayMarker($1.substr(12));$$=$1.substr(12);}
| title {yy.setDiagramTitle($1.substr(6));$$=$1.substr(6);}
| 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); }
| 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);}
| clickStatement
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
| directive

View File

@@ -4,13 +4,9 @@ import { log } from '../../logger';
import { getConfig } from '../../config';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import svgDraw from './svgDraw';
import cytoscape from 'cytoscape/dist/cytoscape.umd.js';
import coseBilkent from 'cytoscape-cose-bilkent';
import * as db from './mindmapDb';
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
let cytoscape;
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The mindmap data and hierarchy
@@ -93,7 +89,14 @@ function addNodes(mindmap, cy, conf, level) {
* @param conf
* @param cy
*/
function layoutMindmap(node, conf) {
async function layoutMindmap(node, conf) {
if (!cytoscape) {
cytoscape = (await import('cytoscape')).default;
const coseBilkent = (await import('cytoscape-cose-bilkent')).default;
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
}
return new Promise((resolve) => {
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@@ -167,15 +170,12 @@ function positionNodes(cy) {
export const draw = async (text, id, version, diagObj) => {
const conf = getConfig();
// console.log('Config: ', conf);
conf.htmlLabels = false;
// This is done only for throwing the error if the text is not valid.
diagObj.db.clear();
// Parse the graph definition
diagObj.parser.parse(text);
log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
log.debug('Renering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sandbox mode

View File

@@ -12,13 +12,12 @@
%}
%x NODE
%x NSTR
%x NSTR2
%x ICON
%x CLASS
%%
\s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';}
\s*\%\%.* {yy.getLogger().trace('Found comment',yytext);}
// \%\%[^\n]*\n /* skip comments */
"mindmap" return 'MINDMAP';
":::" { this.begin('CLASS'); }
@@ -42,9 +41,6 @@
// !(-\() return 'NODE_ID';
[^\(\[\n\-\)\{\}]+ return 'NODE_ID';
<<EOF>> return 'EOF';
<NODE>["][`] { this.begin("NSTR2");}
<NSTR2>[^`"]+ { return "NODE_DESCR";}
<NSTR2>[`]["] { this.popState();}
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
<NSTR>["] {this.popState();}

View File

@@ -70,12 +70,5 @@ const getStyles = (options) =>
.edge {
fill: none;
}
.mindmap-node-label {
dy: 1em;
alignment-baseline: middle;
text-anchor: middle;
dominant-baseline: middle;
text-align: center;
}
`;
export default getStyles;

View File

@@ -1,6 +1,5 @@
import { select } from 'd3';
import * as db from './mindmapDb';
import { createText } from '../../rendering-util/createText';
const MAX_SECTIONS = 12;
/**
@@ -12,7 +11,7 @@ function wrap(text, width) {
var text = select(this),
words = text
.text()
.split(/(\s+|<br\/>)/)
.split(/(\s+|<br>)/)
.reverse(),
word,
line = [],
@@ -29,10 +28,10 @@ function wrap(text, width) {
word = words[words.length - 1 - j];
line.push(word);
tspan.text(line.join(' ').trim());
if (tspan.node().getComputedTextLength() > width || word === '<br/>') {
if (tspan.node().getComputedTextLength() > width || word === '<br>') {
line.pop();
tspan.text(line.join(' ').trim());
if (word === '<br/>') {
if (word === '<br>') {
line = [''];
} else {
line = [word];
@@ -204,7 +203,6 @@ const roundedRectBkg = function (elem, node) {
* @returns {number} The height nodes dom element
*/
export const drawNode = function (elem, node, fullSection, conf) {
const htmlLabels = conf.htmlLabels;
const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g');
node.section = section;
@@ -217,22 +215,15 @@ export const drawNode = function (elem, node, fullSection, conf) {
// Create the wrapped text element
const textElem = nodeElem.append('g');
const description = node.descr.replace(/(<br\/*>)/g, '\n');
const newEl = createText(textElem, description, {
useHtmlLabels: htmlLabels,
width: node.width,
classes: 'mindmap-node-label',
});
if (!htmlLabels) {
textElem
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle');
}
// .call(wrap, node.width);
const bbox = textElem.node().getBBox();
const txt = textElem
.append('text')
.text(node.descr)
.attr('dy', '1em')
.attr('alignment-baseline', 'middle')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.call(wrap, node.width);
const bbox = txt.node().getBBox();
const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
node.width = bbox.width + 2 * node.padding;
@@ -276,16 +267,7 @@ export const drawNode = function (elem, node, fullSection, conf) {
);
}
} else {
if (!htmlLabels) {
const dx = node.width / 2;
const dy = node.padding / 2;
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
// textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
} else {
const dx = (node.width - bbox.width) / 2;
const dy = (node.height - bbox.height) / 2;
textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')');
}
textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
}
switch (node.type) {

View File

@@ -3,7 +3,6 @@ import { select, scaleOrdinal, pie as d3pie, arc } from 'd3';
import { log } from '../../logger';
import { configureSvgSize } from '../../setupGraphViewbox';
import * as configApi from '../../config';
import { parseFontSize } from '../../utils';
let conf = configApi.getConfig();
@@ -89,10 +88,6 @@ export const draw = (txt, id, _version, diagObj) => {
themeVariables.pie12,
];
const textPosition = conf.pie?.textPosition ?? 0.75;
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
outerStrokeWidth ??= 2;
// Set the color scale
var color = scaleOrdinal().range(myGeneratedColors);
@@ -116,16 +111,6 @@ export const draw = (txt, id, _version, diagObj) => {
// Shape helper to build arcs:
var arcGenerator = arc().innerRadius(0).outerRadius(radius);
var labelArcGenerator = arc()
.innerRadius(radius * textPosition)
.outerRadius(radius * textPosition);
svg
.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', radius + outerStrokeWidth / 2)
.attr('class', 'pieOuterCircle');
// Build the pie chart: each part of the pie is a path that we build using the arc function.
svg
@@ -150,7 +135,7 @@ export const draw = (txt, id, _version, diagObj) => {
return ((d.data.value / sum) * 100).toFixed(0) + '%';
})
.attr('transform', function (d) {
return 'translate(' + labelArcGenerator.centroid(d) + ')';
return 'translate(' + arcGenerator.centroid(d) + ')';
})
.style('text-anchor', 'middle')
.attr('class', 'slice');

View File

@@ -5,11 +5,6 @@ const getStyles = (options) =>
stroke-width : ${options.pieStrokeWidth};
opacity : ${options.pieOpacity};
}
.pieOuterCircle{
stroke: ${options.pieOuterStrokeColor};
stroke-width: ${options.pieOuterStrokeWidth};
fill: none;
}
.pieTitleText {
text-anchor: middle;
font-size: ${options.pieTitleTextSize};

View File

@@ -232,9 +232,6 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) =>
type: newNode.type,
padding: 15, //getConfig().flowchart.padding
};
// if (useHtmlLabels) {
nodeData.centerLabel = true;
// }
if (parsedItem.note) {
// Todo: set random id
@@ -243,7 +240,6 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) =>
shape: SHAPE_NOTE,
labelText: parsedItem.note.text,
classes: CSS_DIAGRAM_NOTE,
// useHtmlLabels: false,
style: '', // styles.style,
id: itemId + NOTE_ID + '-' + graphItemCount,
domId: stateDomId(itemId, graphItemCount, NOTE),

View File

@@ -1,37 +1,25 @@
// @ts-ignore - db not typed yet
import { select, Selection } from 'd3';
// @ts-nocheck TODO: fix file
import { select } from 'd3';
import svgDraw from './svgDraw';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { setupGraphViewbox } from '../../setupGraphViewbox';
import { Diagram } from '../../Diagram';
import { MermaidConfig } from '../../config.type';
interface Block<TDesc, TSection> {
number: number;
descr: TDesc;
section: TSection;
width: number;
padding: number;
maxHeight: number;
}
export const setConf = function (cnf) {
const keys = Object.keys(cnf);
interface TimelineTask {
id: number;
section: string;
type: string;
task: string;
score: number;
events: string[];
}
export const draw = function (text: string, id: string, version: string, diagObj: Diagram) {
keys.forEach(function (key) {
conf[key] = cnf[key];
});
};
export const draw = function (text, id, version, diagObj) {
//1. Fetch the configuration
const conf = getConfig();
// @ts-expect-error - wrong config?
const LEFT_MARGIN = conf.leftMargin ?? 50;
const LEFT_MARGIN = conf.leftMargin ? conf.leftMargin : 50;
//2. Clear the diagram db before parsing
diagObj.db.clear?.();
diagObj.db.clear();
//3. Parse the diagram text
diagObj.parser.parse(text + '\n');
@@ -46,19 +34,15 @@ export const draw = function (text: string, id: string, version: string, diagObj
}
const root =
securityLevel === 'sandbox'
? // @ts-ignore d3 types are wrong
select(sandboxElement.nodes()[0].contentDocument.body)
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// @ts-ignore d3 types are wrong
const svg = root.select('#' + id);
svg.append('g');
//4. Fetch the diagram data
// @ts-expect-error - db not typed yet
const tasks: TimelineTask[] = diagObj.db.getTasks();
// @ts-expect-error - db not typed yet
const tasks = diagObj.db.getTasks();
const title = diagObj.db.getCommonDb().getDiagramTitle();
log.debug('task', tasks);
@@ -66,8 +50,7 @@ export const draw = function (text: string, id: string, version: string, diagObj
svgDraw.initGraphics(svg);
// fetch Sections
// @ts-expect-error - db not typed yet
const sections: string[] = diagObj.db.getSections();
const sections = diagObj.db.getSections();
log.debug('sections', sections);
let maxSectionHeight = 0;
@@ -84,8 +67,8 @@ export const draw = function (text: string, id: string, version: string, diagObj
let hasSections = true;
//Calculate the max height of the sections
sections.forEach(function (section: string) {
const sectionNode: Block<string, number> = {
sections.forEach(function (section) {
const sectionNode = {
number: sectionNumber,
descr: section,
section: sectionNumber,
@@ -104,9 +87,8 @@ export const draw = function (text: string, id: string, version: string, diagObj
log.debug('tasks.length', tasks.length);
//calculate max task height
// for loop till tasks.length
for (const [i, task] of tasks.entries()) {
const taskNode: Block<TimelineTask, string> = {
const taskNode = {
number: i,
descr: task,
section: task.section,
@@ -142,14 +124,11 @@ export const draw = function (text: string, id: string, version: string, diagObj
if (sections && sections.length > 0) {
sections.forEach((section) => {
//filter task where tasks.section == section
const tasksForSection = tasks.filter((task) => task.section === section);
const sectionNode: Block<string, number> = {
const sectionNode = {
number: sectionNumber,
descr: section,
section: sectionNumber,
width: 200 * Math.max(tasksForSection.length, 1) - 50,
width: 150,
padding: 20,
maxHeight: maxSectionHeight,
};
@@ -163,6 +142,8 @@ export const draw = function (text: string, id: string, version: string, diagObj
masterY += maxSectionHeight + 50;
//draw tasks for this section
//filter task where tasks.section == section
const tasksForSection = tasks.filter((task) => task.section === section);
if (tasksForSection.length > 0) {
drawTasks(
svg,
@@ -234,25 +215,25 @@ export const draw = function (text: string, id: string, version: string, diagObj
setupGraphViewbox(
undefined,
svg,
conf.timeline?.padding ?? 50,
conf.timeline?.useMaxWidth ?? false
conf.timeline.padding ? conf.timeline.padding : 50,
conf.timeline.useMaxWidth ? conf.timeline.useMaxWidth : false
);
// addSVGAccessibilityFields(diagObj.db, diagram, id);
};
export const drawTasks = function (
diagram: Selection<SVGElement, unknown, null, undefined>,
tasks: TimelineTask[],
sectionColor: number,
masterX: number,
masterY: number,
maxTaskHeight: number,
conf: MermaidConfig,
maxEventCount: number,
maxEventLineLength: number,
maxSectionHeight: number,
isWithoutSections: boolean
diagram,
tasks,
sectionColor,
masterX,
masterY,
maxTaskHeight,
conf,
maxEventCount,
maxEventLineLength,
maxSectionHeight,
isWithoutSections
) {
// Draw the tasks
for (const task of tasks) {
@@ -268,7 +249,6 @@ export const drawTasks = function (
log.debug('taskNode', taskNode);
// create task wrapper
const taskWrapper = diagram.append('g').attr('class', 'taskWrapper');
const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf);
const taskHeight = node.height;
@@ -283,11 +263,11 @@ export const drawTasks = function (
if (task.events) {
// draw a line between the task and the events
const lineWrapper = diagram.append('g').attr('class', 'lineWrapper');
let lineLength = maxTaskHeight;
let linelength = maxTaskHeight;
//add margin to task
masterY += 100;
lineLength =
lineLength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf);
linelength =
linelength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf);
masterY -= 100;
lineWrapper
@@ -310,7 +290,7 @@ export const drawTasks = function (
}
masterX = masterX + 200;
if (isWithoutSections && !conf.timeline?.disableMulticolor) {
if (isWithoutSections && !getConfig().timeline.disableMulticolor) {
sectionColor++;
}
}
@@ -319,21 +299,14 @@ export const drawTasks = function (
masterY = masterY - 10;
};
export const drawEvents = function (
diagram: Selection<SVGElement, unknown, null, undefined>,
events: string[],
sectionColor: number,
masterX: number,
masterY: number,
conf: MermaidConfig
) {
export const drawEvents = function (diagram, events, sectionColor, masterX, masterY, conf) {
let maxEventHeight = 0;
const eventBeginY = masterY;
masterY = masterY + 100;
// Draw the events
for (const event of events) {
// create node from event
const eventNode: Block<string, number> = {
const eventNode = {
descr: event,
section: sectionColor,
number: sectionColor,
@@ -358,8 +331,6 @@ export const drawEvents = function (
};
export default {
setConf: () => {
// no-op
},
setConf,
draw,
};

View File

@@ -224,17 +224,6 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
num = sectionNumber % fills.length;
colour = textColours[sectionNumber % textColours.length];
// count how many consecutive tasks have the same section
let taskInSectionCount = 0;
const currentSection = task.section;
for (let taskIndex = i; taskIndex < tasks.length; taskIndex++) {
if (tasks[taskIndex].section == currentSection) {
taskInSectionCount = taskInSectionCount + 1;
} else {
break;
}
}
const section = {
x: i * conf.taskMargin + i * conf.width + LEFT_MARGIN,
y: 50,
@@ -242,7 +231,6 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
fill,
num,
colour,
taskCount: taskInSectionCount,
};
svgDraw.drawSection(diagram, section, conf);

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