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 d860c5e33..94276b683 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -47,6 +47,7 @@
"graphviz",
"grav",
"greywolf",
+ "huynh",
"inkdrop",
"jaoude",
"jison",
@@ -90,6 +91,7 @@
"sidharthv",
"sphinxcontrib",
"statediagram",
+ "steph",
"stylis",
"substate",
"sveidqvist",
diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js
index b11182d90..d0ef42c5d 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 d396d45e7..eaa14ed50 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/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/platform/knsv2.html b/cypress/platform/knsv2.html
index cfa967e79..e8e9b55a6 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/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md index 93708863c..8ab259885 100644 --- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -16,4 +16,4 @@ #### Defined in -[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70) +[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md index ec59f8796..f84a51b87 100644 --- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[mermaidAPI.ts:91](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L91) +[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98) --- @@ -51,4 +51,4 @@ The svg code for the rendered graph. #### Defined in -[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81) +[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 9cfb563e8..ad8f90248 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2103) +[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115) --- diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 9bc6d3056..683850fd3 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75) +[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82) ## Variables @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:660](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L660) +[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673) ## Functions @@ -127,7 +127,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:305](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L305) +[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312) --- @@ -153,7 +153,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256) +[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263) --- @@ -179,7 +179,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185) +[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192) --- @@ -202,7 +202,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233) +[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240) --- @@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169) +[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176) --- @@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:149](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L149) +[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156) --- @@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:120](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L120) +[mermaidAPI.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L127) --- @@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:284](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L284) +[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291) --- @@ -320,4 +320,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:355](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L355) +[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362) diff --git a/docs/ecosystem/integrations.md b/docs/ecosystem/integrations.md index 3a9b7cd74..9cd8bc9a5 100644 --- a/docs/ecosystem/integrations.md +++ b/docs/ecosystem/integrations.md @@ -23,6 +23,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Mermaid Flow Visual Editor](https://www.mermaidflow.app) (**Native support**) - [Deepdwn](https://billiam.itch.io/deepdwn) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Slab](https://slab.com) (**Native support**) - [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) diff --git a/docs/intro/index.md b/docs/intro/index.md index 848df4301..8ebc4c530 100644 --- a/docs/intro/index.md +++ b/docs/intro/index.md @@ -263,7 +263,7 @@ To Deploy Mermaid: ### [Mermaid API](../config/setup/README.md): -**To deploy mermaid without a bundler, one can insert a `script` tag with an absolute address and a `mermaid.initialize` call into the HTML like so:** +**To deploy mermaid without a bundler, insert a `script` tag with an absolute address and a `mermaid.initialize` call into the HTML using the following example:** ```html ``` -**Doing so will command the mermaid parser to look for the `` or `` tags with `class="mermaid"`. From these tags mermaid will try to read the diagram/chart definitions and render them into SVG charts.** +**Doing so commands the mermaid parser to look for the `` or `` tags with `class="mermaid"`. From these tags, mermaid tries read the diagram/chart definitions and render them into SVG charts.** -**Examples can be found at** [Other examples](../syntax/examples.md) +**Examples can be found in** [Other examples](../syntax/examples.md) ## Sibling projects 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 c79f12b23..6f5b973e8 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -710,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 1b12063d7..091cdeabe 100644 --- a/docs/syntax/gantt.md +++ b/docs/syntax/gantt.md @@ -464,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 babe47756..9687bbef7 100644 --- a/docs/syntax/mindmap.md +++ b/docs/syntax/mindmap.md @@ -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/package.json b/package.json index 2fce7570f..0485d6f6e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mermaid-monorepo", "private": true, - "version": "10.0.2", + "version": "10.1.0", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "packageManager": "pnpm@7.30.1", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 15730a6c5..12da8c2ff 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "10.0.2", + "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", @@ -53,6 +53,7 @@ }, "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", @@ -86,6 +87,7 @@ "coveralls": "^3.1.1", "cpy-cli": "^4.2.0", "cspell": "^6.14.3", + "csstree-validator": "^3.0.0", "globby": "^13.1.2", "jison": "^0.4.18", "js-base64": "^3.7.2", diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 83e43af3e..4fb329b28 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -44,8 +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(cleanupComments(extractFrontMatter(text, this.db))); + this.parser.parser.yy = this.db; if (diagram.init) { diagram.init(cnf); diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 68441732b..545fdbbfb 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -386,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 959fff73d..1ce716689 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.js'; import { log } from '../logger.js'; import createLabel from './createLabel.js'; +import { createText } from '../rendering-util/createText.js'; import { select } from 'd3'; import { getConfig } from '../config.js'; import { evaluate } from '../diagrams/common/common.js'; 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 330da31c1..a8351c812 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 fc4864624..1581658b0 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.js'; import createLabel from './createLabel.js'; +import { createText } from '../rendering-util/createText.js'; import { line, curveBasis, select } from 'd3'; import { getConfig } from '../config.js'; import utils from '../utils.js'; @@ -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 dd764dd9e..590242b02 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.js'; import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js'; import { log } from '../logger.js'; -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/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index 5ae70148d..57d092fdf 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -142,7 +142,7 @@ const point = (elem, type) => { .append('marker') .attr('id', type + '-pointEnd') .attr('class', 'marker ' + type) - .attr('viewBox', '0 0 12 20') + .attr('viewBox', '0 0 10 10') .attr('refX', 10) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 51764d818..b842fa9a5 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.js'; import { parseMember } from '../diagrams/class/svgDraw.js'; import { evaluate } from '../diagrams/common/common.js'; -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 0051fbb11..17661e169 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.js'; import { log } from '../../logger.js'; +import { getConfig } from '../../config.js'; 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 38300bb44..7ad412bdb 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.js'; +import { createText } from '../../rendering-util/createText.js'; import { getConfig } from '../../config.js'; import { decodeEntities } from '../../mermaidAPI.js'; import { select } from 'd3'; import { evaluate, sanitizeText } from '../../diagrams/common/common.js'; -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 b982e188f..9c6d6f46e 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 */ diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 8c4ac8b5b..82447c54f 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/styles.js b/packages/mermaid/src/diagrams/class/styles.js index 981cd7b73..15386bf9e 100644 --- a/packages/mermaid/src/diagrams/class/styles.js +++ b/packages/mermaid/src/diagrams/class/styles.js @@ -41,7 +41,7 @@ const getStyles = (options) => .divider { stroke: ${options.nodeBorder}; - stroke: 1; + stroke-width: 1; } g.clickable { diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js index 539f2d230..b86c537dd 100644 --- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -3,6 +3,7 @@ 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.js'; +import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; import { getConfig } from '../../../config.js'; import { log } from '../../../logger.js'; @@ -12,7 +13,7 @@ import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; import ELK from 'elkjs/lib/elk.bundled.js'; const elk = new 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; }; @@ -520,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) { @@ -775,6 +769,7 @@ export const draw = async function (text, id, _version, diagObj) { // Add temporary render element diagObj.db.clear(); nodeDb = {}; + portPos = {}; diagObj.db.setGen('gen-2'); // Parse the graph definition diagObj.parser.parse(text); @@ -845,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'); @@ -860,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'); @@ -887,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; @@ -895,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); @@ -930,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 b11c0811b..f7e1a38db 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 d7f5e442d..0b8d47543 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -47,7 +47,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { if (vertex.classes.length > 0) { classStr = vertex.classes.join(' '); } - + classStr = classStr + ' flowchart-label'; const styles = getStylesFromArray(vertex.styles); // Use vertex id as text in the box if no text is provided by the graph definition @@ -55,31 +55,36 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { // We create a SVG label, either by delegating to addHtmlLabel or manually let vertexNode; - if (evaluate(getConfig().flowchart.htmlLabels)) { - // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? - const node = { - label: vertexText.replace( - /fa[blrs]?:fa-[\w-]+/g, - (s) => `` - ), - }; - 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) => `` + ), + }; + vertexNode = addHtmlLabel(svg, node).node(); + vertexNode.parentNode.removeChild(vertexNode); + } else { + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); + svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); - const rows = vertexText.split(common.lineBreakRegex); + const rows = vertexText.split(common.lineBreakRegex); - for (const row of rows) { - const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); - tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - tspan.setAttribute('dy', '1em'); - tspan.setAttribute('x', '1'); - tspan.textContent = row; - svgLabel.appendChild(tspan); + for (const row of rows) { + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + tspan.setAttribute('dy', '1em'); + tspan.setAttribute('x', '1'); + tspan.textContent = row; + svgLabel.appendChild(tspan); + } + vertexNode = svgLabel; } - vertexNode = svgLabel; } let radious = 0; @@ -146,6 +151,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { labelStyle: styles.labelStyle, shape: _shape, labelText: vertexText, + labelType: vertex.labelType, rx: radious, ry: radious, class: classStr, @@ -165,6 +171,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { log.info('setNode', { labelStyle: styles.labelStyle, + labelType: vertex.labelType, shape: _shape, labelText: vertexText, rx: radious, @@ -312,7 +319,7 @@ export const addEdges = function (edges, g, diagObj) { edgeData.labelpos = 'c'; } - edgeData.labelType = 'text'; + edgeData.labelType = edge.labelType; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); if (edge.style === undefined) { @@ -355,7 +362,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'); @@ -405,7 +412,14 @@ export const draw = function (text, id, _version, diagObj) { for (let i = subGraphs.length - 1; i >= 0; i--) { subG = subGraphs[i]; log.info('Subgraph - ', subG); - diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir); + diagObj.db.addVertex( + subG.id, + { text: subG.title, type: subG.labelType }, + 'group', + undefined, + subG.classes, + subG.dir + ); } // Fetch the vertices/nodes and edges/links from the parsed graph definition @@ -437,7 +451,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-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 0aeb30062..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 @@ -35,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"; @@ -432,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/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts index a89d33d3d..964505c2d 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 .label { text-align: center; @@ -83,7 +92,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 48b9891b6..dc811cb64 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.js'; -import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js'; -import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js'; +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.js'; import * as configApi from '../../config.js'; import utils from '../../utils.js'; diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts index 0696a3223..c7e00bf69 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.js'; import { convert } from '../../tests/util.js'; diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index ff16fef7c..a148d8a9e 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.js'; import { select, diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js index e462f2fc3..01d675d83 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js @@ -167,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 69975e6bb..22132dae6 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.js'; +import { createText } from '../../rendering-util/createText.js'; 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/requirement/styles.js b/packages/mermaid/src/diagrams/requirement/styles.js index d0579d204..9db0fa00a 100644 --- a/packages/mermaid/src/diagrams/requirement/styles.js +++ b/packages/mermaid/src/diagrams/requirement/styles.js @@ -16,7 +16,7 @@ const getStyles = (options) => ` .reqBox { fill: ${options.requirementBackground}; - fill-opacity: 100%; + fill-opacity: 1.0; stroke: ${options.requirementBorderColor}; stroke-width: ${options.requirementBorderSize}; } @@ -26,7 +26,7 @@ const getStyles = (options) => ` } .reqLabelBox { fill: ${options.relationLabelBackground}; - fill-opacity: 100%; + fill-opacity: 1.0; } .req-title-line { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 47e0afe87..20ae0d112 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 ec5154fc4..2ca53b348 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/ecosystem/integrations.md b/packages/mermaid/src/docs/ecosystem/integrations.md index ac8f2659b..a0a904425 100644 --- a/packages/mermaid/src/docs/ecosystem/integrations.md +++ b/packages/mermaid/src/docs/ecosystem/integrations.md @@ -17,6 +17,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Mermaid Flow Visual Editor](https://www.mermaidflow.app) (**Native support**) - [Deepdwn](https://billiam.itch.io/deepdwn) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Slab](https://slab.com) (**Native support**) - [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) 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/intro/index.md b/packages/mermaid/src/docs/intro/index.md index 2e94e9f5c..e886b5f60 100644 --- a/packages/mermaid/src/docs/intro/index.md +++ b/packages/mermaid/src/docs/intro/index.md @@ -76,7 +76,7 @@ To Deploy Mermaid: ### [Mermaid API](../config/setup/README.md): -**To deploy mermaid without a bundler, one can insert a `script` tag with an absolute address and a `mermaid.initialize` call into the HTML like so:** +**To deploy mermaid without a bundler, insert a `script` tag with an absolute address and a `mermaid.initialize` call into the HTML using the following example:** ```html ``` -**Doing so will command the mermaid parser to look for the `` or `` tags with `class="mermaid"`. From these tags mermaid will try to read the diagram/chart definitions and render them into SVG charts.** +**Doing so commands the mermaid parser to look for the `` or `` tags with `class="mermaid"`. From these tags, mermaid tries read the diagram/chart definitions and render them into SVG charts.** -**Examples can be found at** [Other examples](../syntax/examples.md) +**Examples can be found in** [Other examples](../syntax/examples.md) ## Sibling projects 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 8e73f597b..936607cbd 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -446,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 eaa07c707..422358d3e 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -374,3 +374,24 @@ Beginner's tip—a full example using interactive links in an html context: