diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index aff5852db..f877ca265 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -21,11 +21,7 @@ jobs:
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
- # Need to skip setup if Cypress run is skipped, otherwise an error
- # is thrown since the pnpm cache step fails
- if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with:
- cache: pnpm
node-version: ${{ matrix.node-version }}
# Install NPM dependencies, cache them correctly
diff --git a/.github/workflows/link-checker.yml b/.github/workflows/link-checker.yml
index 566548ecf..b2c17f9fc 100644
--- a/.github/workflows/link-checker.yml
+++ b/.github/workflows/link-checker.yml
@@ -14,6 +14,7 @@ on:
pull_request:
branches:
- master
+ workflow_dispatch:
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '30 8 * * *'
@@ -35,9 +36,16 @@ jobs:
restore-keys: cache-lychee-
- name: Link Checker
- uses: lycheeverse/lychee-action@v1.5.4
+ uses: lycheeverse/lychee-action@v1.6.1
with:
- args: --verbose --no-progress --cache --max-cache-age 1d packages/mermaid/src/docs/**/*.md README.md README.zh-CN.md
+ args: >-
+ --verbose
+ --no-progress
+ --cache
+ --max-cache-age 1d
+ packages/mermaid/src/docs/**/*.md
+ README.md
+ README.zh-CN.md
fail: true
jobSummary: true
env:
diff --git a/.lycheeignore b/.lycheeignore
index 4c781f6a0..5f7b9679e 100644
--- a/.lycheeignore
+++ b/.lycheeignore
@@ -4,16 +4,8 @@
# Network error: Forbidden
https://codepen.io
-# Network error: The certificate was not trusted
-https://mkdocs.org/
-https://osawards.com/javascript/#nominees
-https://osawards.com/javascript/2019
-
# Timeout error, maybe Twitter has anti-bot defenses against GitHub's CI servers?
https://twitter.com/mermaidjs_
# Don't check files that are generated during the build via `pnpm docs:code`
packages/mermaid/src/docs/config/setup/*
-
-# Network error: 502, since few days
-https://bundlephobia.com/
diff --git a/.npmrc b/.npmrc
index 289684302..4c2f52b3b 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,3 +1,2 @@
auto-install-peers=true
strict-peer-dependencies=false
-use-inline-specifiers-lockfile-format=true
diff --git a/.vite/server.ts b/.vite/server.ts
index 82b75232d..5a86b3d5b 100644
--- a/.vite/server.ts
+++ b/.vite/server.ts
@@ -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 middlewares
+ appType: 'custom', // don't include Vite's default HTML handling middleware
});
app.use(cors());
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b0320b36e..150a22341 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -55,6 +55,8 @@ The documentation is written in **Markdown**. For more information about Markdow
The source files for the project documentation are located in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory. This is where you should make changes.
The files under `/packages/mermaid/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory.
+After editing files in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory, be sure to run `pnpm install` and `pnpm run --filter mermaid docs:build` locally to build the `/docs` directory.
+
```mermaid
flowchart LR
classDef default fill:#fff,color:black,stroke:black
diff --git a/README.md b/README.md
index d42e2f7e1..31c6d62c6 100644
--- a/README.md
+++ b/README.md
@@ -226,6 +226,44 @@ pie
### Git graph [experimental - live editor ]
+### Bar chart (using gantt chart) [docs - live editor ]
+
+```
+gantt
+ title Git Issues - days since last update
+ dateFormat X
+ axisFormat %s
+
+ section Issue19062
+ 71 : 0, 71
+ section Issue19401
+ 36 : 0, 36
+ section Issue193
+ 34 : 0, 34
+ section Issue7441
+ 9 : 0, 9
+ section Issue1300
+ 5 : 0, 5
+```
+
+```mermaid
+gantt
+ title Git Issues - days since last update
+ dateFormat X
+ axisFormat %s
+
+ section Issue19062
+ 71 : 0, 71
+ section Issue19401
+ 36 : 0, 36
+ section Issue193
+ 34 : 0, 34
+ section Issue7441
+ 9 : 0, 9
+ section Issue1300
+ 5 : 0, 5
+```
+
### User Journey diagram [docs - live editor ]
```
diff --git a/cSpell.json b/cSpell.json
index 6f35a0142..df4b03251 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -47,6 +47,7 @@
"graphviz",
"grav",
"greywolf",
+ "huynh",
"inkdrop",
"jaoude",
"jison",
@@ -91,6 +92,7 @@
"sidharthv",
"sphinxcontrib",
"statediagram",
+ "steph",
"stylis",
"substate",
"sveidqvist",
diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js
index df1fac0cd..faa511626 100644
--- a/cypress/integration/rendering/erDiagram.spec.js
+++ b/cypress/integration/rendering/erDiagram.spec.js
@@ -10,7 +10,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render an ER diagram with a recursive relationship', () => {
@@ -23,7 +22,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render an ER diagram with multiple relationships between the same two entities', () => {
@@ -35,7 +33,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render a cyclical ER diagram', () => {
@@ -48,7 +45,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render a not-so-simple ER diagram', () => {
@@ -66,7 +62,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render multiple ER diagrams', () => {
@@ -85,7 +80,6 @@ describe('Entity Relationship Diagram', () => {
],
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render an ER diagram with blank or empty labels', () => {
@@ -98,7 +92,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render an ER diagrams when useMaxWidth is true (default)', () => {
@@ -151,7 +144,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ er: { useMaxWidth: false } }
);
- cy.get('svg');
});
it('should render entities with and without attributes', () => {
@@ -164,7 +156,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with generic and array attributes', () => {
@@ -179,7 +170,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with length in attributes type', () => {
@@ -193,7 +183,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities and attributes with big and small entity names', () => {
@@ -209,7 +198,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with keys', () => {
@@ -228,7 +216,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with comments', () => {
@@ -247,7 +234,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with keys and comments', () => {
@@ -267,7 +253,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render entities with aliases', () => {
@@ -285,7 +270,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('1433: should render a simple ER diagram with a title', () => {
diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js
new file mode 100644
index 000000000..e837565d3
--- /dev/null
+++ b/cypress/integration/rendering/errorDiagram.spec.js
@@ -0,0 +1,45 @@
+import { imgSnapshotTest } from '../../helpers/util';
+
+describe('Error Diagrams', () => {
+ beforeEach(() => {
+ cy.on('uncaught:exception', (err) => {
+ expect(err.message).to.include('Parse error');
+ // return false to prevent the error from
+ // failing this test
+ return false;
+ });
+ });
+
+ it('should render a simple ER diagram', () => {
+ imgSnapshotTest(
+ `
+ error
+ `,
+ { logLevel: 1 }
+ );
+ });
+
+ it('should render error diagram for actual errors', () => {
+ imgSnapshotTest(
+ `
+ flowchart TD
+ A[Christmas] --|Get money| B(Go shopping)
+ `,
+ { logLevel: 1 }
+ );
+ });
+
+ it('should render error for wrong ER diagram', () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
+ ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
+ ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
+ MONGODB-CLUSTERS ||..|{
+ ATLAS-TEAMS ||..|{
+ `,
+ { logLevel: 1 }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js
index 414037651..68d8b3ce5 100644
--- a/cypress/integration/rendering/flowchart-elk.spec.js
+++ b/cypress/integration/rendering/flowchart-elk.spec.js
@@ -684,4 +684,149 @@ A --> B
{ titleTopMargin: 0 }
);
});
+ describe('Markdown strings flowchart-elk (#4220)', () => {
+ describe('html labels', () => {
+ it('With styling and classes', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+ A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+ id1(Start)-->id2(Stop)
+ style id1 fill:#f9f,stroke:#333,stroke-width:4px
+ style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+ classDef someclass fill:#f96
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('With formatting in a node', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+ a{"\`The **cat** in the hat\`"} -- 1o --> b
+ a -- 2o --> c
+ a -- 3o --> d
+ g --2i--> a
+ d --1i--> a
+ h --3i -->a
+ b --> d(The dog in the hog)
+ c --> d
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('New line in node and formatted edge label', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('Wrapping long text with a new line', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+b(\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`) --> c
+
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('Sub graphs and markdown strings', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+subgraph "One"
+ a("\`The **cat**
+ in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+ c("\`The **cat**
+ in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ });
+
+ describe('svg text labels', () => {
+ it('With styling and classes', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+ A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+ id1(Start)-->id2(Stop)
+ style id1 fill:#f9f,stroke:#333,stroke-width:4px
+ style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+ classDef someclass fill:#f96
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('With formatting in a node', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+ a{"\`The **cat** in the hat\`"} -- 1o --> b
+ a -- 2o --> c
+ a -- 3o --> d
+ g --2i--> a
+ d --1i--> a
+ h --3i -->a
+ b --> d(The dog in the hog)
+ c --> d
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('New line in node and formatted edge label', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('Wrapping long text with a new line', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
+
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ it('Sub graphs and markdown strings', () => {
+ imgSnapshotTest(
+ `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+subgraph "One"
+ a("\`The **cat**
+ in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+ c("\`The **cat**
+ in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ });
+ });
});
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index abdb22265..836d8ad79 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -685,4 +685,159 @@ A ~~~ B
{ titleTopMargin: 0 }
);
});
+ it('4023: Should render html labels with images and-or text correctly', () => {
+ imgSnapshotTest(
+ `flowchart TD
+ B[ ]
+ B-->C[ more text ]
+ B-->D( some text)
+ B-->E(plain)`,
+ {}
+ );
+ });
+ 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 }
+ );
+ });
+ });
+ });
});
diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js
index c0156eee3..cb65f73b0 100644
--- a/cypress/integration/rendering/gantt.spec.js
+++ b/cypress/integration/rendering/gantt.spec.js
@@ -133,6 +133,24 @@ describe('Gantt diagram', () => {
);
});
+ it('should default to showing today marker', () => {
+ // This test only works if the environment thinks today is 1010-10-10
+ imgSnapshotTest(
+ `
+ gantt
+ title Show today marker (vertical line should be visible)
+ dateFormat YYYY-MM-DD
+ axisFormat %d
+ %% Should default to being on
+ %% todayMarker on
+ section Section1
+ Yesterday: 1010-10-09, 1d
+ Today: 1010-10-10, 1d
+ `,
+ {}
+ );
+ });
+
it('should hide today marker', () => {
imgSnapshotTest(
`
@@ -142,7 +160,8 @@ describe('Gantt diagram', () => {
axisFormat %d
todayMarker off
section Section1
- Today: 1, -1h
+ Yesterday: 1010-10-09, 1d
+ Today: 1010-10-10, 1d
`,
{}
);
@@ -157,7 +176,8 @@ describe('Gantt diagram', () => {
axisFormat %d
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
- Today: 1, -1h
+ Yesterday: 1010-10-09, 1d
+ Today: 1010-10-10, 1d
`,
{}
);
@@ -435,4 +455,39 @@ describe('Gantt diagram', () => {
{ gantt: { topAxis: true } }
);
});
+
+ it('should render when compact is true', () => {
+ imgSnapshotTest(
+ `
+ ---
+ displayMode: compact
+ ---
+ gantt
+ title GANTT compact
+ dateFormat HH:mm:ss
+ axisFormat %Hh%M
+
+ section DB Clean
+ Clean: 12:00:00, 10m
+ Clean: 12:30:00, 12m
+ Clean: 13:00:00, 8m
+ Clean: 13:30:00, 9m
+ Clean: 14:00:00, 13m
+ Clean: 14:30:00, 10m
+ Clean: 15:00:00, 11m
+
+ section Sessions
+ A: 12:00:00, 63m
+ B: 12:30:00, 12m
+ C: 13:05:00, 12m
+ D: 13:06:00, 33m
+ E: 13:15:00, 55m
+ F: 13:20:00, 12m
+ G: 13:32:00, 18m
+ H: 13:50:00, 20m
+ I: 14:10:00, 10m
+ `,
+ {}
+ );
+ });
});
diff --git a/cypress/integration/rendering/mindmap.spec.ts b/cypress/integration/rendering/mindmap.spec.ts
index 4663f6225..94b3f9ca0 100644
--- a/cypress/integration/rendering/mindmap.spec.ts
+++ b/cypress/integration/rendering/mindmap.spec.ts
@@ -223,5 +223,18 @@ mindmap
shouldHaveRoot
);
});
+ describe('Markdown strings mindmaps (#4220)', () => {
+ it('Formatted label with linebreak and a wrapping label and emojis', () => {
+ imgSnapshotTest(
+ `mindmap
+ id1[\`**Start** with
+ a second line 😎\`]
+ id2[\`The dog in **the** hog... a *very long text* about it
+Word!\`]
+`,
+ { titleTopMargin: 0 }
+ );
+ });
+ });
/* The end */
});
diff --git a/cypress/integration/rendering/pie.spec.js b/cypress/integration/rendering/pie.spec.js
index 019fa41af..8a89a0cde 100644
--- a/cypress/integration/rendering/pie.spec.js
+++ b/cypress/integration/rendering/pie.spec.js
@@ -75,4 +75,15 @@ describe('Pie Chart', () => {
expect(svg).to.not.have.attr('style');
});
});
+ it('should render a pie diagram when textPosition is set', () => {
+ imgSnapshotTest(
+ `
+ pie
+ "Dogs": 50
+ "Cats": 25
+ `,
+ { logLevel: 1, pie: { textPosition: 0.9 } }
+ );
+ cy.get('svg');
+ });
});
diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html
index fccd65004..49fe075c3 100644
--- a/cypress/platform/knsv2.html
+++ b/cypress/platform/knsv2.html
@@ -29,9 +29,9 @@
}
.mermaid svg {
/* font-size: 18px !important; */
- background-color: #eee;
- background-image: radial-gradient(#fff 1%, transparent 11%),
- radial-gradient(#fff 1%, transparent 11%);
+ background-color: #efefef;
+ background-image: radial-gradient(#fff 51%, transparent 91%),
+ radial-gradient(#fff 51%, transparent 91%);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
background-repeat: repeat;
@@ -51,29 +51,103 @@
font-family: monospace;
font-size: 72px;
}
+ /* tspan {
+ font-size: 6px !important;
+ } */
-%%{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
+stateDiagram-v2
+ [*] --> Still
+ Still --> [*]
+ Still --> Moving
+ Moving --> Still
+ Moving --> Crash
+ Crash --> [*]
+
+flowchart RL
+ subgraph "`one`"
+ a1 -- l1 --> a2
+ a1 -- l2 --> a2
+ end
+
+
+flowchart RL
+ subgraph "`one`"
+ a1 -- l1 --> a2
+ a1 -- l2 --> a2
+ end
-flowchart-elk TB
- a --> b
- a --> c
- b --> d
- c --> d
+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`"]
+
+flowchart LR
+ A[A text that needs to be wrapped wraps to another line]
+ B[A text that needs to be wrapped wraps to another line]
+ C["`A text that needs to be wrapped to another line`"]
+
+flowchart LR
+ C["`A text
+ that needs
+ to be wrapped
+ in another
+ way`"]
+
+
+ classDiagram-v2
+ note "I love this diagram!\nDo you love it?"
+
+
+ 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
+
+mindmap
+root
+ Child3(A node with an icon and with a long text that wraps to keep the node size in check)
+
+
+ %%{init: {"theme": "forest"} }%%
+mindmap
+ id1[**Start2** end]
+ id2[**Start2** end]
+ %% Another comment
+ id3[**Start2** end] %% Comment
+ id4[**Start2** end the very end]
+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
+ `"]
+
+
+mindmap
+ id1("`**Root**`"]
+ id2["`A formatted text... with **bold** and *italics*`"]
+ id3[Regular labels works as usual]
+ id4["`Emojis and unicode works too: 🤓
+ शान्तिः سلام 和平 `"]
+
+
+
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid
@@ -89,7 +163,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]
@@ -128,6 +202,62 @@ flowchart TB
rtc{{rtc}}
+
+%%{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}}
+
+
flowchart TB
@@ -270,14 +400,16 @@ mindmap
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
- theme: 'forest',
+ // theme: 'forest',
startOnLoad: true,
- logLevel: 5,
+ logLevel: 0,
flowchart: {
// defaultRenderer: 'elk',
useMaxWidth: false,
+ // htmlLabels: false,
htmlLabels: true,
},
+ // htmlLabels: false,
gantt: {
useMaxWidth: false,
},
diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js
index 2e1093519..99533192d 100644
--- a/cypress/platform/viewer.js
+++ b/cypress/platform/viewer.js
@@ -47,7 +47,6 @@ const contentLoaded = async function () {
await mermaid2.registerExternalDiagrams([externalExample]);
mermaid2.initialize(graphObj.mermaid);
await mermaid2.run();
- markRendered();
}
};
@@ -123,7 +122,6 @@ const contentLoadedApi = async function () {
bindFunctions(div);
}
}
- markRendered();
};
if (typeof document !== 'undefined') {
@@ -135,10 +133,10 @@ if (typeof document !== 'undefined') {
function () {
if (this.location.href.match('xss.html')) {
this.console.log('Using api');
- void contentLoadedApi();
+ void contentLoadedApi().finally(markRendered);
} else {
this.console.log('Not using api');
- void contentLoaded();
+ void contentLoaded().finally(markRendered);
}
},
false
diff --git a/demos/error.html b/demos/error.html
new file mode 100644
index 000000000..2d6d1b01f
--- /dev/null
+++ b/demos/error.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+ Error | Mermaid Quick Test Page
+
+
+
+
+
+ erDiagram
+ ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
+ ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
+ ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
+
+
+ erDiagram
+ ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many"
+ ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many"
+ ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many"
+ MONGODB-CLUSTERS ||..|{
+ ATLAS-TEAMS ||..|{
+
+
+
+ flowchart TD
+ A[Christmas] -->|Get money| B(Go shopping)
+
+
+ flowchart TD
+ A[Christmas] --|Get money| B(Go shopping)
+
+
+
+
diff --git a/demos/gantt.html b/demos/gantt.html
index 613dc8694..88f52ef5c 100644
--- a/demos/gantt.html
+++ b/demos/gantt.html
@@ -78,7 +78,7 @@
axisFormat %d/%m
todayMarker off
section Section1
- Today: 1, -01:00, 5min
+ Today: 1, 08-08-09-01:00, 5min
@@ -89,7 +89,7 @@
axisFormat %d/%m
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
- Today: 1, -01:00, 5min
+ Today: 1, 08-08-09-01:00, 5min
@@ -166,6 +166,37 @@
+
+ ---
+ 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
+
+
+
```
+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
+
+```
+
### Binding events
Sometimes the generated graph also has defined interactions like tooltip and click events. When using the API one must
diff --git a/docs/ecosystem/integrations.md b/docs/ecosystem/integrations.md
index 3db4a17bc..3a9b7cd74 100644
--- a/docs/ecosystem/integrations.md
+++ b/docs/ecosystem/integrations.md
@@ -20,6 +20,7 @@ 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**)
@@ -88,7 +89,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [FosWiki](https://foswiki.org)
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
- [DokuWiki](https://dokuwiki.org)
- - [Flowcharts](https://www.dokuwiki.org/plugin:flowcharts?s[]=mermaid)
+ - [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
- [ComboStrap](https://combostrap.com/mermaid)
- [TiddlyWiki](https://tiddlywiki.com/)
- [mermaid-tw5: full js library](https://github.com/efurlanm/mermaid-tw5)
@@ -149,7 +150,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [remark-mermaid](https://github.com/temando/remark-mermaid)
- [jSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
-- [MkDocs](https://mkdocs.org)
+- [MkDocs](https://www.mkdocs.org)
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Type Doc](https://typedoc.org/)
diff --git a/docs/news/announcements.md b/docs/news/announcements.md
new file mode 100644
index 000000000..112bde52c
--- /dev/null
+++ b/docs/news/announcements.md
@@ -0,0 +1,13 @@
+> **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
+
+## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here)
+
+3 April 2023 · 3 mins
+
+Markdown Strings reduce the hassle # Starting from v10.
diff --git a/docs/news/blog.md b/docs/news/blog.md
new file mode 100644
index 000000000..dc6f3f635
--- /dev/null
+++ b/docs/news/blog.md
@@ -0,0 +1,31 @@
+> **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
+
+## [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 it’s 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 can’t write # It’s an annoying stereotype that developers don’t know how to write, speak, and otherwise communicate.
diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico
index d41818c5b..05d8a737b 100644
Binary files a/docs/public/favicon.ico and b/docs/public/favicon.ico differ
diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md
index 547847f54..6f5b973e8 100644
--- a/docs/syntax/flowchart.md
+++ b/docs/syntax/flowchart.md
@@ -183,20 +183,6 @@ flowchart LR
### A hexagon node
-Code:
-
-```mermaid-example
-flowchart LR
- id1{{This is the text in the box}}
-```
-
-```mermaid
-flowchart LR
- id1{{This is the text in the box}}
-```
-
-Render:
-
```mermaid-example
flowchart LR
id1{{This is the text in the box}}
@@ -724,6 +710,44 @@ 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 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 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'`.
diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md
index 6a7af3331..091cdeabe 100644
--- a/docs/syntax/gantt.md
+++ b/docs/syntax/gantt.md
@@ -257,9 +257,41 @@ The pattern is:
More info in:
+## 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
@@ -432,3 +464,41 @@ Beginner's tip—a full example using interactive links in an html context:
```
+
+## Examples
+
+### Bar chart (using gantt chart)
+
+```mermaid-example
+gantt
+ title Git Issues - days since last update
+ dateFormat X
+ axisFormat %s
+ section Issue19062
+ 71 : 0, 71
+ section Issue19401
+ 36 : 0, 36
+ section Issue193
+ 34 : 0, 34
+ section Issue7441
+ 9 : 0, 9
+ section Issue1300
+ 5 : 0, 5
+```
+
+```mermaid
+gantt
+ title Git Issues - days since last update
+ dateFormat X
+ axisFormat %s
+ section Issue19062
+ 71 : 0, 71
+ section Issue19401
+ 36 : 0, 36
+ section Issue193
+ 34 : 0, 34
+ section Issue7441
+ 9 : 0, 9
+ section Issue1300
+ 5 : 0, 5
+```
diff --git a/docs/syntax/mindmap.md b/docs/syntax/mindmap.md
index ad8aab77f..9687bbef7 100644
--- a/docs/syntax/mindmap.md
+++ b/docs/syntax/mindmap.md
@@ -224,7 +224,7 @@ mindmap
C
```
-_These classes needs top be supplied by the site administrator._
+_These classes need to be supplied by the site administrator._
## Unclear indentation
@@ -254,6 +254,34 @@ 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 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 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.
diff --git a/docs/syntax/pie.md b/docs/syntax/pie.md
index 63f371e87..8b1de3856 100644
--- a/docs/syntax/pie.md
+++ b/docs/syntax/pie.md
@@ -48,6 +48,7 @@ Drawing a pie chart is really simple in mermaid.
## Example
```mermaid-example
+%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
pie showData
title Key elements in Product X
"Calcium" : 42.96
@@ -57,6 +58,7 @@ pie showData
```
```mermaid
+%%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
pie showData
title Key elements in Product X
"Calcium" : 42.96
@@ -64,3 +66,11 @@ pie showData
"Magnesium" : 10.01
"Iron" : 5
```
+
+## Configuration
+
+Possible pie diagram configuration parameters:
+
+| Parameter | Description | Default value |
+| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
+| `textPosition` | The axial position of the pie slice labels, from 0.0 at the center to 1.0 at the outside edge of the circle. | `0.75` |
diff --git a/docs/syntax/timeline.md b/docs/syntax/timeline.md
index 58b12313d..f3db4bb59 100644
--- a/docs/syntax/timeline.md
+++ b/docs/syntax/timeline.md
@@ -8,7 +8,7 @@
> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
-"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia
+"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia
### An example of a timeline.
@@ -213,7 +213,7 @@ However, if there is no section defined, then we have two possibilities:
```
-Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme.
+Note that there are no sections defined, and each time period and its corresponding events will have its own color scheme.
2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme.
@@ -257,7 +257,7 @@ let us look at same example, where we have disabled the multiColor option.
### Customizing Color scheme
-You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
+You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
In case you have more than 12 sections, the color scheme will start to repeat.
NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
diff --git a/package.json b/package.json
index 8e4c50118..92b979d75 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
"name": "mermaid-monorepo",
"private": true,
- "version": "9.4.0",
+ "version": "10.1.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
- "packageManager": "pnpm@7.27.0",
+ "packageManager": "pnpm@7.30.1",
"keywords": [
"diagram",
"markdown",
@@ -70,9 +70,9 @@
"@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
- "@vitest/coverage-c8": "^0.28.4",
- "@vitest/spy": "^0.28.4",
- "@vitest/ui": "^0.28.4",
+ "@vitest/coverage-c8": "^0.29.0",
+ "@vitest/spy": "^0.29.0",
+ "@vitest/ui": "^0.29.0",
"concurrently": "^7.5.0",
"cors": "^2.8.5",
"coveralls": "^3.1.1",
@@ -109,9 +109,9 @@
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^4.1.1",
- "vitest": "^0.28.5"
+ "vitest": "^0.29.0"
},
"volta": {
- "node": "18.14.0"
+ "node": "18.15.0"
}
}
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 871ff163b..5d020eedd 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -1,6 +1,6 @@
{
"name": "mermaid",
- "version": "10.0.0",
+ "version": "10.1.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,7 +8,8 @@
"exports": {
".": {
"types": "./dist/mermaid.d.ts",
- "import": "./dist/mermaid.core.mjs"
+ "import": "./dist/mermaid.core.mjs",
+ "default": "./dist/mermaid.core.mjs"
},
"./*": "./*"
},
@@ -52,13 +53,14 @@
},
"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.9",
+ "dagre-d3-es": "7.0.10",
"dayjs": "^1.11.7",
- "dompurify": "2.4.3",
+ "dompurify": "2.4.5",
"elkjs": "^0.8.2",
"katex": "^0.15.2",
"khroma": "^2.0.0",
@@ -74,7 +76,7 @@
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.4.0",
"@types/jsdom": "^21.0.0",
- "@types/lodash-es": "^4.17.6",
+ "@types/lodash-es": "^4.17.7",
"@types/micromatch": "^4.0.2",
"@types/prettier": "^2.7.1",
"@types/stylis": "^4.0.2",
diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts
index 1e7539aeb..adb596734 100644
--- a/packages/mermaid/src/Diagram.ts
+++ b/packages/mermaid/src/Diagram.ts
@@ -5,8 +5,14 @@ import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { extractFrontMatter } from './diagram-api/frontmatter';
import { UnknownDiagramError } from './errors';
import { DetailedError } from './utils';
+import { cleanupComments } from './diagram-api/comments';
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
+
+/**
+ * An object representing a parsed mermaid diagram definition.
+ * @privateRemarks This is exported as part of the public mermaidAPI.
+ */
export class Diagram {
type = 'graph';
parser;
@@ -38,7 +44,10 @@ export class Diagram {
// Similarly, we can't do this in getDiagramFromText() because some code
// calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter().
- this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
+
+ this.parser.parse = (text: string) =>
+ originalParse(cleanupComments(extractFrontMatter(text, this.db)));
+
this.parser.parser.yy = this.db;
if (diagram.init) {
diagram.init(cnf);
@@ -68,6 +77,15 @@ 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 => {
const type = detectType(text, configApi.getConfig());
try {
diff --git a/packages/mermaid/src/commonDb.ts b/packages/mermaid/src/commonDb.ts
index 42ffde004..a9ad0f54b 100644
--- a/packages/mermaid/src/commonDb.ts
+++ b/packages/mermaid/src/commonDb.ts
@@ -3,6 +3,7 @@ import { getConfig } from './config';
let title = '';
let diagramTitle = '';
let description = '';
+
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
export const clear = function (): void {
@@ -36,10 +37,10 @@ export const getDiagramTitle = function (): string {
};
export default {
- setAccTitle,
getAccTitle,
+ setAccTitle,
+ getDiagramTitle,
setDiagramTitle,
- getDiagramTitle: getDiagramTitle,
getAccDescription,
setAccDescription,
clear,
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index 39ab3f4d9..545fdbbfb 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -222,7 +222,9 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
maxNodeWidth: number;
}
-export type PieDiagramConfig = BaseDiagramConfig;
+export interface PieDiagramConfig extends BaseDiagramConfig {
+ textPosition?: number;
+}
export interface ErDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
@@ -333,6 +335,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
axisFormat?: string;
tickInterval?: string;
topAxis?: boolean;
+ displayMode?: string;
}
export interface SequenceDiagramConfig extends BaseDiagramConfig {
@@ -383,6 +386,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
curve?: string;
padding?: number;
defaultRenderer?: string;
+ wrappingWidth?: number;
}
export interface FontConfig {
diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js
index 57c3ff513..2b87b91a6 100644
--- a/packages/mermaid/src/dagre-wrapper/clusters.js
+++ b/packages/mermaid/src/dagre-wrapper/clusters.js
@@ -1,12 +1,13 @@
import intersectRect from './intersect/intersect-rect';
import { log } from '../logger';
import createLabel from './createLabel';
+import { createText } from '../rendering-util/createText';
import { select } from 'd3';
import { getConfig } from '../config';
import { evaluate } from '../diagrams/common/common';
const rect = (parent, node) => {
- log.trace('Creating subgraph rect for ', node.id, node);
+ log.info('Creating subgraph rect for ', node.id, node);
// Add outer g element
const shapeSvg = parent
@@ -17,12 +18,18 @@ const rect = (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
+ const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
+
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
- const text = label
- .node()
- .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
+ // const text = label
+ // .node()
+ // .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
+ const text =
+ node.labelType === 'markdown'
+ ? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
+ : label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@@ -56,13 +63,20 @@ 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;
diff --git a/packages/mermaid/src/dagre-wrapper/createLabel.js b/packages/mermaid/src/dagre-wrapper/createLabel.js
index af5032096..ff7834c4f 100644
--- a/packages/mermaid/src/dagre-wrapper/createLabel.js
+++ b/packages/mermaid/src/dagre-wrapper/createLabel.js
@@ -41,7 +41,13 @@ function addHtmlLabel(node) {
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
return fo.node();
}
-
+/**
+ * @param _vertexText
+ * @param style
+ * @param isTitle
+ * @param isNode
+ * @deprecated svg-util/createText instead
+ */
const createLabel = (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') {
diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js
index f8c113694..d4b7d37b8 100644
--- a/packages/mermaid/src/dagre-wrapper/edges.js
+++ b/packages/mermaid/src/dagre-wrapper/edges.js
@@ -1,5 +1,6 @@
import { log } from '../logger';
import createLabel from './createLabel';
+import { createText } from '../rendering-util/createText';
import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config';
import utils from '../utils';
@@ -14,8 +15,17 @@ export const clear = () => {
};
export const insertEdgeLabel = (elem, edge) => {
+ const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the actual text element
- const labelElement = createLabel(edge.label, edge.labelStyle);
+ const labelElement =
+ edge.labelType === 'markdown'
+ ? createText(elem, edge.label, {
+ style: edge.labelStyle,
+ useHtmlLabels,
+ addSvgBackground: true,
+ })
+ : createLabel(edge.label, edge.labelStyle);
+ log.info('abc82', edge, edge.labelType);
// Create outer g, edgeLabel, this will be positioned after graph layout
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@@ -26,7 +36,7 @@ export const insertEdgeLabel = (elem, edge) => {
// Center the label
let bbox = labelElement.getBBox();
- if (evaluate(getConfig().flowchart.htmlLabels)) {
+ if (useHtmlLabels) {
const div = labelElement.children[0];
const dv = select(labelElement);
bbox = div.getBoundingClientRect();
diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js
index ce3ef6014..f569fc8c5 100644
--- a/packages/mermaid/src/dagre-wrapper/index.js
+++ b/packages/mermaid/src/dagre-wrapper/index.js
@@ -14,7 +14,7 @@ import { insertCluster, clear as clearClusters } from './clusters';
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges';
import { log } from '../logger';
-const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
+const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
@@ -35,44 +35,46 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
// to the abstract node and is later used by dagre for the layout
- graph.nodes().forEach(function (v) {
- const node = graph.node(v);
- if (parentCluster !== undefined) {
- const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
- // data.clusterPositioning = true;
- log.info('Setting data for cluster XXX (', v, ') ', data, parentCluster);
- graph.setNode(parentCluster.id, data);
- if (!graph.parent(v)) {
- log.trace('Setting parent', v, parentCluster.id);
- graph.setParent(v, parentCluster.id, data);
+ await Promise.all(
+ graph.nodes().map(async function (v) {
+ const node = graph.node(v);
+ if (parentCluster !== undefined) {
+ const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
+ // data.clusterPositioning = true;
+ log.info('Setting data for cluster XXX (', v, ') ', data, parentCluster);
+ graph.setNode(parentCluster.id, data);
+ if (!graph.parent(v)) {
+ log.trace('Setting parent', v, parentCluster.id);
+ graph.setParent(v, parentCluster.id, data);
+ }
}
- }
- log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
- if (node && node.clusterNode) {
- // const children = graph.children(v);
- log.info('Cluster identified', v, node.width, graph.node(v));
- const o = recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
- const newEl = o.elem;
- updateNodeBounds(node, newEl);
- node.diff = o.diff || 0;
- log.info('Node bounds (abc123)', v, node, node.width, node.x, node.y);
- setNodeElem(newEl, node);
+ log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
+ if (node && node.clusterNode) {
+ // const children = graph.children(v);
+ log.info('Cluster identified', v, node.width, graph.node(v));
+ const o = await recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
+ const newEl = o.elem;
+ updateNodeBounds(node, newEl);
+ node.diff = o.diff || 0;
+ log.info('Node bounds (abc123)', v, node, node.width, node.x, node.y);
+ setNodeElem(newEl, node);
- log.warn('Recursive render complete ', newEl, node);
- } else {
- if (graph.children(v).length > 0) {
- // This is a cluster but not to be rendered recursively
- // Render as before
- log.info('Cluster - the non recursive path XXX', v, node.id, node, graph);
- log.info(findNonClusterChild(node.id, graph));
- clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
- // insertCluster(clusters, graph.node(v));
+ log.warn('Recursive render complete ', newEl, node);
} else {
- log.info('Node - the non recursive path', v, node.id, node);
- insertNode(nodes, graph.node(v), dir);
+ if (graph.children(v).length > 0) {
+ // This is a cluster but not to be rendered recursively
+ // Render as before
+ log.info('Cluster - the non recursive path XXX', v, node.id, node, graph);
+ log.info(findNonClusterChild(node.id, graph));
+ clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
+ // insertCluster(clusters, graph.node(v));
+ } else {
+ log.info('Node - the non recursive path', v, node.id, node);
+ await insertNode(nodes, graph.node(v), dir);
+ }
}
- }
- });
+ })
+ );
// Insert labels, this will insert them into the dom so that the width can be calculated
// Also figure out which edges point to/from clusters and adjust them accordingly
@@ -146,7 +148,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => {
return { elem, diff };
};
-export const render = (elem, graph, markers, diagramtype, id) => {
+export const render = async (elem, graph, markers, diagramtype, id) => {
insertMarkers(elem, markers, diagramtype, id);
clearNodes();
clearEdges();
@@ -157,7 +159,7 @@ export const render = (elem, graph, markers, diagramtype, id) => {
adjustClustersAndEdges(graph);
log.warn('Graph after:', graphlibJson.write(graph));
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
- recursiveRender(elem, graph, diagramtype);
+ await recursiveRender(elem, graph, diagramtype);
};
// const shapeDefinitions = {};
diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js
index 49b96b685..e0eafe032 100644
--- a/packages/mermaid/src/dagre-wrapper/nodes.js
+++ b/packages/mermaid/src/dagre-wrapper/nodes.js
@@ -8,8 +8,8 @@ import note from './shapes/note';
import { parseMember } from '../diagrams/class/svgDraw';
import { evaluate } from '../diagrams/common/common';
-const question = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const question = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -69,8 +69,8 @@ const choice = (parent, node) => {
return shapeSvg;
};
-const hexagon = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const hexagon = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const f = 4;
const h = bbox.height + node.padding;
@@ -96,8 +96,8 @@ const hexagon = (parent, node) => {
return shapeSvg;
};
-const rect_left_inv_arrow = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const rect_left_inv_arrow = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -122,8 +122,8 @@ const rect_left_inv_arrow = (parent, node) => {
return shapeSvg;
};
-const lean_right = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const lean_right = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -145,8 +145,8 @@ const lean_right = (parent, node) => {
return shapeSvg;
};
-const lean_left = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const lean_left = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -168,8 +168,8 @@ const lean_left = (parent, node) => {
return shapeSvg;
};
-const trapezoid = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const trapezoid = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -191,8 +191,8 @@ const trapezoid = (parent, node) => {
return shapeSvg;
};
-const inv_trapezoid = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const inv_trapezoid = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -214,8 +214,8 @@ const inv_trapezoid = (parent, node) => {
return shapeSvg;
};
-const rect_right_inv_arrow = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const rect_right_inv_arrow = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -238,8 +238,8 @@ const rect_right_inv_arrow = (parent, node) => {
return shapeSvg;
};
-const cylinder = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const cylinder = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const rx = w / 2;
@@ -310,13 +310,19 @@ const cylinder = (parent, node) => {
return shapeSvg;
};
-const rect = (parent, node) => {
- const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
+const rect = async (parent, node) => {
+ const { shapeSvg, bbox, halfPadding } = await 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
@@ -324,6 +330,8 @@ 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)
@@ -349,8 +357,8 @@ const rect = (parent, node) => {
return shapeSvg;
};
-const labelRect = (parent, node) => {
- const { shapeSvg } = labelHelper(parent, node, 'label', true);
+const labelRect = async (parent, node) => {
+ const { shapeSvg } = await labelHelper(parent, node, 'label', true);
log.trace('Classes = ', node.classes);
// add the rect
@@ -536,8 +544,8 @@ const rectWithTitle = (parent, node) => {
return shapeSvg;
};
-const stadium = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const stadium = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const h = bbox.height + node.padding;
const w = bbox.width + h / 4 + node.padding;
@@ -562,8 +570,8 @@ const stadium = (parent, node) => {
return shapeSvg;
};
-const circle = (parent, node) => {
- const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, undefined, true);
+const circle = async (parent, node) => {
+ const { shapeSvg, bbox, halfPadding } = await labelHelper(parent, node, undefined, true);
const circle = shapeSvg.insert('circle', ':first-child');
// center the circle around its coordinate
@@ -587,8 +595,8 @@ const circle = (parent, node) => {
return shapeSvg;
};
-const doublecircle = (parent, node) => {
- const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, undefined, true);
+const doublecircle = async (parent, node) => {
+ const { shapeSvg, bbox, halfPadding } = await labelHelper(parent, node, undefined, true);
const gap = 5;
const circleGroup = shapeSvg.insert('g', ':first-child');
const outerCircle = circleGroup.insert('circle');
@@ -623,8 +631,8 @@ const doublecircle = (parent, node) => {
return shapeSvg;
};
-const subroutine = (parent, node) => {
- const { shapeSvg, bbox } = labelHelper(parent, node, undefined, true);
+const subroutine = async (parent, node) => {
+ const { shapeSvg, bbox } = await labelHelper(parent, node, undefined, true);
const w = bbox.width + node.padding;
const h = bbox.height + node.padding;
@@ -973,7 +981,7 @@ const shapes = {
let nodeElems = {};
-export const insertNode = (elem, node, dir) => {
+export const insertNode = async (elem, node, dir) => {
let newEl;
let el;
@@ -986,9 +994,9 @@ export const insertNode = (elem, node, dir) => {
target = node.linkTarget || '_blank';
}
newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target);
- el = shapes[node.shape](newEl, node, dir);
+ el = await shapes[node.shape](newEl, node, dir);
} else {
- el = shapes[node.shape](elem, node, dir);
+ el = await shapes[node.shape](elem, node, dir);
newEl = el;
}
if (node.tooltip) {
@@ -1014,6 +1022,7 @@ export const clear = () => {
export const positionNode = (node) => {
const el = nodeElems[node.id];
+
log.trace(
'Transforming node',
node.diff,
diff --git a/packages/mermaid/src/dagre-wrapper/shapes/note.js b/packages/mermaid/src/dagre-wrapper/shapes/note.js
index 6b693fdf6..415df13e3 100644
--- a/packages/mermaid/src/dagre-wrapper/shapes/note.js
+++ b/packages/mermaid/src/dagre-wrapper/shapes/note.js
@@ -1,9 +1,19 @@
import { updateNodeBounds, labelHelper } from './util';
import { log } from '../../logger';
+import { getConfig } from '../../config';
import intersect from '../intersect/index.js';
-const note = (parent, node) => {
- const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes, true);
+const note = async (parent, node) => {
+ const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart.htmlLabels;
+ if (!useHtmlLabels) {
+ node.centerLabel = true;
+ }
+ const { shapeSvg, bbox, halfPadding } = await labelHelper(
+ parent,
+ node,
+ 'node ' + node.classes,
+ true
+ );
log.info('Classes = ', node.classes);
// add the rect
diff --git a/packages/mermaid/src/dagre-wrapper/shapes/util.js b/packages/mermaid/src/dagre-wrapper/shapes/util.js
index 6de0da638..d6cb4cd8d 100644
--- a/packages/mermaid/src/dagre-wrapper/shapes/util.js
+++ b/packages/mermaid/src/dagre-wrapper/shapes/util.js
@@ -1,10 +1,13 @@
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) => {
+
+export const labelHelper = async (parent, node, _classes, isNode) => {
let classes;
+ const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
if (!_classes) {
classes = 'node default';
} else {
@@ -27,9 +30,17 @@ export const labelHelper = (parent, node, _classes, isNode) => {
labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0];
}
- const text = label
- .node()
- .appendChild(
+ const textNode = label.node();
+ let text;
+ if (node.labelType === 'markdown') {
+ // text = textNode;
+ text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
+ useHtmlLabels,
+ width: node.width || getConfig().flowchart.wrappingWidth,
+ classes: 'markdown-node-label',
+ });
+ } else {
+ text = textNode.appendChild(
createLabel(
sanitizeText(decodeEntities(labelText), getConfig()),
node.labelStyle,
@@ -37,23 +48,61 @@ export const labelHelper = (parent, node, _classes, isNode) => {
isNode
)
);
+ }
// Get the size of the label
let bbox = text.getBBox();
+ const halfPadding = node.padding / 2;
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
+
+ // if there are images, need to wait for them to load before getting the bounding box
+ const images = div.getElementsByTagName('img');
+ if (images) {
+ const noImgText = labelText.replace(/ ]*>/g, '').trim() === '';
+
+ await Promise.all(
+ [...images].map(
+ (img) =>
+ new Promise((res) =>
+ img.addEventListener('load', function () {
+ img.style.display = 'flex';
+ img.style.flexDirection = 'column';
+
+ if (noImgText) {
+ // default size if no text
+ const bodyFontSize = getConfig().fontSize
+ ? getConfig().fontSize
+ : window.getComputedStyle(document.body).fontSize;
+ const enlargingFactor = 5;
+ img.style.width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
+ } else {
+ img.style.width = '100%';
+ }
+ res(img);
+ })
+ )
+ )
+ );
+ }
+
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
- const halfPadding = node.padding / 2;
-
// Center the label
- label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
-
+ 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');
return { shapeSvg, bbox, halfPadding, label };
};
diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts
index ec741e908..2ca995251 100644
--- a/packages/mermaid/src/defaultConfig.ts
+++ b/packages/mermaid/src/defaultConfig.ts
@@ -258,6 +258,18 @@ const config: Partial = {
* Default value: 'dagre-wrapper'
*/
defaultRenderer: 'dagre-wrapper',
+ /**
+ * | Parameter | Description | Type | Required | Values |
+ * | --------------- | ----------- | ------- | -------- | ----------------------- |
+ * | wrappingWidth | See notes | number | 4 | width of nodes where text is wrapped |
+ *
+ * **Notes:**
+ *
+ * When using markdown strings the text ius wrapped automatically, this
+ * value sets the max width of a text before it continues on a new line.
+ * Default value: 'dagre-wrapper'
+ */
+ wrappingWidth: 200,
},
/** The object containing configurations specific for sequence diagrams */
@@ -659,6 +671,17 @@ const config: Partial = {
*/
numberSectionStyles: 4,
+ /**
+ * | Parameter | Description | Type | Required | Values |
+ * | ----------- | ------------------------- | ------ | -------- | --------- |
+ * | displayMode | Controls the display mode | string | 4 | 'compact' |
+ *
+ * **Notes**:
+ *
+ * - **compact**: Enables displaying multiple tasks on the same row.
+ */
+ displayMode: '',
+
/**
* | Parameter | Description | Type | Required | Values |
* | ---------- | ---------------------------- | ---- | -------- | ---------------- |
@@ -684,7 +707,6 @@ const config: Partial = {
* Default value: undefined
*/
tickInterval: undefined,
-
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
@@ -1247,6 +1269,15 @@ const config: Partial = {
* 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 */
diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts
new file mode 100644
index 000000000..a2c896079
--- /dev/null
+++ b/packages/mermaid/src/diagram-api/comments.spec.ts
@@ -0,0 +1,94 @@
+// tests to check that comments are removed
+
+import { cleanupComments } from './comments';
+import { describe, it, expect } from 'vitest';
+
+describe('comments', () => {
+ it('should remove comments', () => {
+ const text = `
+
+%% This is a comment
+%% This is another comment
+graph TD
+ A-->B
+%% This is a comment
+`;
+ expect(cleanupComments(text)).toMatchInlineSnapshot(`
+ "graph TD
+ A-->B
+ "
+ `);
+ });
+
+ it('should keep init statements when removing comments', () => {
+ const text = `
+%% This is a comment
+
+%% This is another comment
+%%{init: {'theme': 'forest'}}%%
+%%{ init: {'theme': 'space before init'}}%%
+%%{init: {'theme': 'space after ending'}}%%
+graph TD
+ A-->B
+
+ B-->C
+%% This is a comment
+`;
+ expect(cleanupComments(text)).toMatchInlineSnapshot(`
+ "%%{init: {'theme': 'forest'}}%%
+ %%{ init: {'theme': 'space before init'}}%%
+ %%{init: {'theme': 'space after ending'}}%%
+ graph TD
+ A-->B
+
+ B-->C
+ "
+ `);
+ });
+
+ it('should remove indented comments', () => {
+ const text = `
+%% This is a comment
+graph TD
+ A-->B
+ %% This is a comment
+ C-->D
+`;
+ expect(cleanupComments(text)).toMatchInlineSnapshot(`
+ "graph TD
+ A-->B
+ C-->D
+ "
+ `);
+ });
+
+ it('should remove empty newlines from start', () => {
+ const text = `
+
+
+
+
+%% This is a comment
+graph TD
+ A-->B
+`;
+ expect(cleanupComments(text)).toMatchInlineSnapshot(`
+ "graph TD
+ A-->B
+ "
+ `);
+ });
+
+ it('should remove comments at end of text with no newline', () => {
+ const text = `
+graph TD
+ A-->B
+%% This is a comment`;
+
+ expect(cleanupComments(text)).toMatchInlineSnapshot(`
+ "graph TD
+ A-->B
+ "
+ `);
+ });
+});
diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts
new file mode 100644
index 000000000..be39b0a0f
--- /dev/null
+++ b/packages/mermaid/src/diagram-api/comments.ts
@@ -0,0 +1,8 @@
+/**
+ * Remove all lines starting with `%%` from the text that don't contain a `%%{`
+ * @param text - The text to remove comments from
+ * @returns cleaned text
+ */
+export const cleanupComments = (text: string): string => {
+ return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
+};
diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
index 73bfcf084..a88a34f19 100644
--- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts
+++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
@@ -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 error from '../diagrams/error/errorDetector';
+import errorDiagram from '../diagrams/error/errorDiagram';
import flowchartElk from '../diagrams/flowchart/elk/detector';
import timeline from '../diagrams/timeline/detector';
import mindmap from '../diagrams/mindmap/detector';
@@ -28,6 +28,9 @@ 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
@@ -57,7 +60,6 @@ export const addDiagrams = () => {
);
// Ordering of detectors is important. The first one to return true will be used.
registerLazyLoadedDiagrams(
- error,
c4,
classDiagramV2,
classDiagram,
diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts
index d6811388c..a9c4995e6 100644
--- a/packages/mermaid/src/diagram-api/frontmatter.ts
+++ b/packages/mermaid/src/diagram-api/frontmatter.ts
@@ -11,6 +11,8 @@ export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
type FrontMatterMetadata = {
title?: string;
+ // Allows custom display modes. Currently used for compact mode in gantt charts.
+ displayMode?: string;
};
/**
@@ -33,6 +35,10 @@ export function extractFrontMatter(text: string, db: DiagramDb): string {
db.setDiagramTitle?.(parsed.title);
}
+ if (parsed?.displayMode) {
+ db.setDisplayMode?.(parsed.displayMode);
+ }
+
return text.slice(matches[0].length);
} else {
return text;
diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts
index 081136563..8c5d14eb8 100644
--- a/packages/mermaid/src/diagram-api/types.ts
+++ b/packages/mermaid/src/diagram-api/types.ts
@@ -16,6 +16,7 @@ export interface InjectUtils {
export interface DiagramDb {
clear?: () => void;
setDiagramTitle?: (title: string) => void;
+ setDisplayMode?: (title: string) => void;
getAccTitle?: () => string;
getAccDescription?: () => string;
bindFunctions?: (element: Element) => void;
diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts
index 5ff193186..24fbc41ea 100644
--- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts
+++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts
@@ -190,6 +190,37 @@ describe('class diagram, ', function () {
parser.parse(str);
});
+ it('should handle cssClass shorthand with members', () => {
+ parser.parse(`classDiagram-v2
+ class Class10:::exClass2 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }`);
+
+ expect(classDb.getClass('Class10')).toMatchInlineSnapshot(`
+ {
+ "annotations": [],
+ "cssClasses": [
+ "exClass2",
+ ],
+ "domId": "classId-Class10-27",
+ "id": "Class10",
+ "label": "Class10",
+ "members": [
+ "int[] id",
+ "List~int~ ids",
+ ],
+ "methods": [
+ "test(List~int~ ids) List~bool~",
+ "testArray() bool[]",
+ ],
+ "type": "",
+ }
+ `);
+ });
+
it('should handle method statements', function () {
const str =
'classDiagram\n' +
@@ -1023,6 +1054,7 @@ 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');
});
@@ -1038,6 +1070,7 @@ 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');
});
diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
index e308990c6..e2cd3ddf7 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
+++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
@@ -248,7 +248,7 @@ export const setConf = function (cnf: any) {
* @param _version -
* @param diagObj -
*/
-export const draw = function (text: string, id: string, _version: string, diagObj: any) {
+export const draw = async function (text: string, id: string, _version: string, diagObj: any) {
log.info('Drawing class - ', id);
// TODO V10: Why flowchart? Might be a mistake when copying.
@@ -300,7 +300,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb
// Run the renderer. This is what draws the final graph.
// @ts-ignore Ignore type error for now
const element = root.select('#' + id + ' g');
- render(
+ await render(
element,
g,
['aggregation', 'extension', 'composition', 'dependency', 'lollipop'],
diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
index e98b0253b..0c9ad2f2a 100644
--- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
+++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
@@ -283,7 +283,7 @@ 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,$3);}
+ | classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
;
classIdentifier
diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
index d9f03c387..8ffa87c63 100644
--- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
+++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
@@ -19,8 +19,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
":" { this.popState(); this.begin('arg_directive'); return ':'; }
\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
((?:(?!\}\%\%).|\n)*) return 'arg_directive';
-\%%(?!\{)[^\n]* /* skip comments */
-[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
[\s]+ return 'SPACE';
@@ -35,8 +33,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
[A-Za-z_][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD'
\"[^"]*\" return 'COMMENT';
[\n]+ /* nothing */
-\%%(?!\{)[^\n]* /* skip comments in attribute block */
-[^\}]\%\%[^\n]* /* skip comments in attribute block */
"}" { this.popState(); return 'BLOCK_STOP'; }
. return yytext[0];
diff --git a/packages/mermaid/src/diagrams/error/errorDetector.ts b/packages/mermaid/src/diagrams/error/errorDetector.ts
deleted file mode 100644
index 2bdcd7028..000000000
--- a/packages/mermaid/src/diagrams/error/errorDetector.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-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;
diff --git a/packages/mermaid/src/diagrams/error/errorDiagram.ts b/packages/mermaid/src/diagrams/error/errorDiagram.ts
index d081e1028..7b9f18c3b 100644
--- a/packages/mermaid/src/diagrams/error/errorDiagram.ts
+++ b/packages/mermaid/src/diagrams/error/errorDiagram.ts
@@ -19,3 +19,5 @@ export const diagram: DiagramDefinition = {
// no op
},
};
+
+export default diagram;
diff --git a/packages/mermaid/src/diagrams/error/errorRenderer.ts b/packages/mermaid/src/diagrams/error/errorRenderer.ts
index db4000558..589abae44 100644
--- a/packages/mermaid/src/diagrams/error/errorRenderer.ts
+++ b/packages/mermaid/src/diagrams/error/errorRenderer.ts
@@ -4,15 +4,13 @@ import { select } from 'd3';
import { log } from '../../logger';
import { getErrorMessage } from '../../utils';
-let conf = {};
-
/**
* Merges the value of `conf` with the passed `cnf`
*
* @param cnf - Config to merge
*/
-export const setConf = function (cnf: any) {
- conf = { ...conf, ...cnf };
+export const setConf = function () {
+ // no-op
};
/**
diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
index b8458efeb..efadb4199 100644
--- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
+++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
@@ -3,16 +3,17 @@ import { insertNode } from '../../../dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils';
+import { labelHelper } from '../../../dagre-wrapper/shapes/util';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { getConfig } from '../../../config';
import { log } from '../../../logger';
import { setupGraphViewbox } from '../../../setupGraphViewbox';
import common, { evaluate } from '../../common/common';
import { interpolateToCurve, getStylesFromArray } from '../../../utils';
+import ELK from 'elkjs/lib/elk.bundled.js';
+const elk = new ELK();
-let elk;
-
-const portPos = {};
+let portPos = {};
const conf = {};
export const setConf = function (cnf) {
@@ -34,238 +35,231 @@ let nodeDb = {};
// * @param doc
// * @param diagObj
// */
-export const addVertices = function (vert, svgId, root, doc, diagObj, parentLookupDb, graph) {
+export const addVertices = async function (vert, svgId, root, doc, diagObj, parentLookupDb, graph) {
const svg = root.select(`[id="${svgId}"]`);
const nodes = svg.insert('g').attr('class', 'nodes');
const keys = Object.keys(vert);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
- keys.forEach(function (id) {
- const vertex = vert[id];
+ await Promise.all(
+ keys.map(async function (id) {
+ const vertex = vert[id];
- /**
- * Variable for storing the classes for the vertex
- *
- * @type {string}
- */
- let classStr = 'default';
- if (vertex.classes.length > 0) {
- classStr = vertex.classes.join(' ');
- }
-
- const styles = getStylesFromArray(vertex.styles);
-
- // Use vertex id as text in the box if no text is provided by the graph definition
- let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
-
- // We create a SVG label, either by delegating to addHtmlLabel or manually
- let vertexNode;
- 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) => ` `
- ),
- };
- 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);
+ /**
+ * Variable for storing the classes for the vertex
+ *
+ * @type {string}
+ */
+ let classStr = 'default';
+ if (vertex.classes.length > 0) {
+ classStr = vertex.classes.join(' ');
}
- vertexNode = svgLabel;
- const bbox = vertexNode.getBBox();
- labelData.width = bbox.width;
- labelData.height = bbox.height;
- labelData.labelNode = vertexNode;
- }
+ classStr = classStr + ' flowchart-label';
+ const styles = getStylesFromArray(vertex.styles);
- const ports = [
- {
- id: vertex.id + '-west',
- layoutOptions: {
- 'port.side': 'WEST',
- },
- },
- {
- id: vertex.id + '-east',
- layoutOptions: {
- 'port.side': 'EAST',
- },
- },
- {
- id: vertex.id + '-south',
- layoutOptions: {
- 'port.side': 'SOUTH',
- },
- },
- {
- id: vertex.id + '-north',
- layoutOptions: {
- 'port.side': 'NORTH',
- },
- },
- ];
+ // 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;
- let radious = 0;
- let _shape = '';
- let layoutOptions = {};
- // Set the shape based parameters
- switch (vertex.type) {
- case 'round':
- radious = 5;
- _shape = 'rect';
- break;
- case 'square':
- _shape = 'rect';
- break;
- case 'diamond':
- _shape = 'question';
- layoutOptions = {
- portConstraints: 'FIXED_SIDE',
- };
- break;
- case 'hexagon':
- _shape = 'hexagon';
- break;
- case 'odd':
- _shape = 'rect_left_inv_arrow';
- break;
- case 'lean_right':
- _shape = 'lean_right';
- break;
- case 'lean_left':
- _shape = 'lean_left';
- break;
- case 'trapezoid':
- _shape = 'trapezoid';
- break;
- case 'inv_trapezoid':
- _shape = 'inv_trapezoid';
- break;
- case 'odd_right':
- _shape = 'rect_left_inv_arrow';
- break;
- case 'circle':
- _shape = 'circle';
- break;
- case 'ellipse':
- _shape = 'ellipse';
- break;
- case 'stadium':
- _shape = 'stadium';
- break;
- case 'subroutine':
- _shape = 'subroutine';
- break;
- case 'cylinder':
- _shape = 'cylinder';
- break;
- case 'group':
- _shape = 'rect';
- break;
- case 'doublecircle':
- _shape = 'doublecircle';
- break;
- default:
- _shape = 'rect';
- }
- // Add the node
- const node = {
- labelStyle: styles.labelStyle,
- shape: _shape,
- labelText: vertexText,
- rx: radious,
- ry: radious,
- class: classStr,
- style: styles.style,
- id: vertex.id,
- link: vertex.link,
- linkTarget: vertex.linkTarget,
- tooltip: diagObj.db.getTooltip(vertex.id) || '',
- domId: diagObj.db.lookUpDomId(vertex.id),
- haveCallback: vertex.haveCallback,
- width: vertex.type === 'group' ? 500 : undefined,
- dir: vertex.dir,
- type: vertex.type,
- props: vertex.props,
- padding: getConfig().flowchart.padding,
- };
- let boundingBox;
- let nodeEl;
- if (node.type !== 'group') {
- nodeEl = insertNode(nodes, node, vertex.dir);
- boundingBox = nodeEl.node().getBBox();
- }
+ // We create a SVG label, either by delegating to addHtmlLabel or manually
+ let vertexNode;
+ const labelData = { width: 0, height: 0 };
- const data = {
- id: vertex.id,
- ports: vertex.type === 'diamond' ? ports : [],
- // labelStyle: styles.labelStyle,
- // shape: _shape,
- layoutOptions,
- labelText: vertexText,
- labelData,
- // labels: [{ text: vertexText }],
- // rx: radius,
- // ry: radius,
- // class: classStr,
- // style: styles.style,
- // link: vertex.link,
- // linkTarget: vertex.linkTarget,
- // tooltip: diagObj.db.getTooltip(vertex.id) || '',
- domId: diagObj.db.lookUpDomId(vertex.id),
- // haveCallback: vertex.haveCallback,
- width: boundingBox?.width,
- height: boundingBox?.height,
- // dir: vertex.dir,
- type: vertex.type,
- // props: vertex.props,
- // padding: getConfig().flowchart.padding,
- // boundingBox,
- el: nodeEl,
- parent: parentLookupDb.parentById[vertex.id],
- };
- // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
- // graph.children.push({
- // ...data,
- // });
- // }
- nodeDb[node.id] = data;
- // log.trace('setNode', {
- // labelStyle: styles.labelStyle,
- // shape: _shape,
- // labelText: vertexText,
- // rx: radius,
- // ry: radius,
- // class: classStr,
- // style: styles.style,
- // id: vertex.id,
- // domId: diagObj.db.lookUpDomId(vertex.id),
- // width: vertex.type === 'group' ? 500 : undefined,
- // type: vertex.type,
- // dir: vertex.dir,
- // props: vertex.props,
- // padding: getConfig().flowchart.padding,
- // parent: parentLookupDb.parentById[vertex.id],
- // });
- });
+ const ports = [
+ {
+ id: vertex.id + '-west',
+ layoutOptions: {
+ 'port.side': 'WEST',
+ },
+ },
+ {
+ id: vertex.id + '-east',
+ layoutOptions: {
+ 'port.side': 'EAST',
+ },
+ },
+ {
+ id: vertex.id + '-south',
+ layoutOptions: {
+ 'port.side': 'SOUTH',
+ },
+ },
+ {
+ id: vertex.id + '-north',
+ layoutOptions: {
+ 'port.side': 'NORTH',
+ },
+ },
+ ];
+
+ let radious = 0;
+ let _shape = '';
+ let layoutOptions = {};
+ // Set the shape based parameters
+ switch (vertex.type) {
+ case 'round':
+ radious = 5;
+ _shape = 'rect';
+ break;
+ case 'square':
+ _shape = 'rect';
+ break;
+ case 'diamond':
+ _shape = 'question';
+ layoutOptions = {
+ portConstraints: 'FIXED_SIDE',
+ };
+ break;
+ case 'hexagon':
+ _shape = 'hexagon';
+ break;
+ case 'odd':
+ _shape = 'rect_left_inv_arrow';
+ break;
+ case 'lean_right':
+ _shape = 'lean_right';
+ break;
+ case 'lean_left':
+ _shape = 'lean_left';
+ break;
+ case 'trapezoid':
+ _shape = 'trapezoid';
+ break;
+ case 'inv_trapezoid':
+ _shape = 'inv_trapezoid';
+ break;
+ case 'odd_right':
+ _shape = 'rect_left_inv_arrow';
+ break;
+ case 'circle':
+ _shape = 'circle';
+ break;
+ case 'ellipse':
+ _shape = 'ellipse';
+ break;
+ case 'stadium':
+ _shape = 'stadium';
+ break;
+ case 'subroutine':
+ _shape = 'subroutine';
+ break;
+ case 'cylinder':
+ _shape = 'cylinder';
+ break;
+ case 'group':
+ _shape = 'rect';
+ break;
+ case 'doublecircle':
+ _shape = 'doublecircle';
+ break;
+ default:
+ _shape = 'rect';
+ }
+
+ // Add the node
+ const node = {
+ labelStyle: styles.labelStyle,
+ shape: _shape,
+ labelText: vertexText,
+ labelType: vertex.labelType,
+ rx: radious,
+ ry: radious,
+ class: classStr,
+ style: styles.style,
+ id: vertex.id,
+ link: vertex.link,
+ linkTarget: vertex.linkTarget,
+ tooltip: diagObj.db.getTooltip(vertex.id) || '',
+ domId: diagObj.db.lookUpDomId(vertex.id),
+ haveCallback: vertex.haveCallback,
+ width: vertex.type === 'group' ? 500 : undefined,
+ dir: vertex.dir,
+ type: vertex.type,
+ props: vertex.props,
+ padding: getConfig().flowchart.padding,
+ };
+ 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 } = await 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 } = await labelHelper(svg, node, undefined, true);
+
+ const data = {
+ id: vertex.id,
+ ports: vertex.type === 'diamond' ? ports : [],
+ // labelStyle: styles.labelStyle,
+ // shape: _shape,
+ layoutOptions,
+ labelText: vertexText,
+ labelData,
+ // labels: [{ text: vertexText }],
+ // rx: radius,
+ // ry: radius,
+ // class: classStr,
+ // style: styles.style,
+ // link: vertex.link,
+ // linkTarget: vertex.linkTarget,
+ // tooltip: diagObj.db.getTooltip(vertex.id) || '',
+ domId: diagObj.db.lookUpDomId(vertex.id),
+ // haveCallback: vertex.haveCallback,
+ width: boundingBox?.width,
+ height: boundingBox?.height,
+ // dir: vertex.dir,
+ type: vertex.type,
+ // props: vertex.props,
+ // padding: getConfig().flowchart.padding,
+ // boundingBox,
+ el: nodeEl,
+ parent: parentLookupDb.parentById[vertex.id],
+ };
+ // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
+ // graph.children.push({
+ // ...data,
+ // });
+ // }
+ nodeDb[node.id] = data;
+ // log.trace('setNode', {
+ // labelStyle: styles.labelStyle,
+ // shape: _shape,
+ // labelText: vertexText,
+ // rx: radius,
+ // ry: radius,
+ // class: classStr,
+ // style: styles.style,
+ // id: vertex.id,
+ // domId: diagObj.db.lookUpDomId(vertex.id),
+ // width: vertex.type === 'group' ? 500 : undefined,
+ // type: vertex.type,
+ // dir: vertex.dir,
+ // props: vertex.props,
+ // padding: getConfig().flowchart.padding,
+ // parent: parentLookupDb.parentById[vertex.id],
+ // });
+ })
+ );
return graph;
};
@@ -371,6 +365,10 @@ const getEdgeStartEndPoint = (edge, dir) => {
let source = edge.start;
let target = edge.end;
+ // Save the original source and target
+ const sourceId = source;
+ const targetId = target;
+
const startNode = nodeDb[source];
const endNode = nodeDb[target];
@@ -387,7 +385,7 @@ const getEdgeStartEndPoint = (edge, dir) => {
}
// Add the edge to the graph
- return { source, target };
+ return { source, target, sourceId, targetId };
};
/**
@@ -516,7 +514,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
edgeData.labelpos = 'c';
}
- edgeData.labelType = 'text';
+ edgeData.labelType = edge.labelType;
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
@@ -530,14 +528,17 @@ export const addEdges = function (edges, diagObj, graph, svg) {
const labelEl = insertEdgeLabel(labelsEl, edgeData);
- // calculate start and end points of the edge
- const { source, target } = getEdgeStartEndPoint(edge, dir);
+ // calculate start and end points of the edge, note that the source and target
+ // can be modified for shapes that have ports
+ const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
log.debug('abc78 source and target', source, target);
// Add the edge to the graph
graph.edges.push({
id: 'e' + edge.start + edge.end,
sources: [source],
targets: [target],
+ sourceId,
+ targetId,
labelEl: labelEl,
labels: [
{
@@ -698,7 +699,7 @@ const calcOffset = function (src, dest, parentLookupDb) {
};
const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
- const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb);
+ const offset = calcOffset(edge.sourceId, edge.targetId, parentLookupDb);
const src = edge.sections[0].startPoint;
const dest = edge.sections[0].endPoint;
@@ -765,13 +766,10 @@ const insertChildren = (nodeArray, parentLookupDb) => {
*/
export const draw = async function (text, id, _version, diagObj) {
- if (!elk) {
- const ELK = (await import('elkjs/lib/elk.bundled.js')).default;
- elk = new ELK();
- }
// Add temporary render element
diagObj.db.clear();
nodeDb = {};
+ portPos = {};
diagObj.db.setGen('gen-2');
// Parse the graph definition
diagObj.parser.parse(text);
@@ -842,9 +840,17 @@ export const draw = async function (text, id, _version, diagObj) {
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
- diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
+ diagObj.db.addVertex(
+ subG.id,
+ { text: subG.title, type: subG.labelType },
+ 'group',
+ undefined,
+ subG.classes,
+ subG.dir
+ );
}
+ // debugger;
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
@@ -857,7 +863,7 @@ export const draw = async function (text, id, _version, diagObj) {
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
- graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
+ graph = await 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');
@@ -884,6 +890,8 @@ export const draw = async function (text, id, _version, diagObj) {
},
width: node.labelData.width,
height: node.labelData.height,
+ // width: 100,
+ // height: 100,
},
];
delete node.x;
@@ -892,6 +900,7 @@ export const draw = async function (text, id, _version, diagObj) {
delete node.height;
}
});
+
insertChildren(graph.children, parentLookupDb);
log.info('after layout', JSON.stringify(graph, null, 2));
const g = await elk.layout(graph);
@@ -927,9 +936,12 @@ 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}, ${node.labels[0].y + relY + node.y})`
+ `translate(${node.labels[0].x + relX + node.x + labelCentering}, ${
+ node.labels[0].y + relY + node.y + 3
+ })`
);
label.node().appendChild(node.labelData.labelNode);
diff --git a/packages/mermaid/src/diagrams/flowchart/elk/styles.ts b/packages/mermaid/src/diagrams/flowchart/elk/styles.ts
index e8e7065a0..60659df45 100644
--- a/packages/mermaid/src/diagrams/flowchart/elk/styles.ts
+++ b/packages/mermaid/src/diagrams/flowchart/elk/styles.ts
@@ -81,7 +81,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
.edgeLabel {
background-color: ${options.edgeLabelBackground};
rect {
- opacity: 0.5;
+ opacity: 0.85;
background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground};
}
@@ -132,6 +132,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
// fill:#ccc;
// // stroke:black;
// }
+
+ .flowchart-label text {
+ text-anchor: middle;
+ }
+
${genSections(options)}
`;
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js
index 31196ba96..85dc5337e 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js
@@ -59,13 +59,14 @@ export const lookUpDomId = function (id) {
*
* @param _id
* @param text
+ * @param textObj
* @param type
* @param style
* @param classes
* @param dir
* @param props
*/
-export const addVertex = function (_id, text, type, style, classes, dir, props = {}) {
+export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) {
let txt;
let id = _id;
if (id === undefined) {
@@ -80,16 +81,17 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
if (vertices[id] === undefined) {
vertices[id] = {
id: id,
+ labelType: 'text',
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter,
styles: [],
classes: [],
};
}
vertexCounter++;
- if (text !== undefined) {
+ if (textObj !== undefined) {
config = configApi.getConfig();
- txt = sanitizeText(text.trim());
-
+ txt = sanitizeText(textObj.text.trim());
+ vertices[id].labelType = textObj.type;
// strip quotes if string starts and ends with a quote
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
txt = txt.substring(1, txt.length - 1);
@@ -131,24 +133,27 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
* @param _end
* @param type
* @param linkText
+ * @param linkTextObj
*/
-export const addSingleLink = function (_start, _end, type, linkText) {
+export const addSingleLink = function (_start, _end, type) {
let start = _start;
let end = _end;
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
// log.info('Got edge...', start, end);
- const edge = { start: start, end: end, type: undefined, text: '' };
- linkText = type.text;
+ const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
+ log.info('abc78 Got edge...', edge);
+ const linkTextObj = type.text;
- if (linkText !== undefined) {
- edge.text = sanitizeText(linkText.trim());
+ if (linkTextObj !== undefined) {
+ edge.text = sanitizeText(linkTextObj.text.trim());
// strip quotes if string starts and ends with a quote
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
edge.text = edge.text.substring(1, edge.text.length - 1);
}
+ edge.labelType = linkTextObj.type;
}
if (type !== undefined) {
@@ -158,11 +163,12 @@ export const addSingleLink = function (_start, _end, type, linkText) {
}
edges.push(edge);
};
-export const addLink = function (_start, _end, type, linktext) {
+export const addLink = function (_start, _end, type) {
+ log.info('addLink (abc78)', _start, _end, type);
let i, j;
for (i = 0; i < _start.length; i++) {
for (j = 0; j < _end.length; j++) {
- addSingleLink(_start[i], _end[j], type, linktext);
+ addSingleLink(_start[i], _end[j], type);
}
}
};
@@ -457,10 +463,9 @@ export const defaultStyle = function () {
* @param _title
*/
export const addSubGraph = function (_id, list, _title) {
- // console.log('addSubGraph', _id, list, _title);
- let id = _id.trim();
- let title = _title;
- if (_id === _title && _title.match(/\s/)) {
+ let id = _id.text.trim();
+ let title = _title.text;
+ if (_id === _title && _title.text.match(/\s/)) {
id = undefined;
}
/** @param a */
@@ -502,7 +507,14 @@ export const addSubGraph = function (_id, list, _title) {
title = title || '';
title = sanitizeText(title);
subCount = subCount + 1;
- const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [], dir };
+ const subGraph = {
+ id: id,
+ nodes: nodeList,
+ title: title.trim(),
+ classes: [],
+ dir,
+ labelType: _title.type,
+ };
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
index 4c26153a5..b8e7efeb7 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
@@ -48,7 +48,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
@@ -56,35 +56,40 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
- if (evaluate(getConfig().flowchart.htmlLabels)) {
- // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
- const node = {
- label: vertexText
- .replace(/fa[blrs]?:fa-[\w-]+/g, (s) => ` `)
- .replace(/\$\$(.*)\$\$/g, (r, c) =>
- katex
- .renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
- .replace(/\n/g, ' ')
- .replace(//g, '')
- ),
- };
- vertexNode = addHtmlLabel(svg, node).node();
- vertexNode.parentNode.removeChild(vertexNode);
+ log.info('vertex', vertex, vertex.labelType);
+ if (vertex.labelType === 'markdown') {
+ log.info('vertex', vertex, vertex.labelType);
} else {
- const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
- svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
+ if (evaluate(getConfig().flowchart.htmlLabels)) {
+ // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
+ const node = {
+ label: vertexText
+ .replace(/fa[blrs]?:fa-[\w-]+/g, (s) => ` `)
+ .replace(/\$\$(.*)\$\$/g, (r, c) =>
+ katex
+ .renderToString(c, { throwOnError: true, displayMode: true, output: 'mathml' })
+ .replace(/\n/g, ' ')
+ .replace(//g, '')
+ ),
+ };
+ vertexNode = addHtmlLabel(svg, node).node();
+ vertexNode.parentNode.removeChild(vertexNode);
+ } else {
+ const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
+ svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
- const rows = vertexText.split(common.lineBreakRegex);
+ const rows = vertexText.split(common.lineBreakRegex);
- for (const row of rows) {
- const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
- tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
- tspan.setAttribute('dy', '1em');
- tspan.setAttribute('x', '1');
- tspan.textContent = row;
- svgLabel.appendChild(tspan);
+ for (const row of rows) {
+ const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
+ tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
+ tspan.setAttribute('dy', '1em');
+ tspan.setAttribute('x', '1');
+ tspan.textContent = row;
+ svgLabel.appendChild(tspan);
+ }
+ vertexNode = svgLabel;
}
- vertexNode = svgLabel;
}
let radious = 0;
@@ -156,7 +161,8 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
shape: _shape,
- labelText,
+ labelText: vertexText,
+ labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
@@ -176,6 +182,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) {
log.info('setNode', {
labelStyle: styles.labelStyle,
+ labelType: vertex.labelType,
shape: _shape,
labelText,
rx: radious,
@@ -323,7 +330,7 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.labelpos = 'c';
}
- edgeData.labelType = 'text';
+ edgeData.labelType = edge.labelType;
edgeData.label = edge.text
.replace(common.lineBreakRegex, '\n')
.replace(/\$\$(.*)\$\$/g, (r, c) =>
@@ -373,7 +380,7 @@ export const getClasses = function (text, diagObj) {
* @param id
*/
-export const draw = function (text, id, _version, diagObj) {
+export const draw = async function (text, id, _version, diagObj) {
log.info('Drawing flowchart');
diagObj.db.clear();
flowDb.setGen('gen-2');
@@ -423,7 +430,14 @@ export const draw = function (text, id, _version, diagObj) {
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
log.info('Subgraph - ', subG);
- diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
+ diagObj.db.addVertex(
+ subG.id,
+ { text: subG.title, type: subG.labelType },
+ 'group',
+ undefined,
+ subG.classes,
+ subG.dir
+ );
}
// Fetch the vertices/nodes and edges/links from the parsed graph definition
@@ -455,7 +469,7 @@ export const draw = function (text, id, _version, diagObj) {
// Run the renderer. This is what draws the final graph.
const element = root.select('#' + id + ' g');
- render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
+ await render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
index 7aeed304c..cb1e4a923 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
@@ -1,6 +1,7 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
+import { cleanupComments } from '../../../diagram-api/comments';
setConfig({
securityLevel: 'strict',
@@ -13,7 +14,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments', function () {
- const res = flow.parser.parse('graph TD;\n%% Comment\n A-->B;');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -28,7 +29,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the start', function () {
- const res = flow.parser.parse('%% Comment\ngraph TD;\n A-->B;');
+ const res = flow.parser.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -43,7 +44,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end', function () {
- const res = flow.parser.parse('graph TD;\n A-->B\n %% Comment at the end\n');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n %% Comment at the end\n'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -58,7 +59,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end no trailing newline', function () {
- const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -73,7 +74,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end many trailing newlines', function () {
- const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment\n\n\n');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -88,7 +89,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle no trailing newlines', function () {
- const res = flow.parser.parse('graph TD;\n A-->B');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -103,7 +104,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle many trailing newlines', function () {
- const res = flow.parser.parse('graph TD;\n A-->B\n\n');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n\n'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -118,7 +119,7 @@ describe('[Comments] when parsing', () => {
});
it('should handle a comment with blank rows in-between', function () {
- const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B;');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
@@ -134,7 +135,9 @@ describe('[Comments] when parsing', () => {
it('should handle a comment with mermaid flowchart code in them', function () {
const res = flow.parser.parse(
- 'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line edge comment|ro;\n A-->B;'
+ cleanupComments(
+ 'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line edge comment|ro;\n A-->B;'
+ )
);
const vert = flow.parser.yy.getVertices();
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
new file mode 100644
index 000000000..005d257e0
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
@@ -0,0 +1,64 @@
+import flowDb from '../flowDb';
+import flow from './flow';
+import { setConfig } from '../../../config';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('parsing a flow chart with markdown strings', function () {
+ beforeEach(function () {
+ flow.parser.yy = flowDb;
+ flow.parser.yy.clear();
+ });
+
+ it('mardown formatting in nodes and labels', function () {
+ const res = flow.parser.parse(`flowchart
+A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in the hog"] -- "The rat in the mat" -->C;`);
+
+ const vert = flow.parser.yy.getVertices();
+ const edges = flow.parser.yy.getEdges();
+
+ expect(vert['A'].id).toBe('A');
+ expect(vert['A'].text).toBe('The cat in **the** hat');
+ expect(vert['A'].labelType).toBe('markdown');
+ expect(vert['B'].id).toBe('B');
+ expect(vert['B'].text).toBe('The dog in the hog');
+ expect(vert['B'].labelType).toBe('text');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_point');
+ expect(edges[0].text).toBe('The *bat* in the chat');
+ expect(edges[0].labelType).toBe('markdown');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ expect(edges[1].type).toBe('arrow_point');
+ expect(edges[1].text).toBe('The rat in the mat');
+ expect(edges[1].labelType).toBe('text');
+ });
+ it('mardown formatting in subgraphs', function () {
+ const res = flow.parser.parse(`flowchart LR
+subgraph "One"
+ a("\`The **cat**
+ in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+ c("\`The **cat**
+ in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end`);
+
+ const subgraphs = flow.parser.yy.getSubGraphs();
+ expect(subgraphs.length).toBe(2);
+ const subgraph = subgraphs[0];
+
+ expect(subgraph.nodes.length).toBe(2);
+ expect(subgraph.title).toBe('One');
+ expect(subgraph.labelType).toBe('text');
+
+ const subgraph2 = subgraphs[1];
+ expect(subgraph2.nodes.length).toBe(2);
+ expect(subgraph2.title).toBe('**Two**');
+ expect(subgraph2.labelType).toBe('markdown');
+ });
+});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index e2dad5881..51427118f 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -7,6 +7,7 @@
/* lexical grammar */
%lex
%x string
+%x md_string
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -27,8 +28,6 @@
":" { this.popState(); this.begin('arg_directive'); return ':'; }
\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
((?:(?!\}\%\%).|\n)*) return 'arg_directive';
-\%\%(?!\{)[^\n]* /* skip comments */
-[^\}]\%\%[^\n]* /* skip comments */
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
@@ -37,6 +36,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
[\}] { this.popState(); }
[^\}]* return "acc_descr_multiline_value";
// .*[^\n]* { return "acc_descr_line"}
+["][`] { this.begin("md_string");}
+[^`"]+ { return "MD_STR";}
+[`]["] { this.popState();}
["] this.begin("string");
["] this.popState();
[^"]* return "STR";
@@ -434,11 +436,13 @@ arrowText:
;
text: textToken
- {$$=$1;}
+ { $$={text:$1, type: 'text'};}
| text textToken
- {$$=$1+''+$2;}
+ { $$={text:$1.text+''+$2, type: $1.type};}
| STR
- {$$=$1;}
+ { $$={text: $1, type: 'text'};}
+ | MD_STR
+ { $$={text: $1, type: 'markdown'};}
;
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
index 6b440da79..692e72f37 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
@@ -1,6 +1,7 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
+import { cleanupComments } from '../../../diagram-api/comments';
setConfig({
securityLevel: 'strict',
@@ -13,7 +14,7 @@ describe('parsing a flow chart', function () {
});
it('should handle a trailing whitespaces after statements', function () {
- const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;');
+ const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;'));
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts
index 879bd17ad..f889b7cd5 100644
--- a/packages/mermaid/src/diagrams/flowchart/styles.ts
+++ b/packages/mermaid/src/diagrams/flowchart/styles.ts
@@ -23,11 +23,11 @@ const getStyles = (options: FlowChartStyleOptions) =>
.cluster-label text {
fill: ${options.titleColor};
}
- .cluster-label span {
+ .cluster-label span,p {
color: ${options.titleColor};
}
- .label text,span {
+ .label text,span,p {
fill: ${options.nodeTextColor || options.textColor};
color: ${options.nodeTextColor || options.textColor};
}
@@ -41,6 +41,15 @@ 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 .katex path {
fill: #000;
@@ -89,7 +98,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
fill: ${options.titleColor};
}
- .cluster span {
+ .cluster span,p {
color: ${options.titleColor};
}
/* .cluster div {
diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js
index 27ad1a0b8..6ce5336fd 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttDb.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js
@@ -1,8 +1,8 @@
import { sanitizeUrl } from '@braintree/sanitize-url';
-import dayjs from 'dayjs';
-import dayjsIsoWeek from 'dayjs/plugin/isoWeek';
-import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat';
-import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat';
+import dayjs from 'dayjs/esm/index.js';
+import dayjsIsoWeek from 'dayjs/esm/plugin/isoWeek/index.js';
+import dayjsCustomParseFormat from 'dayjs/esm/plugin/customParseFormat/index.js';
+import dayjsAdvancedFormat from 'dayjs/esm/plugin/advancedFormat/index.js';
import { log } from '../../logger';
import * as configApi from '../../config';
import utils from '../../utils';
@@ -32,6 +32,7 @@ let links = {};
let sections = [];
let tasks = [];
let currentSection = '';
+let displayMode = '';
const tags = ['active', 'done', 'crit', 'milestone'];
let funs = [];
let inclusiveEndDates = false;
@@ -55,6 +56,7 @@ export const clear = function () {
rawTasks = [];
dateFormat = '';
axisFormat = '';
+ displayMode = '';
tickInterval = undefined;
todayMarker = '';
includes = [];
@@ -110,6 +112,14 @@ 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;
};
@@ -143,11 +153,11 @@ export const getSections = function () {
};
export const getTasks = function () {
- let allItemsPricessed = compileTasks();
+ let allItemsProcessed = compileTasks();
const maxDepth = 10;
let iterationCount = 0;
- while (!allItemsPricessed && iterationCount < maxDepth) {
- allItemsPricessed = compileTasks();
+ while (!allItemsProcessed && iterationCount < maxDepth) {
+ allItemsProcessed = compileTasks();
iterationCount++;
}
@@ -719,6 +729,8 @@ export default {
getAccTitle,
setDiagramTitle,
getDiagramTitle,
+ setDisplayMode,
+ getDisplayMode,
setAccDescription,
getAccDescription,
addSection,
diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
index d65f2fdfd..7dc2e2a59 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
+++ b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
@@ -1,5 +1,5 @@
// @ts-nocheck TODO: Fix TS
-import dayjs from 'dayjs';
+import dayjs from 'dayjs/esm/index.js';
import ganttDb from './ganttDb';
import { convert } from '../../tests/util';
@@ -34,6 +34,7 @@ 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');
@@ -53,6 +54,7 @@ describe('when using the ganttDb', function () {
${'getExcludes'} | ${[]}
${'getSections'} | ${[]}
${'endDatesAreInclusive'} | ${false}
+ ${'getDisplayMode'} | ${''}
`)('should clear $fn', ({ fn, expected }) => {
expect(ganttDb[fn]()).toEqual(expected);
});
diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
index 7a012beb5..fb692828f 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
@@ -1,4 +1,4 @@
-import dayjs from 'dayjs';
+import dayjs from 'dayjs/esm/index.js';
import { log } from '../../logger';
import {
select,
@@ -24,12 +24,43 @@ 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;
@@ -56,7 +87,40 @@ export const draw = function (text, id, version, diagObj) {
const taskArray = diagObj.db.getTasks();
// Set height based on number of tasks
- const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
+
+ 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;
+ }
+ }
// Set viewBox
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
@@ -74,16 +138,6 @@ 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
@@ -157,11 +211,15 @@ 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(theArray)
+ .data(uniqueTasks)
.enter()
.append('rect')
.attr('x', 0)
@@ -582,12 +640,9 @@ export const draw = function (text, id, version, diagObj) {
* @param theTopPad
*/
function vertLabels(theGap, theTopPad) {
- const numOccurances = [];
let prevGap = 0;
- for (const [i, category] of categories.entries()) {
- numOccurances[i] = [category, getCount(category, catsUnfiltered)];
- }
+ const numOccurances = Object.keys(categoryHeights).map((d) => [d, categoryHeights[d]]);
svg
.append('g') // without doing this, impossible to put grid lines behind text
@@ -625,7 +680,6 @@ 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) {
@@ -682,31 +736,6 @@ 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 {
diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
index 2223aa378..0eb45ec41 100644
--- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
+++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
@@ -131,9 +131,10 @@ 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
diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js
index 0d814212e..86260e155 100644
--- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js
+++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js
@@ -4,9 +4,13 @@ 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';
-let cytoscape;
+// Inject the layout algorithm into cytoscape
+cytoscape.use(coseBilkent);
+
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The mindmap data and hierarchy
@@ -89,14 +93,7 @@ function addNodes(mindmap, cy, conf, level) {
* @param conf
* @param cy
*/
-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);
- }
-
+function layoutMindmap(node, conf) {
return new Promise((resolve) => {
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@@ -170,12 +167,15 @@ 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('Renering info diagram\n' + text);
+ log.debug('Rendering mindmap diagram\n' + text, diagObj.parser);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sandbox mode
diff --git a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
index d2f6bbf1a..9dd046a3d 100644
--- a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
+++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
@@ -12,12 +12,13 @@
%}
%x NODE
%x NSTR
+%x NSTR2
%x ICON
%x CLASS
%%
-\s*\%\%.* {yy.getLogger().trace('Found comment',yytext);}
+\s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';}
// \%\%[^\n]*\n /* skip comments */
"mindmap" return 'MINDMAP';
":::" { this.begin('CLASS'); }
@@ -41,6 +42,9 @@
// !(-\() return 'NODE_ID';
[^\(\[\n\-\)\{\}]+ return 'NODE_ID';
<> return 'EOF';
+["][`] { this.begin("NSTR2");}
+[^`"]+ { return "NODE_DESCR";}
+[`]["] { this.popState();}
["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
["] {this.popState();}
diff --git a/packages/mermaid/src/diagrams/mindmap/styles.js b/packages/mermaid/src/diagrams/mindmap/styles.js
index 986a04514..863522fdf 100644
--- a/packages/mermaid/src/diagrams/mindmap/styles.js
+++ b/packages/mermaid/src/diagrams/mindmap/styles.js
@@ -70,5 +70,12 @@ 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;
diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js
index 2b1aa021e..ab7dcc1e3 100644
--- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js
+++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js
@@ -1,5 +1,6 @@
import { select } from 'd3';
import * as db from './mindmapDb';
+import { createText } from '../../rendering-util/createText';
const MAX_SECTIONS = 12;
/**
@@ -11,7 +12,7 @@ function wrap(text, width) {
var text = select(this),
words = text
.text()
- .split(/(\s+| )/)
+ .split(/(\s+| )/)
.reverse(),
word,
line = [],
@@ -28,10 +29,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 === ' ') {
+ if (tspan.node().getComputedTextLength() > width || word === ' ') {
line.pop();
tspan.text(line.join(' ').trim());
- if (word === ' ') {
+ if (word === ' ') {
line = [''];
} else {
line = [word];
@@ -203,6 +204,7 @@ 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;
@@ -215,15 +217,22 @@ export const drawNode = function (elem, node, fullSection, conf) {
// Create the wrapped text element
const textElem = nodeElem.append('g');
- 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 description = node.descr.replace(/( )/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 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;
@@ -267,7 +276,16 @@ export const drawNode = function (elem, node, fullSection, conf) {
);
}
} else {
- textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
+ 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 + ')');
+ }
}
switch (node.type) {
diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js
index 83f301207..9b25f5f43 100644
--- a/packages/mermaid/src/diagrams/pie/pieRenderer.js
+++ b/packages/mermaid/src/diagrams/pie/pieRenderer.js
@@ -3,6 +3,7 @@ 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();
@@ -88,6 +89,10 @@ 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);
@@ -111,6 +116,16 @@ 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
@@ -135,7 +150,7 @@ export const draw = (txt, id, _version, diagObj) => {
return ((d.data.value / sum) * 100).toFixed(0) + '%';
})
.attr('transform', function (d) {
- return 'translate(' + arcGenerator.centroid(d) + ')';
+ return 'translate(' + labelArcGenerator.centroid(d) + ')';
})
.style('text-anchor', 'middle')
.attr('class', 'slice');
diff --git a/packages/mermaid/src/diagrams/pie/styles.js b/packages/mermaid/src/diagrams/pie/styles.js
index 8544501a3..6f0f60006 100644
--- a/packages/mermaid/src/diagrams/pie/styles.js
+++ b/packages/mermaid/src/diagrams/pie/styles.js
@@ -5,6 +5,11 @@ 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};
diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
index 8629f74db..1c617cfb4 100644
--- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
@@ -232,6 +232,9 @@ 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
@@ -240,6 +243,7 @@ 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),
@@ -378,7 +382,7 @@ const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
* @param _version
* @param diag
*/
-export const draw = function (text, id, _version, diag) {
+export const draw = async function (text, id, _version, diag) {
log.info('Drawing state diagram (v2)', id);
// diag.sb.clear();
nodeDb = {};
@@ -432,7 +436,7 @@ export const draw = function (text, id, _version, diag) {
// Run the renderer. This is what draws the final graph.
const element = root.select('#' + id + ' g');
- render(element, g, ['barb'], CSS_DIAGRAM, id);
+ await render(element, g, ['barb'], CSS_DIAGRAM, id);
const padding = 8;
diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts
index 9b01fdbde..040b6a71a 100644
--- a/packages/mermaid/src/docs/.vitepress/config.ts
+++ b/packages/mermaid/src/docs/.vitepress/config.ts
@@ -28,7 +28,16 @@ export default defineConfig({
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/mermaid-js/mermaid' },
- { icon: 'slack', link: 'https://mermaid-talk.slack.com' },
+ {
+ icon: 'slack',
+ link: 'https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE',
+ },
+ {
+ icon: {
+ svg: ' ',
+ },
+ link: 'https://www.mermaidchart.com/',
+ },
],
},
});
@@ -42,6 +51,11 @@ function nav() {
activeMatch: '/config/',
},
{ text: 'Integrations', link: '/ecosystem/integrations', activeMatch: '/ecosystem/' },
+ {
+ text: 'Latest News',
+ link: '/news/announcements',
+ activeMatch: '/announcements',
+ },
{
text: version,
items: [
@@ -80,6 +94,7 @@ function sidebarAll() {
...sidebarEcosystem(),
...sidebarConfig(),
...sidebarCommunity(),
+ ...sidebarNews(),
];
}
@@ -162,3 +177,16 @@ function sidebarCommunity() {
},
];
}
+
+function sidebarNews() {
+ return [
+ {
+ text: '📰 Latest News',
+ collapsible: true,
+ items: [
+ { text: 'Announcements', link: '/news/announcements' },
+ { text: 'Blog', link: '/news/blog' },
+ ],
+ },
+ ];
+}
diff --git a/packages/mermaid/src/docs/config/Tutorials.md b/packages/mermaid/src/docs/config/Tutorials.md
index e07635641..875f15245 100644
--- a/packages/mermaid/src/docs/config/Tutorials.md
+++ b/packages/mermaid/src/docs/config/Tutorials.md
@@ -1,6 +1,6 @@
# Tutorials
-This is list of publicly available Tutorials for using Mermaid.JS . This is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
+This is a list of publicly available Tutorials for using Mermaid.JS and is intended as a basic introduction for the use of the Live Editor for generating diagrams, and deploying Mermaid.JS through HTML.
**Note that these tutorials might display an older interface, but the usage of the live-editor will largely be the same.**
diff --git a/packages/mermaid/src/docs/config/theming.md b/packages/mermaid/src/docs/config/theming.md
index da021f7f8..0e4571d15 100644
--- a/packages/mermaid/src/docs/config/theming.md
+++ b/packages/mermaid/src/docs/config/theming.md
@@ -183,6 +183,34 @@ The theming engine will only recognize hex colors and not color names. So, the v
| activationBkgColor | secondaryColor | Activation Background Color |
| sequenceNumberColor | calculated from lineColor | Sequence Number Color |
+## Pie Diagram Variables
+
+| Variable | Default value | Description |
+| ------------------- | ------------------------------ | ------------------------------------------ |
+| pie1 | primaryColor | Fill for 1st section in pie diagram |
+| pie2 | secondaryColor | Fill for 2nd section in pie diagram |
+| pie3 | calculated from tertiary | Fill for 3rd section in pie diagram |
+| pie4 | calculated from primaryColor | Fill for 4th section in pie diagram |
+| pie5 | calculated from secondaryColor | Fill for 5th section in pie diagram |
+| pie6 | calculated from tertiaryColor | Fill for 6th section in pie diagram |
+| pie7 | calculated from primaryColor | Fill for 7th section in pie diagram |
+| pie8 | calculated from primaryColor | Fill for 8th section in pie diagram |
+| pie9 | calculated from primaryColor | Fill for 9th section in pie diagram |
+| pie10 | calculated from primaryColor | Fill for 10th section in pie diagram |
+| pie11 | calculated from primaryColor | Fill for 11th section in pie diagram |
+| pie12 | calculated from primaryColor | Fill for 12th section in pie diagram |
+| pieTitleTextSize | 25px | Title text size |
+| pieTitleTextColor | taskTextDarkColor | Title text color |
+| pieSectionTextSize | 17px | Text size of individual section labels |
+| pieSectionTextColor | textColor | Text color of individual section labels |
+| pieLegendTextSize | 17px | Text size of labels in diagram legend |
+| pieLegendTextColor | taskTextDarkColor | Text color of labels in diagram legend |
+| pieStrokeColor | black | Border color of individual pie sections |
+| pieStrokeWidth | 2px | Border width of individual pie sections |
+| pieOuterStrokeWidth | 2px | Border width of pie diagram's outer circle |
+| pieOuterStrokeColor | black | Border color of pie diagram's outer circle |
+| pieOpacity | 0.7 | Opacity of individual pie sections |
+
## State Colors
| Variable | Default value | Description |
diff --git a/packages/mermaid/src/docs/config/usage.md b/packages/mermaid/src/docs/config/usage.md
index c74023952..211a06af7 100644
--- a/packages/mermaid/src/docs/config/usage.md
+++ b/packages/mermaid/src/docs/config/usage.md
@@ -244,6 +244,23 @@ The example below show an outline of how this could be used. The example just lo
```
+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
+
+```
+
### Binding events
Sometimes the generated graph also has defined interactions like tooltip and click events. When using the API one must
diff --git a/packages/mermaid/src/docs/ecosystem/integrations.md b/packages/mermaid/src/docs/ecosystem/integrations.md
index 727580664..ac8f2659b 100644
--- a/packages/mermaid/src/docs/ecosystem/integrations.md
+++ b/packages/mermaid/src/docs/ecosystem/integrations.md
@@ -14,6 +14,7 @@ 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**)
@@ -82,7 +83,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [FosWiki](https://foswiki.org)
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
- [DokuWiki](https://dokuwiki.org)
- - [Flowcharts](https://www.dokuwiki.org/plugin:flowcharts?s[]=mermaid)
+ - [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
- [ComboStrap](https://combostrap.com/mermaid)
- [TiddlyWiki](https://tiddlywiki.com/)
- [mermaid-tw5: full js library](https://github.com/efurlanm/mermaid-tw5)
@@ -143,7 +144,7 @@ They also serve as proof of concept, for the variety of things that can be built
- [remark-mermaid](https://github.com/temando/remark-mermaid)
- [jSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
-- [MkDocs](https://mkdocs.org)
+- [MkDocs](https://www.mkdocs.org)
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Type Doc](https://typedoc.org/)
diff --git a/packages/mermaid/src/docs/index.md b/packages/mermaid/src/docs/index.md
index b0b38bd79..59b6607fe 100644
--- a/packages/mermaid/src/docs/index.md
+++ b/packages/mermaid/src/docs/index.md
@@ -23,15 +23,15 @@ features:
- title: ➕ Easy to use!
details: Easily create and render detailed diagrams and charts with the Mermaid Live Editor.
link: https://mermaid.live/
- - title: 🎥 Video Tutorials!
- details: Curated list of video tutorials and examples created by the community.
- link: ../../config/Tutorials.html
- title: 🧩 Integrations available!
details: Use Mermaid with your favorite applications, check out the integrations list.
link: ../../ecosystem/integrations.md
- title: 🏆 Award winning!
details: 2019 JavaScript Open Source Award winner for "The Most Exciting Use of Technology".
link: https://osawards.com/javascript/2019
+ - title: 🥰 Mermaid + Mermaid Chart
+ details: Mermaid Chart is a major supporter of the Mermaid project.
+ link: https://www.mermaidchart.com/
---
diff --git a/packages/mermaid/src/docs/news/announcements.md b/packages/mermaid/src/docs/news/announcements.md
new file mode 100644
index 000000000..4dd07bf3b
--- /dev/null
+++ b/packages/mermaid/src/docs/news/announcements.md
@@ -0,0 +1,7 @@
+# Announcements
+
+## [Automatic text wrapping in flowcharts is here!](https://www.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here)
+
+3 April 2023 · 3 mins
+
+Markdown Strings reduce the hassle # Starting from v10.
diff --git a/packages/mermaid/src/docs/news/blog.md b/packages/mermaid/src/docs/news/blog.md
new file mode 100644
index 000000000..b835bbe35
--- /dev/null
+++ b/packages/mermaid/src/docs/news/blog.md
@@ -0,0 +1,25 @@
+# Blog
+
+## [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 it’s 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 can’t write # It’s an annoying stereotype that developers don’t know how to write, speak, and otherwise communicate.
diff --git a/packages/mermaid/src/docs/public/favicon.ico b/packages/mermaid/src/docs/public/favicon.ico
index d41818c5b..05d8a737b 100644
Binary files a/packages/mermaid/src/docs/public/favicon.ico and b/packages/mermaid/src/docs/public/favicon.ico differ
diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md
index 6997ca088..936607cbd 100644
--- a/packages/mermaid/src/docs/syntax/flowchart.md
+++ b/packages/mermaid/src/docs/syntax/flowchart.md
@@ -122,16 +122,7 @@ flowchart LR
### A hexagon node
-Code:
-
-```mmd
-flowchart LR
- id1{{This is the text in the box}}
-```
-
-Render:
-
-```mermaid
+```mermaid-example
flowchart LR
id1{{This is the text in the box}}
```
@@ -455,6 +446,31 @@ 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
+```
+
+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 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 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'`.
diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md
index 7c446d91f..422358d3e 100644
--- a/packages/mermaid/src/docs/syntax/gantt.md
+++ b/packages/mermaid/src/docs/syntax/gantt.md
@@ -189,9 +189,27 @@ The pattern is:
More info in: [https://github.com/d3/d3-time#interval_every](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.
+
+```mmd
+---
+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.
```mmd
gantt
@@ -356,3 +374,24 @@ Beginner's tip—a full example using interactive links in an html context: