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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DagreDagre with roughELKELK with rough
+ +
+
+
+              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]]
+    
+
+
+
+
+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)]
+    
+
+
+
+
+          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))
+    
+
+
+
+
+          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)))
+    
+
+
+
+
+          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]
+    
+
+
+
+
+          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}
+    
+
+
+
+
+          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}}
+    
+
+
+
+
+          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/]
+    
+
+
+
+
+          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\]
+    
+
+
+
+
+          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\]
+    
+
+
+
+
+          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/]
+    
+
+
+
+
+          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)
+    
+
+
+
+
+          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]
+    
+
+
+
+
+          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 roughFlowchart rough
+ +
+
+
+      flowchart LR
+    id1([This is the text in the box])
+
+  
+
+
+
+
+%%{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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DagreDagre with roughELKELK with rough
+ +
+
+
+      stateId
+  
+
+
+
+
+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
+
+      
+
+ +
+
+
+
+    state "description text" as s2
+  
+
+
+
+
+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
+      
+
+ +
+
+
+
+    s2 :  description text
+  
+
+
+
+
+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
+
+      
+
+ +
+
+
+
+    s1 --> s2
+
+  
+
+
+
+
+
+    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
+
+
+      
+
+ +
+
+
+
+    s1 --> s2: A transition
+
+  
+
+
+
+
+
+    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
+
+
+      
+
+ +
+
+
+
+      [*] --> test
+    test --> [*]
+
+  
+
+
+
+
+
+    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 --> [*]
+
+
+      
+
+ +
+
+
+
+      [*] --> First
+    state First {
+        [*] --> second
+        second --> [*]
+    }
+
+  
+
+
+
+
+
+    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 --> [*]
+    }
+
+
+      
+
+ +
+
+
+
+
+    [*] --> Level1
+
+    state Level1 {
+        [*] --> Level2
+
+        state Level2 {
+            [*] --> level2
+            level2 --> Level3
+
+            state Level3 {
+                [*] --> level3
+                level3 --> [*]
+            }
+        }
+    }
+
+
+  
+
+
+
+
+
+    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 --> [*]
+            }
+        }
+    }
+
+      
+
+ +
+
+
+    [*] --> B1
+    B1 --> B2
+    B1 --> B3
+
+    state B1 {
+        [*] --> B11
+        B11 --> [*]
+    }
+    state B2 {
+        [*] --> B22
+        B22 --> [*]
+    }
+    state B3 {
+        [*] --> B33
+        B33 --> [*]
+    }
+
+
+
+  
+
+
+
+
+
+    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 --> [*]
+    }
+      
+
+ +
+
+
+    state if_state <<choice>>
+    [*] --> IsPositive
+    IsPositive --> if_state
+    if_state --> False: if n < 0
+    if_state --> True : if n >= 0
+
+
+
+
+  
+
+
+
+
+
+    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
+      
+
+ +
+
+
+    state fork_state <<fork>>
+      [*] --> fork_state
+      fork_state --> State2
+      fork_state --> State3
+
+      state join_state <<join>>
+      State2 --> join_state
+      State3 --> join_state
+      join_state --> State4
+      State4 --> [*]
+
+
+
+  
+
+
+
+
+   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 --> [*]
+
+      
+
+ +
+
+
+
+  TN1: The state with a note
+  note right of TN1
+      note text
+  end note
+  TN1 --> TN2
+  note left of TN2 : note text
+
+  
+
+
+
+
+   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.
+      
+
+ +
+
+
+[*] --> 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
+    [*] --> 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
+    }
+      
+
+ +
+
+
+direction LR
+    [*] --> D1
+    D1 --> D2
+    D2 --> D3
+    state D3 {
+      direction TB
+      D11 --> D22
+    }
+    D2 --> D4
+  
+
+
+
+
+     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
+      
+
Additional ContentNew content 1New content 2New content 3New 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', 'MultiLine'].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: 'MultiLine' }, - { 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: 'MultiLine' }, - ], - 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'; } +