diff --git a/.build/common.ts b/.build/common.ts
index 274977fa26..e2190974f9 100644
--- a/.build/common.ts
+++ b/.build/common.ts
@@ -22,9 +22,9 @@ export const packageOptions = {
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
- 'mermaid-flowchart-elk': {
- name: 'mermaid-flowchart-elk',
- packageName: 'mermaid-flowchart-elk',
- file: 'detector.ts',
+ 'mermaid-layout-elk': {
+ name: 'mermaid-layout-elk',
+ packageName: 'mermaid-layout-elk',
+ file: 'layouts.ts',
},
} as const;
diff --git a/.build/jisonTransformer.ts b/.build/jisonTransformer.ts
index 314df8a33f..b603502306 100644
--- a/.build/jisonTransformer.ts
+++ b/.build/jisonTransformer.ts
@@ -1,6 +1,7 @@
import jison from 'jison';
export const transformJison = (src: string): string => {
+ // @ts-ignore - Jison is not typed properly
const parser = new jison.Generator(src, {
moduleType: 'js',
'token-stack': true,
diff --git a/.cspell/code-terms.txt b/.cspell/code-terms.txt
index 36d3b94c65..9d2f700fcb 100644
--- a/.cspell/code-terms.txt
+++ b/.cspell/code-terms.txt
@@ -28,6 +28,7 @@ controly
CSSCLASS
CYLINDEREND
CYLINDERSTART
+DAGA
datakey
DEND
descr
@@ -90,6 +91,7 @@ reqs
rewritelinks
rgba
RIGHTOF
+roughjs
sankey
sequencenumber
shrc
@@ -109,6 +111,7 @@ strikethrough
stringifying
struct
STYLECLASS
+STYLEDEF
STYLEOPTS
subcomponent
subcomponents
diff --git a/.cspell/libraries.txt b/.cspell/libraries.txt
index 71d2e18a44..c185429b09 100644
--- a/.cspell/libraries.txt
+++ b/.cspell/libraries.txt
@@ -55,6 +55,7 @@ presetAttributify
pyplot
redmine
rehype
+roughjs
rscratch
shiki
sparkline
diff --git a/.cspell/mermaid-terms.txt b/.cspell/mermaid-terms.txt
index 3fa5eff269..46ad6dddb1 100644
--- a/.cspell/mermaid-terms.txt
+++ b/.cspell/mermaid-terms.txt
@@ -9,6 +9,7 @@ elems
gantt
gitgraph
gzipped
+handDrawn
knsv
Knut
marginx
@@ -17,6 +18,7 @@ Markdownish
mermaidjs
mindmap
mindmaps
+mrtree
multigraph
nodesep
NOTEGROUP
diff --git a/.cspell/misc-terms.txt b/.cspell/misc-terms.txt
index 0efd1dcc00..3fc0943094 100644
--- a/.cspell/misc-terms.txt
+++ b/.cspell/misc-terms.txt
@@ -1,4 +1,6 @@
BRANDES
-handdrawn
+circo
+handDrawn
KOEPF
+neato
newbranch
diff --git a/.esbuild/build.ts b/.esbuild/build.ts
index 505c18405d..2bb42a557f 100644
--- a/.esbuild/build.ts
+++ b/.esbuild/build.ts
@@ -8,8 +8,8 @@ import { defaultOptions, getBuildConfig } from './util.js';
const shouldVisualize = process.argv.includes('--visualize');
const buildPackage = async (entryName: keyof typeof packageOptions) => {
- const commonOptions = { ...defaultOptions, entryName } as const;
- const buildConfigs = [
+ const commonOptions: MermaidBuildOptions = { ...defaultOptions, entryName } as const;
+ const buildConfigs: MermaidBuildOptions[] = [
// package.mjs
{ ...commonOptions },
// package.min.mjs
diff --git a/.esbuild/util.ts b/.esbuild/util.ts
index 6d424ab17c..5221761138 100644
--- a/.esbuild/util.ts
+++ b/.esbuild/util.ts
@@ -8,7 +8,7 @@ import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
-export interface MermaidBuildOptions {
+export interface MermaidBuildOptions extends BuildOptions {
minify: boolean;
core: boolean;
metafile: boolean;
diff --git a/.gitignore b/.gitignore
index 7948faee49..7448f2a810 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,7 +35,7 @@ cypress/snapshots/
.tsbuildinfo
tsconfig.tsbuildinfo
-knsv*.html
+#knsv*.html
local*.html
stats/
diff --git a/.prettierignore b/.prettierignore
index 7da0646e32..c700804260 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -16,3 +16,5 @@ generated/
# Ignore the files creates in /demos/dev except for example.html
demos/dev/**
!/demos/dev/example.html
+# TODO: Lots of errors to fix
+cypress/platform/state-refactor.html
diff --git a/cypress/integration/other/flowchart-elk.spec.js b/cypress/integration/other/flowchart-elk.spec.js
deleted file mode 100644
index 22a6efc0f5..0000000000
--- a/cypress/integration/other/flowchart-elk.spec.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts';
-
-describe('Flowchart elk', () => {
- it('should use dagre as fallback', () => {
- urlSnapshotTest('http://localhost:9000/flow-elk.html', {
- name: 'flow-elk fallback to dagre',
- });
- });
- it('should allow overriding with external package', () => {
- urlSnapshotTest('http://localhost:9000/flow-elk.html?elk=true', {
- name: 'flow-elk overriding dagre with elk',
- });
- });
-});
diff --git a/cypress/integration/rendering/appli.spec.js b/cypress/integration/rendering/appli.spec.js
index 5def968157..51eeb657eb 100644
--- a/cypress/integration/rendering/appli.spec.js
+++ b/cypress/integration/rendering/appli.spec.js
@@ -11,6 +11,27 @@ describe('Git Graph diagram', () => {
{}
);
});
+ it('Should render subgraphs with title margins and edge labels', () => {
+ imgSnapshotTest(
+ `flowchart LR
+
+ subgraph TOP
+ direction TB
+ subgraph B1
+ direction RL
+ i1 --lb1-->f1
+ end
+ subgraph B2
+ direction BT
+ i2 --lb2-->f2
+ end
+ end
+ A --lb3--> TOP --lb4--> B
+ B1 --lb5--> B2
+ `,
+ { flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
+ );
+ });
// it(`ultraFastTest`, function () {
// // Navigate to the url we want to test
// // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run.
diff --git a/cypress/integration/rendering/block.spec.js b/cypress/integration/rendering/block.spec.js
index 9d62c642dc..f5d5103e89 100644
--- a/cypress/integration/rendering/block.spec.js
+++ b/cypress/integration/rendering/block.spec.js
@@ -236,7 +236,7 @@ describe('Block diagram', () => {
);
});
- it('BL16: width alignment - blocks shold be equal in width', () => {
+ it('BL17: width alignment - blocks shold be equal in width', () => {
imgSnapshotTest(
`block-beta
A("This is the text")
@@ -247,7 +247,7 @@ describe('Block diagram', () => {
);
});
- it('BL17: block types 1 - square, rounded and circle', () => {
+ it('BL18: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@@ -258,7 +258,7 @@ describe('Block diagram', () => {
);
});
- it('BL18: block types 2 - odd, diamond and hexagon', () => {
+ it('BL19: block types 2 - odd, diamond and hexagon', () => {
imgSnapshotTest(
`block-beta
A>"rect_left_inv_arrow"]
@@ -269,7 +269,7 @@ describe('Block diagram', () => {
);
});
- it('BL19: block types 3 - stadium', () => {
+ it('BL20: block types 3 - stadium', () => {
imgSnapshotTest(
`block-beta
A(["stadium"])
@@ -278,7 +278,7 @@ describe('Block diagram', () => {
);
});
- it('BL20: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
+ it('BL21: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
imgSnapshotTest(
`block-beta
A[/"lean right"/]
@@ -290,7 +290,7 @@ describe('Block diagram', () => {
);
});
- it('BL21: block types 1 - square, rounded and circle', () => {
+ it('BL22: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@@ -301,7 +301,7 @@ describe('Block diagram', () => {
);
});
- it('BL22: sizing - it should be possible to make a block wider', () => {
+ it('BL23: sizing - it should be possible to make a block wider', () => {
imgSnapshotTest(
`block-beta
A("rounded"):2
@@ -312,7 +312,7 @@ describe('Block diagram', () => {
);
});
- it('BL23: sizing - it should be possible to make a composite block wider', () => {
+ it('BL24: sizing - it should be possible to make a composite block wider', () => {
imgSnapshotTest(
`block-beta
block:2
@@ -324,7 +324,7 @@ describe('Block diagram', () => {
);
});
- it('BL24: block in the middle with space on each side', () => {
+ it('BL25: block in the middle with space on each side', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -335,7 +335,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL25: space and an edge', () => {
+ it('BL26: space and an edge', () => {
imgSnapshotTest(
`block-beta
columns 5
@@ -345,7 +345,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL26: block sizes for regular blocks', () => {
+ it('BL27: block sizes for regular blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -354,7 +354,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL27: composite block with a set width - f should use the available space', () => {
+ it('BL28: composite block with a set width - f should use the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -363,11 +363,12 @@ describe('Block diagram', () => {
f
end
g
- `,
+ `,
{}
);
});
- it('BL23: composite block with a set width - f and g should split the available space', () => {
+
+ it('BL29: composite block with a set width - f and g should split the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -379,7 +380,7 @@ describe('Block diagram', () => {
h
i
j
- `,
+ `,
{}
);
});
diff --git a/cypress/integration/rendering/c4.spec.js b/cypress/integration/rendering/c4.spec.js
index f699bd4298..00e71adec8 100644
--- a/cypress/integration/rendering/c4.spec.js
+++ b/cypress/integration/rendering/c4.spec.js
@@ -1,7 +1,7 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('C4 diagram', () => {
- it('should render a simple C4Context diagram', () => {
+ it('C4.1 should render a simple C4Context diagram', () => {
imgSnapshotTest(
`
C4Context
@@ -31,7 +31,7 @@ describe('C4 diagram', () => {
{}
);
});
- it('should render a simple C4Container diagram', () => {
+ it('C4.2 should render a simple C4Container diagram', () => {
imgSnapshotTest(
`
C4Container
@@ -50,7 +50,7 @@ describe('C4 diagram', () => {
{}
);
});
- it('should render a simple C4Component diagram', () => {
+ it('C4.3 should render a simple C4Component diagram', () => {
imgSnapshotTest(
`
C4Component
@@ -68,7 +68,7 @@ describe('C4 diagram', () => {
{}
);
});
- it('should render a simple C4Dynamic diagram', () => {
+ it('C4.4 should render a simple C4Dynamic diagram', () => {
imgSnapshotTest(
`
C4Dynamic
@@ -91,7 +91,7 @@ describe('C4 diagram', () => {
{}
);
});
- it('should render a simple C4Deployment diagram', () => {
+ it('C4.5 should render a simple C4Deployment diagram', () => {
imgSnapshotTest(
`
C4Deployment
diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js
index 20a1aea0ab..258f8529f6 100644
--- a/cypress/integration/rendering/classDiagram-v2.spec.js
+++ b/cypress/integration/rendering/classDiagram-v2.spec.js
@@ -76,7 +76,7 @@ describe('Class diagram V2', () => {
);
});
- it('should render a simple class diagram with different visibilities', () => {
+ it('2.1 should render a simple class diagram with different visibilities', () => {
imgSnapshotTest(
`
classDiagram-v2
@@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
);
});
- it('should render multiple class diagrams', () => {
+ it('3: should render multiple class diagrams', () => {
imgSnapshotTest(
[
`
diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js
index e837565d3a..9eeb2e3106 100644
--- a/cypress/integration/rendering/errorDiagram.spec.js
+++ b/cypress/integration/rendering/errorDiagram.spec.js
@@ -3,7 +3,7 @@ import { imgSnapshotTest } from '../../helpers/util';
describe('Error Diagrams', () => {
beforeEach(() => {
cy.on('uncaught:exception', (err) => {
- expect(err.message).to.include('Parse error');
+ expect(err.message).to.include('error');
// return false to prevent the error from
// failing this test
return false;
diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js
index e931025e91..b5caef9733 100644
--- a/cypress/integration/rendering/flowchart-elk.spec.js
+++ b/cypress/integration/rendering/flowchart-elk.spec.js
@@ -837,6 +837,26 @@ subgraph "\`**Two**\`"
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+ it('Sub graphs and markdown strings', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+
+flowchart LR
+ subgraph subgraph_ko6czgs5u["Untitled subgraph"]
+ D["Option 1"]
+ end
+ C{"Evaluate"} -- One --> D
+ C -- Two --> E(("Option 2"))
+ D --> E
+ A["A"]
+
`,
{ flowchart: { titleTopMargin: 0 } }
);
@@ -855,7 +875,7 @@ describe('Title and arrow styling #4813', () => {
flowchart LR
A-->B
A-->C`,
- { flowchart: { defaultRenderer: 'elk' } }
+ { layout: 'elk' }
);
cy.get('svg').should((svg) => {
const title = svg[0].querySelector('text');
@@ -871,15 +891,14 @@ describe('Title and arrow styling #4813', () => {
B-.-oC
C==xD
D ~~~ A`,
- { flowchart: { defaultRenderer: 'elk' } }
+ { layout: 'elk' }
);
cy.get('svg').should((svg) => {
const edges = svg[0].querySelectorAll('.edges path');
- console.log(edges);
- expect(edges[0]).to.have.attr('pattern', 'solid');
- expect(edges[1]).to.have.attr('pattern', 'dotted');
- expect(edges[2]).to.have.css('stroke-width', '3.5px');
- expect(edges[3]).to.have.css('stroke-width', '1.5px');
+ expect(edges[0].getAttribute('class')).to.contain('edge-pattern-solid');
+ expect(edges[1].getAttribute('class')).to.contain('edge-pattern-dotted');
+ expect(edges[2].getAttribute('class')).to.contain('edge-thickness-thick');
+ expect(edges[3].getAttribute('class')).to.contain('edge-thickness-invisible');
});
});
});
diff --git a/cypress/integration/rendering/flowchart-handDrawn.spec.js b/cypress/integration/rendering/flowchart-handDrawn.spec.js
new file mode 100644
index 0000000000..d2e3edde0f
--- /dev/null
+++ b/cypress/integration/rendering/flowchart-handDrawn.spec.js
@@ -0,0 +1,1060 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+
+describe('Flowchart HandDrawn', () => {
+ it('FHD1: should render a simple flowchart no htmlLabels', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD2: should render a simple flowchart with htmlLabels', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: true },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD3: should render a simple flowchart with line breaks', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me thinksssss ssssssssssssssssssssss sssssssssssssssssssssssssss}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD4: should render a simple flowchart with trapezoid and inverse trapezoid vertex options.', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[/Christmas\\]
+ A -->|Get money| B[\\Go shopping/]
+ B --> C{Let me thinksssss ssssssssssssssssssssss sssssssssssssssssssssssssss}
+ C -->|One| D[/Laptop/]
+ C -->|Two| E[\\iPhone\\]
+ C -->|Three| F[Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD5: should style nodes via a class.', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ 1A --> 1B
+ 1B --> 1C
+ 1C --> D
+ 1C --> E
+
+ classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f
+ class 1A,1B,D,E processHead
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD6: should render a flowchart full of circles', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ 47(SAM.CommonFA.FMESummary)-->48(SAM.CommonFA.CommonFAFinanceBudget)
+ 37(SAM.CommonFA.BudgetSubserviceLineVolume)-->48(SAM.CommonFA.CommonFAFinanceBudget)
+ 35(SAM.CommonFA.PopulationFME)-->47(SAM.CommonFA.FMESummary)
+ 41(SAM.CommonFA.MetricCost)-->47(SAM.CommonFA.FMESummary)
+ 44(SAM.CommonFA.MetricOutliers)-->47(SAM.CommonFA.FMESummary)
+ 46(SAM.CommonFA.MetricOpportunity)-->47(SAM.CommonFA.FMESummary)
+ 40(SAM.CommonFA.OPVisits)-->47(SAM.CommonFA.FMESummary)
+ 38(SAM.CommonFA.CommonFAFinanceRefund)-->47(SAM.CommonFA.FMESummary)
+ 43(SAM.CommonFA.CommonFAFinancePicuDays)-->47(SAM.CommonFA.FMESummary)
+ 42(SAM.CommonFA.CommonFAFinanceNurseryDays)-->47(SAM.CommonFA.FMESummary)
+ 45(SAM.CommonFA.MetricPreOpportunity)-->46(SAM.CommonFA.MetricOpportunity)
+ 35(SAM.CommonFA.PopulationFME)-->45(SAM.CommonFA.MetricPreOpportunity)
+ 41(SAM.CommonFA.MetricCost)-->45(SAM.CommonFA.MetricPreOpportunity)
+ 41(SAM.CommonFA.MetricCost)-->44(SAM.CommonFA.MetricOutliers)
+ 39(SAM.CommonFA.ChargeDetails)-->43(SAM.CommonFA.CommonFAFinancePicuDays)
+ 39(SAM.CommonFA.ChargeDetails)-->42(SAM.CommonFA.CommonFAFinanceNurseryDays)
+ 39(SAM.CommonFA.ChargeDetails)-->41(SAM.CommonFA.MetricCost)
+ 39(SAM.CommonFA.ChargeDetails)-->40(SAM.CommonFA.OPVisits)
+ 35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
+ 36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD7: should render a flowchart full of icons', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ 9e122290_1ec3_e711_8c5a_005056ad0002("fa:fa-creative-commons My System | Test Environment")
+ 82072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 1")
+ db052290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 2")
+ 4e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 1")
+ 30122290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 2")
+ 5e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 1")
+ c1112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 2")
+ b7042290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SupportDb]")
+ 8f102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[DevelopmentDb]")
+ 0e102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[TestDb]")
+ 07132290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SharedReportingDb]")
+ c7072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Business Logic Server")
+ ca122290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Report Server")
+ 68102290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Dedicated Test Business Logic Server")
+ f4112290_1ec3_e711_8c5a_005056ad0002("fa:fa-database [DBServer\\SharedDbInstance]")
+ d6072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server DBServer")
+ 71082290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:MSSQLSERVER")
+ c0102290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLAgent")
+ 9a072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLBrowser")
+ 1d0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost1")
+ 200a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost2")
+ 1c0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost3")
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->db052290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->30122290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->c1112290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
+ db052290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
+ db052290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->07132290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
+ 30122290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
+ 30122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
+ 5e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 5e112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
+ c1112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
+ c1112290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
+ b7042290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 8f102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 0e102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 07132290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ c7072290_1ec3_e711_8c5a_005056ad0002-->1d0a2290_1ec3_e711_8c5a_005056ad0002
+ ca122290_1ec3_e711_8c5a_005056ad0002-->200a2290_1ec3_e711_8c5a_005056ad0002
+ 68102290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->c0102290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->9a072290_1ec3_e711_8c5a_005056ad0002
+ d6072290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
+ 71082290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ c0102290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ c0102290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ 9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ 9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD8: should render labels with numbers at the start', () => {
+ imgSnapshotTest(
+ `
+ graph TB;subgraph "number as labels";1;end;
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD9: should render subgraphs', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph One
+ a1-->a2
+ end
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD10: should render subgraphs with a title starting with a digit', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph 2Two
+ a1-->a2
+ end
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD11: should render styled subgraphs', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ A
+ B
+ subgraph foo[Foo SubGraph]
+ C
+ D
+ end
+ subgraph bar[Bar SubGraph]
+ E
+ F
+ end
+ G
+
+ A-->B
+ B-->C
+ C-->D
+ B-->D
+ D-->E
+ E-->A
+ E-->F
+ F-->D
+ F-->G
+ B-->G
+ G-->D
+
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD12: should render a flowchart with long names and class definitions', () => {
+ imgSnapshotTest(
+ `graph LR
+ sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
+
+ 提交申请
+ 熊大
+ "];
+ class sid-B3655226-6C29-4D00-B685-3D5C734DC7E1 node-executed;
+ sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A["
+ 负责人审批
+ 强子
+ "];
+ class sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A node-executed;
+ sid-E27C0367-E6D6-497F-9736-3CDC21FDE221["
+ DBA审批
+ 强子
+ "];
+ class sid-E27C0367-E6D6-497F-9736-3CDC21FDE221 node-executed;
+ sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD["
+ SA审批
+ 阿美
+ "];
+ class sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD node-executed;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7["
+ 主管审批
+ 光头强
+ "];
+ class sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7 node-executed;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89["
+ DBA确认
+ 强子
+ "];
+ class sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89 node-executed;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937["
+ SA确认
+ 阿美
+ "];
+ class sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937 node-executed;
+ sid-4FC27B48-A6F9-460A-A675-021F5854FE22["
+ 结束
+ "];
+ class sid-4FC27B48-A6F9-460A-A675-021F5854FE22 node-executed;
+ sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E["
+ SA执行1
+ 强子
+ "];
+ class sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E node-executed;
+ sid-6C2120F3-D940-4958-A067-0903DCE879C4["
+ SA执行2
+ 强子
+ "];
+ class sid-6C2120F3-D940-4958-A067-0903DCE879C4 node-executed;
+ sid-9180E2A0-5C4B-435F-B42F-0D152470A338["
+ DBA执行1
+ 强子
+ "];
+ class sid-9180E2A0-5C4B-435F-B42F-0D152470A338 node-executed;
+ sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD["
+ DBA执行3
+ 强子
+ "];
+ class sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD node-executed;
+ sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756["
+ DBA执行2
+ 强子
+ "];
+ class sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756 node-executed;
+ sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93["
+ DBA执行4
+ 强子
+ "];
+ class sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93 node-executed;
+ sid-1897B30A-9C5C-4D5B-B80B-76A038785070["
+ 负责人确认
+ 梁静茹
+ "];
+ class sid-1897B30A-9C5C-4D5B-B80B-76A038785070 node-executed;
+ sid-B3655226-6C29-4D00-B685-3D5C734DC7E1-->sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7;
+ sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A-->sid-1897B30A-9C5C-4D5B-B80B-76A038785070;
+ sid-E27C0367-E6D6-497F-9736-3CDC21FDE221-->sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89;
+ sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD-->sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937;
+ sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
+ sid-9180E2A0-5C4B-435F-B42F-0D152470A338-->sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756;
+ sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD-->sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93;
+ sid-6C2120F3-D940-4958-A067-0903DCE879C4-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-1897B30A-9C5C-4D5B-B80B-76A038785070-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-9180E2A0-5C4B-435F-B42F-0D152470A338;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD;
+ sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-E27C0367-E6D6-497F-9736-3CDC21FDE221;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD13: should render color of styled nodes', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ foo-->bar
+
+ classDef foo fill:lightblue,color:green,stroke:#FF9E2C,font-weight:bold
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F
+ style bar fill:#999,color: #00ff00, stroke-width:10px,stroke:#0F0
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ listUrl: false,
+ listId: 'color styling',
+ fontFamily: 'courier',
+ logLevel: 0,
+ }
+ );
+ });
+
+ it('FHD14: should render hexagons', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{{Let me think... Do I want something for work, something to spend every free second with, or something to get around?}}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[Car]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ listUrl: false,
+ listId: 'color styling',
+ fontFamily: 'courier',
+ logLevel: 0,
+ }
+ );
+ });
+
+ it('FHD15: should render a simple flowchart with comments', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD16: Render Stadium shape', () => {
+ imgSnapshotTest(
+ ` graph TD
+ A([stadium shape test])
+ A -->|Get money| B([Go shopping])
+ B --> C([Let me think... Do I want something for work, something to spend every free second with, or something to get around?])
+ C -->|One| D([Laptop])
+ C -->|Two| E([iPhone])
+ C -->|Three| F([Car wroom wroom])
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD17: Render multiline texts', () => {
+ imgSnapshotTest(
+ `graph LR
+ A1[Multi Line] -->|Multi Line| B1(Multi Line)
+ C1[Multi Line] -->|Multi Line| D1(Multi Line)
+ E1[Multi Line] -->|Multi Line| F1(Multi Line)
+ A2[Multi Line] -->|Multi Line| B2(Multi Line)
+ C2[Multi Line] -->|Multi Line| D2(Multi Line)
+ E2[Multi Line] -->|Multi Line| F2(Multi Line)
+ linkStyle 0 stroke:DarkGray,stroke-width:2px
+ linkStyle 1 stroke:DarkGray,stroke-width:2px
+ linkStyle 2 stroke:DarkGray,stroke-width:2px
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD18: Chaining of nodes', () => {
+ imgSnapshotTest(
+ `graph LR
+ a --> b --> c
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD19: Multiple nodes and chaining in one statement', () => {
+ imgSnapshotTest(
+ `graph LR
+ a --> b & c--> d
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD20: Multiple nodes and chaining in one statement', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
+ classDef exClass background:#bbb,border:1px solid red;
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH21: Render cylindrical shape', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[(cylindrical shape test)]
+ A -->|Get money| B1[(Go shopping 1)]
+ A -->|Get money| B2[(Go shopping 2)]
+ A -->|Get money| B3[(Go shopping 3)]
+ C[(Let me think... Do I want something for work, something to spend every free second with, or something to get around?)]
+ B1 --> C
+ B2 --> C
+ B3 --> C
+ C -->|One| D[(Laptop)]
+ C -->|Two| E[(iPhone)]
+ C -->|Three| F[(Car)]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;`,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH22: Render a simple flowchart with nodeSpacing set to 100', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
+ );
+ });
+
+ it('FDH23: Render a simple flowchart with rankSpacing set to 100', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { rankSpacing: '100' },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH24: Keep node label text (if already defined) when a style is applied', () => {
+ imgSnapshotTest(
+ `graph LR
+ A(( )) -->|step 1| B(( ))
+ B(( )) -->|step 2| C(( ))
+ C(( )) -->|step 3| D(( ))
+ linkStyle 1 stroke:greenyellow,stroke-width:2px
+ style C fill:greenyellow,stroke:green,stroke-width:4px
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH25: Handle link click events (link, anchor, mailto, other protocol, script)', () => {
+ imgSnapshotTest(
+ `graph TB
+ TITLE["Link Click Events (click the nodes below)"]
+ A["link test (open in same tab)"]
+ B["link test (open in new tab)"]
+ C[anchor test]
+ D[mailto test]
+ E[other protocol test]
+ F[script test]
+ TITLE --> A & B & C & D & E & F
+ click A "https://mermaid-js.github.io/mermaid/#/" "link test (open in same tab)"
+ click B "https://mermaid-js.github.io/mermaid/#/" "link test (open in new tab)" _blank
+ click C "#link-clicked"
+ click D "mailto:user@user.user" "mailto test"
+ click E "notes://do-your-thing/id" "other protocol test"
+ click F "javascript:alert('test')" "script test"
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, securityLevel: 'loose', fontFamily: 'courier' }
+ );
+ });
+
+ it('FDH26: Set text color of nodes and links according to styles when html labels are enabled', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[red text] -->|red text| B(blue text)
+ C[/red text/] -->|blue text| D{blue text}
+ E{{default style}} -->|default style| F([default style])
+ linkStyle default color:Sienna;
+ linkStyle 0 color:red;
+ linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
+ style A color:red;
+ style B color:blue;
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ click B "index.html#link-clicked" "link test"
+ click D testClick "click test"
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { htmlLabels: true } }
+ );
+ });
+
+ it('FDH27: Set text color of nodes and links according to styles when html labels are disabled', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[red text] -->|red text| B(blue text)
+ C[/red text/] -->|blue text| D{blue text}
+ E{{default style}} -->|default style| F([default style])
+ linkStyle default color:Sienna;
+ linkStyle 0 color:red;
+ linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
+ style A color:red;
+ style B color:blue;
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ click B "index.html#link-clicked" "link test"
+ click D testClick "click test"
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH28: Apply default class to all nodes which do not have another class assigned (htmlLabels enabled)', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[myClass1] --> B[default] & C[default]
+ B[default] & C[default] --> D[myClass2]
+ classDef default stroke-width:2px,fill:none,stroke:silver
+ classDef node color:red
+ classDef myClass1 color:#0000ff
+ classDef myClass2 stroke:#0000ff,fill:#ccccff
+ class A myClass1
+ class D myClass2
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { htmlLabels: true } }
+ );
+ });
+
+ it('FDH29: Apply default class to all nodes which do not have another class assigned (htmlLabels disabled)', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[myClass1] --> B[default] & C[default]
+ B[default] & C[default] --> D[myClass2]
+ classDef default stroke-width:2px,fill:none,stroke:silver
+ classDef node color:red
+ classDef myClass1 color:#0000ff
+ classDef myClass2 stroke:#0000ff,fill:#ccccff
+ class A myClass1
+ class D myClass2
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH30: Possibility to style text color of nodes and subgraphs as well as apply classes to subgraphs', () => {
+ imgSnapshotTest(
+ `graph LR
+ subgraph id1 [title is set]
+ A-->B
+ end
+ subgraph id2 [title]
+ E
+ end
+
+ B-->C
+
+ subgraph id3
+ C-->D
+ end
+ class id3,id2,A redBg;
+ class id3,A whiteTxt;
+ classDef redBg fill:#622;
+ classDef whiteTxt color: white;
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH31: should not slice off edges that are to the left of the left-most vertex', () => {
+ imgSnapshotTest(
+ `graph TD
+ work --> sleep
+ sleep --> work
+ eat --> sleep
+ work --> eat
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH32: Render Subroutine shape', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[[subroutine shape test]]
+ A -->|Get money| B[[Go shopping]]
+ B --> C[[Let me think... Do I want something for work, something to spend every free second with, or something to get around?]]
+ C -->|One| D[[Laptop]]
+ C -->|Two| E[[iPhone]]
+ C -->|Three| F[[Car wroom wroom]]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH33: should render a simple flowchart with diagramPadding set to 0', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { diagramPadding: 0 } }
+ );
+ });
+
+ it('FDH34: testing the label width in percy', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0 }
+ );
+ });
+
+ it('FDH35: should honor minimum edge length as specified by the user', () => {
+ imgSnapshotTest(
+ `graph TD
+ L1 --- L2
+ L2 --- C
+ M1 ---> C
+ R1 .-> R2
+ R2 <.-> C
+ C -->|Label 1| E1
+ C -- Label 2 ---> E2
+ C ----> E3
+ C -----> E4
+ C ======> E5
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0 }
+ );
+ });
+ it('FDH36: should render escaped without html labels', () => {
+ imgSnapshotTest(
+ `graph TD
+ a["Haiya "]-->b
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, htmlLabels: false, flowchart: { htmlLabels: false } }
+ );
+ });
+ it('FDH37: should render non-escaped with html labels', () => {
+ imgSnapshotTest(
+ `graph TD
+ a["Haiya "]-->b
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH38: should render a flowchart when useMaxWidth is true (default)', () => {
+ renderGraph(
+ `flowchart TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { useMaxWidth: true } }
+ );
+ cy.get('svg').should((svg) => {
+ expect(svg).to.have.attr('width', '100%');
+ // expect(svg).to.have.attr('height');
+ // use within because the absolute value can be slightly different depending on the environment ±10%
+ // const height = parseFloat(svg.attr('height'));
+ // expect(height).to.be.within(446 * 0.95, 446 * 1.05);
+ const style = svg.attr('style');
+ expect(style).to.match(/^max-width: [\d.]+px;$/);
+ const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
+ expect(maxWidthValue).to.be.within(446 * 0.9, 446 * 1.1);
+ });
+ });
+ it('FDH39: should render a flowchart when useMaxWidth is false', () => {
+ renderGraph(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 0, flowchart: { useMaxWidth: false } }
+ );
+ cy.get('svg').should((svg) => {
+ // const height = parseFloat(svg.attr('height'));
+ const width = parseFloat(svg.attr('width'));
+ // use within because the absolute value can be slightly different depending on the environment ±10%
+ // expect(height).to.be.within(446 * 0.95, 446 * 1.05);
+ expect(width).to.be.within(446 * 0.9, 446 * 1.1);
+ expect(svg).to.not.have.attr('style');
+ });
+ });
+ it('FDH40: handle styling with style expressions', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ 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
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH41: handle styling for all node shapes', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ A[red text] -->|default style| B(blue text)
+ C([red text]) -->|default style| D[[blue text]]
+ E[(red text)] -->|default style| F((blue text))
+ G>red text] -->|default style| H{blue text}
+ I{{red text}} -->|default style| J[/blue text/]
+ linkStyle default color:Sienna;
+ style A stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style B stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style E stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style F stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style G stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style H stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style I stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style J stroke:#0000ff,fill:#ccccff,color:#0000ff
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH42: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+graph TD
+ C -->|fa:fa-car Car| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH43: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph bar[Bar]
+ F
+ end
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH44: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ A
+ B
+ subgraph foo[Foo SubGraph]
+ C
+ D
+ end
+ subgraph bar[Bar SubGraph]
+ E
+ F
+ end
+ G
+
+ A-->B
+ B-->C
+ C-->D
+ B-->D
+ D-->E
+ E-->A
+ E-->F
+ F-->D
+ F-->G
+ B-->G
+ G-->D
+
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH45: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ %%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%%
+ flowchart LR
+ subgraph A
+ a --> b
+ end
+ subgraph B
+ i -->f
+ end
+ A --> B
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH46: text-color from classes', () => {
+ imgSnapshotTest(
+ `
+ flowchart LR
+ classDef dark fill:#000,stroke:#000,stroke-width:4px,color:#fff
+ Lorem --> Ipsum --> Dolor
+ class Lorem,Dolor dark
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH47: apply class called default on node called default', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
+ hello --> default
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+
+ it('FDH48: should be able to style default node independently', () => {
+ imgSnapshotTest(
+ `
+ flowchart TD
+ classDef default fill:#a34
+ hello --> default
+
+ style default stroke:#000,stroke-width:4px
+ `,
+ {
+ look: 'handDrawn',
+ handDrawnSeed: 0,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index 3eb2a0432a..c2fd0b0119 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- expect(maxWidthValue).to.be.within(290 * 0.95 - 1, 290 * 1.05);
+ expect(maxWidthValue).to.be.within(446 * 0.95 - 1, 446 * 1.05);
});
});
it('8: should render a flowchart when useMaxWidth is false', () => {
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±5%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
- expect(width).to.be.within(290 * 0.95 - 1, 290 * 1.05);
+ expect(width).to.be.within(446 * 0.95 - 1, 446 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
@@ -1047,7 +1047,9 @@ end
A --lb3--> TOP --lb4--> B
B1 --lb5--> B2
`,
- { flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
+ {
+ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } },
+ }
);
});
});
diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js
index e4766e7923..d3a83ae5f2 100644
--- a/cypress/integration/rendering/flowchart.spec.js
+++ b/cypress/integration/rendering/flowchart.spec.js
@@ -733,7 +733,7 @@ describe('Graph', () => {
});
it('38: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph(
- `graph TD
+ `flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
@@ -751,7 +751,7 @@ describe('Graph', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- expect(maxWidthValue).to.be.within(300 * 0.9, 300 * 1.1);
+ expect(maxWidthValue).to.be.within(446 * 0.9, 446 * 1.1);
});
});
it('39: should render a flowchart when useMaxWidth is false', () => {
@@ -770,7 +770,7 @@ describe('Graph', () => {
const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±10%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
- expect(width).to.be.within(300 * 0.9, 300 * 1.1);
+ expect(width).to.be.within(446 * 0.9, 446 * 1.1);
expect(svg).to.not.have.attr('style');
});
});
@@ -905,13 +905,16 @@ graph TD
it('67: should be able to style default node independently', () => {
imgSnapshotTest(
`
- flowchart TD
+ flowchart TD
classDef default fill:#a34
hello --> default
style default stroke:#000,stroke-width:4px
`,
- { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
+ {
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
);
});
});
diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js
index cb40aa8dc0..606a1a3f57 100644
--- a/cypress/integration/rendering/stateDiagram-v2.spec.js
+++ b/cypress/integration/rendering/stateDiagram-v2.spec.js
@@ -542,6 +542,43 @@ stateDiagram-v2
{ logLevel: 0, fontFamily: 'courier' }
);
});
+ it(' can have styles applied ', () => {
+ imgSnapshotTest(
+ `
+stateDiagram-v2
+AState
+style AState fill:#636,border:1px solid red,color:white;
+ `,
+ { logLevel: 0, fontFamily: 'courier' }
+ );
+ });
+ it(' should let styles take preceedence over classes', () => {
+ imgSnapshotTest(
+ `
+stateDiagram-v2
+AState: Should NOT be white
+BState
+classDef exampleStyleClass fill:#fff,color: blue;
+class AState,BState exampleStyleClass
+style AState fill:#636,border:1px solid red,color:white;
+ `,
+ { logLevel: 0, fontFamily: 'courier' }
+ );
+ });
+ it(' should allow styles to take effect in stubgraphs', () => {
+ imgSnapshotTest(
+ `
+ stateDiagram
+ state roundWithTitle {
+ C: Black with white text
+ }
+ D: Black with white text
+
+ style C,D stroke:#00f, fill:black, color:white
+ `,
+ { logLevel: 0, fontFamily: 'courier' }
+ );
+ });
});
it('1433: should render a simple state diagram with a title', () => {
imgSnapshotTest(
@@ -551,6 +588,20 @@ title: simple state diagram
stateDiagram-v2
[*] --> State1
State1 --> [*]
+`,
+ {}
+ );
+ });
+ it('should align dividers correctly', () => {
+ imgSnapshotTest(
+ `stateDiagram-v2
+ state s2 {
+ s3
+ --
+ s4
+ --
+ 55
+ }
`,
{}
);
diff --git a/cypress/platform/flowchart-refactor.html b/cypress/platform/flowchart-refactor.html
new file mode 100644
index 0000000000..034e79a529
--- /dev/null
+++ b/cypress/platform/flowchart-refactor.html
@@ -0,0 +1,866 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dagre
+ Dagre with rough
+ ELK
+ ELK with rough
+
+
+
+
+
+flowchart LR
+ id1([This is the text in the box])
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+flowchart LR
+ id1([This is the text in the box])
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+flowchart LR
+ id1([This is the text in the box])
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+flowchart LR
+ id1([This is the text in the box])
+
+
+
+
+
+
+
+
+
+
+
+
+
+flowchart LR
+ id1[[This is the text in the box]]
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+flowchart LR
+ id1[[This is the text in the box]]
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+flowchart LR
+ id1[[This is the text in the box]]
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+flowchart LR
+ id1[[This is the text in the box]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1[(Database)]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1[(Database)]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1[(Database)]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1[(Database)]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1((This is the text in the circle))
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1((This is the text in the circle))
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1((This is the text in the circle))
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1((This is the text in the circle))
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart TD
+ id1(((This is the text in the circle)))
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart TD
+ id1(((This is the text in the circle)))
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart TD
+ id1(((This is the text in the circle)))
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart TD
+ id1(((This is the text in the circle)))
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1>This is the text in the box]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1>This is the text in the box]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1>This is the text in the box]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1>This is the text in the box]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1{This is the text in the box}
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1{This is the text in the box}
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1{This is the text in the box}
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1{This is the text in the box}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1{{This is the text in the box}}
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1{{This is the text in the box}}
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1{{This is the text in the box}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart TD
+ id1[/This is the text in the box/]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart TD
+ id1[/This is the text in the box/]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart TD
+ id1[/This is the text in the box/]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart TD
+ id1[/This is the text in the box/]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart TD
+ id1[\This is the text in the box\]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart TD
+ id1[\This is the text in the box\]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart TD
+ id1[\This is the text in the box\]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart TD
+ id1[\This is the text in the box\]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart TD
+ A[/Christmas\]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart TD
+ A[/Christmas\]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart TD
+ A[/Christmas\]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart TD
+ A[/Christmas\]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart TD
+ A[\Christmas/]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart TD
+ A[\Christmas/]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart TD
+ A[\Christmas/]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart TD
+ A[\Christmas/]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1(This is the text in the box)
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1(This is the text in the box)
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1(This is the text in the box)
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1(This is the text in the box)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ flowchart LR
+ id1[This is the text in the box]
+
+
+
+
+ %%{init: {"look": "handDrawn"} }%%
+ flowchart LR
+ id1[This is the text in the box]
+
+
+
+
+ %%{init: {"handDrawn": false, "layout": "elk"} }%%
+ flowchart LR
+ id1[This is the text in the box]
+
+
+
+
+ %%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ flowchart LR
+ id1[This is the text in the box]
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cypress/platform/flowchart-sate.html b/cypress/platform/flowchart-sate.html
new file mode 100644
index 0000000000..37cf4a8287
--- /dev/null
+++ b/cypress/platform/flowchart-sate.html
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ State rough
+ Flowchart rough
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+stateDiagram-v2
+ stateA
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+flowchart LR
+ id1[[This is the text in the box]]
+
+
+
+
+
+
+
+
+
+
diff --git a/cypress/platform/knsv-4442.html b/cypress/platform/knsv-4442.html
new file mode 100644
index 0000000000..fc15a1685f
--- /dev/null
+++ b/cypress/platform/knsv-4442.html
@@ -0,0 +1,433 @@
+
+
+
+
+
+
+
+
+
+
+
+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
+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
+ 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}}
+
+
+%%{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
+ %% 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 LR
+ B1 --be be--x B2
+ B1 --bo bo--o B3
+ subgraph Ugge
+ B2
+ B3
+ subgraph inner
+ B4
+ B5
+ end
+ subgraph inner2
+ subgraph deeper
+ C4
+ C5
+ end
+ C6
+ end
+
+ B4 --> C4
+
+ B3 -- X --> B4
+ B2 --> inner
+
+ C4 --> C5
+ end
+
+ subgraph outer
+ B6
+ end
+ B6 --> B5
+
+
+sequenceDiagram
+ Customer->>+Stripe: Makes a payment request
+ Stripe->>+Bank: Forwards the payment request to the bank
+ Bank->>+Customer: Asks for authorization
+ Customer->>+Bank: Provides authorization
+ Bank->>+Stripe: Sends a response with payment details
+ Stripe->>+Merchant: Sends a notification of payment receipt
+ Merchant->>+Stripe: Confirms the payment
+ Stripe->>+Customer: Sends a confirmation of payment
+ Customer->>+Merchant: Receives goods or services
+
+
+mindmap
+ root((mindmap))
+ Origins
+ Long history
+ ::icon(fa fa-book)
+ Popularisation
+ British popular psychology author Tony Buzan
+ Research
+ On effectiveness and features
+ On Automatic creation
+ Uses
+ Creative techniques
+ Strategic planning
+ Argument mapping
+ Tools
+ Pen and paper
+ Mermaid
+
+
+
+ example-diagram
+
+
+
+
+
+
+
+
+
+ // import mindmap from '../../packages/mermaid-mindmap/src/detector'; // import example from
+ '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs'; import mermaid
+ from './mermaid.esm.mjs'; // await mermaid.registerExternalDiagrams([example]);
+ mermaid.parseError = function (err, hash) { // console.error('Mermaid error: ', err); };
+ mermaid.initialize({ // theme: 'forest', startOnLoad: true, logLevel: 0, flowchart: { //
+ defaultRenderer: 'elk', useMaxWidth: false, // htmlLabels: false, htmlLabels: true, }, //
+ htmlLabels: false, gantt: { useMaxWidth: false, }, useMaxWidth: false, }); function callback()
+ { alert('It worked'); } mermaid.parseError = function (err, hash) { console.error('In parse
+ error:'); console.error(err); }; // mermaid.test1('first_slow', 1200).then((r) =>
+ console.info(r)); // mermaid.test1('second_fast', 200).then((r) => console.info(r)); //
+ mermaid.test1('third_fast', 200).then((r) => console.info(r)); // mermaid.test1('forth_slow',
+ 1200).then((r) => console.info(r));
+
+
+
+
+
diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html
index b12a2e765d..ab7d9f68d0 100644
--- a/cypress/platform/knsv2.html
+++ b/cypress/platform/knsv2.html
@@ -4,7 +4,7 @@
+
+
+
+
+
+
-
-
- block-beta
- blockArrowId<["Label"]>(right)
- blockArrowId2<["Label"]>(left)
- blockArrowId3<["Label"]>(up)
- blockArrowId4<["Label"]>(down)
- blockArrowId5<["Label"]>(x)
- blockArrowId6<["Label"]>(y)
- blockArrowId6<["Label"]>(x, down)
-
-
-block-beta
- block:e:4
- columns 2
- f
- g
- end
-
-
-
-block-beta
- block:e:4
- columns 2
- f
- g
- h
- end
-
-
-
-block-beta
- columns 4
- a b c d
- block:e:4
- columns 2
- f
- g
- h
- end
- i:4
-
-
-flowchart LR
- X-- "y" -->z
-
-
-block-beta
-columns 5
- A space B
- A --x B
-
-
-block-beta
-columns 3
- a["A wide one"] b:2 c:2 d
-
-
-block-beta
- block:e
- f
- end
-
-
-block-beta
- columns 3
- a:3
- block:e:3
- f
- end
- g
-
-
-block-beta
- columns 3
- a:3
- block:e:3
- f
- g
- end
- h
- i
- j
-
-
-
-block-beta
-columns 3
- a b:2
- block:e:3
- f
- end
- g h i
-
-
-block-beta
-columns 3
- a b c
- e:3
- f g h
-
-
-block-beta
-columns 1
- db(("DB"))
- blockArrowId6<[" "]>(down)
- block:ID
- A
- B["A wide one in the middle"]
- C
- end
- space
- D
- ID --> D
- C --> D
- style B fill:#f9F,stroke:#333,stroke-width:4px
-
-
-block-beta
- columns 5
- A1:3
- A2:1
- A3
- B1 B2 B3:3
-
-
-block-beta
- block
- D
- E
- end
- db("This is the text in the box")
-
-
-block-beta
-
- block
- D
- end
- A["A: I am a wide one"]
-
-
-block-beta
- A["square"]
- B("rounded")
- C(("circle"))
-
-
-block-beta
- A>"rect_left_inv_arrow"]
- B{"diamond"}
- C{{"hexagon"}}
-
-
-block-beta
- A(["stadium"])
-
-
-block-beta
- %% A[["subroutine"]]
- %% B[("cylinder")]
- C>"surprise"]
-
-
-block-beta
- A[/"lean right"/]
- B[\"lean left"\]
- C[/"trapezoid"\]
- D[\"trapezoid"/]
-
-
-
+
+
+
+---
+config:
+ look: handDrawn
+ flowchart:
+ htmlLabels: false
+---
flowchart
- B
- style B fill:#f9F,stroke:#333,stroke-width:4px
-
-
-
- flowchart LR
- a1 -- apa --> b1
-
-
-
-flowchart RL
- subgraph "`one`"
- id
- end
-
-
-flowchart RL
- subgraph "`one`"
- a1 -- l1 --> a2
- a1 -- l2 --> a2
- end
-
-
+ A[I am a long text, where do I go??? handdrawn - false]
+
+
+---
+config:
+ look: handdrawn
+ flowchart:
+ htmlLabels: true
+---
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)
+ A[I am a long text, where do I go??? handdrawn - true]
-
- %%{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
- 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}}
+ >
+
+
+
+---
+config:
+ flowchart:
+ htmlLabels: false
+---
+flowchart
+ A[I am a long text, where do I go??? classic - false]
-
-%%{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}}
+ >
+
+---
+config:
+ flowchart:
+ htmlLabels: true
+---
+flowchart
+ A[I am a long text, where do I go??? classic - true]
-
-
-
-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]
+ >
+
+
+flowchart LR
+ 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
- mem --- ddr3
- dram --- ddr3
- edram --- ddr3
- core1 --- mem
+
- exi --- rtc
- rtc{{rtc}}
-
-
-
-
+
flowchart LR
- B1 --be be--x B2
- B1 --bo bo--o B3
- subgraph Ugge
- B2
- B3
- subgraph inner
- B4
- B5
- end
- subgraph inner2
- subgraph deeper
- C4
- C5
- end
- C6
- end
+ A:::foo & B:::bar --> C:::foobar
+ classDef foo stroke:#f00
+ classDef bar stroke:#0f0
+ classDef ash color:red
+ class C ash
+ style C stroke:#00f, fill:black
- B4 --> C4
-
- B3 -- X --> B4
- B2 --> inner
+
- C4 --> C5
- end
+
+ stateDiagram
+ A:::foo
+ B:::bar --> C:::foobar
+ classDef foo stroke:#f00
+ classDef bar stroke:#0f0
+ style C stroke:#00f, fill:black, color:white
- subgraph outer
- B6
- end
- B6 --> B5
-
-
-sequenceDiagram
- Customer->>+Stripe: Makes a payment request
- Stripe->>+Bank: Forwards the payment request to the bank
- Bank->>+Customer: Asks for authorization
- Customer->>+Bank: Provides authorization
- Bank->>+Stripe: Sends a response with payment details
- Stripe->>+Merchant: Sends a notification of payment receipt
- Merchant->>+Stripe: Confirms the payment
- Stripe->>+Customer: Sends a confirmation of payment
- Customer->>+Merchant: Receives goods or services
-
-
-mindmap
- root((mindmap))
- Origins
- Long history
- ::icon(fa fa-book)
- Popularisation
- British popular psychology author Tony Buzan
- Research
- On effectiveness and features
- On Automatic creation
- Uses
- Creative techniques
- Strategic planning
- Argument mapping
- Tools
- Pen and paper
- Mermaid
-
-
- example-diagram
-
-
-
-
-
-
-
diff --git a/cypress/platform/knsv3.html b/cypress/platform/knsv3.html
index 26368a62a5..1d65c2ddfb 100644
--- a/cypress/platform/knsv3.html
+++ b/cypress/platform/knsv3.html
@@ -1,4 +1,4 @@
-
+
diff --git a/cypress/platform/state-refactor.html b/cypress/platform/state-refactor.html
new file mode 100644
index 0000000000..c9347eee96
--- /dev/null
+++ b/cypress/platform/state-refactor.html
@@ -0,0 +1,1271 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dagre
+ Dagre with rough
+ ELK
+ ELK with rough
+
+
+
+
+
+stateDiagram-v2
+ stateId
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+stateDiagram-v2
+ stateId
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ stateId
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ stateId
+
+
+
+
+
+
+
+
+
+
+
+
+stateDiagram-v2
+ state "This is a state description" as s2
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+stateDiagram-v2
+ state "This is a state description" as s3
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ state "This is a state description" as s4
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ state "This is a state description" as s5
+
+
+
+
+
+
+
+
+
+
+
+stateDiagram-v2
+ s21 : This is a state description
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+stateDiagram-v2
+ s22 : This is a state description
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ s23 : This is a state description
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ s24 : This is a state description
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ s31 --> s32
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ s41 --> s42
+
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ s51 --> s52
+
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ s61 --> s62
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ a1 --> a2: A transition
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ a3 --> a4: A transition
+
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ a5 --> a6: A transition
+
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ a7 --> a8: A transition
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ [*] --> test
+ test --> [*]
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ [*] --> test
+ test --> [*]
+
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ [*] --> test
+ test --> [*]
+
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ [*] --> test
+ test --> [*]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ [*] --> First
+ state First {
+ [*] --> second
+ second --> [*]
+ }
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ [*] --> First
+ state First {
+ [*] --> second
+ second --> [*]
+ }
+
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+stateDiagram-v2
+ [*] --> First
+ state First {
+ [*] --> second
+ second --> [*]
+ }
+
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+stateDiagram-v2
+ [*] --> First
+ state First {
+ [*] --> second
+ second --> [*]
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ [*] --> Level1
+
+ state Level1 {
+ [*] --> Level2
+
+ state Level2 {
+ [*] --> level2
+ level2 --> Level3
+
+ state Level3 {
+ [*] --> level3
+ level3 --> [*]
+ }
+ }
+ }
+
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ [*] --> Level1
+
+ state Level1 {
+ [*] --> Level2
+
+ state Level2 {
+ [*] --> level2
+ level2 --> Level3
+
+ state Level3 {
+ [*] --> level3
+ level3 --> [*]
+ }
+ }
+ }
+
+
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ [*] --> Level1
+
+ state Level1 {
+ [*] --> Level2
+
+ state Level2 {
+ [*] --> level2
+ level2 --> Level3
+
+ state Level3 {
+ [*] --> level3
+ level3 --> [*]
+ }
+ }
+ }
+
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ [*] --> Level1
+
+ state Level1 {
+ [*] --> Level2
+
+ state Level2 {
+ [*] --> level2
+ level2 --> Level3
+
+ state Level3 {
+ [*] --> level3
+ level3 --> [*]
+ }
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ [*] --> B1
+ B1 --> B2
+ B1 --> B3
+
+ state B1 {
+ [*] --> B11
+ B11 --> [*]
+ }
+ state B2 {
+ [*] --> B22
+ B22 --> [*]
+ }
+ state B3 {
+ [*] --> B33
+ B33 --> [*]
+ }
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ [*] --> B1
+ B1 --> B2
+ B1 --> B3
+
+ state B1 {
+ [*] --> B11
+ B11 --> [*]
+ }
+ state B2 {
+ [*] --> B22
+ B22 --> [*]
+ }
+ state B3 {
+ [*] --> B33
+ B33 --> [*]
+ }
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ [*] --> B1
+ B1 --> B2
+ B1 --> B3
+
+ state B1 {
+ [*] --> B11
+ B11 --> [*]
+ }
+ state B2 {
+ [*] --> B22
+ B22 --> [*]
+ }
+ state B3 {
+ [*] --> B33
+ B33 --> [*]
+ }
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+[*] --> B1
+ B1 --> B2
+ B1 --> B3
+
+ state B1 {
+ [*] --> B11
+ B11 --> [*]
+ }
+ state B2 {
+ [*] --> B22
+ B22 --> [*]
+ }
+ state B3 {
+ [*] --> B33
+ B33 --> [*]
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ state if_state <>
+ [*] --> IsPositive
+ IsPositive --> if_state
+ if_state --> False: if n < 0
+ if_state --> True : if n >= 0
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+
+ stateDiagram-v2
+ state if_state <>
+ [*] --> IsPositive
+ IsPositive --> if_state
+ if_state --> False: if n < 0
+ if_state --> True : if n >= 0
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ state if_state <>
+ [*] --> IsPositive
+ IsPositive --> if_state
+ if_state --> False: if n < 0
+ if_state --> True : if n >= 0
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ state if_state <>
+ [*] --> IsPositive
+ IsPositive --> if_state
+ if_state --> False: if n < 0
+ if_state --> True : if n >= 0
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ state fork_state <>
+ [*] --> fork_state
+ fork_state --> State2
+ fork_state --> State3
+
+ state join_state <>
+ State2 --> join_state
+ State3 --> join_state
+ join_state --> State4
+ State4 --> [*]
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+ stateDiagram-v2
+ state fork_state <>
+ [*] --> fork_state
+ fork_state --> State2
+ fork_state --> State3
+
+ state join_state <>
+ State2 --> join_state
+ State3 --> join_state
+ join_state --> State4
+ State4 --> [*]
+
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ state fork_state <>
+ [*] --> fork_state
+ fork_state --> State2
+ fork_state --> State3
+
+ state join_state <>
+ State2 --> join_state
+ State3 --> join_state
+ join_state --> State4
+ State4 --> [*]
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ state fork_state <>
+ [*] --> fork_state
+ fork_state --> State2
+ fork_state --> State3
+
+ state join_state <>
+ State2 --> join_state
+ State3 --> join_state
+ join_state --> State4
+ State4 --> [*]
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ TN1: The state with a note
+ note right of TN1
+ Important information! You can write
+ notes.
+ end note
+ TN1 --> TN2
+ note left of TN2 : This is the note to the left.
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+ stateDiagram-v2
+ TN3: The state with a note
+ note right of TN3
+ Important information! You can write
+ notes.
+ end note
+ TN3 --> TN4
+ note left of TN4 : This is the note to the left.
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ TN5: The state with a note
+ note right of TN5
+ Important information! You can write
+ notes.
+ end note
+ TN5 --> TN6
+ note left of TN6 : This is the note to the left.
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ TN7: The state with a note
+ note right of TN7
+ Important information! You can write
+ notes.
+ end note
+ TN7 --> TN8
+ note left of TN8 : This is the note to the left.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ [*] --> Active
+
+ state Active {
+ [*] --> NumLockOff
+ NumLockOff --> NumLockOn : EvNumLockPressed
+ NumLockOn --> NumLockOff : EvNumLockPressed
+ --
+ [*] --> CapsLockOff
+ CapsLockOff --> CapsLockOn : EvCapsLockPressed
+ CapsLockOn --> CapsLockOff : EvCapsLockPressed
+ --
+ [*] --> ScrollLockOff
+ ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
+ ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
+ }
+
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+ stateDiagram-v2
+ [*] --> Active
+
+ state Active {
+ [*] --> NumLockOff
+ NumLockOff --> NumLockOn : EvNumLockPressed
+ NumLockOn --> NumLockOff : EvNumLockPressed
+ --
+ [*] --> CapsLockOff
+ CapsLockOff --> CapsLockOn : EvCapsLockPressed
+ CapsLockOn --> CapsLockOff : EvCapsLockPressed
+ --
+ [*] --> ScrollLockOff
+ ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
+ ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
+ }
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ [*] --> Active
+
+ state Active {
+ [*] --> NumLockOff
+ NumLockOff --> NumLockOn : EvNumLockPressed
+ NumLockOn --> NumLockOff : EvNumLockPressed
+ --
+ [*] --> CapsLockOff
+ CapsLockOff --> CapsLockOn : EvCapsLockPressed
+ CapsLockOn --> CapsLockOff : EvCapsLockPressed
+ --
+ [*] --> ScrollLockOff
+ ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
+ ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
+ }
+
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ [*] --> Active
+
+ state Active {
+ [*] --> NumLockOff
+ NumLockOff --> NumLockOn : EvNumLockPressed
+ NumLockOn --> NumLockOff : EvNumLockPressed
+ --
+ [*] --> CapsLockOff
+ CapsLockOff --> CapsLockOn : EvCapsLockPressed
+ CapsLockOn --> CapsLockOff : EvCapsLockPressed
+ --
+ [*] --> ScrollLockOff
+ ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
+ ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stateDiagram-v2
+ direction LR
+ [*] --> D1
+ D1 --> D2
+ D2 --> D3
+ state D3 {
+ direction TB
+ D11 --> D22
+ }
+ D2 --> D4
+
+
+
+
+%%{init: {"look": "handDrawn"} }%%
+ stateDiagram-v2
+ direction LR
+ [*] --> D1
+ D1 --> D2
+ D2 --> D3
+ state D3 {
+ direction TB
+ D11 --> D22
+ }
+ D2 --> D4
+
+
+
+
+%%{init: {"handDrawn": false, "layout": "elk"} }%%
+ stateDiagram-v2
+ direction LR
+ [*] --> D1
+ D1 --> D2
+ D2 --> D3
+ state D3 {
+ direction TB
+ D11 --> D22
+ }
+ D2 --> D4
+
+
+
+
+%%{init: {"look": "handDrawn", "layout": "elk"} }%%
+ stateDiagram-v2
+ direction LR
+ [*] --> D1
+ D1 --> D2
+ D2 --> D3
+ state D3 {
+ direction TB
+ D11 --> D22
+ }
+ D2 --> D4
+
+
+
+
+
+
+
+
+
+
+
+
+ New content 1
+ New content 2
+ New content 3
+ New content 4
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js
index c397f0e168..0b480b8bc3 100644
--- a/cypress/platform/viewer.js
+++ b/cypress/platform/viewer.js
@@ -1,5 +1,5 @@
import mermaid from './mermaid.esm.mjs';
-import flowchartELK from './mermaid-flowchart-elk.esm.mjs';
+import { layouts } from './mermaid-layout-elk.esm.mjs';
import externalExample from './mermaid-example-diagram.esm.mjs';
import zenUml from './mermaid-zenuml.esm.mjs';
@@ -46,7 +46,9 @@ const contentLoaded = async function () {
document.getElementsByTagName('body')[0].appendChild(div);
}
- await mermaid.registerExternalDiagrams([externalExample, zenUml, flowchartELK]);
+ await mermaid.registerExternalDiagrams([externalExample, zenUml]);
+
+ mermaid.registerLayoutLoaders(layouts);
mermaid.initialize(graphObj.mermaid);
await mermaid.run();
}
diff --git a/docs/config/setup/interfaces/mermaid.LayoutData.md b/docs/config/setup/interfaces/mermaid.LayoutData.md
new file mode 100644
index 0000000000..6f128f4a71
--- /dev/null
+++ b/docs/config/setup/interfaces/mermaid.LayoutData.md
@@ -0,0 +1,43 @@
+> **Warning**
+>
+> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
+>
+> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaid.LayoutData.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaid.LayoutData.md).
+
+# Interface: LayoutData
+
+[mermaid](../modules/mermaid.md).LayoutData
+
+## Indexable
+
+▪ \[key: `string`]: `any`
+
+## Properties
+
+### config
+
+• **config**: `MermaidConfig`
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/types.d.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L118)
+
+---
+
+### edges
+
+• **edges**: `Edge`\[]
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/types.d.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L117)
+
+---
+
+### nodes
+
+• **nodes**: `Node`\[]
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/types.d.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.d.ts#L116)
diff --git a/docs/config/setup/interfaces/mermaid.LayoutLoaderDefinition.md b/docs/config/setup/interfaces/mermaid.LayoutLoaderDefinition.md
new file mode 100644
index 0000000000..cb8e1a00b5
--- /dev/null
+++ b/docs/config/setup/interfaces/mermaid.LayoutLoaderDefinition.md
@@ -0,0 +1,39 @@
+> **Warning**
+>
+> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
+>
+> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaid.LayoutLoaderDefinition.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaid.LayoutLoaderDefinition.md).
+
+# Interface: LayoutLoaderDefinition
+
+[mermaid](../modules/mermaid.md).LayoutLoaderDefinition
+
+## Properties
+
+### algorithm
+
+• `Optional` **algorithm**: `string`
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/render.ts:9](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L9)
+
+---
+
+### loader
+
+• **loader**: `LayoutLoader`
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/render.ts:8](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L8)
+
+---
+
+### name
+
+• **name**: `string`
+
+#### Defined in
+
+[packages/mermaid/src/rendering-util/render.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L7)
diff --git a/docs/config/setup/interfaces/mermaid.Mermaid.md b/docs/config/setup/interfaces/mermaid.Mermaid.md
index 3654be172d..a340c7a970 100644
--- a/docs/config/setup/interfaces/mermaid.Mermaid.md
+++ b/docs/config/setup/interfaces/mermaid.Mermaid.md
@@ -28,7 +28,7 @@ page.
#### Defined in
-[packages/mermaid/src/mermaid.ts:425](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L425)
+[packages/mermaid/src/mermaid.ts:432](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L432)
---
@@ -59,7 +59,7 @@ A graph definition key
#### Defined in
-[packages/mermaid/src/mermaid.ts:427](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L427)
+[packages/mermaid/src/mermaid.ts:434](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L434)
---
@@ -89,7 +89,7 @@ Use [initialize](mermaid.Mermaid.md#initialize) and [run](mermaid.Mermaid.md#run
#### Defined in
-[packages/mermaid/src/mermaid.ts:421](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L421)
+[packages/mermaid/src/mermaid.ts:427](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L427)
---
@@ -116,7 +116,50 @@ This function should be called before the run function.
#### Defined in
-[packages/mermaid/src/mermaid.ts:424](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L424)
+[packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
+
+---
+
+### internalHelpers
+
+• **internalHelpers**: `Object`
+
+Internal helpers for mermaid
+
+**`Deprecated`**
+
+- This should not be used by external packages, as the definitions will change without notice.
+
+#### Type declaration
+
+| Name | Type |
+| :--------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `common` | { `evaluate`: (`val?`: `string` \| `boolean`) => `boolean` ; `getMax`: (...`values`: `number`\[]) => `number` ; `getMin`: (...`values`: `number`\[]) => `number` ; `getRows`: (`s?`: `string`) => `string`\[] ; `getUrl`: (`useAbsolute`: `boolean`) => `string` ; `hasBreaks`: (`text`: `string`) => `boolean` ; `lineBreakRegex`: `RegExp` ; `removeScript`: (`txt`: `string`) => `string` ; `sanitizeText`: (`text`: `string`, `config`: [`MermaidConfig`](mermaid.MermaidConfig.md)) => `string` ; `sanitizeTextOrArray`: (`a`: `string` \| `string`\[] \| `string`\[]\[], `config`: [`MermaidConfig`](mermaid.MermaidConfig.md)) => `string` \| `string`\[] ; `splitBreaks`: (`text`: `string`) => `string`\[] } |
+| `common.evaluate` | (`val?`: `string` \| `boolean`) => `boolean` |
+| `common.getMax` | (...`values`: `number`\[]) => `number` |
+| `common.getMin` | (...`values`: `number`\[]) => `number` |
+| `common.getRows` | (`s?`: `string`) => `string`\[] |
+| `common.getUrl` | (`useAbsolute`: `boolean`) => `string` |
+| `common.hasBreaks` | (`text`: `string`) => `boolean` |
+| `common.lineBreakRegex` | `RegExp` |
+| `common.removeScript` | (`txt`: `string`) => `string` |
+| `common.sanitizeText` | (`text`: `string`, `config`: [`MermaidConfig`](mermaid.MermaidConfig.md)) => `string` |
+| `common.sanitizeTextOrArray` | (`a`: `string` \| `string`\[] \| `string`\[]\[], `config`: [`MermaidConfig`](mermaid.MermaidConfig.md)) => `string` \| `string`\[] |
+| `common.splitBreaks` | (`text`: `string`) => `string`\[] |
+| `getConfig` | () => [`MermaidConfig`](mermaid.MermaidConfig.md) |
+| `insertCluster` | (`elem`: `any`, `node`: `any`) => `any` |
+| `insertEdge` | (`elem`: `any`, `edge`: `any`, `clusterDb`: `any`, `diagramType`: `any`, `startNode`: `any`, `endNode`: `any`, `id`: `any`) => { `originalPath`: `any` ; `updatedPath`: `any` } |
+| `insertEdgeLabel` | (`elem`: `any`, `edge`: `any`) => `Promise`<`any`> |
+| `insertMarkers` | (`elem`: `any`, `markerArray`: `any`, `type`: `any`, `id`: `any`) => `void` |
+| `insertNode` | (`elem`: `any`, `node`: `any`, `dir`: `any`) => `Promise`<`any`> |
+| `interpolateToCurve` | (`interpolate`: `undefined` \| `string`, `defaultCurve`: `CurveFactory`) => `CurveFactory` |
+| `labelHelper` | (`parent`: `any`, `node`: `any`, `_classes`: `any`) => `Promise`<{ `bbox`: `any` ; `halfPadding`: `number` ; `label`: `any` = labelEl; `shapeSvg`: `any` }> |
+| `log` | `Record`<`LogLevel`, (...`data`: `any`\[]) => `void`(`message?`: `any`, ...`optionalParams`: `any`\[]) => `void`> |
+| `positionEdgeLabel` | (`edge`: `any`, `paths`: `any`) => `void` |
+
+#### Defined in
+
+[packages/mermaid/src/mermaid.ts:439](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L439)
---
@@ -130,7 +173,7 @@ Use [parse](mermaid.Mermaid.md#parse) and [render](mermaid.Mermaid.md#render) in
#### Defined in
-[packages/mermaid/src/mermaid.ts:415](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L415)
+[packages/mermaid/src/mermaid.ts:421](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L421)
---
@@ -180,7 +223,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
#### Defined in
-[packages/mermaid/src/mermaid.ts:416](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L416)
+[packages/mermaid/src/mermaid.ts:422](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L422)
---
@@ -190,7 +233,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
#### Defined in
-[packages/mermaid/src/mermaid.ts:410](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L410)
+[packages/mermaid/src/mermaid.ts:416](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L416)
---
@@ -218,7 +261,31 @@ Used to register external diagram types.
#### Defined in
-[packages/mermaid/src/mermaid.ts:423](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L423)
+[packages/mermaid/src/mermaid.ts:430](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L430)
+
+---
+
+### registerLayoutLoaders
+
+• **registerLayoutLoaders**: (`loaders`: [`LayoutLoaderDefinition`](mermaid.LayoutLoaderDefinition.md)\[]) => `void`
+
+#### Type declaration
+
+▸ (`loaders`): `void`
+
+##### Parameters
+
+| Name | Type |
+| :-------- | :--------------------------------------------------------------- |
+| `loaders` | [`LayoutLoaderDefinition`](mermaid.LayoutLoaderDefinition.md)\[] |
+
+##### Returns
+
+`void`
+
+#### Defined in
+
+[packages/mermaid/src/mermaid.ts:429](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L429)
---
@@ -244,7 +311,7 @@ Used to register external diagram types.
#### Defined in
-[packages/mermaid/src/mermaid.ts:417](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L417)
+[packages/mermaid/src/mermaid.ts:423](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L423)
---
@@ -292,7 +359,7 @@ Renders the mermaid diagrams
#### Defined in
-[packages/mermaid/src/mermaid.ts:422](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L422)
+[packages/mermaid/src/mermaid.ts:428](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L428)
---
@@ -327,7 +394,7 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
#### Defined in
-[packages/mermaid/src/mermaid.ts:426](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L426)
+[packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
---
@@ -337,4 +404,4 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
#### Defined in
-[packages/mermaid/src/mermaid.ts:409](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L409)
+[packages/mermaid/src/mermaid.ts:415](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L415)
diff --git a/docs/config/setup/interfaces/mermaid.MermaidConfig.md b/docs/config/setup/interfaces/mermaid.MermaidConfig.md
index ef5a4313a9..ca7828f529 100644
--- a/docs/config/setup/interfaces/mermaid.MermaidConfig.md
+++ b/docs/config/setup/interfaces/mermaid.MermaidConfig.md
@@ -16,7 +16,7 @@
#### Defined in
-[packages/mermaid/src/config.type.ts:85](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L85)
+[packages/mermaid/src/config.type.ts:112](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L112)
---
@@ -29,7 +29,7 @@ This matters if you are using base tag settings.
#### Defined in
-[packages/mermaid/src/config.type.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L104)
+[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
---
@@ -39,7 +39,7 @@ This matters if you are using base tag settings.
#### Defined in
-[packages/mermaid/src/config.type.ts:162](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L162)
+[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
---
@@ -49,7 +49,7 @@ This matters if you are using base tag settings.
#### Defined in
-[packages/mermaid/src/config.type.ts:159](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L159)
+[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
---
@@ -59,7 +59,7 @@ This matters if you are using base tag settings.
#### Defined in
-[packages/mermaid/src/config.type.ts:150](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L150)
+[packages/mermaid/src/config.type.ts:177](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L177)
---
@@ -69,7 +69,7 @@ This matters if you are using base tag settings.
#### Defined in
-[packages/mermaid/src/config.type.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L76)
+[packages/mermaid/src/config.type.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L103)
---
@@ -83,7 +83,7 @@ You can set this attribute to base the seed on a static string.
#### Defined in
-[packages/mermaid/src/config.type.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L144)
+[packages/mermaid/src/config.type.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L171)
---
@@ -101,7 +101,7 @@ should not change unless content is changed.
#### Defined in
-[packages/mermaid/src/config.type.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L137)
+[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164)
---
@@ -111,7 +111,24 @@ should not change unless content is changed.
#### Defined in
-[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163)
+[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
+
+---
+
+### elk
+
+• `Optional` **elk**: `Object`
+
+#### Type declaration
+
+| Name | Type | Description |
+| :----------------------- | :---------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
+| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
+
+#### Defined in
+
+[packages/mermaid/src/config.type.ts:91](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L91)
---
@@ -121,7 +138,7 @@ should not change unless content is changed.
#### Defined in
-[packages/mermaid/src/config.type.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L152)
+[packages/mermaid/src/config.type.ts:179](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L179)
---
@@ -131,7 +148,7 @@ should not change unless content is changed.
#### Defined in
-[packages/mermaid/src/config.type.ts:145](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L145)
+[packages/mermaid/src/config.type.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L172)
---
@@ -145,7 +162,7 @@ See
#### Defined in
-[packages/mermaid/src/config.type.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L84)
+[packages/mermaid/src/config.type.ts:111](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L111)
---
@@ -155,7 +172,7 @@ See
#### Defined in
-[packages/mermaid/src/config.type.ts:165](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L165)
+[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
---
@@ -169,7 +186,7 @@ If set to true, ignores legacyMathML.
#### Defined in
-[packages/mermaid/src/config.type.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L126)
+[packages/mermaid/src/config.type.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L153)
---
@@ -179,7 +196,7 @@ If set to true, ignores legacyMathML.
#### Defined in
-[packages/mermaid/src/config.type.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L147)
+[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
---
@@ -189,7 +206,19 @@ If set to true, ignores legacyMathML.
#### Defined in
-[packages/mermaid/src/config.type.ts:158](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L158)
+[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
+
+---
+
+### handDrawnSeed
+
+• `Optional` **handDrawnSeed**: `number`
+
+Defines the seed to be used when using handDrawn look. This is important for the automated tests as they will always find differences without the seed. The default value is 0 which gives a random seed.
+
+#### Defined in
+
+[packages/mermaid/src/config.type.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L76)
---
@@ -199,7 +228,7 @@ If set to true, ignores legacyMathML.
#### Defined in
-[packages/mermaid/src/config.type.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L77)
+[packages/mermaid/src/config.type.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L104)
---
@@ -209,7 +238,19 @@ If set to true, ignores legacyMathML.
#### Defined in
-[packages/mermaid/src/config.type.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L148)
+[packages/mermaid/src/config.type.ts:175](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L175)
+
+---
+
+### layout
+
+• `Optional` **layout**: `string`
+
+Defines which layout algorithm to use for rendering the diagram.
+
+#### Defined in
+
+[packages/mermaid/src/config.type.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L81)
---
@@ -224,7 +265,7 @@ fall back to legacy rendering for KaTeX.
#### Defined in
-[packages/mermaid/src/config.type.ts:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L119)
+[packages/mermaid/src/config.type.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L146)
---
@@ -236,7 +277,19 @@ This option decides the amount of logging to be used by mermaid.
#### Defined in
-[packages/mermaid/src/config.type.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L90)
+[packages/mermaid/src/config.type.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L117)
+
+---
+
+### look
+
+• `Optional` **look**: `"classic"` | `"handDrawn"`
+
+Defines which main look to use for the diagram.
+
+#### Defined in
+
+[packages/mermaid/src/config.type.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L71)
---
@@ -246,7 +299,7 @@ This option decides the amount of logging to be used by mermaid.
#### Defined in
-[packages/mermaid/src/config.type.ts:166](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L166)
+[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
---
@@ -258,7 +311,7 @@ Defines the maximum number of edges that can be drawn in a graph.
#### Defined in
-[packages/mermaid/src/config.type.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L75)
+[packages/mermaid/src/config.type.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L90)
---
@@ -270,7 +323,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L70)
+[packages/mermaid/src/config.type.ts:85](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L85)
---
@@ -280,7 +333,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:157](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L157)
+[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
---
@@ -290,7 +343,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:161](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L161)
+[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
---
@@ -300,7 +353,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L153)
+[packages/mermaid/src/config.type.ts:180](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L180)
---
@@ -310,7 +363,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L154)
+[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
---
@@ -320,7 +373,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L156)
+[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
---
@@ -330,7 +383,7 @@ The maximum allowed size of the users text diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:160](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L160)
+[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
---
@@ -344,7 +397,7 @@ This prevents malicious graph directives from overriding a site's default securi
#### Defined in
-[packages/mermaid/src/config.type.ts:111](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L111)
+[packages/mermaid/src/config.type.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L138)
---
@@ -356,7 +409,7 @@ Level of trust for parsed diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L94)
+[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
---
@@ -366,7 +419,7 @@ Level of trust for parsed diagram
#### Defined in
-[packages/mermaid/src/config.type.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L146)
+[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173)
---
@@ -378,7 +431,7 @@ Dictates whether mermaid starts on Page load
#### Defined in
-[packages/mermaid/src/config.type.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L98)
+[packages/mermaid/src/config.type.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L125)
---
@@ -388,7 +441,7 @@ Dictates whether mermaid starts on Page load
#### Defined in
-[packages/mermaid/src/config.type.ts:151](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L151)
+[packages/mermaid/src/config.type.ts:178](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L178)
---
@@ -401,13 +454,13 @@ This is useful when you want to control how to handle syntax errors in your appl
#### Defined in
-[packages/mermaid/src/config.type.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L172)
+[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
---
### theme
-• `Optional` **theme**: `"default"` | `"forest"` | `"dark"` | `"neutral"` | `"null"`
+• `Optional` **theme**: `"default"` | `"base"` | `"dark"` | `"forest"` | `"neutral"` | `"null"`
Theme, the CSS style sheet.
You may also use `themeCSS` to override this value.
@@ -444,7 +497,7 @@ You may also use `themeCSS` to override this value.
#### Defined in
-[packages/mermaid/src/config.type.ts:149](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L149)
+[packages/mermaid/src/config.type.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L176)
---
@@ -454,7 +507,7 @@ You may also use `themeCSS` to override this value.
#### Defined in
-[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164)
+[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
---
@@ -464,4 +517,4 @@ You may also use `themeCSS` to override this value.
#### Defined in
-[packages/mermaid/src/config.type.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L155)
+[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
diff --git a/docs/config/setup/interfaces/mermaid.RunOptions.md b/docs/config/setup/interfaces/mermaid.RunOptions.md
index 228b38b388..6fccdc4545 100644
--- a/docs/config/setup/interfaces/mermaid.RunOptions.md
+++ b/docs/config/setup/interfaces/mermaid.RunOptions.md
@@ -18,7 +18,7 @@ The nodes to render. If this is set, `querySelector` will be ignored.
#### Defined in
-[packages/mermaid/src/mermaid.ts:39](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L39)
+[packages/mermaid/src/mermaid.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L45)
---
@@ -44,7 +44,7 @@ A callback to call after each diagram is rendered.
#### Defined in
-[packages/mermaid/src/mermaid.ts:43](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L43)
+[packages/mermaid/src/mermaid.ts:49](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L49)
---
@@ -56,7 +56,7 @@ The query selector to use when finding elements to render. Default: `".mermaid"`
#### Defined in
-[packages/mermaid/src/mermaid.ts:35](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L35)
+[packages/mermaid/src/mermaid.ts:41](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L41)
---
@@ -68,4 +68,4 @@ If `true`, errors will be logged to the console, but not thrown. Default: `false
#### Defined in
-[packages/mermaid/src/mermaid.ts:47](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L47)
+[packages/mermaid/src/mermaid.ts:53](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L53)
diff --git a/docs/config/setup/modules/config.md b/docs/config/setup/modules/config.md
index 8f13fda443..78a301bf08 100644
--- a/docs/config/setup/modules/config.md
+++ b/docs/config/setup/modules/config.md
@@ -118,7 +118,7 @@ The siteConfig
#### Defined in
-[packages/mermaid/src/config.ts:218](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L218)
+[packages/mermaid/src/config.ts:221](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L221)
---
diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md
index 512d240362..0f0ace33ca 100644
--- a/docs/config/setup/modules/defaultConfig.md
+++ b/docs/config/setup/modules/defaultConfig.md
@@ -14,7 +14,7 @@
#### Defined in
-[packages/mermaid/src/defaultConfig.ts:275](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L275)
+[packages/mermaid/src/defaultConfig.ts:279](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L279)
---
diff --git a/docs/config/setup/modules/mermaid.md b/docs/config/setup/modules/mermaid.md
index 10e9330b5c..ac1bbece02 100644
--- a/docs/config/setup/modules/mermaid.md
+++ b/docs/config/setup/modules/mermaid.md
@@ -14,6 +14,8 @@
- [DetailedError](../interfaces/mermaid.DetailedError.md)
- [ExternalDiagramDefinition](../interfaces/mermaid.ExternalDiagramDefinition.md)
+- [LayoutData](../interfaces/mermaid.LayoutData.md)
+- [LayoutLoaderDefinition](../interfaces/mermaid.LayoutLoaderDefinition.md)
- [Mermaid](../interfaces/mermaid.Mermaid.md)
- [MermaidConfig](../interfaces/mermaid.MermaidConfig.md)
- [ParseOptions](../interfaces/mermaid.ParseOptions.md)
@@ -54,4 +56,4 @@
#### Defined in
-[packages/mermaid/src/mermaid.ts:430](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L430)
+[packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
diff --git a/eslint.config.js b/eslint.config.js
index ed703ea714..8b4807bc57 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -109,6 +109,7 @@ export default tseslint.config(
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/only-throw-error': 'warn',
+ '@typescript-eslint/prefer-nullish-coalescing': 'warn',
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
// END
'json/*': ['error', 'allowComments'],
diff --git a/package.json b/package.json
index 1ee2cff2d4..4e17d516c6 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
]
},
"devDependencies": {
- "@applitools/eyes-cypress": "^3.42.3",
+ "@applitools/eyes-cypress": "^3.44.4",
"@argos-ci/cypress": "^2.1.0",
"@cspell/eslint-plugin": "^8.8.4",
"@cypress/code-coverage": "^3.12.30",
@@ -84,7 +84,7 @@
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"cspell": "^8.6.0",
- "cypress": "^13.7.1",
+ "cypress": "^13.11.0",
"cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.21.5",
"eslint": "^9.4.0",
diff --git a/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js b/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js
index 6d30292f42..fc540073a4 100644
--- a/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js
+++ b/packages/mermaid-flowchart-elk/src/flowRenderer-elk.js
@@ -663,7 +663,6 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb, i
/**
* Recursive function that iterates over an array of nodes and inserts the children of each node.
* It also recursively populates the inserts the children of the children and so on.
- * @param {*} graph
* @param nodeArray
* @param parentLookupDb
*/
diff --git a/packages/mermaid-layout-elk/package.json b/packages/mermaid-layout-elk/package.json
new file mode 100644
index 0000000000..4f80546827
--- /dev/null
+++ b/packages/mermaid-layout-elk/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@mermaid-js/layout-elk",
+ "version": "0.0.1",
+ "description": "ELK layout engine for mermaid",
+ "module": "dist/mermaid-layout-elk.core.mjs",
+ "types": "dist/packages/mermaid-layout-elk/src/index.d.ts",
+ "type": "module",
+ "exports": {
+ ".": {
+ "import": "./dist/mermaid-layout-elk.core.mjs",
+ "types": "./dist/packages/mermaid-layout-elk/src/index.d.ts"
+ },
+ "./*": "./*"
+ },
+ "keywords": [
+ "diagram",
+ "markdown",
+ "elk",
+ "mermaid"
+ ],
+ "scripts": {
+ "prepublishOnly": "pnpm -w run build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mermaid-js/mermaid"
+ },
+ "contributors": [
+ "Knut Sveidqvist",
+ "Sidharth Vinod"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "elkjs": "^0.9.3",
+ "d3": "^7.9.0"
+ },
+ "peerDependencies": {
+ "mermaid": "workspace:^"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/packages/mermaid-layout-elk/src/find-common-ancestor.ts b/packages/mermaid-layout-elk/src/find-common-ancestor.ts
new file mode 100644
index 0000000000..2d513b66dc
--- /dev/null
+++ b/packages/mermaid-layout-elk/src/find-common-ancestor.ts
@@ -0,0 +1,32 @@
+export interface TreeData {
+ parentById: Record;
+ childrenById: Record;
+}
+
+export const findCommonAncestor = (id1: string, id2: string, { parentById }: TreeData) => {
+ const visited = new Set();
+ let currentId = id1;
+
+ // Edge case with self edges
+ if (id1 === id2) {
+ return parentById[id1] || 'root';
+ }
+
+ while (currentId) {
+ visited.add(currentId);
+ if (currentId === id2) {
+ return currentId;
+ }
+ currentId = parentById[currentId];
+ }
+
+ currentId = id2;
+ while (currentId) {
+ if (visited.has(currentId)) {
+ return currentId;
+ }
+ currentId = parentById[currentId];
+ }
+
+ return 'root';
+};
diff --git a/packages/mermaid-layout-elk/src/layouts.ts b/packages/mermaid-layout-elk/src/layouts.ts
new file mode 100644
index 0000000000..a6075386b3
--- /dev/null
+++ b/packages/mermaid-layout-elk/src/layouts.ts
@@ -0,0 +1,17 @@
+import type { LayoutLoaderDefinition } from 'mermaid';
+
+const loader = async () => await import(`./render.js`);
+const algos = ['elk.stress', 'elk.force', 'elk.mrtree', 'elk.sporeOverlap'];
+
+export const layouts: LayoutLoaderDefinition[] = [
+ {
+ name: 'elk',
+ loader,
+ algorithm: 'elk.layered',
+ },
+ ...algos.map((algo) => ({
+ name: algo,
+ loader,
+ algorithm: algo,
+ })),
+];
diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts
new file mode 100644
index 0000000000..81453f47f6
--- /dev/null
+++ b/packages/mermaid-layout-elk/src/render.ts
@@ -0,0 +1,997 @@
+// @ts-nocheck File not ready to check types
+import { curveLinear } from 'd3';
+import ELK from 'elkjs/lib/elk.bundled.js';
+import mermaid, { type LayoutData } from 'mermaid';
+import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
+
+const {
+ common,
+ getConfig,
+ insertCluster,
+ insertEdge,
+ insertEdgeLabel,
+ insertMarkers,
+ insertNode,
+ interpolateToCurve,
+ labelHelper,
+ log,
+ positionEdgeLabel,
+} = mermaid.internalHelpers;
+// import { insertEdge } from '../../mermaid/src/rendering-util/rendering-elements/edges.js';
+const nodeDb = {};
+const portPos = {};
+const clusterDb = {};
+
+export const addVertex = async (nodeEl, graph, nodeArr, node) => {
+ const labelData = { width: 0, height: 0 };
+ // const ports = [
+ // {
+ // id: node.id + '-west',
+ // layoutOptions: {
+ // 'port.side': 'WEST',
+ // },
+ // },
+ // {
+ // id: node.id + '-east',
+ // layoutOptions: {
+ // 'port.side': 'EAST',
+ // },
+ // },
+ // {
+ // id: node.id + '-south',
+ // layoutOptions: {
+ // 'port.side': 'SOUTH',
+ // },
+ // },
+ // {
+ // id: node.id + '-north',
+ // layoutOptions: {
+ // 'port.side': 'NORTH',
+ // },
+ // },
+ // ];
+
+ let boundingBox;
+ const child = {
+ ...node,
+ // ports: node.shape === 'diamond' ? ports : [],
+ };
+ graph.children.push(child);
+ nodeDb[node.id] = child;
+
+ // Add the element to the DOM
+ if (!node.isGroup) {
+ const childNodeEl = await insertNode(nodeEl, node, node.dir);
+ boundingBox = childNodeEl.node().getBBox();
+ child.domId = childNodeEl;
+ child.width = boundingBox.width;
+ child.height = boundingBox.height;
+ } else {
+ // A subgraph
+ child.children = [];
+ await addVertices(nodeEl, nodeArr, child, node.id);
+
+ if (node.label) {
+ const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true);
+ labelData.width = bbox.width;
+ labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
+ // Give some padding for elk
+ labelData.height = bbox.height - 2;
+ labelData.labelNode = shapeSvg.node();
+ // We need the label hight to be able to size the subgraph;
+ shapeSvg.remove();
+ } else {
+ // Subgraph without label
+ labelData.width = 0;
+ labelData.height = 0;
+ }
+ child.labelData = labelData;
+ child.domId = nodeEl;
+ }
+};
+
+export const addVertices = async function (nodeEl, nodeArr, graph, parentId) {
+ const siblings = nodeArr.filter((node) => node.parentId === parentId);
+ log.info('addVertices APA12', siblings, parentId);
+ // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
+ await Promise.all(
+ siblings.map(async (node) => {
+ await addVertex(nodeEl, graph, nodeArr, node);
+ })
+ );
+ return graph;
+};
+
+const drawNodes = async (relX, relY, nodeArray, svg, subgraphsEl, depth) => {
+ await Promise.all(
+ nodeArray.map(async function (node) {
+ if (node) {
+ nodeDb[node.id] = node;
+ nodeDb[node.id].offset = {
+ posX: node.x + relX,
+ posY: node.y + relY,
+ x: relX,
+ y: relY,
+ depth,
+ width: Math.max(node.width, node.labels ? node.labels[0]?.width || 0 : 0),
+ height: node.height,
+ };
+ if (node.isGroup) {
+ log.debug('Id abc88 subgraph = ', node.id, node.x, node.y, node.labelData);
+ const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');
+ // TODO use faster way of cloning
+ const clusterNode = JSON.parse(JSON.stringify(node));
+ clusterNode.x = node.offset.posX + node.width / 2;
+ clusterNode.y = node.offset.posY + node.height / 2;
+ await insertCluster(subgraphEl, clusterNode);
+
+ log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);
+ } else {
+ log.info(
+ 'Id NODE = ',
+ node.id,
+ node.x,
+ node.y,
+ relX,
+ relY,
+ node.domId.node(),
+ `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
+ );
+ node.domId.attr(
+ 'transform',
+ `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
+ );
+ }
+ }
+ })
+ );
+
+ await Promise.all(
+ nodeArray.map(async function (node) {
+ if (node?.isGroup) {
+ await drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, depth + 1);
+ }
+ })
+ );
+};
+
+const getNextPort = (node, edgeDirection, graphDirection) => {
+ log.info('getNextPort abc88', { node, edgeDirection, graphDirection });
+ if (!portPos[node]) {
+ switch (graphDirection) {
+ case 'TB':
+ case 'TD':
+ portPos[node] = {
+ inPosition: 'north',
+ outPosition: 'south',
+ };
+ break;
+ case 'BT':
+ portPos[node] = {
+ inPosition: 'south',
+ outPosition: 'north',
+ };
+ break;
+ case 'RL':
+ portPos[node] = {
+ inPosition: 'east',
+ outPosition: 'west',
+ };
+ break;
+ case 'LR':
+ portPos[node] = {
+ inPosition: 'west',
+ outPosition: 'east',
+ };
+ break;
+ }
+ }
+ const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition;
+
+ if (edgeDirection === 'in') {
+ portPos[node].inPosition = getNextPosition(
+ portPos[node].inPosition,
+ edgeDirection,
+ graphDirection
+ );
+ } else {
+ portPos[node].outPosition = getNextPosition(
+ portPos[node].outPosition,
+ edgeDirection,
+ graphDirection
+ );
+ }
+ return result;
+};
+
+const addSubGraphs = (nodeArr): TreeData => {
+ const parentLookupDb: TreeData = { parentById: {}, childrenById: {} };
+ const subgraphs = nodeArr.filter((node) => node.isGroup);
+ log.info('Subgraphs - ', subgraphs);
+ subgraphs.forEach((subgraph) => {
+ const children = nodeArr.filter((node) => node.parentId === subgraph.id);
+ children.forEach((node) => {
+ parentLookupDb.parentById[node.id] = subgraph.id;
+ if (parentLookupDb.childrenById[subgraph.id] === undefined) {
+ parentLookupDb.childrenById[subgraph.id] = [];
+ }
+ parentLookupDb.childrenById[subgraph.id].push(node);
+ });
+ });
+
+ subgraphs.forEach(function (subgraph) {
+ const data = { id: subgraph.id };
+ if (parentLookupDb.parentById[subgraph.id] !== undefined) {
+ data.parent = parentLookupDb.parentById[subgraph.id];
+ }
+ });
+ return parentLookupDb;
+};
+
+const getEdgeStartEndPoint = (edge, dir) => {
+ let source = edge.start;
+ let target = edge.end;
+
+ // Save the original source and target
+ const sourceId = source;
+ const targetId = target;
+
+ const startNode = nodeDb[edge.start.id];
+ const endNode = nodeDb[edge.end.id];
+
+ if (!startNode || !endNode) {
+ return { source, target };
+ }
+
+ if (startNode.shape === 'diamond') {
+ source = `${source}-${getNextPort(source, 'out', dir)}`;
+ }
+
+ if (endNode.shape === 'diamond') {
+ target = `${target}-${getNextPort(target, 'in', dir)}`;
+ }
+
+ // Add the edge to the graph
+ return { source, target, sourceId, targetId };
+};
+
+const calcOffset = function (src: string, dest: string, parentLookupDb: TreeData) {
+ const ancestor = findCommonAncestor(src, dest, parentLookupDb);
+ if (ancestor === undefined || ancestor === 'root') {
+ return { x: 0, y: 0 };
+ }
+
+ const ancestorOffset = nodeDb[ancestor].offset;
+ return { x: ancestorOffset.posX, y: ancestorOffset.posY };
+};
+
+/**
+ * Add edges to graph based on parsed graph definition
+ */
+export const addEdges = async function (dataForLayout, graph, svg) {
+ log.info('abc78 DAGA edges = ', dataForLayout);
+ const edges = dataForLayout.edges;
+ const labelsEl = svg.insert('g').attr('class', 'edgeLabels');
+ const linkIdCnt = {};
+ const dir = dataForLayout.direction || 'DOWN';
+ let defaultStyle;
+ let defaultLabelStyle;
+
+ await Promise.all(
+ edges.map(async function (edge) {
+ // Identify Link
+ const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;
+ // count the links from+to the same node to give unique id
+ if (linkIdCnt[linkIdBase] === undefined) {
+ linkIdCnt[linkIdBase] = 0;
+ log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
+ } else {
+ linkIdCnt[linkIdBase]++;
+ log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
+ }
+ const linkId = linkIdBase + '_' + linkIdCnt[linkIdBase];
+ edge.id = linkId;
+ log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
+ const linkNameStart = 'LS_' + edge.start;
+ const linkNameEnd = 'LE_' + edge.end;
+
+ const edgeData = { style: '', labelStyle: '' };
+ edgeData.minlen = edge.length || 1;
+ edge.text = edge.label;
+ // Set link type for rendering
+ if (edge.type === 'arrow_open') {
+ edgeData.arrowhead = 'none';
+ } else {
+ edgeData.arrowhead = 'normal';
+ }
+
+ // Check of arrow types, placed here in order not to break old rendering
+ edgeData.arrowTypeStart = 'arrow_open';
+ edgeData.arrowTypeEnd = 'arrow_open';
+
+ /* eslint-disable no-fallthrough */
+ switch (edge.type) {
+ case 'double_arrow_cross':
+ edgeData.arrowTypeStart = 'arrow_cross';
+ case 'arrow_cross':
+ edgeData.arrowTypeEnd = 'arrow_cross';
+ break;
+ case 'double_arrow_point':
+ edgeData.arrowTypeStart = 'arrow_point';
+ case 'arrow_point':
+ edgeData.arrowTypeEnd = 'arrow_point';
+ break;
+ case 'double_arrow_circle':
+ edgeData.arrowTypeStart = 'arrow_circle';
+ case 'arrow_circle':
+ edgeData.arrowTypeEnd = 'arrow_circle';
+ break;
+ }
+
+ let style = '';
+ let labelStyle = '';
+
+ switch (edge.stroke) {
+ case 'normal':
+ style = 'fill:none;';
+ if (defaultStyle !== undefined) {
+ style = defaultStyle;
+ }
+ if (defaultLabelStyle !== undefined) {
+ labelStyle = defaultLabelStyle;
+ }
+ edgeData.thickness = 'normal';
+ edgeData.pattern = 'solid';
+ break;
+ case 'dotted':
+ edgeData.thickness = 'normal';
+ edgeData.pattern = 'dotted';
+ edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
+ break;
+ case 'thick':
+ edgeData.thickness = 'thick';
+ edgeData.pattern = 'solid';
+ edgeData.style = 'stroke-width: 3.5px;fill:none;';
+ break;
+ }
+
+ edgeData.style = edgeData.style += style;
+ edgeData.labelStyle = edgeData.labelStyle += labelStyle;
+
+ const conf = getConfig();
+ if (edge.interpolate !== undefined) {
+ edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
+ } else if (edges.defaultInterpolate !== undefined) {
+ edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
+ } else {
+ edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
+ }
+
+ if (edge.text === undefined) {
+ if (edge.style !== undefined) {
+ edgeData.arrowheadStyle = 'fill: #333';
+ }
+ } else {
+ edgeData.arrowheadStyle = 'fill: #333';
+ edgeData.labelpos = 'c';
+ }
+
+ edgeData.labelType = edge.labelType;
+ edgeData.label = (edge?.text || '').replace(common.lineBreakRegex, '\n');
+
+ if (edge.style === undefined) {
+ edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
+ }
+
+ edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
+
+ edgeData.id = linkId;
+ edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
+
+ const labelEl = await insertEdgeLabel(labelsEl, edgeData);
+
+ // calculate start and end points of the edge, note that the source and target
+ // can be modified for shapes that have ports
+ const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
+ log.debug('abc78 source and target', source, target);
+ // Add the edge to the graph
+ graph.edges.push({
+ id: 'e' + edge.start + edge.end,
+ ...edge,
+ sources: [source],
+ targets: [target],
+ sourceId,
+ targetId,
+ labelEl: labelEl,
+ labels: [
+ {
+ width: edgeData.width,
+ height: edgeData.height,
+ orgWidth: edgeData.width,
+ orgHeight: edgeData.height,
+ text: edgeData.label,
+ layoutOptions: {
+ 'edgeLabels.inline': 'true',
+ 'edgeLabels.placement': 'CENTER',
+ },
+ },
+ ],
+ edgeData,
+ });
+ })
+ );
+ return graph;
+};
+
+function dir2ElkDirection(dir) {
+ switch (dir) {
+ case 'LR':
+ return 'RIGHT';
+ case 'RL':
+ return 'LEFT';
+ case 'TB':
+ return 'DOWN';
+ case 'BT':
+ return 'UP';
+ default:
+ return 'DOWN';
+ }
+}
+
+function setIncludeChildrenPolicy(nodeId: string, ancestorId: string) {
+ const node = nodeDb[nodeId];
+
+ if (!node) {
+ return;
+ }
+ if (node?.layoutOptions === undefined) {
+ node.layoutOptions = {};
+ }
+ node.layoutOptions['elk.hierarchyHandling'] = 'INCLUDE_CHILDREN';
+ if (node.id !== ancestorId) {
+ setIncludeChildrenPolicy(node.parentId, ancestorId);
+ }
+}
+
+export const render = async (data4Layout: LayoutData, svg, element, algorithm) => {
+ const elk = new ELK();
+
+ // Add the arrowheads to the svg
+ insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
+
+ // Setup the graph with the layout options and the data for the layout
+ let elkGraph = {
+ id: 'root',
+ layoutOptions: {
+ 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
+ 'elk.algorithm': algorithm,
+ 'nodePlacement.strategy': data4Layout.config.elk.nodePlacementStrategy,
+ 'elk.layered.mergeEdges': data4Layout.config.elk.mergeEdges,
+ 'elk.direction': 'DOWN',
+ 'spacing.baseValue': 30,
+ // 'spacing.nodeNode': 40,
+ // 'spacing.nodeNodeBetweenLayers': 45,
+ // 'spacing.edgeNode': 40,
+ // 'spacing.edgeNodeBetweenLayers': 30,
+ // 'spacing.edgeEdge': 30,
+ // 'spacing.edgeEdgeBetweenLayers': 40,
+ // 'spacing.nodeSelfLoop': 50,
+ },
+ children: [],
+ edges: [],
+ };
+
+ log.info('Drawing flowchart using v4 renderer', elk);
+
+ // Set the direction of the graph based on the parsed information
+ const dir = data4Layout.direction || 'DOWN';
+ elkGraph.layoutOptions['elk.direction'] = dir2ElkDirection(dir);
+
+ // Create the lookup db for the subgraphs and their children to used when creating
+ // the tree structured graph
+ const parentLookupDb = addSubGraphs(data4Layout.nodes);
+
+ // Add elements in the svg to be used to hold the subgraphs container
+ // elements and the nodes
+ const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
+
+ const nodeEl = svg.insert('g').attr('class', 'nodes');
+
+ // Add the nodes to the graph, this will entail creating the actual nodes
+ // 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
+ elkGraph = await addVertices(nodeEl, data4Layout.nodes, elkGraph);
+ // 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 edgePaths');
+
+ // Add the edges to the elk graph, this will entail creating the actual edges
+ elkGraph = await addEdges(data4Layout, elkGraph, svg);
+
+ // Iterate through all nodes and add the top level nodes to the graph
+ const nodes = data4Layout.nodes;
+ nodes.forEach((n) => {
+ const node = nodeDb[n.id];
+
+ // Subgraph
+ if (parentLookupDb.childrenById[node.id] !== undefined) {
+ node.labels = [
+ {
+ text: node.label,
+ width: node?.labelData?.width || 50,
+ height: node?.labelData?.height || 50,
+ },
+ (node.width = node.width + 2 * node.padding),
+ log.debug('UIO node label', node?.labelData?.width, node.padding),
+ ];
+ node.layoutOptions = {
+ 'spacing.baseValue': 30,
+ 'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]',
+ };
+ if (node.dir) {
+ node.layoutOptions = {
+ ...node.layoutOptions,
+ 'elk.algorithm': algorithm,
+ 'elk.direction': dir2ElkDirection(node.dir),
+ 'nodePlacement.strategy': data4Layout.config['elk.nodePlacement.strategy'],
+ 'elk.layered.mergeEdges': data4Layout.config['elk.mergeEdges'],
+ 'elk.hierarchyHandling': 'SEPARATE_CHILDREN',
+ };
+ }
+ delete node.x;
+ delete node.y;
+ delete node.width;
+ delete node.height;
+ }
+ });
+ elkGraph.edges.forEach((edge) => {
+ const source = edge.sources[0];
+ const target = edge.targets[0];
+
+ if (nodeDb[source].parentId !== nodeDb[target].parentId) {
+ const ancestorId = findCommonAncestor(source, target, parentLookupDb);
+ // an edge that breaks a subgraph has been identified, set configuration accordingly
+ setIncludeChildrenPolicy(source, ancestorId);
+ setIncludeChildrenPolicy(target, ancestorId);
+ }
+ });
+
+ const g = await elk.layout(elkGraph);
+
+ // debugger;
+ await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
+ g.edges?.map((edge) => {
+ // (elem, edge, clusterDb, diagramType, graph, id)
+ const startNode = nodeDb[edge.sources[0]];
+ const startCluster = parentLookupDb[edge.sources[0]];
+ const endNode = nodeDb[edge.targets[0]];
+ const sourceId = edge.start;
+ const targetId = edge.end;
+
+ const offset = calcOffset(sourceId, targetId, parentLookupDb);
+ log.debug(
+ 'offset',
+ offset,
+ sourceId,
+ ' ==> ',
+ targetId,
+ 'edge:',
+ edge,
+ 'cluster:',
+ startCluster,
+ startNode
+ );
+ if (edge.sections) {
+ const src = edge.sections[0].startPoint;
+ const dest = edge.sections[0].endPoint;
+ const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];
+
+ const segPoints = segments.map((segment) => {
+ return { x: segment.x + offset.x, y: segment.y + offset.y };
+ });
+ edge.points = [
+ { x: src.x + offset.x, y: src.y + offset.y },
+ ...segPoints,
+ { x: dest.x + offset.x, y: dest.y + offset.y },
+ ];
+
+ let sw = startNode.width;
+ let ew = endNode.width;
+ if (startNode.isGroup) {
+ const bbox = startNode.domId.node().getBBox();
+ // sw = Math.max(bbox.width, startNode.width, startNode.labels[0].width);
+ sw = Math.max(startNode.width, startNode.labels[0].width + startNode.padding);
+ // sw = startNode.width;
+ log.debug(
+ 'UIO width',
+ startNode.id,
+ startNode.with,
+ 'bbox.width=',
+ bbox.width,
+ 'lw=',
+ startNode.labels[0].width,
+ 'node:',
+ startNode.width,
+ 'SW = ',
+ sw
+ // 'HTML:',
+ // startNode.domId.node().innerHTML
+ );
+ }
+ if (endNode.isGroup) {
+ const bbox = endNode.domId.node().getBBox();
+ ew = Math.max(endNode.width, endNode.labels[0].width + endNode.padding);
+
+ log.debug(
+ 'UIO width',
+ startNode.id,
+ startNode.with,
+ bbox.width,
+ 'EW = ',
+ ew,
+ 'HTML:',
+ startNode.innerHTML
+ );
+ }
+ if (startNode.shape === 'diamond') {
+ edge.points.unshift({
+ x: startNode.x + startNode.width / 2 + offset.x,
+ y: startNode.y + startNode.height / 2 + offset.y,
+ });
+ }
+ if (endNode.shape === 'diamond') {
+ edge.points.push({
+ x: endNode.x + endNode.width / 2 + offset.x,
+ y: endNode.y + endNode.height / 2 + offset.y,
+ });
+ }
+
+ edge.points = cutPathAtIntersect(
+ edge.points.reverse(),
+ {
+ x: startNode.x + startNode.width / 2 + offset.x,
+ y: startNode.y + startNode.height / 2 + offset.y,
+ width: sw,
+ height: startNode.height,
+ padding: startNode.padding,
+ },
+ startNode.shape === 'diamond'
+ ).reverse();
+
+ edge.points = cutPathAtIntersect(
+ edge.points,
+ {
+ x: endNode.x + ew / 2 + endNode.offset.x,
+ y: endNode.y + endNode.height / 2 + endNode.offset.y,
+ width: ew,
+ height: endNode.height,
+ padding: endNode.padding,
+ },
+ endNode.shape === 'diamond'
+ );
+
+ const paths = insertEdge(
+ edgesEl,
+ edge,
+ clusterDb,
+ data4Layout.type,
+ startNode,
+ endNode,
+ data4Layout.diagramId
+ );
+ log.info('APA12 edge points after insert', JSON.stringify(edge.points));
+
+ edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
+ edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;
+ positionEdgeLabel(edge, paths);
+ }
+ // const src = edge.sections[0].startPoint;
+ // const dest = edge.sections[0].endPoint;
+ // const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];
+
+ // const segPoints = segments.map((segment) => {
+ // return { x: segment.x + offset.x, y: segment.y + offset.y };
+ // });
+ // edge.points = [
+ // { x: src.x + offset.x, y: src.y + offset.y },
+ // ...segPoints,
+ // { x: dest.x + offset.x, y: dest.y + offset.y },
+ // ];
+ // const paths = insertEdge(
+ // edgesEl,
+ // edge,
+ // clusterDb,
+ // data4Layout.type,
+ // startNode,
+ // endNode,
+ // data4Layout.diagramId
+ // );
+ // log.info('APA12 edge points after insert', JSON.stringify(edge.points));
+
+ // edge.x = edge.labels[0].x + offset.x + edge.labels[0].width / 2;
+ // edge.y = edge.labels[0].y + offset.y + edge.labels[0].height / 2;
+ // positionEdgeLabel(edge, paths);
+ });
+};
+
+function intersectLine(p1, p2, q1, q2) {
+ log.debug('UIO intersectLine', p1, p2, q1, q2);
+ // Algorithm from J. Avro, (ed.) Graphics Gems, No 2, Morgan Kaufmann, 1994,
+ // p7 and p473.
+
+ // let a1, a2, b1, b2, c1, c2;
+ // let r1, r2, r3, r4;
+ // let denom, offset, num;
+ // let x, y;
+
+ // Compute a1, b1, c1, where line joining points 1 and 2 is F(x,y) = a1 x +
+ // b1 y + c1 = 0.
+ const a1 = p2.y - p1.y;
+ const b1 = p1.x - p2.x;
+ const c1 = p2.x * p1.y - p1.x * p2.y;
+
+ // Compute r3 and r4.
+ const r3 = a1 * q1.x + b1 * q1.y + c1;
+ const r4 = a1 * q2.x + b1 * q2.y + c1;
+
+ // Check signs of r3 and r4. If both point 3 and point 4 lie on
+ // same side of line 1, the line segments do not intersect.
+ if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
+ return /*DON'T_INTERSECT*/;
+ }
+
+ // Compute a2, b2, c2 where line joining points 3 and 4 is G(x,y) = a2 x + b2 y + c2 = 0
+ const a2 = q2.y - q1.y;
+ const b2 = q1.x - q2.x;
+ const c2 = q2.x * q1.y - q1.x * q2.y;
+
+ // Compute r1 and r2
+ const r1 = a2 * p1.x + b2 * p1.y + c2;
+ const r2 = a2 * p2.x + b2 * p2.y + c2;
+
+ // Check signs of r1 and r2. If both point 1 and point 2 lie
+ // on same side of second line segment, the line segments do
+ // not intersect.
+ if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
+ return /*DON'T_INTERSECT*/;
+ }
+
+ // Line segments intersect: compute intersection point.
+ const denom = a1 * b2 - a2 * b1;
+ if (denom === 0) {
+ return /*COLLINEAR*/;
+ }
+
+ const offset = Math.abs(denom / 2);
+
+ // The denom/2 is to get rounding instead of truncating. It
+ // is added or subtracted to the numerator, depending upon the
+ // sign of the numerator.
+ let num = b1 * c2 - b2 * c1;
+ const x = num < 0 ? (num - offset) / denom : (num + offset) / denom;
+
+ num = a2 * c1 - a1 * c2;
+ const y = num < 0 ? (num - offset) / denom : (num + offset) / denom;
+
+ return { x: x, y: y };
+}
+
+function sameSign(r1, r2) {
+ return r1 * r2 > 0;
+}
+const diamondIntersection = (bounds, outsidePoint, insidePoint) => {
+ const x1 = bounds.x;
+ const y1 = bounds.y;
+
+ const w = bounds.width; //+ bounds.padding;
+ const h = bounds.height; // + bounds.padding;
+
+ const polyPoints = [
+ { x: x1, y: y1 - h / 2 },
+ { x: x1 + w / 2, y: y1 },
+ { x: x1, y: y1 + h / 2 },
+ { x: x1 - w / 2, y: y1 },
+ ];
+ log.debug(
+ `UIO diamondIntersection calc abc89:
+ outsidePoint: ${JSON.stringify(outsidePoint)}
+ insidePoint : ${JSON.stringify(insidePoint)}
+ node : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
+ polyPoints
+ );
+
+ const intersections = [];
+
+ let minX = Number.POSITIVE_INFINITY;
+ let minY = Number.POSITIVE_INFINITY;
+ if (typeof polyPoints.forEach === 'function') {
+ polyPoints.forEach(function (entry) {
+ minX = Math.min(minX, entry.x);
+ minY = Math.min(minY, entry.y);
+ });
+ } else {
+ minX = Math.min(minX, polyPoints.x);
+ minY = Math.min(minY, polyPoints.y);
+ }
+
+ // const left = x1 - w / 2;
+ // const top = y1 + h / 2;
+
+ for (let i = 0; i < polyPoints.length; i++) {
+ const p1 = polyPoints[i];
+ const p2 = polyPoints[i < polyPoints.length - 1 ? i + 1 : 0];
+ const intersect = intersectLine(
+ bounds,
+ outsidePoint,
+ { x: p1.x, y: p1.y },
+ { x: p2.x, y: p2.y }
+ );
+
+ if (intersect) {
+ intersections.push(intersect);
+ }
+ }
+
+ if (!intersections.length) {
+ return bounds;
+ }
+
+ log.debug('UIO intersections', intersections);
+
+ if (intersections.length > 1) {
+ // More intersections, find the one nearest to edge end point
+ intersections.sort(function (p, q) {
+ const pdx = p.x - outsidePoint.x;
+ const pdy = p.y - outsidePoint.y;
+ const distp = Math.sqrt(pdx * pdx + pdy * pdy);
+
+ const qdx = q.x - outsidePoint.x;
+ const qdy = q.y - outsidePoint.y;
+ const distq = Math.sqrt(qdx * qdx + qdy * qdy);
+
+ return distp < distq ? -1 : distp === distq ? 0 : 1;
+ });
+ }
+
+ return intersections[0];
+};
+
+export const intersection = (node, outsidePoint, insidePoint) => {
+ log.debug(`intersection calc abc89:
+ outsidePoint: ${JSON.stringify(outsidePoint)}
+ insidePoint : ${JSON.stringify(insidePoint)}
+ node : x:${node.x} y:${node.y} w:${node.width} h:${node.height}`);
+ const x = node.x;
+ const y = node.y;
+
+ const dx = Math.abs(x - insidePoint.x);
+ // const dy = Math.abs(y - insidePoint.y);
+ const w = node.width / 2;
+ let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
+ const h = node.height / 2;
+
+ const Q = Math.abs(outsidePoint.y - insidePoint.y);
+ const R = Math.abs(outsidePoint.x - insidePoint.x);
+
+ if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
+ // Intersection is top or bottom of rect.
+ const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
+ r = (R * q) / Q;
+ const res = {
+ x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
+ y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
+ };
+
+ if (r === 0) {
+ res.x = outsidePoint.x;
+ res.y = outsidePoint.y;
+ }
+ if (R === 0) {
+ res.x = outsidePoint.x;
+ }
+ if (Q === 0) {
+ res.y = outsidePoint.y;
+ }
+
+ log.debug(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res); // cspell: disable-line
+
+ return res;
+ } else {
+ // Intersection onn sides of rect
+ if (insidePoint.x < outsidePoint.x) {
+ r = outsidePoint.x - w - x;
+ } else {
+ // r = outsidePoint.x - w - x;
+ r = x - w - outsidePoint.x;
+ }
+ const q = (Q * r) / R;
+ // OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w;
+ // OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
+ let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
+ // let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
+ let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
+ log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
+ if (r === 0) {
+ _x = outsidePoint.x;
+ _y = outsidePoint.y;
+ }
+ if (R === 0) {
+ _x = outsidePoint.x;
+ }
+ if (Q === 0) {
+ _y = outsidePoint.y;
+ }
+
+ return { x: _x, y: _y };
+ }
+};
+const outsideNode = (node, point) => {
+ const x = node.x;
+ const y = node.y;
+ const dx = Math.abs(point.x - x);
+ const dy = Math.abs(point.y - y);
+ const w = node.width / 2;
+ const h = node.height / 2;
+ if (dx >= w || dy >= h) {
+ return true;
+ }
+ return false;
+};
+/**
+ * This function will page a path and node where the last point(s) in the path is inside the node
+ * and return an update path ending by the border of the node.
+ */
+const cutPathAtIntersect = (_points, bounds, isDiamond: boolean) => {
+ log.debug('UIO cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
+ const points = [];
+ let lastPointOutside = _points[0];
+ let isInside = false;
+ _points.forEach((point) => {
+ // const node = clusterDb[edge.toCluster].node;
+ log.debug(' checking point', point, bounds);
+
+ // check if point is inside the boundary rect
+ if (!outsideNode(bounds, point) && !isInside) {
+ // First point inside the rect found
+ // Calc the intersection coord between the point anf the last point outside the rect
+ let inter;
+
+ if (isDiamond) {
+ const inter2 = diamondIntersection(bounds, lastPointOutside, point);
+ const distance = Math.sqrt(
+ (lastPointOutside.x - inter2.x) ** 2 + (lastPointOutside.y - inter2.y) ** 2
+ );
+ if (distance > 1) {
+ inter = inter2;
+ }
+ }
+ if (!inter) {
+ inter = intersection(bounds, lastPointOutside, point);
+ }
+
+ // Check case where the intersection is the same as the last point
+ let pointPresent = false;
+ points.forEach((p) => {
+ pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
+ });
+ // if (!pointPresent) {
+ if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
+ points.push(inter);
+ } else {
+ log.debug('abc88 no intersect', inter, points);
+ }
+ // points.push(inter);
+ isInside = true;
+ } else {
+ // Outside
+ log.debug('abc88 outside', point, lastPointOutside, points);
+ lastPointOutside = point;
+ // points.push(point);
+ if (!isInside) {
+ points.push(point);
+ }
+ }
+ });
+ log.debug('returning points', points);
+ return points;
+};
diff --git a/packages/mermaid-layout-elk/tsconfig.json b/packages/mermaid-layout-elk/tsconfig.json
new file mode 100644
index 0000000000..0d701ceded
--- /dev/null
+++ b/packages/mermaid-layout-elk/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "types": ["vitest/importMeta", "vitest/globals"]
+ },
+ "include": ["./src/**/*.ts"],
+ "typeRoots": ["./src/types"]
+}
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 585a29ce3e..f3a2d82995 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -81,6 +81,7 @@
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"marked": "^13.0.2",
+ "roughjs": "^4.6.6",
"stylis": "^4.3.1",
"ts-dedent": "^2.2.0",
"uuid": "^9.0.1"
@@ -133,7 +134,6 @@
"dist/",
"README.md"
],
- "sideEffects": false,
"publishConfig": {
"access": "public"
}
diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts
index 1229bcd22b..31f0592de9 100644
--- a/packages/mermaid/src/config.ts
+++ b/packages/mermaid/src/config.ts
@@ -190,7 +190,10 @@ export const addDirective = (directive: MermaidConfig) => {
// If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
if (directive.fontFamily && !directive.themeVariables?.fontFamily) {
- directive.themeVariables = { fontFamily: directive.fontFamily };
+ directive.themeVariables = {
+ ...directive.themeVariables,
+ fontFamily: directive.fontFamily,
+ };
}
directives.push(directive);
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index b7cf27e72b..972f85bc41 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -61,9 +61,24 @@ export interface MermaidConfig {
* You may also use `themeCSS` to override this value.
*
*/
- theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
+ theme?: 'default' | 'base' | 'dark' | 'forest' | 'neutral' | 'null';
themeVariables?: any;
themeCSS?: string;
+ /**
+ * Defines which main look to use for the diagram.
+ *
+ */
+ look?: 'classic' | 'handDrawn';
+ /**
+ * Defines the seed to be used when using handDrawn look. This is important for the automated tests as they will always find differences without the seed. The default value is 0 which gives a random seed.
+ *
+ */
+ handDrawnSeed?: number;
+ /**
+ * Defines which layout algorithm to use for rendering the diagram.
+ *
+ */
+ layout?: string;
/**
* The maximum allowed size of the users text diagram
*/
@@ -73,6 +88,18 @@ export interface MermaidConfig {
*
*/
maxEdges?: number;
+ elk?: {
+ /**
+ * Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram.
+ *
+ */
+ mergeEdges?: boolean;
+ /**
+ * Elk specific option affecting how nodes are placed.
+ *
+ */
+ nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
+ };
darkMode?: boolean;
htmlLabels?: boolean;
/**
@@ -697,6 +724,8 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
textHeight?: number;
titleShift?: number;
noteMargin?: number;
+ nodeSpacing?: number;
+ rankSpacing?: number;
forkWidth?: number;
forkHeight?: number;
miniPadding?: number;
diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js
index c870566a7f..fa2b70ca24 100644
--- a/packages/mermaid/src/dagre-wrapper/index.js
+++ b/packages/mermaid/src/dagre-wrapper/index.js
@@ -97,25 +97,26 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
// Also figure out which edges point to/from clusters and adjust them accordingly
// Edges from/to clusters really points to the first child in the cluster.
// TODO: pick optimal child in the cluster to us as link anchor
- graph.edges().forEach(function (e) {
+ graph.edges().forEach(async function (e) {
const edge = graph.edge(e.v, e.w, e.name);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster
log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
- insertEdgeLabel(edgeLabels, edge);
+ await insertEdgeLabel(edgeLabels, edge);
});
graph.edges().forEach(function (e) {
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
});
+ log.info('Graph before layout:', JSON.stringify(graphlibJson.write(graph)));
log.info('#############################################');
log.info('### Layout ###');
log.info('#############################################');
log.info(graph);
dagreLayout(graph);
- log.info('Graph after layout:', graphlibJson.write(graph));
+ log.info('Graph after layout:', JSON.stringify(graphlibJson.write(graph)));
// Move the nodes to the correct place
let diff = 0;
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js
index 611cc5b6ee..b841064b6f 100644
--- a/packages/mermaid/src/dagre-wrapper/nodes.js
+++ b/packages/mermaid/src/dagre-wrapper/nodes.js
@@ -1,12 +1,12 @@
import { select } from 'd3';
-import { log } from '../logger.js';
-import { labelHelper, updateNodeBounds, insertPolygonShape } from './shapes/util.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
-import intersect from './intersect/index.js';
-import createLabel from './createLabel.js';
-import note from './shapes/note.js';
import { evaluate } from '../diagrams/common/common.js';
+import { log } from '../logger.js';
import { getArrowPoints } from './blockArrowHelper.js';
+import createLabel from './createLabel.js';
+import intersect from './intersect/index.js';
+import note from './shapes/note.js';
+import { insertPolygonShape, labelHelper, updateNodeBounds } from './shapes/util.js';
const formatClass = (str) => {
if (str) {
@@ -395,6 +395,7 @@ const rect = async (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
+ // console.log('Rect node:', node, 'bbox:', bbox, 'halfPadding:', halfPadding, 'node.padding:', node.padding);
// const totalWidth = bbox.width + node.padding * 2;
// const totalHeight = bbox.height + node.padding * 2;
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;
@@ -1154,9 +1155,6 @@ export const insertNode = async (elem, node, dir) => {
if (node.class) {
el.attr('class', 'node default ' + node.class);
}
- // MC Special
- newEl.attr('data-node', 'true');
- newEl.attr('data-id', node.id);
nodeElems[node.id] = newEl;
diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts
index 727842bba1..0fa897d118 100644
--- a/packages/mermaid/src/defaultConfig.ts
+++ b/packages/mermaid/src/defaultConfig.ts
@@ -20,6 +20,10 @@ const config: RequiredDeep = {
// Set, even though they're `undefined` so that `configKeys` finds these keys
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
deterministicIDSeed: undefined,
+ elk: {
+ mergeEdges: false,
+ nodePlacementStrategy: 'SIMPLE',
+ },
themeCSS: undefined,
// add non-JSON default config values
diff --git a/packages/mermaid/src/diagrams/block/blockRenderer.ts b/packages/mermaid/src/diagrams/block/blockRenderer.ts
index 99b89ceeb2..509b5b7150 100644
--- a/packages/mermaid/src/diagrams/block/blockRenderer.ts
+++ b/packages/mermaid/src/diagrams/block/blockRenderer.ts
@@ -8,13 +8,6 @@ import type { BlockDB } from './blockDB.js';
import { layout } from './layout.js';
import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.js';
-/**
- * Returns the all the styles from classDef statements in the graph definition.
- *
- * @param text - The text with the classes
- * @param diagObj - The diagram object
- * @returns ClassDef - The styles
- */
export const getClasses = function (text: any, diagObj: any) {
return diagObj.db.getClasses();
};
@@ -45,8 +38,6 @@ export const draw = async function (
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
- // insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
- // insertMarkers(svg, markers, diagObj.type, true);
insertMarkers(svg, markers, diagObj.type, id);
const bl = db.getBlocks();
@@ -59,11 +50,7 @@ export const draw = async function (
await insertBlocks(nodes, bl, db);
await insertEdges(nodes, edges, blArr, db, id);
- // log.debug('Here', bl);
-
// Establish svg dimensions and get width and height
- //
- // const bounds2 = nodes.node().getBoundingClientRect();
// Why, oh why ????
if (bounds) {
const bounds2 = bounds;
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index d03f1d9890..488ecc83dd 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -1,7 +1,8 @@
import { select } from 'd3';
-import utils from '../../utils.js';
+import utils, { getEdgeId } from '../../utils.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
+import type { Node, Edge } from '../../rendering-util/types.js';
import { log } from '../../logger.js';
import {
setAccTitle,
@@ -200,10 +201,17 @@ export const updateLink = function (positions: ('default' | number)[], style: st
if (pos === 'default') {
edges.defaultStyle = style;
} else {
- if (utils.isSubstringInArray('fill', style) === -1) {
- style.push('fill:none');
- }
+ // if (utils.isSubstringInArray('fill', style) === -1) {
+ // style.push('fill:none');
+ // }
edges[pos].style = style;
+ // if edges[pos].style does have fill not set, set it to none
+ if (
+ (edges[pos]?.style?.length ?? 0) > 0 &&
+ !edges[pos]?.style?.some((s) => s?.startsWith('fill'))
+ ) {
+ edges[pos]?.style?.push('fill:none');
+ }
}
});
};
@@ -219,7 +227,7 @@ export const addClass = function (ids: string, style: string[]) {
if (style !== undefined && style !== null) {
style.forEach(function (s) {
if (/color/.exec(s)) {
- const newStyle = s.replace('fill', 'bgFill').replace('color', 'fill');
+ const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
classNode.textStyles.push(newStyle);
}
classNode.styles.push(s);
@@ -728,14 +736,12 @@ export const destructLink = (_str: string, _startStr: string) => {
// Todo optimizer this by caching existing nodes
const exists = (allSgs: FlowSubGraph[], _id: string) => {
- let res = false;
- allSgs.forEach((sg) => {
- const pos = sg.nodes.indexOf(_id);
- if (pos >= 0) {
- res = true;
+ for (const sg of allSgs) {
+ if (sg.nodes.includes(_id)) {
+ return true;
}
- });
- return res;
+ }
+ return false;
};
/**
* Deletes an id from all subgraphs
@@ -755,11 +761,174 @@ export const lex = {
firstGraph,
};
+const getTypeFromVertex = (vertex: FlowVertex) => {
+ if (vertex.type === 'square') {
+ return 'squareRect';
+ }
+ if (vertex.type === 'round') {
+ return 'roundedRect';
+ }
+
+ return vertex.type ?? 'squareRect';
+};
+
+const findNode = (nodes: Node[], id: string) => nodes.find((node) => node.id === id);
+const destructEdgeType = (type: string | undefined) => {
+ let arrowTypeStart = 'none';
+ let arrowTypeEnd = 'arrow_point';
+ switch (type) {
+ case 'arrow_point':
+ case 'arrow_circle':
+ case 'arrow_cross':
+ arrowTypeEnd = type;
+ break;
+
+ case 'double_arrow_point':
+ case 'double_arrow_circle':
+ case 'double_arrow_cross':
+ arrowTypeStart = type.replace('double_', '');
+ arrowTypeEnd = arrowTypeStart;
+ break;
+ }
+ return { arrowTypeStart, arrowTypeEnd };
+};
+
+const addNodeFromVertex = (
+ vertex: FlowVertex,
+ nodes: Node[],
+ parentDB: Map,
+ subGraphDB: Map,
+ config: any,
+ look: string
+) => {
+ const parentId = parentDB.get(vertex.id);
+ const isGroup = subGraphDB.get(vertex.id) ?? false;
+
+ const node = findNode(nodes, vertex.id);
+ if (node) {
+ node.cssStyles = vertex.styles;
+ node.cssCompiledStyles = getCompiledStyles(vertex.classes);
+ node.cssClasses = vertex.classes.join(' ');
+ } else {
+ nodes.push({
+ id: vertex.id,
+ label: vertex.text,
+ labelStyle: '',
+ parentId,
+ padding: config.flowchart?.padding || 8,
+ cssStyles: vertex.styles,
+ cssCompiledStyles: getCompiledStyles(['default', 'node', ...vertex.classes]),
+ cssClasses: 'default ' + vertex.classes.join(' '),
+ shape: getTypeFromVertex(vertex),
+ dir: vertex.dir,
+ domId: vertex.domId,
+ isGroup,
+ look,
+ link: vertex.link,
+ linkTarget: vertex.linkTarget,
+ tooltip: getTooltip(vertex.id),
+ });
+ }
+};
+
+function getCompiledStyles(classDefs: string[]) {
+ let compiledStyles: string[] = [];
+ for (const customClass of classDefs) {
+ const cssClass = classes.get(customClass);
+ if (cssClass?.styles) {
+ compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])].map((s) => s.trim());
+ }
+ if (cssClass?.textStyles) {
+ compiledStyles = [...compiledStyles, ...(cssClass.textStyles ?? [])].map((s) => s.trim());
+ }
+ }
+ return compiledStyles;
+}
+
+export const getData = () => {
+ const config = getConfig();
+ const nodes: Node[] = [];
+ const edges: Edge[] = [];
+
+ const subGraphs = getSubGraphs();
+ const parentDB = new Map();
+ const subGraphDB = new Map();
+
+ // Setup the subgraph data for adding nodes
+ for (let i = subGraphs.length - 1; i >= 0; i--) {
+ const subGraph = subGraphs[i];
+ if (subGraph.nodes.length > 0) {
+ subGraphDB.set(subGraph.id, true);
+ }
+ for (const id of subGraph.nodes) {
+ parentDB.set(id, subGraph.id);
+ }
+ }
+
+ // Data is setup, add the nodes
+ for (let i = subGraphs.length - 1; i >= 0; i--) {
+ const subGraph = subGraphs[i];
+ nodes.push({
+ id: subGraph.id,
+ label: subGraph.title,
+ labelStyle: '',
+ parentId: parentDB.get(subGraph.id),
+ padding: 8,
+ cssCompiledStyles: getCompiledStyles(subGraph.classes),
+ cssClasses: subGraph.classes.join(' '),
+ shape: 'rect',
+ dir: subGraph.dir,
+ isGroup: true,
+ look: config.look,
+ });
+ }
+
+ const n = getVertices();
+ n.forEach((vertex) => {
+ addNodeFromVertex(vertex, nodes, parentDB, subGraphDB, config, config.look || 'classic');
+ });
+
+ const e = getEdges();
+ e.forEach((rawEdge, index) => {
+ const { arrowTypeStart, arrowTypeEnd } = destructEdgeType(rawEdge.type);
+ const styles = [...(e.defaultStyle ?? [])];
+
+ if (rawEdge.style) {
+ styles.push(...rawEdge.style);
+ }
+ const edge: Edge = {
+ id: getEdgeId(rawEdge.start, rawEdge.end, { counter: index, prefix: 'L' }),
+ start: rawEdge.start,
+ end: rawEdge.end,
+ type: rawEdge.type ?? 'normal',
+ label: rawEdge.text,
+ labelpos: 'c',
+ thickness: rawEdge.stroke,
+ minlen: rawEdge.length,
+ classes:
+ rawEdge?.stroke === 'invisible'
+ ? ''
+ : 'edge-thickness-normal edge-pattern-solid flowchart-link',
+ arrowTypeStart: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeStart,
+ arrowTypeEnd: rawEdge?.stroke === 'invisible' ? 'none' : arrowTypeEnd,
+ arrowheadStyle: 'fill: #333',
+ labelStyle: styles,
+ style: styles,
+ pattern: rawEdge.stroke,
+ look: config.look,
+ };
+ edges.push(edge);
+ });
+
+ return { nodes, edges, other: {}, config };
+};
+
export default {
defaultConfig: () => defaultConfig.flowchart,
setAccTitle,
getAccTitle,
getAccDescription,
+ getData,
setAccDescription,
addVertex,
lookUpDomId,
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts
index 368a98ccae..5b8012edea 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram-v2.ts
@@ -1,7 +1,7 @@
// @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison';
import flowDb from './flowDb.js';
-import flowRendererV2 from './flowRenderer-v2.js';
+import renderer from './flowRenderer-v3-unified.js';
import flowStyles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
@@ -9,16 +9,14 @@ import { setConfig } from '../../diagram-api/diagramAPI.js';
export const diagram = {
parser: flowParser,
db: flowDb,
- renderer: flowRendererV2,
+ renderer,
styles: flowStyles,
init: (cnf: MermaidConfig) => {
if (!cnf.flowchart) {
cnf.flowchart = {};
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
- // flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
- flowRendererV2.setConf(cnf.flowchart);
flowDb.clear();
flowDb.setGen('gen-2');
},
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
index ca4f8fba8a..5b8012edea 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
@@ -1,24 +1,23 @@
// @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison';
import flowDb from './flowDb.js';
-import flowRenderer from './flowRenderer.js';
-import flowRendererV2 from './flowRenderer-v2.js';
+import renderer from './flowRenderer-v3-unified.js';
import flowStyles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
+import { setConfig } from '../../diagram-api/diagramAPI.js';
export const diagram = {
parser: flowParser,
db: flowDb,
- renderer: flowRendererV2,
+ renderer,
styles: flowStyles,
init: (cnf: MermaidConfig) => {
if (!cnf.flowchart) {
cnf.flowchart = {};
}
- // TODO, broken as of 2022-09-14 (13809b50251845475e6dca65cc395761be38fbd2)
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
- flowRenderer.setConf(cnf.flowchart);
+ setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
flowDb.clear();
- flowDb.setGen('gen-1');
+ flowDb.setGen('gen-2');
},
};
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
deleted file mode 100644
index 1dce1391ef..0000000000
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
+++ /dev/null
@@ -1,515 +0,0 @@
-import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
-import { select, curveLinear, selectAll } from 'd3';
-import { getConfig } from '../../diagram-api/diagramAPI.js';
-import utils, { getEdgeId } from '../../utils.js';
-import { render } from '../../dagre-wrapper/index.js';
-import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
-import { log } from '../../logger.js';
-import common, { evaluate, renderKatex } from '../common/common.js';
-import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
-import { setupGraphViewbox } from '../../setupGraphViewbox.js';
-
-const conf = {};
-export const setConf = function (cnf) {
- const keys = Object.keys(cnf);
- for (const key of keys) {
- conf[key] = cnf[key];
- }
-};
-
-/**
- * Function that adds the vertices found during parsing to the graph to be rendered.
- *
- * @param vert Object containing the vertices.
- * @param g The graph that is to be drawn.
- * @param svgId
- * @param root
- * @param doc
- * @param diagObj
- */
-export const addVertices = async function (vert, g, svgId, root, doc, diagObj) {
- const svg = root.select(`[id="${svgId}"]`);
- const keys = vert.keys();
-
- // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
- for (const id of keys) {
- const vertex = vert.get(id);
-
- /**
- * Variable for storing the classes for the vertex
- *
- * @type {string}
- */
- let classStr = 'default';
- 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
- let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
-
- // We create a SVG label, either by delegating to addHtmlLabel or manually
- let vertexNode;
- log.info('vertex', vertex, vertex.labelType);
- if (vertex.labelType === 'markdown') {
- log.info('vertex', vertex, vertex.labelType);
- } else {
- if (evaluate(getConfig().flowchart.htmlLabels)) {
- // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
- const node = {
- label: vertexText,
- };
- 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);
-
- for (const row of rows) {
- const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
- tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
- tspan.setAttribute('dy', '1em');
- tspan.setAttribute('x', '1');
- tspan.textContent = row;
- svgLabel.appendChild(tspan);
- }
- vertexNode = svgLabel;
- }
- }
-
- let radius = 0;
- let _shape = '';
- // Set the shape based parameters
- switch (vertex.type) {
- case 'round':
- radius = 5;
- _shape = 'rect';
- break;
- case 'square':
- _shape = 'rect';
- break;
- case 'diamond':
- _shape = 'question';
- 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';
- }
- const labelText = await renderKatex(vertexText, getConfig());
-
- // Add the node
- g.setNode(vertex.id, {
- labelStyle: styles.labelStyle,
- shape: _shape,
- labelText,
- labelType: vertex.labelType,
- rx: radius,
- ry: radius,
- 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,
- });
-
- log.info('setNode', {
- labelStyle: styles.labelStyle,
- labelType: vertex.labelType,
- shape: _shape,
- labelText,
- 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,
- });
- }
-};
-
-/**
- * Add edges to graph based on parsed graph definition
- *
- * @param {object} edges The edges to add to the graph
- * @param {object} g The graph object
- * @param _diagObj
- */
-export const addEdges = async function (edges, g, _diagObj) {
- log.info('abc78 edges = ', edges);
- let cnt = 0;
- let linkIdCnt = {};
-
- let defaultStyle;
- let defaultLabelStyle;
-
- if (edges.defaultStyle !== undefined) {
- const defaultStyles = getStylesFromArray(edges.defaultStyle);
- defaultStyle = defaultStyles.style;
- defaultLabelStyle = defaultStyles.labelStyle;
- }
-
- for (const edge of edges) {
- cnt++;
-
- // Identify Link
- const linkIdBase = getEdgeId(edge.start, edge.end, {
- counter: cnt,
- prefix: 'L',
- });
-
- // count the links from+to the same node to give unique id
- if (linkIdCnt[linkIdBase] === undefined) {
- linkIdCnt[linkIdBase] = 0;
- log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
- } else {
- linkIdCnt[linkIdBase]++;
- log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
- }
- let linkId = `${linkIdBase}_${linkIdCnt[linkIdBase]}`;
-
- log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
- const linkNameStart = 'LS-' + edge.start;
- const linkNameEnd = 'LE-' + edge.end;
-
- const edgeData = { style: '', labelStyle: '' };
- edgeData.minlen = edge.length || 1;
- //edgeData.id = 'id' + cnt;
-
- // Set link type for rendering
- if (edge.type === 'arrow_open') {
- edgeData.arrowhead = 'none';
- } else {
- edgeData.arrowhead = 'normal';
- }
-
- // Check of arrow types, placed here in order not to break old rendering
- edgeData.arrowTypeStart = 'arrow_open';
- edgeData.arrowTypeEnd = 'arrow_open';
-
- /* eslint-disable no-fallthrough */
- switch (edge.type) {
- case 'double_arrow_cross':
- edgeData.arrowTypeStart = 'arrow_cross';
- case 'arrow_cross':
- edgeData.arrowTypeEnd = 'arrow_cross';
- break;
- case 'double_arrow_point':
- edgeData.arrowTypeStart = 'arrow_point';
- case 'arrow_point':
- edgeData.arrowTypeEnd = 'arrow_point';
- break;
- case 'double_arrow_circle':
- edgeData.arrowTypeStart = 'arrow_circle';
- case 'arrow_circle':
- edgeData.arrowTypeEnd = 'arrow_circle';
- break;
- }
-
- let style = '';
- let labelStyle = '';
-
- switch (edge.stroke) {
- case 'normal':
- style = 'fill:none;';
- if (defaultStyle !== undefined) {
- style = defaultStyle;
- }
- if (defaultLabelStyle !== undefined) {
- labelStyle = defaultLabelStyle;
- }
- edgeData.thickness = 'normal';
- edgeData.pattern = 'solid';
- break;
- case 'dotted':
- edgeData.thickness = 'normal';
- edgeData.pattern = 'dotted';
- edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
- break;
- case 'thick':
- edgeData.thickness = 'thick';
- edgeData.pattern = 'solid';
- edgeData.style = 'stroke-width: 3.5px;fill:none;';
- break;
- case 'invisible':
- edgeData.thickness = 'invisible';
- edgeData.pattern = 'solid';
- edgeData.style = 'stroke-width: 0;fill:none;';
- break;
- }
- if (edge.style !== undefined) {
- const styles = getStylesFromArray(edge.style);
- style = styles.style;
- labelStyle = styles.labelStyle;
- }
-
- edgeData.style = edgeData.style += style;
- edgeData.labelStyle = edgeData.labelStyle += labelStyle;
-
- if (edge.interpolate !== undefined) {
- edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
- } else if (edges.defaultInterpolate !== undefined) {
- edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
- } else {
- edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
- }
-
- if (edge.text === undefined) {
- if (edge.style !== undefined) {
- edgeData.arrowheadStyle = 'fill: #333';
- }
- } else {
- edgeData.arrowheadStyle = 'fill: #333';
- edgeData.labelpos = 'c';
- }
- edgeData.labelType = edge.labelType;
- edgeData.label = await renderKatex(edge.text.replace(common.lineBreakRegex, '\n'), getConfig());
-
- if (edge.style === undefined) {
- edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
- }
-
- edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
-
- edgeData.id = linkId;
- edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
-
- // Add the edge to the graph
- g.setEdge(edge.start, edge.end, edgeData, cnt);
- }
-};
-
-/**
- * Returns the all the styles from classDef statements in the graph definition.
- *
- * @param text
- * @param diagObj
- * @returns {Map} ClassDef styles
- */
-export const getClasses = function (text, diagObj) {
- return diagObj.db.getClasses();
-};
-
-/**
- * Draws a flowchart in the tag with id: id based on the graph definition in text.
- *
- * @param text
- * @param id
- * @param _version
- * @param diagObj
- */
-
-export const draw = async function (text, id, _version, diagObj) {
- log.info('Drawing flowchart');
-
- // Fetch the default direction, use TD if none was found
- let dir = diagObj.db.getDirection();
- if (dir === undefined) {
- dir = 'TD';
- }
-
- const { securityLevel, flowchart: conf } = getConfig();
- const nodeSpacing = conf.nodeSpacing ?? 50;
- const rankSpacing = conf.rankSpacing ?? 50;
-
- // Handle root and document for when rendering in sandbox mode
- let sandboxElement;
- if (securityLevel === 'sandbox') {
- sandboxElement = select('#i' + id);
- }
- const root =
- securityLevel === 'sandbox'
- ? select(sandboxElement.nodes()[0].contentDocument.body)
- : select('body');
- const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
-
- // Create the input mermaid.graph
- const g = new graphlib.Graph({
- multigraph: true,
- compound: true,
- })
- .setGraph({
- rankdir: dir,
- nodesep: nodeSpacing,
- ranksep: rankSpacing,
- marginx: 0,
- marginy: 0,
- })
- .setDefaultEdgeLabel(function () {
- return {};
- });
-
- let subG;
- const subGraphs = diagObj.db.getSubGraphs();
- log.info('Subgraphs - ', subGraphs);
- for (let i = subGraphs.length - 1; i >= 0; i--) {
- subG = subGraphs[i];
- log.info('Subgraph - ', subG);
- diagObj.db.addVertex(
- subG.id,
- { text: subG.title, type: subG.labelType },
- 'group',
- undefined,
- subG.classes,
- subG.dir
- );
- }
-
- // Fetch the vertices/nodes and edges/links from the parsed graph definition
- const vert = diagObj.db.getVertices();
-
- const edges = diagObj.db.getEdges();
-
- log.info('Edges', edges);
- let i = 0;
- for (i = subGraphs.length - 1; i >= 0; i--) {
- subG = subGraphs[i];
-
- selectAll('cluster').append('text');
-
- for (const node of subG.nodes) {
- log.info('Setting up subgraphs', node, subG.id);
- g.setParent(node, subG.id);
- }
- }
- await addVertices(vert, g, id, root, doc, diagObj);
- await addEdges(edges, g, diagObj);
-
- // Add custom shapes
- // flowChartShapes.addToRenderV2(addShape);
-
- // Set up an SVG group so that we can translate the final graph.
- const svg = root.select(`[id="${id}"]`);
-
- // Run the renderer. This is what draws the final graph.
- const element = root.select('#' + id + ' g');
- await render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
-
- utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
-
- setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
-
- // Index nodes
- diagObj.db.indexNodes('subGraph' + i);
-
- // Add label rects for non html labels
- if (!conf.htmlLabels) {
- const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
- for (const label of labels) {
- // Get dimensions of label
- const dim = label.getBBox();
-
- const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
- rect.setAttribute('rx', 0);
- rect.setAttribute('ry', 0);
- rect.setAttribute('width', dim.width);
- rect.setAttribute('height', dim.height);
-
- label.insertBefore(rect, label.firstChild);
- }
- }
-
- // If node has a link, wrap it in an anchor SVG object.
- const keys = [...vert.keys()];
- keys.forEach((key) => {
- const vertex = vert.get(key);
-
- if (vertex.link) {
- const node = select('#' + id + ' [id="' + key + '"]');
- if (node) {
- const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a');
- link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
- link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
- link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
- if (securityLevel === 'sandbox') {
- link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top');
- } else if (vertex.linkTarget) {
- link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget);
- }
-
- const linkNode = node.insert(function () {
- return link;
- }, ':first-child');
-
- const shape = node.select('.label-container');
- if (shape) {
- linkNode.append(function () {
- return shape.node();
- });
- }
-
- const label = node.select('.label');
- if (label) {
- linkNode.append(function () {
- return label.node();
- });
- }
- }
- }
- });
-};
-
-export default {
- setConf,
- addVertices,
- addEdges,
- getClasses,
- draw,
-};
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts
new file mode 100644
index 0000000000..102662ee68
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts
@@ -0,0 +1,99 @@
+import { select } from 'd3';
+import { getConfig } from '../../diagram-api/diagramAPI.js';
+import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
+import { log } from '../../logger.js';
+import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
+import { render } from '../../rendering-util/render.js';
+import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
+import type { LayoutData } from '../../rendering-util/types.js';
+import utils from '../../utils.js';
+import { getDirection } from './flowDb.js';
+
+export const getClasses = function (
+ text: string,
+ diagramObj: any
+): Map {
+ return diagramObj.db.getClasses();
+};
+
+export const draw = async function (text: string, id: string, _version: string, diag: any) {
+ log.info('REF0:');
+ log.info('Drawing state diagram (v2)', id);
+ const { securityLevel, flowchart: conf, layout } = getConfig();
+
+ // Handle root and document for when rendering in sandbox mode
+ let sandboxElement;
+ if (securityLevel === 'sandbox') {
+ sandboxElement = select('#i' + id);
+ }
+
+ // @ts-ignore - document is always available
+ const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
+
+ // The getData method provided in all supported diagrams is used to extract the data from the parsed structure
+ // into the Layout data format
+ log.debug('Before getData: ');
+ const data4Layout = diag.db.getData() as LayoutData;
+ log.debug('Data: ', data4Layout);
+ // Create the root SVG - the element is the div containing the SVG element
+ const { element, svg } = getDiagramElements(id, securityLevel);
+ const direction = getDirection();
+
+ data4Layout.type = diag.type;
+ data4Layout.layoutAlgorithm = layout;
+ data4Layout.direction = direction;
+ data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
+ data4Layout.rankSpacing = conf?.rankSpacing || 50;
+ data4Layout.markers = ['point', 'circle', 'cross'];
+
+ data4Layout.diagramId = id;
+ log.debug('REF1:', data4Layout);
+ await render(data4Layout, svg, element);
+ const padding = data4Layout.config.flowchart?.padding ?? 8;
+ utils.insertTitle(
+ svg,
+ 'flowchartTitleText',
+ conf?.titleTopMargin || 0,
+ diag.db.getDiagramTitle()
+ );
+ setupViewPortForSVG(svg, padding, 'flowchart', conf?.useMaxWidth || false);
+
+ // If node has a link, wrap it in an anchor SVG object.
+ for (const vertex of data4Layout.nodes) {
+ const node = select(`#${id} [id="${vertex.id}"]`);
+ if (!node || !vertex.link) {
+ continue;
+ }
+ const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a');
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.cssClasses);
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
+ if (securityLevel === 'sandbox') {
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top');
+ } else if (vertex.linkTarget) {
+ link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget);
+ }
+
+ const linkNode = node.insert(function () {
+ return link;
+ }, ':first-child');
+
+ const shape = node.select('.label-container');
+ if (shape) {
+ linkNode.append(function () {
+ return shape.node();
+ });
+ }
+
+ const label = node.select('.label');
+ if (label) {
+ linkNode.append(function () {
+ return label.node();
+ });
+ }
+ }
+};
+
+export default {
+ getClasses,
+ draw,
+};
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js
deleted file mode 100644
index 00f9ef8512..0000000000
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import flowDb from './flowDb.js';
-import { parser } from './parser/flow.jison';
-import flowRenderer from './flowRenderer.js';
-import { addDiagrams } from '../../diagram-api/diagram-orchestration.js';
-
-const diag = {
- db: flowDb,
-};
-addDiagrams();
-
-describe('when using mermaid and ', function () {
- describe('when calling addEdges ', function () {
- beforeEach(function () {
- parser.yy = flowDb;
- flowDb.clear();
- flowDb.setGen('gen-2');
- });
- it('should handle edges with text', async () => {
- parser.parse('graph TD;A-->|text ex|B;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('normal');
- expect(options.label.match('text ex')).toBeTruthy();
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
-
- it('should handle edges without text', async function () {
- parser.parse('graph TD;A-->B;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('normal');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
-
- it('should handle open-ended edges', async () => {
- parser.parse('graph TD;A---B;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('none');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
-
- it('should handle edges with styles defined', async () => {
- parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('none');
- expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
- it('should handle edges with interpolation defined', async () => {
- parser.parse('graph TD;A---B; linkStyle 0 interpolate basis');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('none');
- expect(options.curve).toBe('basis'); // mocked as string
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
- it('should handle edges with text and styles defined', async () => {
- parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('none');
- expect(options.label.match('the text')).toBeTruthy();
- expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
-
- it('should set fill to "none" by default when handling edges', async () => {
- parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
-
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B');
- expect(options.arrowhead).toBe('none');
- expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
-
- it('should not set fill to none if fill is set in linkStyle', async () => {
- parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;');
- flowDb.getVertices();
- const edges = flowDb.getEdges();
- const mockG = {
- setEdge: function (start, end, options) {
- expect(start).toContain('flowchart-A-');
- expect(end).toContain('flowchart-B-');
- expect(options.arrowhead).toBe('none');
- expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;');
- },
- };
-
- await flowRenderer.addEdges(edges, mockG, diag);
- });
- });
-});
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js
deleted file mode 100644
index 314c6aa52b..0000000000
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js
+++ /dev/null
@@ -1,503 +0,0 @@
-import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
-import { select, curveLinear, selectAll } from 'd3';
-import { getConfig } from '../../diagram-api/diagramAPI.js';
-import { render as Render } from 'dagre-d3-es';
-import { applyStyle } from 'dagre-d3-es/src/dagre-js/util.js';
-import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
-import { log } from '../../logger.js';
-import common, { evaluate, renderKatex } from '../common/common.js';
-import { interpolateToCurve, getStylesFromArray, getEdgeId } from '../../utils.js';
-import { setupGraphViewbox } from '../../setupGraphViewbox.js';
-import flowChartShapes from './flowChartShapes.js';
-import { replaceIconSubstring } from '../../rendering-util/createText.js';
-
-const conf = {};
-export const setConf = function (cnf) {
- const keys = Object.keys(cnf);
- for (const key of keys) {
- conf[key] = cnf[key];
- }
-};
-
-/**
- * Function that adds the vertices found in the graph definition to the graph to be rendered.
- *
- * @param vert Object containing the vertices.
- * @param g The graph that is to be drawn.
- * @param svgId
- * @param root
- * @param _doc
- * @param diagObj
- */
-export const addVertices = async function (vert, g, svgId, root, _doc, diagObj) {
- const svg = !root ? select(`[id="${svgId}"]`) : root.select(`[id="${svgId}"]`);
- const doc = !_doc ? document : _doc;
- const keys = Object.keys(vert);
-
- // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
- for (const id of keys) {
- 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;
- if (evaluate(getConfig().flowchart.htmlLabels)) {
- // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
- const replacedVertexText = replaceIconSubstring(vertexText);
- const node = {
- label: await renderKatex(replacedVertexText, getConfig()),
- };
- 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);
-
- for (const row of rows) {
- const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
- tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
- tspan.setAttribute('dy', '1em');
- tspan.setAttribute('x', '1');
- tspan.textContent = row;
- svgLabel.appendChild(tspan);
- }
- vertexNode = svgLabel;
- }
-
- let radius = 0;
- let _shape = '';
- // Set the shape based parameters
- switch (vertex.type) {
- case 'round':
- radius = 5;
- _shape = 'rect';
- break;
- case 'square':
- _shape = 'rect';
- break;
- case 'diamond':
- _shape = 'question';
- 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;
- default:
- _shape = 'rect';
- }
- // Add the node
- log.warn('Adding node', vertex.id, vertex.domId);
- g.setNode(diagObj.db.lookUpDomId(vertex.id), {
- labelType: 'svg',
- labelStyle: styles.labelStyle,
- shape: _shape,
- label: vertexNode,
- rx: radius,
- ry: radius,
- class: classStr,
- style: styles.style,
- id: diagObj.db.lookUpDomId(vertex.id),
- });
- }
-};
-
-/**
- * Add edges to graph based on parsed graph definition
- *
- * @param {object} edges The edges to add to the graph
- * @param {object} g The graph object
- * @param diagObj
- */
-export const addEdges = async function (edges, g, diagObj) {
- let cnt = 0;
-
- let defaultStyle;
- let defaultLabelStyle;
-
- if (edges.defaultStyle !== undefined) {
- const defaultStyles = getStylesFromArray(edges.defaultStyle);
- defaultStyle = defaultStyles.style;
- defaultLabelStyle = defaultStyles.labelStyle;
- }
-
- for (const edge of edges) {
- cnt++;
-
- // Identify Link
- const linkId = getEdgeId(edge.start, edge.end, {
- counter: cnt,
- prefix: 'L',
- });
- const linkNameStart = 'LS-' + edge.start;
- const linkNameEnd = 'LE-' + edge.end;
-
- const edgeData = {};
-
- // Set link type for rendering
- if (edge.type === 'arrow_open') {
- edgeData.arrowhead = 'none';
- } else {
- edgeData.arrowhead = 'normal';
- }
-
- let style = '';
- let labelStyle = '';
-
- if (edge.style !== undefined) {
- const styles = getStylesFromArray(edge.style);
- style = styles.style;
- labelStyle = styles.labelStyle;
- } else {
- switch (edge.stroke) {
- case 'normal':
- style = 'fill:none';
- if (defaultStyle !== undefined) {
- style = defaultStyle;
- }
- if (defaultLabelStyle !== undefined) {
- labelStyle = defaultLabelStyle;
- }
- break;
- case 'dotted':
- style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
- break;
- case 'thick':
- style = ' stroke-width: 3.5px;fill:none';
- break;
- }
- }
-
- edgeData.style = style;
- edgeData.labelStyle = labelStyle;
-
- if (edge.interpolate !== undefined) {
- edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
- } else if (edges.defaultInterpolate !== undefined) {
- edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
- } else {
- edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
- }
-
- if (edge.text === undefined) {
- if (edge.style !== undefined) {
- edgeData.arrowheadStyle = 'fill: #333';
- }
- } else {
- edgeData.arrowheadStyle = 'fill: #333';
- edgeData.labelpos = 'c';
-
- if (evaluate(getConfig().flowchart.htmlLabels)) {
- edgeData.labelType = 'html';
- edgeData.label = `${await renderKatex(replaceIconSubstring(edge.text), getConfig())} `;
- } else {
- edgeData.labelType = 'text';
- edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
-
- if (edge.style === undefined) {
- edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
- }
-
- edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
- }
- }
-
- edgeData.id = linkId;
- edgeData.class = linkNameStart + ' ' + linkNameEnd;
- edgeData.minlen = edge.length || 1;
-
- // Add the edge to the graph
- g.setEdge(diagObj.db.lookUpDomId(edge.start), diagObj.db.lookUpDomId(edge.end), edgeData, cnt);
- }
-};
-
-/**
- * Returns the all the styles from classDef statements in the graph definition.
- *
- * @param text
- * @param diagObj
- * @returns {Map} ClassDef styles
- */
-export const getClasses = function (text, diagObj) {
- log.info('Extracting classes');
- return diagObj.db.getClasses();
-};
-
-/**
- * Draws a flowchart in the tag with id: id based on the graph definition in text.
- *
- * @param text
- * @param id
- * @param _version
- * @param diagObj
- */
-export const draw = async function (text, id, _version, diagObj) {
- log.info('Drawing flowchart');
- const { securityLevel, flowchart: conf } = getConfig();
- let sandboxElement;
- if (securityLevel === 'sandbox') {
- sandboxElement = select('#i' + id);
- }
- const root =
- securityLevel === 'sandbox'
- ? select(sandboxElement.nodes()[0].contentDocument.body)
- : select('body');
- const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
-
- // Fetch the default direction, use TD if none was found
- let dir = diagObj.db.getDirection();
- if (dir === undefined) {
- dir = 'TD';
- }
- const nodeSpacing = conf.nodeSpacing ?? 50;
- const rankSpacing = conf.rankSpacing ?? 50;
-
- // Create the input mermaid.graph
- const g = new graphlib.Graph({
- multigraph: true,
- compound: true,
- })
- .setGraph({
- rankdir: dir,
- nodesep: nodeSpacing,
- ranksep: rankSpacing,
- marginx: 8,
- marginy: 8,
- })
- .setDefaultEdgeLabel(function () {
- return {};
- });
-
- let subG;
- const subGraphs = diagObj.db.getSubGraphs();
- for (let i = subGraphs.length - 1; i >= 0; i--) {
- subG = subGraphs[i];
- diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
- }
-
- // Fetch the vertices/nodes and edges/links from the parsed graph definition
- const vert = diagObj.db.getVertices();
- log.warn('Get vertices', vert);
-
- const edges = diagObj.db.getEdges();
-
- let i = 0;
- for (i = subGraphs.length - 1; i >= 0; i--) {
- subG = subGraphs[i];
-
- selectAll('cluster').append('text');
-
- for (const node of subG.nodes) {
- log.warn(
- 'Setting subgraph',
- node,
- diagObj.db.lookUpDomId(node),
- diagObj.db.lookUpDomId(subG.id)
- );
- g.setParent(diagObj.db.lookUpDomId(node), diagObj.db.lookUpDomId(subG.id));
- }
- }
- await addVertices(vert, g, id, root, doc, diagObj);
- await addEdges(edges, g, diagObj);
-
- // Create the renderer
- const render = new Render();
-
- // Add custom shapes
- flowChartShapes.addToRender(render);
-
- // Add our custom arrow - an empty arrowhead
- render.arrows().none = function normal(parent, id, edge, type) {
- const marker = parent
- .append('marker')
- .attr('id', id)
- .attr('viewBox', '0 0 10 10')
- .attr('refX', 9)
- .attr('refY', 5)
- .attr('markerUnits', 'strokeWidth')
- .attr('markerWidth', 8)
- .attr('markerHeight', 6)
- .attr('orient', 'auto');
-
- const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
- applyStyle(path, edge[type + 'Style']);
- };
-
- // Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
- render.arrows().normal = function normal(parent, id) {
- const marker = parent
- .append('marker')
- .attr('id', id)
- .attr('viewBox', '0 0 10 10')
- .attr('refX', 9)
- .attr('refY', 5)
- .attr('markerUnits', 'strokeWidth')
- .attr('markerWidth', 8)
- .attr('markerHeight', 6)
- .attr('orient', 'auto');
-
- marker
- .append('path')
- .attr('d', 'M 0 0 L 10 5 L 0 10 z')
- .attr('class', 'arrowheadPath')
- .style('stroke-width', 1)
- .style('stroke-dasharray', '1,0');
- };
-
- // Set up an SVG group so that we can translate the final graph.
- const svg = root.select(`[id="${id}"]`);
-
- // Run the renderer. This is what draws the final graph.
- const element = root.select('#' + id + ' g');
- render(element, g);
-
- element.selectAll('g.node').attr('title', function () {
- return diagObj.db.getTooltip(this.id);
- });
-
- // Index nodes
- diagObj.db.indexNodes('subGraph' + i);
-
- // reposition labels
- for (i = 0; i < subGraphs.length; i++) {
- subG = subGraphs[i];
- if (subG.title !== 'undefined') {
- const clusterRects = doc.querySelectorAll(
- '#' + id + ' [id="' + diagObj.db.lookUpDomId(subG.id) + '"] rect'
- );
- const clusterEl = doc.querySelectorAll(
- '#' + id + ' [id="' + diagObj.db.lookUpDomId(subG.id) + '"]'
- );
-
- const xPos = clusterRects[0].x.baseVal.value;
- const yPos = clusterRects[0].y.baseVal.value;
- const _width = clusterRects[0].width.baseVal.value;
- const cluster = select(clusterEl[0]);
- const te = cluster.select('.label');
- te.attr('transform', `translate(${xPos + _width / 2}, ${yPos + 14})`);
- te.attr('id', id + 'Text');
-
- for (const className of subG.classes) {
- clusterEl[0].classList.add(className);
- }
- }
- }
-
- // Add label rects for non html labels
- if (!conf.htmlLabels) {
- const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
- for (const label of labels) {
- // Get dimensions of label
- const dim = label.getBBox();
-
- const rect = doc.createElementNS('http://www.w3.org/2000/svg', 'rect');
- rect.setAttribute('rx', 0);
- rect.setAttribute('ry', 0);
- rect.setAttribute('width', dim.width);
- rect.setAttribute('height', dim.height);
- // rect.setAttribute('style', 'fill:#e8e8e8;');
-
- label.insertBefore(rect, label.firstChild);
- }
- }
- setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
-
- // If node has a link, wrap it in an anchor SVG object.
- const keys = [...vert.keys()];
- keys.forEach(function (key) {
- const vertex = vert.get(key);
-
- if (vertex.link) {
- const node = root.select('#' + id + ' [id="' + diagObj.db.lookUpDomId(key) + '"]');
- if (node) {
- const link = doc.createElementNS('http://www.w3.org/2000/svg', 'a');
- link.setAttributeNS('http://www.w3.org/2000/svg', 'class', vertex.classes.join(' '));
- link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
- link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
- if (securityLevel === 'sandbox') {
- link.setAttributeNS('http://www.w3.org/2000/svg', 'target', '_top');
- } else if (vertex.linkTarget) {
- link.setAttributeNS('http://www.w3.org/2000/svg', 'target', vertex.linkTarget);
- }
-
- const linkNode = node.insert(function () {
- return link;
- }, ':first-child');
-
- const shape = node.select('.label-container');
- if (shape) {
- linkNode.append(function () {
- return shape.node();
- });
- }
-
- const label = node.select('.label');
- if (label) {
- linkNode.append(function () {
- return label.node();
- });
- }
- }
- }
- });
-};
-
-export default {
- setConf,
- addVertices,
- addEdges,
- getClasses,
- draw,
-};
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.spec.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.spec.js
deleted file mode 100644
index 79bf75453c..0000000000
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.spec.js
+++ /dev/null
@@ -1,277 +0,0 @@
-/* eslint-disable @typescript-eslint/restrict-template-expressions */
-import { addVertices, addEdges } from './flowRenderer.js';
-import { setConfig } from '../../diagram-api/diagramAPI.js';
-
-setConfig({
- flowchart: {
- htmlLabels: false,
- },
-});
-
-describe('the flowchart renderer', function () {
- describe('when adding vertices to a graph', function () {
- [
- ['round', 'rect', 5],
- ['square', 'rect'],
- ['diamond', 'question'],
- ['hexagon', 'hexagon'],
- ['odd', 'rect_left_inv_arrow'],
- ['lean_right', 'lean_right'],
- ['lean_left', 'lean_left'],
- ['trapezoid', 'trapezoid'],
- ['inv_trapezoid', 'inv_trapezoid'],
- ['odd_right', 'rect_left_inv_arrow'],
- ['circle', 'circle'],
- ['ellipse', 'ellipse'],
- ['stadium', 'stadium'],
- ['subroutine', 'subroutine'],
- ['cylinder', 'cylinder'],
- ['group', 'rect'],
- ].forEach(function ([type, expectedShape, expectedRadios = 0]) {
- it(`should add the correct shaped node to the graph for vertex type ${type}`, async function () {
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- const addedNodes = [];
- const mockG = {
- setNode: function (id, object) {
- addedNodes.push([id, object]);
- },
- };
- await addVertices(
- {
- v1: {
- type,
- id: 'my-node-id',
- classes: [],
- styles: [],
- text: 'my vertex text',
- },
- },
- mockG,
- 'svg-id',
- undefined,
- undefined,
- fakeDiag
- );
- expect(addedNodes).toHaveLength(1);
- expect(addedNodes[0][0]).toEqual('my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
- expect(addedNodes[0][1]).toHaveProperty('shape', expectedShape);
- expect(addedNodes[0][1]).toHaveProperty('rx', expectedRadios);
- expect(addedNodes[0][1]).toHaveProperty('ry', expectedRadios);
- });
- });
-
- ['Multi Line', 'Multi Line', 'Multi Line', 'Multi Line'].forEach(
- function (labelText) {
- it('should handle multiline texts with different line breaks', async function () {
- const addedNodes = [];
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- const mockG = {
- setNode: function (id, object) {
- addedNodes.push([id, object]);
- },
- };
- await addVertices(
- {
- v1: {
- type: 'rect',
- id: 'my-node-id',
- classes: [],
- styles: [],
- text: 'Multi Line',
- },
- },
- mockG,
- 'svg-id',
- false,
- document,
- fakeDiag
- );
- expect(addedNodes).toHaveLength(1);
- expect(addedNodes[0][0]).toEqual('my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
- expect(addedNodes[0][1].label).toBeDefined();
- expect(addedNodes[0][1].label).toBeDefined(); // node
- expect(addedNodes[0][1].label.firstChild.innerHTML).toEqual('Multi'); // node, line 1
- expect(addedNodes[0][1].label.lastChild.innerHTML).toEqual('Line'); // node, line 2
- });
- }
- );
-
- [
- [['fill:#fff'], 'fill:#fff;', ''],
- [['color:#ccc'], '', 'color:#ccc;'],
- [['fill:#fff', 'color:#ccc'], 'fill:#fff;', 'color:#ccc;'],
- [
- ['fill:#fff', 'color:#ccc', 'text-align:center'],
- 'fill:#fff;',
- 'color:#ccc;text-align:center;',
- ],
- ].forEach(function ([style, expectedStyle, expectedLabelStyle]) {
- it(`should add the styles to style and/or labelStyle for style ${style}`, async function () {
- const addedNodes = [];
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- const mockG = {
- setNode: function (id, object) {
- addedNodes.push([id, object]);
- },
- };
- await addVertices(
- {
- v1: {
- type: 'rect',
- id: 'my-node-id',
- classes: [],
- styles: style,
- text: 'my vertex text',
- },
- },
- mockG,
- 'svg-id',
- undefined,
- undefined,
- fakeDiag
- );
- expect(addedNodes).toHaveLength(1);
- expect(addedNodes[0][0]).toEqual('my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('id', 'my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('labelType', 'svg');
- expect(addedNodes[0][1]).toHaveProperty('style', expectedStyle);
- expect(addedNodes[0][1]).toHaveProperty('labelStyle', expectedLabelStyle);
- });
- });
-
- it(`should add default class to all nodes which do not have another class assigned`, async function () {
- const addedNodes = [];
- const mockG = {
- setNode: function (id, object) {
- addedNodes.push([id, object]);
- },
- };
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- await addVertices(
- {
- v1: {
- type: 'rect',
- id: 'my-node-id',
- classes: [],
- styles: [],
- text: 'my vertex text',
- },
- v2: {
- type: 'rect',
- id: 'myNode',
- classes: ['myClass'],
- styles: [],
- text: 'my vertex text',
- },
- },
- mockG,
- 'svg-id',
- undefined,
- undefined,
- fakeDiag
- );
- expect(addedNodes).toHaveLength(2);
- expect(addedNodes[0][0]).toEqual('my-node-id');
- expect(addedNodes[0][1]).toHaveProperty('class', 'default');
- expect(addedNodes[1][0]).toEqual('my-node-id');
- expect(addedNodes[1][1]).toHaveProperty('class', 'myClass');
- });
- });
-
- describe('when adding edges to a graph', function () {
- it('should handle multiline texts and set centered label position', async function () {
- const addedEdges = [];
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- const mockG = {
- setEdge: function (s, e, data, c) {
- addedEdges.push(data);
- },
- };
- await addEdges(
- [
- { text: 'Multi Line' },
- { text: 'Multi Line' },
- { text: 'Multi Line' },
- { text: 'Multi Line' },
- { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi Line' },
- { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi Line' },
- { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi Line' },
- { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi Line' },
- ],
- mockG,
- fakeDiag
- );
-
- addedEdges.forEach(function (edge) {
- expect(edge).toHaveProperty('label', 'Multi\nLine');
- expect(edge).toHaveProperty('labelpos', 'c');
- });
- });
-
- [
- [['stroke:DarkGray'], 'stroke:DarkGray;', ''],
- [['color:red'], '', 'fill:red;'],
- [['stroke:DarkGray', 'color:red'], 'stroke:DarkGray;', 'fill:red;'],
- [
- ['stroke:DarkGray', 'color:red', 'stroke-width:2px'],
- 'stroke:DarkGray;stroke-width:2px;',
- 'fill:red;',
- ],
- ].forEach(function ([style, expectedStyle, expectedLabelStyle]) {
- it(`should add the styles to style and/or labelStyle for style ${style}`, async function () {
- const addedEdges = [];
- const fakeDiag = {
- db: {
- lookUpDomId: () => {
- return 'my-node-id';
- },
- },
- };
- const mockG = {
- setEdge: function (s, e, data, c) {
- addedEdges.push(data);
- },
- };
- await addEdges([{ style: style, text: 'styling' }], mockG, fakeDiag);
-
- expect(addedEdges).toHaveLength(1);
- expect(addedEdges[0]).toHaveProperty('style', expectedStyle);
- expect(addedEdges[0]).toHaveProperty('labelStyle', expectedLabelStyle);
- });
- });
- });
-});
diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts
index b30c0b9bc7..0ca2c1321c 100644
--- a/packages/mermaid/src/diagrams/flowchart/styles.ts
+++ b/packages/mermaid/src/diagrams/flowchart/styles.ts
@@ -38,11 +38,14 @@ const getStyles = (options: FlowChartStyleOptions) =>
.cluster-label text {
fill: ${options.titleColor};
}
- .cluster-label span,p {
+ .cluster-label span {
color: ${options.titleColor};
}
+ .cluster-label span p {
+ background-color: transparent;
+ }
- .label text,span,p {
+ .label text,span {
fill: ${options.nodeTextColor || options.textColor};
color: ${options.nodeTextColor || options.textColor};
}
@@ -56,7 +59,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
- .flowchart-label text {
+ .rough-node .label text , .node .label text {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
@@ -95,6 +98,9 @@ const getStyles = (options: FlowChartStyleOptions) =>
.edgeLabel {
background-color: ${options.edgeLabelBackground};
+ p {
+ background-color: ${options.edgeLabelBackground};
+ }
rect {
opacity: 0.5;
background-color: ${options.edgeLabelBackground};
@@ -106,7 +112,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
/* For html labels only */
.labelBkg {
background-color: ${fade(options.edgeLabelBackground, 0.5)};
- // background-color:
+ // background-color:
}
.cluster rect {
@@ -119,7 +125,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
fill: ${options.titleColor};
}
- .cluster span,p {
+ .cluster span {
color: ${options.titleColor};
}
/* .cluster div {
diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts
index 954759f393..ee64ae56b4 100644
--- a/packages/mermaid/src/diagrams/flowchart/types.ts
+++ b/packages/mermaid/src/diagrams/flowchart/types.ts
@@ -23,7 +23,7 @@ export interface FlowEdge {
end: string;
interpolate?: string;
type?: string;
- stroke?: string;
+ stroke?: 'normal' | 'thick' | 'invisible' | 'dotted';
style?: string[];
length?: number;
text: string;
diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts
index af49d0415e..5bfa203922 100644
--- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts
+++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.ts
@@ -16,18 +16,20 @@ import defaultConfig from '../../defaultConfig.js';
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
-function drawNodes(
+async function drawNodes(
db: MindmapDB,
svg: D3Element,
mindmap: FilledMindMapNode,
section: number,
conf: MermaidConfig
) {
- drawNode(db, svg, mindmap, section, conf);
+ await drawNode(db, svg, mindmap, section, conf);
if (mindmap.children) {
- mindmap.children.forEach((child, index) => {
- drawNodes(db, svg, child, section < 0 ? index : section, conf);
- });
+ await Promise.all(
+ mindmap.children.map((child, index) =>
+ drawNodes(db, svg, child, section < 0 ? index : section, conf)
+ )
+ );
}
}
@@ -177,7 +179,7 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
- drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
+ await drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
// Next step is to layout the mindmap, giving each node a position
diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts
index 4288cfca69..209a6a0e18 100644
--- a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts
+++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts
@@ -174,13 +174,13 @@ const roundedRectBkg: ShapeFunction = function (db, elem, node) {
* @param conf - The configuration object
* @returns The height nodes dom element
*/
-export const drawNode = function (
+export const drawNode = async function (
db: MindmapDB,
elem: D3Element,
node: FilledMindMapNode,
fullSection: number,
conf: MermaidConfig
-): number {
+): Promise {
const htmlLabels = conf.htmlLabels;
const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g');
@@ -195,7 +195,7 @@ export const drawNode = function (
// Create the wrapped text element
const textElem = nodeElem.append('g');
const description = node.descr.replace(/( )/g, '\n');
- createText(
+ await createText(
textElem,
description,
{
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
index 5299f1b1e1..951d84b862 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
+++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
@@ -1088,6 +1088,19 @@ export const draw = async function (_text: string, id: string, _version: string,
const { bounds: box } = bounds.getBounds();
+ if (box.startx === undefined) {
+ box.startx = 0;
+ }
+ if (box.starty === undefined) {
+ box.starty = 0;
+ }
+ if (box.stopx === undefined) {
+ box.stopx = 0;
+ }
+ if (box.stopy === undefined) {
+ box.stopy = 0;
+ }
+
// Make sure the height of the diagram supports long menus.
let boxHeight = box.stopy - box.starty;
if (boxHeight < requiredBoxSize.maxHeight) {
diff --git a/packages/mermaid/src/diagrams/state/dataFetcher.js b/packages/mermaid/src/diagrams/state/dataFetcher.js
new file mode 100644
index 0000000000..35cde69ab2
--- /dev/null
+++ b/packages/mermaid/src/diagrams/state/dataFetcher.js
@@ -0,0 +1,411 @@
+import { getConfig } from '../../diagram-api/diagramAPI.js';
+import { log } from '../../logger.js';
+import common from '../common/common.js';
+import {
+ CSS_DIAGRAM_CLUSTER,
+ CSS_DIAGRAM_CLUSTER_ALT,
+ CSS_DIAGRAM_NOTE,
+ CSS_DIAGRAM_STATE,
+ CSS_EDGE,
+ CSS_EDGE_NOTE_EDGE,
+ DEFAULT_NESTED_DOC_DIR,
+ DEFAULT_STATE_TYPE,
+ DIVIDER_TYPE,
+ DOMID_STATE,
+ DOMID_TYPE_SPACER,
+ G_EDGE_ARROWHEADSTYLE,
+ G_EDGE_LABELPOS,
+ G_EDGE_LABELTYPE,
+ G_EDGE_STYLE,
+ G_EDGE_THICKNESS,
+ NOTE,
+ NOTE_ID,
+ PARENT,
+ PARENT_ID,
+ SHAPE_DIVIDER,
+ SHAPE_END,
+ SHAPE_GROUP,
+ SHAPE_NOTE,
+ SHAPE_NOTEGROUP,
+ SHAPE_START,
+ SHAPE_STATE,
+ SHAPE_STATE_WITH_DESC,
+ STMT_RELATION,
+ STMT_STATE,
+} from './stateCommon.js';
+
+// List of nodes created from the parsed diagram statement items
+let nodeDb = new Map();
+
+let graphItemCount = 0; // used to construct ids, etc.
+
+/**
+ * Create a standard string for the dom ID of an item.
+ * If a type is given, insert that before the counter, preceded by the type spacer
+ *
+ * @param itemId
+ * @param counter
+ * @param {string | null} type
+ * @param typeSpacer
+ * @returns {string}
+ */
+export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) {
+ const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : '';
+ return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
+}
+
+const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, look, classes) => {
+ // graphItemCount = 0;
+ log.trace('items', doc);
+ doc.forEach((item) => {
+ switch (item.stmt) {
+ case STMT_STATE:
+ dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look, classes);
+ break;
+ case DEFAULT_STATE_TYPE:
+ dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look, classes);
+ break;
+ case STMT_RELATION:
+ {
+ dataFetcher(
+ parentParsedItem,
+ item.state1,
+ diagramStates,
+ nodes,
+ edges,
+ altFlag,
+ look,
+ classes
+ );
+ dataFetcher(
+ parentParsedItem,
+ item.state2,
+ diagramStates,
+ nodes,
+ edges,
+ altFlag,
+ look,
+ classes
+ );
+ const edgeData = {
+ id: 'edge' + graphItemCount,
+ start: item.state1.id,
+ end: item.state2.id,
+ arrowhead: 'normal',
+ arrowTypeEnd: 'arrow_barb',
+ style: G_EDGE_STYLE,
+ labelStyle: '',
+ label: common.sanitizeText(item.description, getConfig()),
+ arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
+ labelpos: G_EDGE_LABELPOS,
+ labelType: G_EDGE_LABELTYPE,
+ thickness: G_EDGE_THICKNESS,
+ classes: CSS_EDGE,
+ look,
+ };
+ edges.push(edgeData);
+ graphItemCount++;
+ }
+ break;
+ }
+ });
+};
+
+/**
+ * Get the direction from the statement items.
+ * Look through all of the documents (docs) in the parsedItems
+ * Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
+ * @param {object[]} parsedItem - the parsed statement item to look through
+ * @param [defaultDir] - the direction to use if none is found
+ * @returns {string}
+ */
+const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
+ let dir = defaultDir;
+ if (parsedItem.doc) {
+ for (const parsedItemDoc of parsedItem.doc) {
+ if (parsedItemDoc.stmt === 'dir') {
+ dir = parsedItemDoc.value;
+ }
+ }
+ }
+ return dir;
+};
+
+function insertOrUpdateNode(nodes, nodeData, classes) {
+ if (!nodeData.id || nodeData.id === '' || nodeData.id === '') {
+ return;
+ }
+
+ //Populate node style attributes if nodeData has classes defined
+ if (nodeData.cssClasses) {
+ if (!Array.isArray(nodeData.cssCompiledStyles)) {
+ nodeData.cssCompiledStyles = [];
+ }
+
+ nodeData.cssClasses.split(' ').forEach((cssClass) => {
+ if (classes.get(cssClass)) {
+ const classDef = classes.get(cssClass);
+ nodeData.cssCompiledStyles = [...nodeData.cssCompiledStyles, ...classDef.styles];
+ }
+ });
+ }
+ const existingNodeData = nodes.find((node) => node.id === nodeData.id);
+ if (existingNodeData) {
+ //update the existing nodeData
+ Object.assign(existingNodeData, nodeData);
+ } else {
+ nodes.push(nodeData);
+ }
+}
+/**
+ * Get classes from the db for the info item.
+ * If there aren't any or if dbInfoItem isn't defined, return an empty string.
+ * Else create 1 string from the list of classes found
+ *
+ * @param {undefined | null | object} dbInfoItem
+ * @returns {string}
+ */
+function getClassesFromDbInfo(dbInfoItem) {
+ if (dbInfoItem === undefined || dbInfoItem === null) {
+ return '';
+ } else {
+ if (dbInfoItem.classes) {
+ let classStr = '';
+ // for each class in classes, add it to the string as comma separated
+ for (let i = 0; i < dbInfoItem.classes.length; i++) {
+ //do not add comma for the last class
+ if (i === dbInfoItem.classes.length - 1) {
+ classStr += dbInfoItem.classes[i];
+ }
+ //add comma for all other classes
+ else {
+ classStr += dbInfoItem.classes[i] + ' ';
+ }
+ }
+ return classStr;
+ } else {
+ return '';
+ }
+ }
+}
+/**
+ * Get classes from the db for the info item.
+ * If there aren't any or if dbInfoItem isn't defined, return an empty string.
+ * Else create 1 string from the list of classes found
+ */
+function getStylesFromDbInfo(dbInfoItem) {
+ if (dbInfoItem === undefined || dbInfoItem === null) {
+ return;
+ } else {
+ if (dbInfoItem.styles) {
+ return dbInfoItem.styles;
+ } else {
+ return [];
+ }
+ }
+}
+
+export const dataFetcher = (
+ parent,
+ parsedItem,
+ diagramStates,
+ nodes,
+ edges,
+ altFlag,
+ look,
+ classes
+) => {
+ const itemId = parsedItem.id;
+ const dbState = diagramStates.get(itemId);
+ const classStr = getClassesFromDbInfo(dbState);
+ const style = getStylesFromDbInfo(dbState);
+
+ log.info('dataFetcher parsedItem', parsedItem, dbState, style);
+
+ if (itemId !== 'root') {
+ let shape = SHAPE_STATE;
+ if (parsedItem.start === true) {
+ shape = SHAPE_START;
+ }
+ if (parsedItem.start === false) {
+ shape = SHAPE_END;
+ }
+ if (parsedItem.type !== DEFAULT_STATE_TYPE) {
+ shape = parsedItem.type;
+ }
+
+ // Add the node to our list (nodeDb)
+ if (!nodeDb.get(itemId)) {
+ nodeDb.set(itemId, {
+ id: itemId,
+ shape,
+ description: common.sanitizeText(itemId, getConfig()),
+ cssClasses: `${classStr} ${CSS_DIAGRAM_STATE}`,
+ cssStyles: style,
+ });
+ }
+
+ const newNode = nodeDb.get(itemId);
+
+ // Save data for description and group so that for instance a statement without description overwrites
+ // one with description @todo TODO What does this mean? If important, add a test for it
+
+ // Build of the array of description strings
+ if (parsedItem.description) {
+ if (Array.isArray(newNode.description)) {
+ // There already is an array of strings,add to it
+ newNode.shape = SHAPE_STATE_WITH_DESC;
+ newNode.description.push(parsedItem.description);
+ } else {
+ if (newNode.description?.length > 0) {
+ // if there is a description already transform it to an array
+ newNode.shape = SHAPE_STATE_WITH_DESC;
+ if (newNode.description === itemId) {
+ // If the previous description was this, remove it
+ newNode.description = [parsedItem.description];
+ } else {
+ newNode.description = [newNode.description, parsedItem.description];
+ }
+ } else {
+ newNode.shape = SHAPE_STATE;
+ newNode.description = parsedItem.description;
+ }
+ }
+ newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig());
+ }
+
+ // If there's only 1 description entry, just use a regular state shape
+ if (newNode.description?.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) {
+ if (newNode.type === 'group') {
+ newNode.shape = SHAPE_GROUP;
+ } else {
+ newNode.shape = SHAPE_STATE;
+ }
+ }
+
+ // group
+ if (!newNode.type && parsedItem.doc) {
+ log.info('Setting cluster for XCX', itemId, getDir(parsedItem));
+ newNode.type = 'group';
+ newNode.isGroup = true;
+ newNode.dir = getDir(parsedItem);
+ newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP;
+ newNode.cssClasses = `${newNode.cssClasses} ${CSS_DIAGRAM_CLUSTER} ${altFlag ? CSS_DIAGRAM_CLUSTER_ALT : ''}`;
+ }
+
+ // This is what will be added to the graph
+ const nodeData = {
+ labelStyle: '',
+ shape: newNode.shape,
+ label: newNode.description,
+ cssClasses: newNode.cssClasses,
+ cssCompiledStyles: [],
+ cssStyles: newNode.cssStyles,
+ id: itemId,
+ dir: newNode.dir,
+ domId: stateDomId(itemId, graphItemCount),
+ type: newNode.type,
+ isGroup: newNode.type === 'group',
+ padding: 8,
+ rx: 10,
+ ry: 10,
+ look,
+ };
+
+ // Clear the label for dividers who have no description
+ if (nodeData.shape === SHAPE_DIVIDER) {
+ nodeData.label = '';
+ }
+
+ if (parent && parent.id !== 'root') {
+ log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
+ nodeData.parentId = parent.id;
+ }
+
+ nodeData.centerLabel = true;
+
+ if (parsedItem.note) {
+ // Todo: set random id
+ const noteData = {
+ labelStyle: '',
+ shape: SHAPE_NOTE,
+ label: parsedItem.note.text,
+ cssClasses: CSS_DIAGRAM_NOTE,
+ // useHtmlLabels: false,
+ cssStyles: [],
+ cssCompilesStyles: [],
+ id: itemId + NOTE_ID + '-' + graphItemCount,
+ domId: stateDomId(itemId, graphItemCount, NOTE),
+ type: newNode.type,
+ isGroup: newNode.type === 'group',
+ padding: getConfig().flowchart.padding,
+ look,
+ position: parsedItem.note.position,
+ };
+ const parentNodeId = itemId + PARENT_ID;
+ const groupData = {
+ labelStyle: '',
+ shape: SHAPE_NOTEGROUP,
+ label: parsedItem.note.text,
+ cssClasses: newNode.cssClasses,
+ cssStyles: [],
+ id: itemId + PARENT_ID,
+ domId: stateDomId(itemId, graphItemCount, PARENT),
+ type: 'group',
+ isGroup: true,
+ padding: 16, //getConfig().flowchart.padding
+ look,
+ position: parsedItem.note.position,
+ };
+ graphItemCount++;
+
+ //add parent id to groupData
+ groupData.id = parentNodeId;
+ //add parent id to noteData
+ noteData.parentId = parentNodeId;
+ //nodeData.parentId = parentNodeId;
+
+ //insert groupData
+ insertOrUpdateNode(nodes, groupData, classes);
+ //insert noteData
+ insertOrUpdateNode(nodes, noteData, classes);
+ //insert nodeData
+ insertOrUpdateNode(nodes, nodeData, classes);
+
+ let from = itemId;
+ let to = noteData.id;
+
+ if (parsedItem.note.position === 'left of') {
+ from = noteData.id;
+ to = itemId;
+ }
+
+ edges.push({
+ id: from + '-' + to,
+ start: from,
+ end: to,
+ arrowhead: 'none',
+ arrowTypeEnd: '',
+ style: G_EDGE_STYLE,
+ labelStyle: '',
+ classes: CSS_EDGE_NOTE_EDGE,
+ arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
+ labelpos: G_EDGE_LABELPOS,
+ labelType: G_EDGE_LABELTYPE,
+ thickness: G_EDGE_THICKNESS,
+ look,
+ });
+ } else {
+ insertOrUpdateNode(nodes, nodeData, classes);
+ }
+ }
+ if (parsedItem.doc) {
+ log.trace('Adding nodes children ');
+ setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, look, classes);
+ }
+};
+
+export const reset = () => {
+ nodeDb.clear();
+ graphItemCount = 0;
+};
diff --git a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
index 1be9148b8d..fed63c4448 100644
--- a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
+++ b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
@@ -217,4 +217,50 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
});
});
});
+
+ describe('style statement for a state (style)', () => {
+ describe('defining (style)', () => {
+ it('has "style" as a keyword, an id, and can set a css style attribute', function () {
+ stateDiagram.parser.parse(`stateDiagram-v2
+ id1
+ style id1 background:#bbb`);
+ stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+ const data4Layout = stateDiagram.parser.yy.getData();
+
+ expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+ });
+ it('has "style" as a keyword, an id, and can set a css style attribute', function () {
+ stateDiagram.parser.parse(`stateDiagram-v2
+ id1
+ id2
+ style id1,id2 background:#bbb`);
+ stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+ const data4Layout = stateDiagram.parser.yy.getData();
+
+ expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+ expect(data4Layout.nodes[1].cssStyles).toEqual(['background:#bbb']);
+ });
+
+ it('can define multiple attributes separated by commas', function () {
+ stateDiagram.parser.parse(`stateDiagram-v2
+ id1
+ id2
+ style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
+
+ stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+ const data4Layout = stateDiagram.parser.yy.getData();
+
+ expect(data4Layout.nodes[0].cssStyles).toEqual([
+ 'background:#bbb',
+ 'font-weight:bold',
+ 'font-style:italic',
+ ]);
+ expect(data4Layout.nodes[1].cssStyles).toEqual([
+ 'background:#bbb',
+ 'font-weight:bold',
+ 'font-style:italic',
+ ]);
+ });
+ });
+ });
});
diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
index 44235ecd40..e3bc512352 100644
--- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
+++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
@@ -27,6 +27,13 @@
%x CLASSDEFID
%x CLASS
%x CLASS_STYLE
+
+// Style statement states
+%x STYLE
+%x STYLE_IDS
+%x STYLEDEF_STYLES
+%x STYLEDEF_STYLEOPTS
+
%x NOTE
%x NOTE_ID
%x NOTE_TEXT
@@ -75,6 +82,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
[^\n]* { this.popState(); return 'STYLECLASS' }
+"style"\s+ { this.pushState('STYLE'); return 'style'; }
+