mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-05 21:34:14 +01:00
Merge branch 'develop' of https://github.com/mermaid-js/mermaid into 5367/architecture-diagram
This commit is contained in:
@@ -22,9 +22,9 @@ export const packageOptions = {
|
|||||||
packageName: 'mermaid-zenuml',
|
packageName: 'mermaid-zenuml',
|
||||||
file: 'detector.ts',
|
file: 'detector.ts',
|
||||||
},
|
},
|
||||||
'mermaid-flowchart-elk': {
|
'mermaid-layout-elk': {
|
||||||
name: 'mermaid-flowchart-elk',
|
name: 'mermaid-layout-elk',
|
||||||
packageName: 'mermaid-flowchart-elk',
|
packageName: 'mermaid-layout-elk',
|
||||||
file: 'detector.ts',
|
file: 'layouts.ts',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import jison from 'jison';
|
import jison from 'jison';
|
||||||
|
|
||||||
export const transformJison = (src: string): string => {
|
export const transformJison = (src: string): string => {
|
||||||
|
// @ts-ignore - Jison is not typed properly
|
||||||
const parser = new jison.Generator(src, {
|
const parser = new jison.Generator(src, {
|
||||||
moduleType: 'js',
|
moduleType: 'js',
|
||||||
'token-stack': true,
|
'token-stack': true,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ controly
|
|||||||
CSSCLASS
|
CSSCLASS
|
||||||
CYLINDEREND
|
CYLINDEREND
|
||||||
CYLINDERSTART
|
CYLINDERSTART
|
||||||
|
DAGA
|
||||||
datakey
|
datakey
|
||||||
DEND
|
DEND
|
||||||
descr
|
descr
|
||||||
@@ -92,6 +93,7 @@ reqs
|
|||||||
rewritelinks
|
rewritelinks
|
||||||
rgba
|
rgba
|
||||||
RIGHTOF
|
RIGHTOF
|
||||||
|
roughjs
|
||||||
sankey
|
sankey
|
||||||
sequencenumber
|
sequencenumber
|
||||||
shrc
|
shrc
|
||||||
@@ -111,6 +113,7 @@ strikethrough
|
|||||||
stringifying
|
stringifying
|
||||||
struct
|
struct
|
||||||
STYLECLASS
|
STYLECLASS
|
||||||
|
STYLEDEF
|
||||||
STYLEOPTS
|
STYLEOPTS
|
||||||
subcomponent
|
subcomponent
|
||||||
subcomponents
|
subcomponents
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ presetAttributify
|
|||||||
pyplot
|
pyplot
|
||||||
redmine
|
redmine
|
||||||
rehype
|
rehype
|
||||||
|
roughjs
|
||||||
rscratch
|
rscratch
|
||||||
shiki
|
shiki
|
||||||
sparkline
|
sparkline
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ elems
|
|||||||
gantt
|
gantt
|
||||||
gitgraph
|
gitgraph
|
||||||
gzipped
|
gzipped
|
||||||
|
handDrawn
|
||||||
knsv
|
knsv
|
||||||
Knut
|
Knut
|
||||||
marginx
|
marginx
|
||||||
@@ -17,6 +18,7 @@ Markdownish
|
|||||||
mermaidjs
|
mermaidjs
|
||||||
mindmap
|
mindmap
|
||||||
mindmaps
|
mindmaps
|
||||||
|
mrtree
|
||||||
multigraph
|
multigraph
|
||||||
nodesep
|
nodesep
|
||||||
NOTEGROUP
|
NOTEGROUP
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
BRANDES
|
BRANDES
|
||||||
handdrawn
|
circo
|
||||||
|
handDrawn
|
||||||
KOEPF
|
KOEPF
|
||||||
|
neato
|
||||||
newbranch
|
newbranch
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { defaultOptions, getBuildConfig } from './util.js';
|
|||||||
const shouldVisualize = process.argv.includes('--visualize');
|
const shouldVisualize = process.argv.includes('--visualize');
|
||||||
|
|
||||||
const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
||||||
const commonOptions = { ...defaultOptions, entryName } as const;
|
const commonOptions: MermaidBuildOptions = { ...defaultOptions, entryName } as const;
|
||||||
const buildConfigs = [
|
const buildConfigs: MermaidBuildOptions[] = [
|
||||||
// package.mjs
|
// package.mjs
|
||||||
{ ...commonOptions },
|
{ ...commonOptions },
|
||||||
// package.min.mjs
|
// package.min.mjs
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { jisonPlugin } from './jisonPlugin.js';
|
|||||||
|
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
||||||
export interface MermaidBuildOptions {
|
export interface MermaidBuildOptions extends BuildOptions {
|
||||||
minify: boolean;
|
minify: boolean;
|
||||||
core: boolean;
|
core: boolean;
|
||||||
metafile: boolean;
|
metafile: boolean;
|
||||||
|
|||||||
2
.github/workflows/autofix.yml
vendored
2
.github/workflows/autofix.yml
vendored
@@ -38,4 +38,4 @@ jobs:
|
|||||||
working-directory: ./packages/mermaid
|
working-directory: ./packages/mermaid
|
||||||
run: pnpm run docs:build
|
run: pnpm run docs:build
|
||||||
|
|
||||||
- uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a
|
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,7 +35,7 @@ cypress/snapshots/
|
|||||||
.tsbuildinfo
|
.tsbuildinfo
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
knsv*.html
|
#knsv*.html
|
||||||
local*.html
|
local*.html
|
||||||
stats/
|
stats/
|
||||||
|
|
||||||
|
|||||||
@@ -16,3 +16,5 @@ generated/
|
|||||||
# Ignore the files creates in /demos/dev except for example.html
|
# Ignore the files creates in /demos/dev except for example.html
|
||||||
demos/dev/**
|
demos/dev/**
|
||||||
!/demos/dev/example.html
|
!/demos/dev/example.html
|
||||||
|
# TODO: Lots of errors to fix
|
||||||
|
cypress/platform/state-refactor.html
|
||||||
|
|||||||
@@ -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',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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 () {
|
// it(`ultraFastTest`, function () {
|
||||||
// // Navigate to the url we want to test
|
// // Navigate to the url we want to test
|
||||||
// // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run.
|
// // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run.
|
||||||
|
|||||||
@@ -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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A("This is the text")
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A["square"]
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A>"rect_left_inv_arrow"]
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A(["stadium"])
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A[/"lean right"/]
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A["square"]
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
A("rounded"):2
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
block:2
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
columns 3
|
columns 3
|
||||||
@@ -335,7 +335,7 @@ describe('Block diagram', () => {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('BL25: space and an edge', () => {
|
it('BL26: space and an edge', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
columns 5
|
columns 5
|
||||||
@@ -345,7 +345,7 @@ describe('Block diagram', () => {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('BL26: block sizes for regular blocks', () => {
|
it('BL27: block sizes for regular blocks', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
columns 3
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
columns 3
|
columns 3
|
||||||
@@ -363,11 +363,12 @@ describe('Block diagram', () => {
|
|||||||
f
|
f
|
||||||
end
|
end
|
||||||
g
|
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(
|
imgSnapshotTest(
|
||||||
`block-beta
|
`block-beta
|
||||||
columns 3
|
columns 3
|
||||||
@@ -379,7 +380,7 @@ describe('Block diagram', () => {
|
|||||||
h
|
h
|
||||||
i
|
i
|
||||||
j
|
j
|
||||||
`,
|
`,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||||
|
|
||||||
describe('C4 diagram', () => {
|
describe('C4 diagram', () => {
|
||||||
it('should render a simple C4Context diagram', () => {
|
it('C4.1 should render a simple C4Context diagram', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
C4Context
|
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(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
C4Container
|
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(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
C4Component
|
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(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
C4Dynamic
|
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(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
C4Deployment
|
C4Deployment
|
||||||
|
|||||||
@@ -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(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
classDiagram-v2
|
classDiagram-v2
|
||||||
@@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render multiple class diagrams', () => {
|
it('3: should render multiple class diagrams', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
[
|
[
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { imgSnapshotTest } from '../../helpers/util';
|
|||||||
describe('Error Diagrams', () => {
|
describe('Error Diagrams', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.on('uncaught:exception', (err) => {
|
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
|
// return false to prevent the error from
|
||||||
// failing this test
|
// failing this test
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -837,6 +837,26 @@ subgraph "\`**Two**\`"
|
|||||||
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
|
||||||
end
|
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 } }
|
{ flowchart: { titleTopMargin: 0 } }
|
||||||
);
|
);
|
||||||
@@ -855,7 +875,7 @@ describe('Title and arrow styling #4813', () => {
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
A-->B
|
A-->B
|
||||||
A-->C`,
|
A-->C`,
|
||||||
{ flowchart: { defaultRenderer: 'elk' } }
|
{ layout: 'elk' }
|
||||||
);
|
);
|
||||||
cy.get('svg').should((svg) => {
|
cy.get('svg').should((svg) => {
|
||||||
const title = svg[0].querySelector('text');
|
const title = svg[0].querySelector('text');
|
||||||
@@ -871,15 +891,14 @@ describe('Title and arrow styling #4813', () => {
|
|||||||
B-.-oC
|
B-.-oC
|
||||||
C==xD
|
C==xD
|
||||||
D ~~~ A`,
|
D ~~~ A`,
|
||||||
{ flowchart: { defaultRenderer: 'elk' } }
|
{ layout: 'elk' }
|
||||||
);
|
);
|
||||||
cy.get('svg').should((svg) => {
|
cy.get('svg').should((svg) => {
|
||||||
const edges = svg[0].querySelectorAll('.edges path');
|
const edges = svg[0].querySelectorAll('.edges path');
|
||||||
console.log(edges);
|
expect(edges[0].getAttribute('class')).to.contain('edge-pattern-solid');
|
||||||
expect(edges[0]).to.have.attr('pattern', 'solid');
|
expect(edges[1].getAttribute('class')).to.contain('edge-pattern-dotted');
|
||||||
expect(edges[1]).to.have.attr('pattern', 'dotted');
|
expect(edges[2].getAttribute('class')).to.contain('edge-thickness-thick');
|
||||||
expect(edges[2]).to.have.css('stroke-width', '3.5px');
|
expect(edges[3].getAttribute('class')).to.contain('edge-thickness-invisible');
|
||||||
expect(edges[3]).to.have.css('stroke-width', '1.5px');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
1060
cypress/integration/rendering/flowchart-handDrawn.spec.js
Normal file
1060
cypress/integration/rendering/flowchart-handDrawn.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
|
|||||||
const style = svg.attr('style');
|
const style = svg.attr('style');
|
||||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
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', () => {
|
it('8: should render a flowchart when useMaxWidth is false', () => {
|
||||||
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
|
|||||||
const width = parseFloat(svg.attr('width'));
|
const width = parseFloat(svg.attr('width'));
|
||||||
// use within because the absolute value can be slightly different depending on the environment ±5%
|
// 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(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');
|
expect(svg).to.not.have.attr('style');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1047,7 +1047,9 @@ end
|
|||||||
A --lb3--> TOP --lb4--> B
|
A --lb3--> TOP --lb4--> B
|
||||||
B1 --lb5--> B2
|
B1 --lb5--> B2
|
||||||
`,
|
`,
|
||||||
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
|
{
|
||||||
|
flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } },
|
||||||
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ describe('Graph', () => {
|
|||||||
});
|
});
|
||||||
it('38: should render a flowchart when useMaxWidth is true (default)', () => {
|
it('38: should render a flowchart when useMaxWidth is true (default)', () => {
|
||||||
renderGraph(
|
renderGraph(
|
||||||
`graph TD
|
`flowchart TD
|
||||||
A[Christmas] -->|Get money| B(Go shopping)
|
A[Christmas] -->|Get money| B(Go shopping)
|
||||||
B --> C{Let me think}
|
B --> C{Let me think}
|
||||||
C -->|One| D[Laptop]
|
C -->|One| D[Laptop]
|
||||||
@@ -751,7 +751,7 @@ describe('Graph', () => {
|
|||||||
const style = svg.attr('style');
|
const style = svg.attr('style');
|
||||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
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', () => {
|
it('39: should render a flowchart when useMaxWidth is false', () => {
|
||||||
@@ -770,7 +770,7 @@ describe('Graph', () => {
|
|||||||
const width = parseFloat(svg.attr('width'));
|
const width = parseFloat(svg.attr('width'));
|
||||||
// use within because the absolute value can be slightly different depending on the environment ±10%
|
// 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(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');
|
expect(svg).to.not.have.attr('style');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -905,13 +905,16 @@ graph TD
|
|||||||
it('67: should be able to style default node independently', () => {
|
it('67: should be able to style default node independently', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
`
|
`
|
||||||
flowchart TD
|
flowchart TD
|
||||||
classDef default fill:#a34
|
classDef default fill:#a34
|
||||||
hello --> default
|
hello --> default
|
||||||
|
|
||||||
style default stroke:#000,stroke-width:4px
|
style default stroke:#000,stroke-width:4px
|
||||||
`,
|
`,
|
||||||
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
|
{
|
||||||
|
flowchart: { htmlLabels: true },
|
||||||
|
securityLevel: 'loose',
|
||||||
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1532,5 +1532,41 @@ gitGraph TB:
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('75: should render a gitGraph with multiple tags on a merge commit on bottom-to-top orientation', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`gitGraph BT:
|
||||||
|
commit id: "ZERO"
|
||||||
|
branch develop
|
||||||
|
commit id:"A"
|
||||||
|
checkout main
|
||||||
|
commit id:"ONE"
|
||||||
|
checkout develop
|
||||||
|
commit id:"B"
|
||||||
|
checkout main
|
||||||
|
merge develop id:"Release 1.0" type:HIGHLIGHT tag: "SAML v2.0" tag: "OpenID v1.1"
|
||||||
|
commit id:"TWO"
|
||||||
|
checkout develop
|
||||||
|
commit id:"C"`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('76: should render a gitGraph with multiple tags on a merge commit on left-to-right orientation', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`gitGraph
|
||||||
|
commit id: "ZERO"
|
||||||
|
branch develop
|
||||||
|
commit id:"A"
|
||||||
|
checkout main
|
||||||
|
commit id:"ONE"
|
||||||
|
checkout develop
|
||||||
|
commit id:"B"
|
||||||
|
checkout main
|
||||||
|
merge develop id:"Release 1.0" type:HIGHLIGHT tag: "SAML v2.0" tag: "OpenID v1.1"
|
||||||
|
commit id:"TWO"
|
||||||
|
checkout develop
|
||||||
|
commit id:"C"`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -542,6 +542,43 @@ stateDiagram-v2
|
|||||||
{ logLevel: 0, fontFamily: 'courier' }
|
{ 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', () => {
|
it('1433: should render a simple state diagram with a title', () => {
|
||||||
imgSnapshotTest(
|
imgSnapshotTest(
|
||||||
@@ -551,6 +588,20 @@ title: simple state diagram
|
|||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
[*] --> State1
|
[*] --> State1
|
||||||
State1 --> [*]
|
State1 --> [*]
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should align dividers correctly', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`stateDiagram-v2
|
||||||
|
state s2 {
|
||||||
|
s3
|
||||||
|
--
|
||||||
|
s4
|
||||||
|
--
|
||||||
|
55
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|||||||
866
cypress/platform/flowchart-refactor.html
Normal file
866
cypress/platform/flowchart-refactor.html
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial';
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
height: 20px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #444;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 18px;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
outline: none;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active,
|
||||||
|
.collapsible:hover {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible:after {
|
||||||
|
content: '\002B';
|
||||||
|
color: #777;
|
||||||
|
font-weight: bold;
|
||||||
|
float: right;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active:after {
|
||||||
|
content: '\2212';
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 5px;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.2s ease-out;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .pre-scrollable {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<!-- Placeholder for the top left corner -->
|
||||||
|
<th>Dagre</th>
|
||||||
|
<th>Dagre with rough</th>
|
||||||
|
<th>ELK</th>
|
||||||
|
<th>ELK with rough</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Stadium shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram1" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram2" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram3" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Sub-Routine shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram5" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram6" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram7" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram8" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Cylindrical shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1[(Database)]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram9" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1[(Database)]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram10" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[(Database)]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram11" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[(Database)]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram12" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[(Database)]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Circle shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1((This is the text in the circle))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram13" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1((This is the text in the circle))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram14" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1((This is the text in the circle))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram15" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1((This is the text in the circle))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram16" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1((This is the text in the circle))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Double Circle shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart TD
|
||||||
|
id1(((This is the text in the circle)))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram17" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
id1(((This is the text in the circle)))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram18" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1(((This is the text in the circle)))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram19" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1(((This is the text in the circle)))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram20" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1(((This is the text in the circle)))
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Asymmetric shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1>This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram21" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1>This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram22" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1>This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram23" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1>This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram24" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1>This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Rhombus/Diamond/Question shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1{This is the text in the box}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram25" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1{This is the text in the box}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram26" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1{This is the text in the box}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram27" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1{This is the text in the box}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram28" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1{This is the text in the box}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Hexagon shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1{{This is the text in the box}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram29" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1{{This is the text in the box}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram31" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1{{This is the text in the box}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram32" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1{{This is the text in the box}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Parallelogram shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart TD
|
||||||
|
id1[/This is the text in the box/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram33" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
id1[/This is the text in the box/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram34" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[/This is the text in the box/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram35" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[/This is the text in the box/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram36" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[/This is the text in the box/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Parallelogram Alt shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart TD
|
||||||
|
id1[\This is the text in the box\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram37" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
id1[\This is the text in the box\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram38" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[\This is the text in the box\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram39" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[\This is the text in the box\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram40" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
id1[\This is the text in the box\]
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Trapezoid shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart TD
|
||||||
|
A[/Christmas\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram41" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
A[/Christmas\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram42" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[/Christmas\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram43" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[/Christmas\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram44" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[/Christmas\]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Trapezoid Alt shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart TD
|
||||||
|
A[\Christmas/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram45" class="mermaid">
|
||||||
|
flowchart TD
|
||||||
|
A[\Christmas/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram46" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[\Christmas/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram47" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[\Christmas/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram48" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart TD
|
||||||
|
A[\Christmas/]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Rect with rounded corner</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1(This is the text in the box)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram49" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1(This is the text in the box)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram50" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1(This is the text in the box)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram51" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1(This is the text in the box)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram52" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1(This is the text in the box)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Rect with sharp corner</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1[This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram53" class="mermaid">
|
||||||
|
flowchart LR
|
||||||
|
id1[This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram54" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram55" class="mermaid">
|
||||||
|
%%{init: {"handDrawn": false, "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram56" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn", "layout": "elk"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[This is the text in the box]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Separator row -->
|
||||||
|
<tr class="separator">
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<!-- This cell spans all columns including the vertical header -->
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
import { layouts } from './mermaid-layout-elk.esm.mjs';
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.parseError = function (err, hash) {};
|
||||||
|
|
||||||
|
mermaid.initialize({
|
||||||
|
handDrawn: false,
|
||||||
|
mergeEdges: true,
|
||||||
|
layout: 'dagre',
|
||||||
|
flowchart: { titleTopMargin: 10 },
|
||||||
|
// fontFamily: 'Caveat',
|
||||||
|
fontFamily: 'Kalam',
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
noteFontFamily: 'courier',
|
||||||
|
messageFontFamily: 'courier',
|
||||||
|
},
|
||||||
|
fontSize: 16,
|
||||||
|
logLevel: 0,
|
||||||
|
});
|
||||||
|
function callback() {
|
||||||
|
alert('It worked');
|
||||||
|
}
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
let coll = document.getElementsByClassName('collapsible');
|
||||||
|
for (const element of coll) {
|
||||||
|
element.addEventListener('click', function () {
|
||||||
|
this.classList.toggle('active');
|
||||||
|
let content = this.nextElementSibling;
|
||||||
|
if (content.style.maxHeight) {
|
||||||
|
content.style.maxHeight = null;
|
||||||
|
} else {
|
||||||
|
content.style.maxHeight = content.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
191
cypress/platform/flowchart-sate.html
Normal file
191
cypress/platform/flowchart-sate.html
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Arial';
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
height: 20px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #444;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 18px;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
outline: none;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active,
|
||||||
|
.collapsible:hover {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible:after {
|
||||||
|
content: '\002B';
|
||||||
|
color: #777;
|
||||||
|
font-weight: bold;
|
||||||
|
float: right;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active:after {
|
||||||
|
content: '\2212';
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 5px;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.2s ease-out;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content .pre-scrollable {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<!-- Placeholder for the top left corner -->
|
||||||
|
<th>State rough</th>
|
||||||
|
<th>Flowchart rough</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="vertical-header">
|
||||||
|
<button class="collapsible">Stadium shape</button>
|
||||||
|
<div class="content">
|
||||||
|
<div class="pre-scrollable">
|
||||||
|
<pre>
|
||||||
|
flowchart LR
|
||||||
|
id1([This is the text in the box])
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram1" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
stateDiagram-v2
|
||||||
|
stateA
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre id="diagram2" class="mermaid">
|
||||||
|
%%{init: {"look": "handDrawn"} }%%
|
||||||
|
flowchart LR
|
||||||
|
id1[[This is the text in the box]]
|
||||||
|
|
||||||
|
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
import { layouts } from './mermaid-layout-elk.esm.mjs';
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.parseError = function (err, hash) {};
|
||||||
|
|
||||||
|
mermaid.initialize({
|
||||||
|
handDrawn: false,
|
||||||
|
mergeEdges: true,
|
||||||
|
layout: 'dagre',
|
||||||
|
flowchart: { titleTopMargin: 10 },
|
||||||
|
// fontFamily: 'Caveat',
|
||||||
|
fontFamily: 'Kalam',
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
noteFontFamily: 'courier',
|
||||||
|
messageFontFamily: 'courier',
|
||||||
|
},
|
||||||
|
fontSize: 16,
|
||||||
|
logLevel: 0,
|
||||||
|
});
|
||||||
|
function callback() {
|
||||||
|
alert('It worked');
|
||||||
|
}
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
let coll = document.getElementsByClassName('collapsible');
|
||||||
|
for (const element of coll) {
|
||||||
|
element.addEventListener('click', function () {
|
||||||
|
this.classList.toggle('active');
|
||||||
|
let content = this.nextElementSibling;
|
||||||
|
if (content.style.maxHeight) {
|
||||||
|
content.style.maxHeight = null;
|
||||||
|
} else {
|
||||||
|
content.style.maxHeight = content.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
433
cypress/platform/knsv-4442.html
Normal file
433
cypress/platform/knsv-4442.html
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
/* background: rgb(221, 208, 208); */
|
||||||
|
/* background:#333; */
|
||||||
|
font-family: 'Arial';
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
.mermaid2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mermaid svg {
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
background-color: #efefef;
|
||||||
|
background-image: radial-gradient(#fff 51%, transparent 91%),
|
||||||
|
radial-gradient(#fff 51%, transparent 91%);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
10px 10px;
|
||||||
|
background-repeat: repeat;
|
||||||
|
}
|
||||||
|
.malware {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 150px;
|
||||||
|
background: red;
|
||||||
|
color: black;
|
||||||
|
display: flex;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 72px;
|
||||||
|
}
|
||||||
|
/* tspan {
|
||||||
|
font-size: 6px !important;
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Still
|
||||||
|
Still --> [*]
|
||||||
|
Still --> Moving
|
||||||
|
Moving --> Still
|
||||||
|
Moving --> Crash
|
||||||
|
Crash --> [*] </pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart RL
|
||||||
|
subgraph "`one`"
|
||||||
|
a1 -- l1 --> a2
|
||||||
|
a1 -- l2 --> a2
|
||||||
|
end
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
flowchart RL
|
||||||
|
subgraph "`one`"
|
||||||
|
a1 -- l1 --> a2
|
||||||
|
a1 -- l2 --> a2
|
||||||
|
end
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart
|
||||||
|
id["`A root with a long text that wraps to keep the node size in check. A root with a long text that wraps to keep the node size in check`"]</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart LR
|
||||||
|
A[A text that needs to be wrapped wraps to another line]
|
||||||
|
B[A text that needs to be<br/>wrapped wraps to another line]
|
||||||
|
C["`A text that needs to be wrapped to another line`"]</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart LR
|
||||||
|
C["`A text
|
||||||
|
that needs
|
||||||
|
to be wrapped
|
||||||
|
in another
|
||||||
|
way`"]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
classDiagram-v2
|
||||||
|
note "I love this diagram!\nDo you love it?"
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
stateDiagram-v2
|
||||||
|
State1: The state with a note with minus - and plus + in it
|
||||||
|
note left of State1
|
||||||
|
Important information! You can write
|
||||||
|
notes with . and in them.
|
||||||
|
end note </pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
mindmap
|
||||||
|
root
|
||||||
|
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
%%{init: {"theme": "forest"} }%%
|
||||||
|
mindmap
|
||||||
|
id1[**Start2**<br/>end]
|
||||||
|
id2[**Start2**<br />end]
|
||||||
|
%% Another comment
|
||||||
|
id3[**Start2**<br>end] %% Comment
|
||||||
|
id4[**Start2**<br >end<br >the very end]
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
mindmap
|
||||||
|
id1["`**Start2**
|
||||||
|
second line 😎 with long text that is wrapping to the next line`"]
|
||||||
|
id2["`Child **with bold** text`"]
|
||||||
|
id3["`Children of which some
|
||||||
|
is using *italic type of* text`"]
|
||||||
|
id4[Child]
|
||||||
|
id5["`Child
|
||||||
|
Row
|
||||||
|
and another
|
||||||
|
`"]
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
mindmap
|
||||||
|
id1("`**Root**`"]
|
||||||
|
id2["`A formatted text... with **bold** and *italics*`"]
|
||||||
|
id3[Regular labels works as usual]
|
||||||
|
id4["`Emojis and unicode works too: 🤓
|
||||||
|
शान्तिः سلام 和平 `"]
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||||
|
flowchart TB
|
||||||
|
%% I could not figure out how to use double quotes in labels in Mermaid
|
||||||
|
subgraph ibm[IBM Espresso CPU]
|
||||||
|
core0[IBM PowerPC Broadway Core 0]
|
||||||
|
core1[IBM PowerPC Broadway Core 1]
|
||||||
|
core2[IBM PowerPC Broadway Core 2]
|
||||||
|
|
||||||
|
rom[16 KB ROM]
|
||||||
|
|
||||||
|
core0 --- core2
|
||||||
|
|
||||||
|
rom --> core2
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph amd["`**AMD** Latte GPU`"]
|
||||||
|
mem[Memory & I/O Bridge]
|
||||||
|
dram[DRAM Controller]
|
||||||
|
edram[32 MB EDRAM MEM1]
|
||||||
|
rom[512 B SEEPROM]
|
||||||
|
|
||||||
|
sata[SATA IF]
|
||||||
|
exi[EXI]
|
||||||
|
|
||||||
|
subgraph gx[GX]
|
||||||
|
sram[3 MB 1T-SRAM]
|
||||||
|
end
|
||||||
|
|
||||||
|
radeon[AMD Radeon R7xx GX2]
|
||||||
|
|
||||||
|
mem --- gx
|
||||||
|
mem --- radeon
|
||||||
|
|
||||||
|
rom --- mem
|
||||||
|
|
||||||
|
mem --- sata
|
||||||
|
mem --- exi
|
||||||
|
|
||||||
|
dram --- sata
|
||||||
|
dram --- exi
|
||||||
|
end
|
||||||
|
|
||||||
|
ddr3[2 GB DDR3 RAM MEM2]
|
||||||
|
|
||||||
|
mem --- ddr3
|
||||||
|
dram --- ddr3
|
||||||
|
edram --- ddr3
|
||||||
|
|
||||||
|
core1 --- mem
|
||||||
|
|
||||||
|
exi --- rtc
|
||||||
|
rtc{{rtc}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
%%{init: {"flowchart": {"defaultRenderer": "elk", "htmlLabels": false}} }%%
|
||||||
|
flowchart TB
|
||||||
|
%% I could not figure out how to use double quotes in labels in Mermaid
|
||||||
|
subgraph ibm[IBM Espresso CPU]
|
||||||
|
core0[IBM PowerPC Broadway Core 0]
|
||||||
|
core1[IBM PowerPC Broadway Core 1]
|
||||||
|
core2[IBM PowerPC Broadway Core 2]
|
||||||
|
|
||||||
|
rom[16 KB ROM]
|
||||||
|
|
||||||
|
core0 --- core2
|
||||||
|
|
||||||
|
rom --> core2
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph amd["`**AMD** Latte GPU`"]
|
||||||
|
mem[Memory & I/O Bridge]
|
||||||
|
dram[DRAM Controller]
|
||||||
|
edram[32 MB EDRAM MEM1]
|
||||||
|
rom[512 B SEEPROM]
|
||||||
|
|
||||||
|
sata[SATA IF]
|
||||||
|
exi[EXI]
|
||||||
|
|
||||||
|
subgraph gx[GX]
|
||||||
|
sram[3 MB 1T-SRAM]
|
||||||
|
end
|
||||||
|
|
||||||
|
radeon[AMD Radeon R7xx GX2]
|
||||||
|
|
||||||
|
mem --- gx
|
||||||
|
mem --- radeon
|
||||||
|
|
||||||
|
rom --- mem
|
||||||
|
|
||||||
|
mem --- sata
|
||||||
|
mem --- exi
|
||||||
|
|
||||||
|
dram --- sata
|
||||||
|
dram --- exi
|
||||||
|
end
|
||||||
|
|
||||||
|
ddr3[2 GB DDR3 RAM MEM2]
|
||||||
|
|
||||||
|
mem --- ddr3
|
||||||
|
dram --- ddr3
|
||||||
|
edram --- ddr3
|
||||||
|
|
||||||
|
core1 --- mem
|
||||||
|
|
||||||
|
exi --- rtc
|
||||||
|
rtc{{rtc}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart TB
|
||||||
|
%% I could not figure out how to use double quotes in labels in Mermaid
|
||||||
|
subgraph ibm[IBM Espresso CPU]
|
||||||
|
core0[IBM PowerPC Broadway Core 0]
|
||||||
|
core1[IBM PowerPC Broadway Core 1]
|
||||||
|
core2[IBM PowerPC Broadway Core 2]
|
||||||
|
|
||||||
|
rom[16 KB ROM]
|
||||||
|
|
||||||
|
core0 --- core2
|
||||||
|
|
||||||
|
rom --> core2
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph amd[AMD Latte GPU]
|
||||||
|
mem[Memory & I/O Bridge]
|
||||||
|
dram[DRAM Controller]
|
||||||
|
edram[32 MB EDRAM MEM1]
|
||||||
|
rom[512 B SEEPROM]
|
||||||
|
|
||||||
|
sata[SATA IF]
|
||||||
|
exi[EXI]
|
||||||
|
|
||||||
|
subgraph gx[GX]
|
||||||
|
sram[3 MB 1T-SRAM]
|
||||||
|
end
|
||||||
|
|
||||||
|
radeon[AMD Radeon R7xx GX2]
|
||||||
|
|
||||||
|
mem --- gx
|
||||||
|
mem --- radeon
|
||||||
|
|
||||||
|
rom --- mem
|
||||||
|
|
||||||
|
mem --- sata
|
||||||
|
mem --- exi
|
||||||
|
|
||||||
|
dram --- sata
|
||||||
|
dram --- exi
|
||||||
|
end
|
||||||
|
|
||||||
|
ddr3[2 GB DDR3 RAM MEM2]
|
||||||
|
|
||||||
|
mem --- ddr3
|
||||||
|
dram --- ddr3
|
||||||
|
edram --- ddr3
|
||||||
|
|
||||||
|
core1 --- mem
|
||||||
|
|
||||||
|
exi --- rtc
|
||||||
|
rtc{{rtc}}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
flowchart LR
|
||||||
|
B1 --be be--x B2
|
||||||
|
B1 --bo bo--o B3
|
||||||
|
subgraph Ugge
|
||||||
|
B2
|
||||||
|
B3
|
||||||
|
subgraph inner
|
||||||
|
B4
|
||||||
|
B5
|
||||||
|
end
|
||||||
|
subgraph inner2
|
||||||
|
subgraph deeper
|
||||||
|
C4
|
||||||
|
C5
|
||||||
|
end
|
||||||
|
C6
|
||||||
|
end
|
||||||
|
|
||||||
|
B4 --> C4
|
||||||
|
|
||||||
|
B3 -- X --> B4
|
||||||
|
B2 --> inner
|
||||||
|
|
||||||
|
C4 --> C5
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph outer
|
||||||
|
B6
|
||||||
|
end
|
||||||
|
B6 --> B5
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
sequenceDiagram
|
||||||
|
Customer->>+Stripe: Makes a payment request
|
||||||
|
Stripe->>+Bank: Forwards the payment request to the bank
|
||||||
|
Bank->>+Customer: Asks for authorization
|
||||||
|
Customer->>+Bank: Provides authorization
|
||||||
|
Bank->>+Stripe: Sends a response with payment details
|
||||||
|
Stripe->>+Merchant: Sends a notification of payment receipt
|
||||||
|
Merchant->>+Stripe: Confirms the payment
|
||||||
|
Stripe->>+Customer: Sends a confirmation of payment
|
||||||
|
Customer->>+Merchant: Receives goods or services
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
Origins
|
||||||
|
Long history
|
||||||
|
::icon(fa fa-book)
|
||||||
|
Popularisation
|
||||||
|
British popular psychology author Tony Buzan
|
||||||
|
Research
|
||||||
|
On effectiveness<br/>and features
|
||||||
|
On Automatic creation
|
||||||
|
Uses
|
||||||
|
Creative techniques
|
||||||
|
Strategic planning
|
||||||
|
Argument mapping
|
||||||
|
Tools
|
||||||
|
Pen and paper
|
||||||
|
Mermaid
|
||||||
|
</pre>
|
||||||
|
<br />
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
example-diagram
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<!-- <div id="cy"></div> -->
|
||||||
|
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
|
||||||
|
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
|
||||||
|
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
|
||||||
|
<!-- <script src="./mermaid.js"></script> -->
|
||||||
|
|
||||||
|
<scrpt>
|
||||||
|
// 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));
|
||||||
|
</scrpt>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="https://cdn.jsdelivr.net/npm/mermaid@10.2.0/dist/mermaid.min.js"
|
||||||
|
></script>
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10.2.0/dist/mermaid.min.js';
|
||||||
|
(function () {
|
||||||
|
mermaid.initialize({ startOnLoad: false });
|
||||||
|
const elements = document.getElementsByClassName('mermaid');
|
||||||
|
console.log(elements);
|
||||||
|
let id = 0;
|
||||||
|
[...elements].forEach((elem) => {
|
||||||
|
const insertSvg = function (svgCode) {
|
||||||
|
elem.innerHTML = svgCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(atob(elem.innerText));
|
||||||
|
|
||||||
|
mermaid.render(`graphDiv-${id++}`, atob(elem.innerText), insertSvg);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/font-awesome.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
@@ -14,34 +14,50 @@
|
|||||||
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
/* background: rgb(221, 208, 208); */
|
/* background: rgb(221, 208, 208); */
|
||||||
background: #333;
|
/* background: #333; */
|
||||||
font-family: 'Arial';
|
font-family: 'Arial';
|
||||||
/* font-size: 18px !important; */
|
/* font-size: 18px !important; */
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
.mermaid {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
.mermaid2 {
|
.mermaid2 {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mermaid svg {
|
.mermaid svg {
|
||||||
/* font-size: 18px !important; */
|
/* font-size: 18px !important; */
|
||||||
/* background-color: #efefef; */
|
|
||||||
background-color: #333;
|
/* background-color: #efefef;
|
||||||
background-image: radial-gradient(#333 51%, transparent 91%),
|
background-image: radial-gradient(#fff 51%, transparent 91%),
|
||||||
radial-gradient(#333 51%, transparent 91%);
|
radial-gradient(#fff 51%, transparent 91%);
|
||||||
background-size: 20px 20px;
|
background-size: 20px 20px;
|
||||||
background-position: 0 0, 10px 10px;
|
background-position:
|
||||||
background-repeat: repeat;
|
0 0,
|
||||||
border: 2px solid rgb(131, 142, 205);
|
10px 10px;
|
||||||
|
background-repeat: repeat; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.malware {
|
.malware {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -57,546 +73,121 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 72px;
|
font-size: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tspan {
|
/* tspan {
|
||||||
font-size: 6px !important;
|
font-size: 6px !important;
|
||||||
} */
|
} */
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<pre id="diagram" class="mermaid">
|
<div class="flex">
|
||||||
block-beta
|
<pre id="diagram" class="mermaid">
|
||||||
blockArrowId<["Label"]>(right)
|
---
|
||||||
blockArrowId2<["Label"]>(left)
|
config:
|
||||||
blockArrowId3<["Label"]>(up)
|
look: handDrawn
|
||||||
blockArrowId4<["Label"]>(down)
|
flowchart:
|
||||||
blockArrowId5<["Label"]>(x)
|
htmlLabels: false
|
||||||
blockArrowId6<["Label"]>(y)
|
---
|
||||||
blockArrowId6<["Label"]>(x, down)
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
block-beta
|
|
||||||
block:e:4
|
|
||||||
columns 2
|
|
||||||
f
|
|
||||||
g
|
|
||||||
end
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
block-beta
|
|
||||||
block:e:4
|
|
||||||
columns 2
|
|
||||||
f
|
|
||||||
g
|
|
||||||
h
|
|
||||||
end
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
block-beta
|
|
||||||
columns 4
|
|
||||||
a b c d
|
|
||||||
block:e:4
|
|
||||||
columns 2
|
|
||||||
f
|
|
||||||
g
|
|
||||||
h
|
|
||||||
end
|
|
||||||
i:4
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart LR
|
|
||||||
X-- "y" -->z
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
columns 5
|
|
||||||
A space B
|
|
||||||
A --x B
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
columns 3
|
|
||||||
a["A wide one"] b:2 c:2 d
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
block:e
|
|
||||||
f
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
columns 3
|
|
||||||
a:3
|
|
||||||
block:e:3
|
|
||||||
f
|
|
||||||
end
|
|
||||||
g
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
columns 3
|
|
||||||
a:3
|
|
||||||
block:e:3
|
|
||||||
f
|
|
||||||
g
|
|
||||||
end
|
|
||||||
h
|
|
||||||
i
|
|
||||||
j
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
columns 3
|
|
||||||
a b:2
|
|
||||||
block:e:3
|
|
||||||
f
|
|
||||||
end
|
|
||||||
g h i
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
block-beta
|
|
||||||
columns 3
|
|
||||||
a b c
|
|
||||||
e:3
|
|
||||||
f g h
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
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
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid">
|
|
||||||
block-beta
|
|
||||||
columns 5
|
|
||||||
A1:3
|
|
||||||
A2:1
|
|
||||||
A3
|
|
||||||
B1 B2 B3:3
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
block
|
|
||||||
D
|
|
||||||
E
|
|
||||||
end
|
|
||||||
db("This is the text in the box")
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
|
|
||||||
block
|
|
||||||
D
|
|
||||||
end
|
|
||||||
A["A: I am a wide one"]
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
A["square"]
|
|
||||||
B("rounded")
|
|
||||||
C(("circle"))
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
A>"rect_left_inv_arrow"]
|
|
||||||
B{"diamond"}
|
|
||||||
C{{"hexagon"}}
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
A(["stadium"])
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
%% A[["subroutine"]]
|
|
||||||
%% B[("cylinder")]
|
|
||||||
C>"surprise"]
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
block-beta
|
|
||||||
A[/"lean right"/]
|
|
||||||
B[\"lean left"\]
|
|
||||||
C[/"trapezoid"\]
|
|
||||||
D[\"trapezoid"/]
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart
|
flowchart
|
||||||
B
|
A[I am a long text, where do I go??? handdrawn - false]
|
||||||
style B fill:#f9F,stroke:#333,stroke-width:4px
|
</pre
|
||||||
</pre>
|
>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
<pre id="diagram" class="mermaid2">
|
---
|
||||||
flowchart LR
|
config:
|
||||||
a1 -- apa --> b1
|
look: handdrawn
|
||||||
</pre>
|
flowchart:
|
||||||
|
htmlLabels: true
|
||||||
<pre id="diagram" class="mermaid2">
|
---
|
||||||
flowchart RL
|
|
||||||
subgraph "`one`"
|
|
||||||
id
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart RL
|
|
||||||
subgraph "`one`"
|
|
||||||
a1 -- l1 --> a2
|
|
||||||
a1 -- l2 --> a2
|
|
||||||
end
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart
|
flowchart
|
||||||
id["`A root with a long text that wraps to keep the node size in check. A root with a long text that wraps to keep the node size in check`"]</pre
|
A[I am a long text, where do I go??? handdrawn - true]
|
||||||
>
|
</pre
|
||||||
<pre id="diagram" class="mermaid2">
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
flowchart:
|
||||||
|
htmlLabels: false
|
||||||
|
---
|
||||||
|
flowchart
|
||||||
|
A[I am a long text, where do I go??? classic - false]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
flowchart:
|
||||||
|
htmlLabels: true
|
||||||
|
---
|
||||||
|
flowchart
|
||||||
|
A[I am a long text, where do I go??? classic - true]
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<pre id="diagram2" class="mermaid2">
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A[A text that needs to be wrapped wraps to another line]
|
id1(Start)-->id2(Stop)
|
||||||
B[A text that needs to be<br/>wrapped wraps to another line]
|
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
C["`A text that needs to be wrapped to another line`"]</pre>
|
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart LR
|
|
||||||
C["`A text
|
|
||||||
that needs
|
|
||||||
to be wrapped
|
|
||||||
in another
|
|
||||||
way`"]
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
classDiagram-v2
|
|
||||||
note "I love this diagram!\nDo you love it?"
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
stateDiagram-v2
|
|
||||||
State1: The state with a note with minus - and plus + in it
|
|
||||||
note left of State1
|
|
||||||
Important information! You can write
|
|
||||||
notes with . and in them.
|
|
||||||
end note </pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
mindmap
|
|
||||||
root
|
|
||||||
Child3(A node with an icon and with a long text that wraps to keep the node size in check)
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
%%{init: {"theme": "forest"} }%%
|
|
||||||
mindmap
|
|
||||||
id1[**Start2**<br/>end]
|
|
||||||
id2[**Start2**<br />end]
|
|
||||||
%% Another comment
|
|
||||||
id3[**Start2**<br>end] %% Comment
|
|
||||||
id4[**Start2**<br >end<br >the very end]
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
mindmap
|
|
||||||
id1["`**Start2**
|
|
||||||
second line 😎 with long text that is wrapping to the next line`"]
|
|
||||||
id2["`Child **with bold** text`"]
|
|
||||||
id3["`Children of which some
|
|
||||||
is using *italic type of* text`"]
|
|
||||||
id4[Child]
|
|
||||||
id5["`Child
|
|
||||||
Row
|
|
||||||
and another
|
|
||||||
`"]
|
|
||||||
</pre>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
mindmap
|
|
||||||
id1("`**Root**`"]
|
|
||||||
id2["`A formatted text... with **bold** and *italics*`"]
|
|
||||||
id3[Regular labels works as usual]
|
|
||||||
id4["`Emojis and unicode works too: 🤓
|
|
||||||
शान्तिः سلام 和平 `"]
|
|
||||||
|
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
%%{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]
|
<pre id="diagram3" class="mermaid2">
|
||||||
|
|
||||||
core0 --- core2
|
|
||||||
|
|
||||||
rom --> core2
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph amd["`**AMD** Latte GPU`"]
|
|
||||||
mem[Memory & I/O Bridge]
|
|
||||||
dram[DRAM Controller]
|
|
||||||
edram[32 MB EDRAM MEM1]
|
|
||||||
rom[512 B SEEPROM]
|
|
||||||
|
|
||||||
sata[SATA IF]
|
|
||||||
exi[EXI]
|
|
||||||
|
|
||||||
subgraph gx[GX]
|
|
||||||
sram[3 MB 1T-SRAM]
|
|
||||||
end
|
|
||||||
|
|
||||||
radeon[AMD Radeon R7xx GX2]
|
|
||||||
|
|
||||||
mem --- gx
|
|
||||||
mem --- radeon
|
|
||||||
|
|
||||||
rom --- mem
|
|
||||||
|
|
||||||
mem --- sata
|
|
||||||
mem --- exi
|
|
||||||
|
|
||||||
dram --- sata
|
|
||||||
dram --- exi
|
|
||||||
end
|
|
||||||
|
|
||||||
ddr3[2 GB DDR3 RAM MEM2]
|
|
||||||
|
|
||||||
mem --- ddr3
|
|
||||||
dram --- ddr3
|
|
||||||
edram --- ddr3
|
|
||||||
|
|
||||||
core1 --- mem
|
|
||||||
|
|
||||||
exi --- rtc
|
|
||||||
rtc{{rtc}}
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
%%{init: {"flowchart": {"defaultRenderer": "elk", "htmlLabels": false}} }%%
|
|
||||||
flowchart TB
|
|
||||||
%% I could not figure out how to use double quotes in labels in Mermaid
|
|
||||||
subgraph ibm[IBM Espresso CPU]
|
|
||||||
core0[IBM PowerPC Broadway Core 0]
|
|
||||||
core1[IBM PowerPC Broadway Core 1]
|
|
||||||
core2[IBM PowerPC Broadway Core 2]
|
|
||||||
|
|
||||||
rom[16 KB ROM]
|
|
||||||
|
|
||||||
core0 --- core2
|
|
||||||
|
|
||||||
rom --> core2
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph amd["`**AMD** Latte GPU`"]
|
|
||||||
mem[Memory & I/O Bridge]
|
|
||||||
dram[DRAM Controller]
|
|
||||||
edram[32 MB EDRAM MEM1]
|
|
||||||
rom[512 B SEEPROM]
|
|
||||||
|
|
||||||
sata[SATA IF]
|
|
||||||
exi[EXI]
|
|
||||||
|
|
||||||
subgraph gx[GX]
|
|
||||||
sram[3 MB 1T-SRAM]
|
|
||||||
end
|
|
||||||
|
|
||||||
radeon[AMD Radeon R7xx GX2]
|
|
||||||
|
|
||||||
mem --- gx
|
|
||||||
mem --- radeon
|
|
||||||
|
|
||||||
rom --- mem
|
|
||||||
|
|
||||||
mem --- sata
|
|
||||||
mem --- exi
|
|
||||||
|
|
||||||
dram --- sata
|
|
||||||
dram --- exi
|
|
||||||
end
|
|
||||||
|
|
||||||
ddr3[2 GB DDR3 RAM MEM2]
|
|
||||||
|
|
||||||
mem --- ddr3
|
|
||||||
dram --- ddr3
|
|
||||||
edram --- ddr3
|
|
||||||
|
|
||||||
core1 --- mem
|
|
||||||
|
|
||||||
exi --- rtc
|
|
||||||
rtc{{rtc}}
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart TB
|
|
||||||
%% I could not figure out how to use double quotes in labels in Mermaid
|
|
||||||
subgraph ibm[IBM Espresso CPU]
|
|
||||||
core0[IBM PowerPC Broadway Core 0]
|
|
||||||
core1[IBM PowerPC Broadway Core 1]
|
|
||||||
core2[IBM PowerPC Broadway Core 2]
|
|
||||||
|
|
||||||
rom[16 KB ROM]
|
|
||||||
|
|
||||||
core0 --- core2
|
|
||||||
|
|
||||||
rom --> core2
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph amd[AMD Latte GPU]
|
|
||||||
mem[Memory & I/O Bridge]
|
|
||||||
dram[DRAM Controller]
|
|
||||||
edram[32 MB EDRAM MEM1]
|
|
||||||
rom[512 B SEEPROM]
|
|
||||||
|
|
||||||
sata[SATA IF]
|
|
||||||
exi[EXI]
|
|
||||||
|
|
||||||
subgraph gx[GX]
|
|
||||||
sram[3 MB 1T-SRAM]
|
|
||||||
end
|
|
||||||
|
|
||||||
radeon[AMD Radeon R7xx GX2]
|
|
||||||
|
|
||||||
mem --- gx
|
|
||||||
mem --- radeon
|
|
||||||
|
|
||||||
rom --- mem
|
|
||||||
|
|
||||||
mem --- sata
|
|
||||||
mem --- exi
|
|
||||||
|
|
||||||
dram --- sata
|
|
||||||
dram --- exi
|
|
||||||
end
|
|
||||||
|
|
||||||
ddr3[2 GB DDR3 RAM MEM2]
|
|
||||||
|
|
||||||
mem --- ddr3
|
|
||||||
dram --- ddr3
|
|
||||||
edram --- ddr3
|
|
||||||
|
|
||||||
core1 --- mem
|
|
||||||
|
|
||||||
exi --- rtc
|
|
||||||
rtc{{rtc}}
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
flowchart LR
|
flowchart LR
|
||||||
B1 --be be--x B2
|
A:::foo & B:::bar --> C:::foobar
|
||||||
B1 --bo bo--o B3
|
classDef foo stroke:#f00
|
||||||
subgraph Ugge
|
classDef bar stroke:#0f0
|
||||||
B2
|
classDef ash color:red
|
||||||
B3
|
class C ash
|
||||||
subgraph inner
|
style C stroke:#00f, fill:black
|
||||||
B4
|
|
||||||
B5
|
|
||||||
end
|
|
||||||
subgraph inner2
|
|
||||||
subgraph deeper
|
|
||||||
C4
|
|
||||||
C5
|
|
||||||
end
|
|
||||||
C6
|
|
||||||
end
|
|
||||||
|
|
||||||
B4 --> C4
|
|
||||||
|
|
||||||
B3 -- X --> B4
|
|
||||||
B2 --> inner
|
|
||||||
|
|
||||||
C4 --> C5
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph outer
|
|
||||||
B6
|
|
||||||
end
|
|
||||||
B6 --> B5
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
sequenceDiagram
|
|
||||||
Customer->>+Stripe: Makes a payment request
|
|
||||||
Stripe->>+Bank: Forwards the payment request to the bank
|
|
||||||
Bank->>+Customer: Asks for authorization
|
|
||||||
Customer->>+Bank: Provides authorization
|
|
||||||
Bank->>+Stripe: Sends a response with payment details
|
|
||||||
Stripe->>+Merchant: Sends a notification of payment receipt
|
|
||||||
Merchant->>+Stripe: Confirms the payment
|
|
||||||
Stripe->>+Customer: Sends a confirmation of payment
|
|
||||||
Customer->>+Merchant: Receives goods or services
|
|
||||||
</pre
|
|
||||||
>
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
mindmap
|
|
||||||
root((mindmap))
|
|
||||||
Origins
|
|
||||||
Long history
|
|
||||||
::icon(fa fa-book)
|
|
||||||
Popularisation
|
|
||||||
British popular psychology author Tony Buzan
|
|
||||||
Research
|
|
||||||
On effectiveness<br/>and features
|
|
||||||
On Automatic creation
|
|
||||||
Uses
|
|
||||||
Creative techniques
|
|
||||||
Strategic planning
|
|
||||||
Argument mapping
|
|
||||||
Tools
|
|
||||||
Pen and paper
|
|
||||||
Mermaid
|
|
||||||
</pre>
|
|
||||||
<br />
|
|
||||||
<pre id="diagram" class="mermaid2">
|
|
||||||
example-diagram
|
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<!-- <div id="cy"></div> -->
|
<pre id="diagram4" class="mermaid2">
|
||||||
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> -->
|
stateDiagram
|
||||||
<!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
|
A:::foo
|
||||||
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
|
B:::bar --> C:::foobar
|
||||||
<!-- <script src="./mermaid.js"></script> -->
|
classDef foo stroke:#f00
|
||||||
|
classDef bar stroke:#0f0
|
||||||
|
style C stroke:#00f, fill:black, color:white
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
// 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';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
// await mermaid.registerExternalDiagrams([example]);
|
// import { layouts } from './mermaid-layout-elk.esm.mjs';
|
||||||
|
// mermaid.registerLayoutLoaders(layouts);
|
||||||
mermaid.parseError = function (err, hash) {
|
mermaid.parseError = function (err, hash) {
|
||||||
// console.error('Mermaid error: ', err);
|
console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
window.callback = function () {
|
||||||
|
alert('A callback was triggered');
|
||||||
};
|
};
|
||||||
// mermaid.initialize({
|
|
||||||
// // theme: 'forest',
|
|
||||||
// startOnLoad: true,
|
|
||||||
// logLevel: 0,
|
|
||||||
// flowchart: {
|
|
||||||
// // defaultRenderer: 'elk',
|
|
||||||
// useMaxWidth: false,
|
|
||||||
// // htmlLabels: false,
|
|
||||||
// htmlLabels: true,
|
|
||||||
// },
|
|
||||||
// // htmlLabels: false,
|
|
||||||
// gantt: {
|
|
||||||
// useMaxWidth: false,
|
|
||||||
// },
|
|
||||||
// useMaxWidth: false,
|
|
||||||
// });
|
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
theme: 'dark',
|
// theme: 'base',
|
||||||
startOnLoad: true,
|
// handDrawnSeed: 12,
|
||||||
|
// look: 'handDrawn',
|
||||||
|
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
|
||||||
|
// layout: 'dagre',
|
||||||
|
// layout: 'elk',
|
||||||
|
// layout: 'fixed',
|
||||||
|
// htmlLabels: false,
|
||||||
|
flowchart: { titleTopMargin: 10 },
|
||||||
|
// fontFamily: 'Caveat',
|
||||||
|
// fontFamily: 'Kalam',
|
||||||
|
// fontFamily: 'courier',
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
noteFontFamily: 'courier',
|
||||||
|
messageFontFamily: 'courier',
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
logLevel: 0,
|
logLevel: 0,
|
||||||
|
securityLevel: 'loose',
|
||||||
});
|
});
|
||||||
function callback() {
|
function callback() {
|
||||||
alert('It worked');
|
alert('It worked');
|
||||||
@@ -605,10 +196,6 @@ mindmap
|
|||||||
console.error('In parse error:');
|
console.error('In parse error:');
|
||||||
console.error(err);
|
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));
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
|||||||
1271
cypress/platform/state-refactor.html
Normal file
1271
cypress/platform/state-refactor.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import mermaid from './mermaid.esm.mjs';
|
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 externalExample from './mermaid-example-diagram.esm.mjs';
|
||||||
import zenUml from './mermaid-zenuml.esm.mjs';
|
import zenUml from './mermaid-zenuml.esm.mjs';
|
||||||
|
|
||||||
@@ -46,7 +46,9 @@ const contentLoaded = async function () {
|
|||||||
document.getElementsByTagName('body')[0].appendChild(div);
|
document.getElementsByTagName('body')[0].appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
await mermaid.registerExternalDiagrams([externalExample, zenUml, flowchartELK]);
|
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
|
||||||
|
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
mermaid.initialize(graphObj.mermaid);
|
mermaid.initialize(graphObj.mermaid);
|
||||||
await mermaid.run();
|
await mermaid.run();
|
||||||
}
|
}
|
||||||
|
|||||||
43
docs/config/setup/interfaces/mermaid.LayoutData.md
Normal file
43
docs/config/setup/interfaces/mermaid.LayoutData.md
Normal file
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -28,7 +28,7 @@ page.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:431](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L431)
|
[packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ A graph definition key
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
|
[packages/mermaid/src/mermaid.ts:440](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L440)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ Use [initialize](mermaid.Mermaid.md#initialize) and [run](mermaid.Mermaid.md#run
|
|||||||
|
|
||||||
#### Defined in
|
#### 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:433](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L433)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -116,7 +116,50 @@ This function should be called before the run function.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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`) => `Promise`<`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:445](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L445)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -130,7 +173,7 @@ Use [parse](mermaid.Mermaid.md#parse) and [render](mermaid.Mermaid.md#render) in
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -180,7 +223,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -190,7 +233,7 @@ Error if the diagram is invalid and parseOptions.suppressErrors is false or not
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -218,7 +261,31 @@ Used to register external diagram types.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:429](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L429)
|
[packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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:435](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L435)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -244,7 +311,7 @@ Used to register external diagram types.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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:429](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L429)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -292,7 +359,7 @@ Renders the mermaid diagrams
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:428](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L428)
|
[packages/mermaid/src/mermaid.ts:434](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L434)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -327,7 +394,7 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:432](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L432)
|
[packages/mermaid/src/mermaid.ts:439](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L439)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -337,4 +404,4 @@ to it (eg. dart interop wrapper). (Initially there is no parseError member of me
|
|||||||
|
|
||||||
#### Defined 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)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L169)
|
[packages/mermaid/src/config.type.ts:196](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L196)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ This matters if you are using base tag settings.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ You can set this attribute to base the seed on a static string.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L143)
|
[packages/mermaid/src/config.type.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L170)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -121,7 +121,24 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L170)
|
[packages/mermaid/src/config.type.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L197)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -131,7 +148,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -141,7 +158,7 @@ should not change unless content is changed.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -155,7 +172,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -165,7 +182,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -179,7 +196,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:132](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L132)
|
[packages/mermaid/src/config.type.ts:159](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L159)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -189,7 +206,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -199,7 +216,19 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -209,7 +238,7 @@ If set to true, ignores legacyMathML.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -222,7 +251,7 @@ For supported diagrams (i.e., Architecture), their syntax allows refering to key
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L125)
|
[packages/mermaid/src/config.type.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L152)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -232,7 +261,19 @@ For supported diagrams (i.e., Architecture), their syntax allows refering to key
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -247,7 +288,7 @@ fall back to legacy rendering for KaTeX.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -259,7 +300,19 @@ This option decides the amount of logging to be used by mermaid.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -269,7 +322,7 @@ This option decides the amount of logging to be used by mermaid.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173)
|
[packages/mermaid/src/config.type.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L200)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -281,7 +334,7 @@ Defines the maximum number of edges that can be drawn in a graph.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -293,7 +346,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -303,7 +356,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -313,7 +366,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L168)
|
[packages/mermaid/src/config.type.ts:195](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L195)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -323,7 +376,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -333,7 +386,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -343,7 +396,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -353,7 +406,7 @@ The maximum allowed size of the users text diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:167](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L167)
|
[packages/mermaid/src/config.type.ts:194](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L194)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -367,7 +420,7 @@ This prevents malicious graph directives from overriding a site's default securi
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -379,7 +432,7 @@ Level of trust for parsed diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -389,7 +442,7 @@ Level of trust for parsed diagram
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -401,7 +454,7 @@ Dictates whether mermaid starts on Page load
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -411,7 +464,7 @@ Dictates whether mermaid starts on Page load
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -424,13 +477,13 @@ This is useful when you want to control how to handle syntax errors in your appl
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:179](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L179)
|
[packages/mermaid/src/config.type.ts:206](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L206)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### theme
|
### theme
|
||||||
|
|
||||||
• `Optional` **theme**: `"default"` | `"forest"` | `"dark"` | `"neutral"` | `"null"`
|
• `Optional` **theme**: `"default"` | `"base"` | `"dark"` | `"forest"` | `"neutral"` | `"null"`
|
||||||
|
|
||||||
Theme, the CSS style sheet.
|
Theme, the CSS style sheet.
|
||||||
You may also use `themeCSS` to override this value.
|
You may also use `themeCSS` to override this value.
|
||||||
@@ -467,7 +520,7 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -477,7 +530,7 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/config.type.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L171)
|
[packages/mermaid/src/config.type.ts:198](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L198)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -487,4 +540,4 @@ You may also use `themeCSS` to override this value.
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ The nodes to render. If this is set, `querySelector` will be ignored.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L45)
|
[packages/mermaid/src/mermaid.ts:51](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L51)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ A callback to call after each diagram is rendered.
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:49](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L49)
|
[packages/mermaid/src/mermaid.ts:55](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L55)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ The query selector to use when finding elements to render. Default: `".mermaid"`
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:41](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L41)
|
[packages/mermaid/src/mermaid.ts:47](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L47)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -68,4 +68,4 @@ If `true`, errors will be logged to the console, but not thrown. Default: `false
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:53](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L53)
|
[packages/mermaid/src/mermaid.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L59)
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ The siteConfig
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### 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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
- [DetailedError](../interfaces/mermaid.DetailedError.md)
|
- [DetailedError](../interfaces/mermaid.DetailedError.md)
|
||||||
- [ExternalDiagramDefinition](../interfaces/mermaid.ExternalDiagramDefinition.md)
|
- [ExternalDiagramDefinition](../interfaces/mermaid.ExternalDiagramDefinition.md)
|
||||||
|
- [LayoutData](../interfaces/mermaid.LayoutData.md)
|
||||||
|
- [LayoutLoaderDefinition](../interfaces/mermaid.LayoutLoaderDefinition.md)
|
||||||
- [Mermaid](../interfaces/mermaid.Mermaid.md)
|
- [Mermaid](../interfaces/mermaid.Mermaid.md)
|
||||||
- [MermaidConfig](../interfaces/mermaid.MermaidConfig.md)
|
- [MermaidConfig](../interfaces/mermaid.MermaidConfig.md)
|
||||||
- [ParseOptions](../interfaces/mermaid.ParseOptions.md)
|
- [ParseOptions](../interfaces/mermaid.ParseOptions.md)
|
||||||
@@ -89,7 +91,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
|
[packages/mermaid/src/mermaid.ts:448](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L448)
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ Cardinality is a property that describes how many elements of another entity can
|
|||||||
| 1+ | 1+ | One or more |
|
| 1+ | 1+ | One or more |
|
||||||
| zero or more | zero or more | Zero or more |
|
| zero or more | zero or more | Zero or more |
|
||||||
| zero or many | zero or many | Zero or more |
|
| zero or many | zero or many | Zero or more |
|
||||||
| many(0) | many(1) | Zero or more |
|
| many(0) | many(0) | Zero or more |
|
||||||
| 0+ | 0+ | Zero or more |
|
| 0+ | 0+ | Zero or more |
|
||||||
| only one | only one | Exactly one |
|
| only one | only one | Exactly one |
|
||||||
| 1 | 1 | Exactly one |
|
| 1 | 1 | Exactly one |
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export default tseslint.config(
|
|||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
'@typescript-eslint/no-unsafe-return': 'off',
|
||||||
'@typescript-eslint/only-throw-error': 'warn',
|
'@typescript-eslint/only-throw-error': 'warn',
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||||
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
|
'@typescript-eslint/prefer-promise-reject-errors': 'warn',
|
||||||
// END
|
// END
|
||||||
'json/*': ['error', 'allowComments'],
|
'json/*': ['error', 'allowComments'],
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@applitools/eyes-cypress": "^3.42.3",
|
"@applitools/eyes-cypress": "^3.44.4",
|
||||||
"@argos-ci/cypress": "^2.1.0",
|
"@argos-ci/cypress": "^2.1.0",
|
||||||
"@cspell/eslint-plugin": "^8.8.4",
|
"@cspell/eslint-plugin": "^8.8.4",
|
||||||
"@cypress/code-coverage": "^3.12.30",
|
"@cypress/code-coverage": "^3.12.30",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cspell": "^8.6.0",
|
"cspell": "^8.6.0",
|
||||||
"cypress": "^13.7.1",
|
"cypress": "^13.11.0",
|
||||||
"cypress-image-snapshot": "^4.0.1",
|
"cypress-image-snapshot": "^4.0.1",
|
||||||
"esbuild": "^0.21.5",
|
"esbuild": "^0.21.5",
|
||||||
"eslint": "^9.4.0",
|
"eslint": "^9.4.0",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"eslint-plugin-markdown": "^5.0.0",
|
"eslint-plugin-markdown": "^5.0.0",
|
||||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||||
"eslint-plugin-tsdoc": "^0.3.0",
|
"eslint-plugin-tsdoc": "^0.3.0",
|
||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^55.0.0",
|
||||||
"express": "^4.19.1",
|
"express": "^4.19.1",
|
||||||
"globals": "^15.4.0",
|
"globals": "^15.4.0",
|
||||||
"globby": "^14.0.1",
|
"globby": "^14.0.1",
|
||||||
|
|||||||
@@ -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.
|
* 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.
|
* It also recursively populates the inserts the children of the children and so on.
|
||||||
* @param {*} graph
|
|
||||||
* @param nodeArray
|
* @param nodeArray
|
||||||
* @param parentLookupDb
|
* @param parentLookupDb
|
||||||
*/
|
*/
|
||||||
|
|||||||
43
packages/mermaid-layout-elk/package.json
Normal file
43
packages/mermaid-layout-elk/package.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
32
packages/mermaid-layout-elk/src/find-common-ancestor.ts
Normal file
32
packages/mermaid-layout-elk/src/find-common-ancestor.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export interface TreeData {
|
||||||
|
parentById: Record<string, string>;
|
||||||
|
childrenById: Record<string, string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
};
|
||||||
17
packages/mermaid-layout-elk/src/layouts.ts
Normal file
17
packages/mermaid-layout-elk/src/layouts.ts
Normal file
@@ -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,
|
||||||
|
})),
|
||||||
|
];
|
||||||
997
packages/mermaid-layout-elk/src/render.ts
Normal file
997
packages/mermaid-layout-elk/src/render.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
10
packages/mermaid-layout-elk/tsconfig.json
Normal file
10
packages/mermaid-layout-elk/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"types": ["vitest/importMeta", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts"],
|
||||||
|
"typeRoots": ["./src/types"]
|
||||||
|
}
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
"dagre-d3-es": "7.0.10",
|
"dagre-d3-es": "7.0.10",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"dompurify": "^3.0.11",
|
"dompurify": "^3.0.11",
|
||||||
"elkjs": "^0.9.2",
|
|
||||||
"katex": "^0.16.9",
|
"katex": "^0.16.9",
|
||||||
"khroma": "^2.1.0",
|
"khroma": "^2.1.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^13.0.2",
|
"marked": "^13.0.2",
|
||||||
|
"roughjs": "^4.6.6",
|
||||||
"stylis": "^4.3.1",
|
"stylis": "^4.3.1",
|
||||||
"ts-dedent": "^2.2.0",
|
"ts-dedent": "^2.2.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
@@ -136,7 +136,6 @@
|
|||||||
"dist/",
|
"dist/",
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"sideEffects": false,
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
|
||||||
if (directive.fontFamily && !directive.themeVariables?.fontFamily) {
|
if (directive.fontFamily && !directive.themeVariables?.fontFamily) {
|
||||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
directive.themeVariables = {
|
||||||
|
...directive.themeVariables,
|
||||||
|
fontFamily: directive.fontFamily,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
directives.push(directive);
|
directives.push(directive);
|
||||||
|
|||||||
@@ -61,9 +61,24 @@ export interface MermaidConfig {
|
|||||||
* You may also use `themeCSS` to override this value.
|
* You may also use `themeCSS` to override this value.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
|
theme?: 'default' | 'base' | 'dark' | 'forest' | 'neutral' | 'null';
|
||||||
themeVariables?: any;
|
themeVariables?: any;
|
||||||
themeCSS?: string;
|
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
|
* The maximum allowed size of the users text diagram
|
||||||
*/
|
*/
|
||||||
@@ -73,6 +88,18 @@ export interface MermaidConfig {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
maxEdges?: number;
|
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;
|
darkMode?: boolean;
|
||||||
htmlLabels?: boolean;
|
htmlLabels?: boolean;
|
||||||
/**
|
/**
|
||||||
@@ -704,6 +731,8 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
|
|||||||
textHeight?: number;
|
textHeight?: number;
|
||||||
titleShift?: number;
|
titleShift?: number;
|
||||||
noteMargin?: number;
|
noteMargin?: number;
|
||||||
|
nodeSpacing?: number;
|
||||||
|
rankSpacing?: number;
|
||||||
forkWidth?: number;
|
forkWidth?: number;
|
||||||
forkHeight?: number;
|
forkHeight?: number;
|
||||||
miniPadding?: number;
|
miniPadding?: number;
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// 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
|
// 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);
|
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 + ': ' + JSON.stringify(e));
|
||||||
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
|
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
|
||||||
|
|
||||||
// Check if link is either from or to a cluster
|
// 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]);
|
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) {
|
graph.edges().forEach(function (e) {
|
||||||
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(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('#############################################');
|
||||||
log.info('### Layout ###');
|
log.info('### Layout ###');
|
||||||
log.info('#############################################');
|
log.info('#############################################');
|
||||||
log.info(graph);
|
log.info(graph);
|
||||||
dagreLayout(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
|
// Move the nodes to the correct place
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { select } from 'd3';
|
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 { 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 { evaluate } from '../diagrams/common/common.js';
|
||||||
|
import { log } from '../logger.js';
|
||||||
import { getArrowPoints } from './blockArrowHelper.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) => {
|
const formatClass = (str) => {
|
||||||
if (str) {
|
if (str) {
|
||||||
@@ -395,6 +395,7 @@ const rect = async (parent, node) => {
|
|||||||
// add the rect
|
// add the rect
|
||||||
const rect = shapeSvg.insert('rect', ':first-child');
|
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 totalWidth = bbox.width + node.padding * 2;
|
||||||
// const totalHeight = bbox.height + node.padding * 2;
|
// const totalHeight = bbox.height + node.padding * 2;
|
||||||
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;
|
const totalWidth = node.positioned ? node.width : bbox.width + node.padding;
|
||||||
@@ -1154,9 +1155,6 @@ export const insertNode = async (elem, node, dir) => {
|
|||||||
if (node.class) {
|
if (node.class) {
|
||||||
el.attr('class', 'node default ' + 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;
|
nodeElems[node.id] = newEl;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ const config: RequiredDeep<MermaidConfig> = {
|
|||||||
// Set, even though they're `undefined` so that `configKeys` finds these keys
|
// 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?
|
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
||||||
deterministicIDSeed: undefined,
|
deterministicIDSeed: undefined,
|
||||||
|
elk: {
|
||||||
|
mergeEdges: false,
|
||||||
|
nodePlacementStrategy: 'SIMPLE',
|
||||||
|
},
|
||||||
themeCSS: undefined,
|
themeCSS: undefined,
|
||||||
|
|
||||||
// add non-JSON default config values
|
// add non-JSON default config values
|
||||||
|
|||||||
@@ -8,13 +8,6 @@ import type { BlockDB } from './blockDB.js';
|
|||||||
import { layout } from './layout.js';
|
import { layout } from './layout.js';
|
||||||
import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.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) {
|
export const getClasses = function (text: any, diagObj: any) {
|
||||||
return diagObj.db.getClasses();
|
return diagObj.db.getClasses();
|
||||||
};
|
};
|
||||||
@@ -45,8 +38,6 @@ export const draw = async function (
|
|||||||
const markers = ['point', 'circle', 'cross'];
|
const markers = ['point', 'circle', 'cross'];
|
||||||
|
|
||||||
// Add the marker definitions to the svg as marker tags
|
// 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);
|
insertMarkers(svg, markers, diagObj.type, id);
|
||||||
|
|
||||||
const bl = db.getBlocks();
|
const bl = db.getBlocks();
|
||||||
@@ -59,11 +50,7 @@ export const draw = async function (
|
|||||||
await insertBlocks(nodes, bl, db);
|
await insertBlocks(nodes, bl, db);
|
||||||
await insertEdges(nodes, edges, blArr, db, id);
|
await insertEdges(nodes, edges, blArr, db, id);
|
||||||
|
|
||||||
// log.debug('Here', bl);
|
|
||||||
|
|
||||||
// Establish svg dimensions and get width and height
|
// Establish svg dimensions and get width and height
|
||||||
//
|
|
||||||
// const bounds2 = nodes.node().getBoundingClientRect();
|
|
||||||
// Why, oh why ????
|
// Why, oh why ????
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
const bounds2 = bounds;
|
const bounds2 = bounds;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import utils from '../../utils.js';
|
import utils, { getEdgeId } from '../../utils.js';
|
||||||
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
|
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
import common from '../common/common.js';
|
import common from '../common/common.js';
|
||||||
|
import type { Node, Edge } from '../../rendering-util/types.js';
|
||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import {
|
import {
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
@@ -200,10 +201,17 @@ export const updateLink = function (positions: ('default' | number)[], style: st
|
|||||||
if (pos === 'default') {
|
if (pos === 'default') {
|
||||||
edges.defaultStyle = style;
|
edges.defaultStyle = style;
|
||||||
} else {
|
} else {
|
||||||
if (utils.isSubstringInArray('fill', style) === -1) {
|
// if (utils.isSubstringInArray('fill', style) === -1) {
|
||||||
style.push('fill:none');
|
// style.push('fill:none');
|
||||||
}
|
// }
|
||||||
edges[pos].style = style;
|
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) {
|
if (style !== undefined && style !== null) {
|
||||||
style.forEach(function (s) {
|
style.forEach(function (s) {
|
||||||
if (/color/.exec(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.textStyles.push(newStyle);
|
||||||
}
|
}
|
||||||
classNode.styles.push(s);
|
classNode.styles.push(s);
|
||||||
@@ -728,14 +736,12 @@ export const destructLink = (_str: string, _startStr: string) => {
|
|||||||
|
|
||||||
// Todo optimizer this by caching existing nodes
|
// Todo optimizer this by caching existing nodes
|
||||||
const exists = (allSgs: FlowSubGraph[], _id: string) => {
|
const exists = (allSgs: FlowSubGraph[], _id: string) => {
|
||||||
let res = false;
|
for (const sg of allSgs) {
|
||||||
allSgs.forEach((sg) => {
|
if (sg.nodes.includes(_id)) {
|
||||||
const pos = sg.nodes.indexOf(_id);
|
return true;
|
||||||
if (pos >= 0) {
|
|
||||||
res = true;
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return res;
|
return false;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Deletes an id from all subgraphs
|
* Deletes an id from all subgraphs
|
||||||
@@ -755,11 +761,174 @@ export const lex = {
|
|||||||
firstGraph,
|
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<string, string>,
|
||||||
|
subGraphDB: Map<string, boolean>,
|
||||||
|
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<string, string>();
|
||||||
|
const subGraphDB = new Map<string, boolean>();
|
||||||
|
|
||||||
|
// 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 {
|
export default {
|
||||||
defaultConfig: () => defaultConfig.flowchart,
|
defaultConfig: () => defaultConfig.flowchart,
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
getAccTitle,
|
getAccTitle,
|
||||||
getAccDescription,
|
getAccDescription,
|
||||||
|
getData,
|
||||||
setAccDescription,
|
setAccDescription,
|
||||||
addVertex,
|
addVertex,
|
||||||
lookUpDomId,
|
lookUpDomId,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// @ts-ignore: JISON doesn't support types
|
// @ts-ignore: JISON doesn't support types
|
||||||
import flowParser from './parser/flow.jison';
|
import flowParser from './parser/flow.jison';
|
||||||
import flowDb from './flowDb.js';
|
import flowDb from './flowDb.js';
|
||||||
import flowRendererV2 from './flowRenderer-v2.js';
|
import renderer from './flowRenderer-v3-unified.js';
|
||||||
import flowStyles from './styles.js';
|
import flowStyles from './styles.js';
|
||||||
import type { MermaidConfig } from '../../config.type.js';
|
import type { MermaidConfig } from '../../config.type.js';
|
||||||
import { setConfig } from '../../diagram-api/diagramAPI.js';
|
import { setConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
@@ -9,16 +9,14 @@ import { setConfig } from '../../diagram-api/diagramAPI.js';
|
|||||||
export const diagram = {
|
export const diagram = {
|
||||||
parser: flowParser,
|
parser: flowParser,
|
||||||
db: flowDb,
|
db: flowDb,
|
||||||
renderer: flowRendererV2,
|
renderer,
|
||||||
styles: flowStyles,
|
styles: flowStyles,
|
||||||
init: (cnf: MermaidConfig) => {
|
init: (cnf: MermaidConfig) => {
|
||||||
if (!cnf.flowchart) {
|
if (!cnf.flowchart) {
|
||||||
cnf.flowchart = {};
|
cnf.flowchart = {};
|
||||||
}
|
}
|
||||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||||
// flowchart-v2 uses dagre-wrapper, which doesn't have access to flowchart cnf
|
|
||||||
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
|
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
|
||||||
flowRendererV2.setConf(cnf.flowchart);
|
|
||||||
flowDb.clear();
|
flowDb.clear();
|
||||||
flowDb.setGen('gen-2');
|
flowDb.setGen('gen-2');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
// @ts-ignore: JISON doesn't support types
|
// @ts-ignore: JISON doesn't support types
|
||||||
import flowParser from './parser/flow.jison';
|
import flowParser from './parser/flow.jison';
|
||||||
import flowDb from './flowDb.js';
|
import flowDb from './flowDb.js';
|
||||||
import flowRenderer from './flowRenderer.js';
|
import renderer from './flowRenderer-v3-unified.js';
|
||||||
import flowRendererV2 from './flowRenderer-v2.js';
|
|
||||||
import flowStyles from './styles.js';
|
import flowStyles from './styles.js';
|
||||||
import type { MermaidConfig } from '../../config.type.js';
|
import type { MermaidConfig } from '../../config.type.js';
|
||||||
|
import { setConfig } from '../../diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
export const diagram = {
|
export const diagram = {
|
||||||
parser: flowParser,
|
parser: flowParser,
|
||||||
db: flowDb,
|
db: flowDb,
|
||||||
renderer: flowRendererV2,
|
renderer,
|
||||||
styles: flowStyles,
|
styles: flowStyles,
|
||||||
init: (cnf: MermaidConfig) => {
|
init: (cnf: MermaidConfig) => {
|
||||||
if (!cnf.flowchart) {
|
if (!cnf.flowchart) {
|
||||||
cnf.flowchart = {};
|
cnf.flowchart = {};
|
||||||
}
|
}
|
||||||
// TODO, broken as of 2022-09-14 (13809b50251845475e6dca65cc395761be38fbd2)
|
|
||||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||||
flowRenderer.setConf(cnf.flowchart);
|
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
|
||||||
flowDb.clear();
|
flowDb.clear();
|
||||||
flowDb.setGen('gen-1');
|
flowDb.setGen('gen-2');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} 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,
|
|
||||||
};
|
|
||||||
@@ -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<string, DiagramStyleClassDef> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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 = `<span id="L-${linkId}" class="edgeLabel L-${linkNameStart}' L-${linkNameEnd}" style="${
|
|
||||||
edgeData.labelStyle
|
|
||||||
}">${await renderKatex(replaceIconSubstring(edge.text), getConfig())}</span>`;
|
|
||||||
} else {
|
|
||||||
edgeData.labelType = 'text';
|
|
||||||
edgeData.label = edge.text.replace(common.lineBreakRegex, '\n');
|
|
||||||
|
|
||||||
if (edge.style === undefined) {
|
|
||||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} 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,
|
|
||||||
};
|
|
||||||
@@ -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<br>Line', 'Multi<br/>Line', 'Multi<br />Line', 'Multi<br\t/>Line'].forEach(
|
|
||||||
function (labelText) {
|
|
||||||
it('should handle multiline texts with different line breaks', async function () {
|
|
||||||
const addedNodes = [];
|
|
||||||
const fakeDiag = {
|
|
||||||
db: {
|
|
||||||
lookUpDomId: () => {
|
|
||||||
return 'my-node-id';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mockG = {
|
|
||||||
setNode: function (id, object) {
|
|
||||||
addedNodes.push([id, object]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await addVertices(
|
|
||||||
{
|
|
||||||
v1: {
|
|
||||||
type: 'rect',
|
|
||||||
id: 'my-node-id',
|
|
||||||
classes: [],
|
|
||||||
styles: [],
|
|
||||||
text: 'Multi<br>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(); // <text> node
|
|
||||||
expect(addedNodes[0][1].label.firstChild.innerHTML).toEqual('Multi'); // <tspan> node, line 1
|
|
||||||
expect(addedNodes[0][1].label.lastChild.innerHTML).toEqual('Line'); // <tspan> 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<br>Line' },
|
|
||||||
{ text: 'Multi<br/>Line' },
|
|
||||||
{ text: 'Multi<br />Line' },
|
|
||||||
{ text: 'Multi<br\t/>Line' },
|
|
||||||
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br>Line' },
|
|
||||||
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br/>Line' },
|
|
||||||
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br />Line' },
|
|
||||||
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br\t/>Line' },
|
|
||||||
],
|
|
||||||
mockG,
|
|
||||||
fakeDiag
|
|
||||||
);
|
|
||||||
|
|
||||||
addedEdges.forEach(function (edge) {
|
|
||||||
expect(edge).toHaveProperty('label', 'Multi\nLine');
|
|
||||||
expect(edge).toHaveProperty('labelpos', 'c');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
[['stroke:DarkGray'], 'stroke:DarkGray;', ''],
|
|
||||||
[['color:red'], '', 'fill:red;'],
|
|
||||||
[['stroke:DarkGray', 'color:red'], 'stroke:DarkGray;', 'fill:red;'],
|
|
||||||
[
|
|
||||||
['stroke:DarkGray', 'color:red', 'stroke-width:2px'],
|
|
||||||
'stroke:DarkGray;stroke-width:2px;',
|
|
||||||
'fill:red;',
|
|
||||||
],
|
|
||||||
].forEach(function ([style, expectedStyle, expectedLabelStyle]) {
|
|
||||||
it(`should add the styles to style and/or labelStyle for style ${style}`, async function () {
|
|
||||||
const addedEdges = [];
|
|
||||||
const fakeDiag = {
|
|
||||||
db: {
|
|
||||||
lookUpDomId: () => {
|
|
||||||
return 'my-node-id';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mockG = {
|
|
||||||
setEdge: function (s, e, data, c) {
|
|
||||||
addedEdges.push(data);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await addEdges([{ style: style, text: 'styling' }], mockG, fakeDiag);
|
|
||||||
|
|
||||||
expect(addedEdges).toHaveLength(1);
|
|
||||||
expect(addedEdges[0]).toHaveProperty('style', expectedStyle);
|
|
||||||
expect(addedEdges[0]).toHaveProperty('labelStyle', expectedLabelStyle);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -38,11 +38,14 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
.cluster-label text {
|
.cluster-label text {
|
||||||
fill: ${options.titleColor};
|
fill: ${options.titleColor};
|
||||||
}
|
}
|
||||||
.cluster-label span,p {
|
.cluster-label span {
|
||||||
color: ${options.titleColor};
|
color: ${options.titleColor};
|
||||||
}
|
}
|
||||||
|
.cluster-label span p {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.label text,span,p {
|
.label text,span {
|
||||||
fill: ${options.nodeTextColor || options.textColor};
|
fill: ${options.nodeTextColor || options.textColor};
|
||||||
color: ${options.nodeTextColor || options.textColor};
|
color: ${options.nodeTextColor || options.textColor};
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
stroke: ${options.nodeBorder};
|
stroke: ${options.nodeBorder};
|
||||||
stroke-width: 1px;
|
stroke-width: 1px;
|
||||||
}
|
}
|
||||||
.flowchart-label text {
|
.rough-node .label text , .node .label text {
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
}
|
}
|
||||||
// .flowchart-label .text-outer-tspan {
|
// .flowchart-label .text-outer-tspan {
|
||||||
@@ -95,6 +98,9 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
|
|
||||||
.edgeLabel {
|
.edgeLabel {
|
||||||
background-color: ${options.edgeLabelBackground};
|
background-color: ${options.edgeLabelBackground};
|
||||||
|
p {
|
||||||
|
background-color: ${options.edgeLabelBackground};
|
||||||
|
}
|
||||||
rect {
|
rect {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
background-color: ${options.edgeLabelBackground};
|
background-color: ${options.edgeLabelBackground};
|
||||||
@@ -106,7 +112,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
/* For html labels only */
|
/* For html labels only */
|
||||||
.labelBkg {
|
.labelBkg {
|
||||||
background-color: ${fade(options.edgeLabelBackground, 0.5)};
|
background-color: ${fade(options.edgeLabelBackground, 0.5)};
|
||||||
// background-color:
|
// background-color:
|
||||||
}
|
}
|
||||||
|
|
||||||
.cluster rect {
|
.cluster rect {
|
||||||
@@ -119,7 +125,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
|||||||
fill: ${options.titleColor};
|
fill: ${options.titleColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
.cluster span,p {
|
.cluster span {
|
||||||
color: ${options.titleColor};
|
color: ${options.titleColor};
|
||||||
}
|
}
|
||||||
/* .cluster div {
|
/* .cluster div {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export interface FlowEdge {
|
|||||||
end: string;
|
end: string;
|
||||||
interpolate?: string;
|
interpolate?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
stroke?: string;
|
stroke?: 'normal' | 'thick' | 'invisible' | 'dotted';
|
||||||
style?: string[];
|
style?: string[];
|
||||||
length?: number;
|
length?: number;
|
||||||
text: string;
|
text: string;
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import {
|
|||||||
getDiagramTitle,
|
getDiagramTitle,
|
||||||
} from '../common/commonDb.js';
|
} from '../common/commonDb.js';
|
||||||
|
|
||||||
let mainBranchName = getConfig().gitGraph.mainBranchName;
|
let { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
||||||
let mainBranchOrder = getConfig().gitGraph.mainBranchOrder;
|
|
||||||
let commits = new Map();
|
let commits = new Map();
|
||||||
let head = null;
|
let head = null;
|
||||||
let branchesConfig = new Map();
|
let branchesConfig = new Map();
|
||||||
@@ -103,17 +102,18 @@ export const getOptions = function () {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const commit = function (msg, id, type, tag) {
|
export const commit = function (msg, id, type, tags) {
|
||||||
log.debug('Entering commit:', msg, id, type, tag);
|
log.debug('Entering commit:', msg, id, type, tags);
|
||||||
id = common.sanitizeText(id, getConfig());
|
const config = getConfig();
|
||||||
msg = common.sanitizeText(msg, getConfig());
|
id = common.sanitizeText(id, config);
|
||||||
tag = common.sanitizeText(tag, getConfig());
|
msg = common.sanitizeText(msg, config);
|
||||||
|
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||||
const commit = {
|
const commit = {
|
||||||
id: id ? id : seq + '-' + getId(),
|
id: id ? id : seq + '-' + getId(),
|
||||||
message: msg,
|
message: msg,
|
||||||
seq: seq++,
|
seq: seq++,
|
||||||
type: type ? type : commitType.NORMAL,
|
type: type ? type : commitType.NORMAL,
|
||||||
tag: tag ? tag : '',
|
tags: tags ?? [],
|
||||||
parents: head == null ? [] : [head.id],
|
parents: head == null ? [] : [head.id],
|
||||||
branch: curBranch,
|
branch: curBranch,
|
||||||
};
|
};
|
||||||
@@ -147,9 +147,10 @@ export const branch = function (name, order) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const merge = function (otherBranch, custom_id, override_type, custom_tag) {
|
export const merge = function (otherBranch, custom_id, override_type, custom_tags) {
|
||||||
otherBranch = common.sanitizeText(otherBranch, getConfig());
|
const config = getConfig();
|
||||||
custom_id = common.sanitizeText(custom_id, getConfig());
|
otherBranch = common.sanitizeText(otherBranch, config);
|
||||||
|
custom_id = common.sanitizeText(custom_id, config);
|
||||||
|
|
||||||
const currentCommit = commits.get(branches.get(curBranch));
|
const currentCommit = commits.get(branches.get(curBranch));
|
||||||
const otherCommit = commits.get(branches.get(otherBranch));
|
const otherCommit = commits.get(branches.get(otherBranch));
|
||||||
@@ -216,12 +217,12 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
' already exists, use different custom Id'
|
' already exists, use different custom Id'
|
||||||
);
|
);
|
||||||
error.hash = {
|
error.hash = {
|
||||||
text: 'merge ' + otherBranch + custom_id + override_type + custom_tag,
|
text: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
||||||
token: 'merge ' + otherBranch + custom_id + override_type + custom_tag,
|
token: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
||||||
line: '1',
|
line: '1',
|
||||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||||
expected: [
|
expected: [
|
||||||
'merge ' + otherBranch + ' ' + custom_id + '_UNIQUE ' + override_type + ' ' + custom_tag,
|
`merge ${otherBranch} ${custom_id}_UNIQUE ${override_type} ${custom_tags?.join(',')}`,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,7 +246,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
type: commitType.MERGE,
|
type: commitType.MERGE,
|
||||||
customType: override_type,
|
customType: override_type,
|
||||||
customId: custom_id ? true : false,
|
customId: custom_id ? true : false,
|
||||||
tag: custom_tag ? custom_tag : '',
|
tags: custom_tags ? custom_tags : [],
|
||||||
};
|
};
|
||||||
head = commit;
|
head = commit;
|
||||||
commits.set(commit.id, commit);
|
commits.set(commit.id, commit);
|
||||||
@@ -255,12 +256,13 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
|
|||||||
log.debug('in mergeBranch');
|
log.debug('in mergeBranch');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cherryPick = function (sourceId, targetId, tag, parentCommitId) {
|
export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
||||||
log.debug('Entering cherryPick:', sourceId, targetId, tag);
|
log.debug('Entering cherryPick:', sourceId, targetId, tags);
|
||||||
sourceId = common.sanitizeText(sourceId, getConfig());
|
const config = getConfig();
|
||||||
targetId = common.sanitizeText(targetId, getConfig());
|
sourceId = common.sanitizeText(sourceId, config);
|
||||||
tag = common.sanitizeText(tag, getConfig());
|
targetId = common.sanitizeText(targetId, config);
|
||||||
parentCommitId = common.sanitizeText(parentCommitId, getConfig());
|
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||||
|
parentCommitId = common.sanitizeText(parentCommitId, config);
|
||||||
|
|
||||||
if (!sourceId || !commits.has(sourceId)) {
|
if (!sourceId || !commits.has(sourceId)) {
|
||||||
let error = new Error(
|
let error = new Error(
|
||||||
@@ -329,11 +331,13 @@ export const cherryPick = function (sourceId, targetId, tag, parentCommitId) {
|
|||||||
parents: [head == null ? null : head.id, sourceCommit.id],
|
parents: [head == null ? null : head.id, sourceCommit.id],
|
||||||
branch: curBranch,
|
branch: curBranch,
|
||||||
type: commitType.CHERRY_PICK,
|
type: commitType.CHERRY_PICK,
|
||||||
tag:
|
tags: tags
|
||||||
tag ??
|
? tags.filter(Boolean)
|
||||||
`cherry-pick:${sourceCommit.id}${
|
: [
|
||||||
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
`cherry-pick:${sourceCommit.id}${
|
||||||
}`,
|
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
||||||
|
}`,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
head = commit;
|
head = commit;
|
||||||
commits.set(commit.id, commit);
|
commits.set(commit.id, commit);
|
||||||
@@ -356,8 +360,6 @@ export const checkout = function (branch) {
|
|||||||
expected: ['"branch ' + branch + '"'],
|
expected: ['"branch ' + branch + '"'],
|
||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
//branches[branch] = head != null ? head.id : null;
|
|
||||||
//log.debug('in createBranch');
|
|
||||||
} else {
|
} else {
|
||||||
curBranch = branch;
|
curBranch = branch;
|
||||||
const id = branches.get(curBranch);
|
const id = branches.get(curBranch);
|
||||||
@@ -444,13 +446,12 @@ export const prettyPrint = function () {
|
|||||||
export const clear = function () {
|
export const clear = function () {
|
||||||
commits = new Map();
|
commits = new Map();
|
||||||
head = null;
|
head = null;
|
||||||
let mainBranch = getConfig().gitGraph.mainBranchName;
|
const { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
||||||
let mainBranchOrder = getConfig().gitGraph.mainBranchOrder;
|
|
||||||
branches = new Map();
|
branches = new Map();
|
||||||
branches.set(mainBranch, null);
|
branches.set(mainBranchName, null);
|
||||||
branchesConfig = new Map();
|
branchesConfig = new Map();
|
||||||
branchesConfig.set(mainBranch, { name: mainBranch, order: mainBranchOrder });
|
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
||||||
curBranch = mainBranch;
|
curBranch = mainBranchName;
|
||||||
seq = 0;
|
seq = 0;
|
||||||
commonClear();
|
commonClear();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('test');
|
expect(commits.get(key).tags).toStrictEqual(['test']);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(2);
|
expect(commits.get(key).type).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test commit');
|
expect(commits.get(key).message).toBe('test commit');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test commit');
|
expect(commits.get(key).message).toBe('test commit');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('');
|
expect(commits.get(key).tags).toStrictEqual([]);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(0);
|
expect(commits.get(key).type).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(2);
|
expect(commits.get(key).type).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).not.toBeNull();
|
expect(commits.get(key).id).not.toBeNull();
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(2);
|
expect(commits.get(key).type).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('');
|
expect(commits.get(key).message).toBe('');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test msg');
|
expect(commits.get(key).message).toBe('test msg');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test msg');
|
expect(commits.get(key).message).toBe('test msg');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test msg');
|
expect(commits.get(key).message).toBe('test msg');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const key = commits.keys().next().value;
|
const key = commits.keys().next().value;
|
||||||
expect(commits.get(key).message).toBe('test msg');
|
expect(commits.get(key).message).toBe('test msg');
|
||||||
expect(commits.get(key).id).toBe('1111');
|
expect(commits.get(key).id).toBe('1111');
|
||||||
expect(commits.get(key).tag).toBe('test tag');
|
expect(commits.get(key).tags).toStrictEqual(['test tag']);
|
||||||
expect(commits.get(key).type).toBe(1);
|
expect(commits.get(key).type).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -616,7 +616,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
commits.get(commit1).id,
|
commits.get(commit1).id,
|
||||||
commits.get(commit2).id,
|
commits.get(commit2).id,
|
||||||
]);
|
]);
|
||||||
expect(commits.get(commit3).tag).toBe('merge-tag');
|
expect(commits.get(commit3).tags).toStrictEqual(['merge-tag']);
|
||||||
expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
|
expect(parser.yy.getBranchesAsObjArray()).toStrictEqual([
|
||||||
{ name: 'main' },
|
{ name: 'main' },
|
||||||
{ name: 'testBranch' },
|
{ name: 'testBranch' },
|
||||||
@@ -671,12 +671,12 @@ describe('when parsing a gitGraph', function () {
|
|||||||
|
|
||||||
expect(testBranchMerge.branch).toBe('main');
|
expect(testBranchMerge.branch).toBe('main');
|
||||||
expect(testBranchMerge.parents).toStrictEqual([mainCommit.id, testBranchCommit.id]);
|
expect(testBranchMerge.parents).toStrictEqual([mainCommit.id, testBranchCommit.id]);
|
||||||
expect(testBranchMerge.tag).toBe('merge-tag');
|
expect(testBranchMerge.tags).toStrictEqual(['merge-tag']);
|
||||||
expect(testBranchMerge.id).toBe('2-222');
|
expect(testBranchMerge.id).toBe('2-222');
|
||||||
|
|
||||||
expect(testBranch2Merge.branch).toBe('main');
|
expect(testBranch2Merge.branch).toBe('main');
|
||||||
expect(testBranch2Merge.parents).toStrictEqual([testBranchMerge.id, testBranch2Commit.id]);
|
expect(testBranch2Merge.parents).toStrictEqual([testBranchMerge.id, testBranch2Commit.id]);
|
||||||
expect(testBranch2Merge.tag).toBe('merge-tag2');
|
expect(testBranch2Merge.tags).toStrictEqual(['merge-tag2']);
|
||||||
expect(testBranch2Merge.id).toBe('4-444');
|
expect(testBranch2Merge.id).toBe('4-444');
|
||||||
expect(testBranch2Merge.customType).toBe(2);
|
expect(testBranch2Merge.customType).toBe(2);
|
||||||
expect(testBranch2Merge.customId).toBe(true);
|
expect(testBranch2Merge.customId).toBe(true);
|
||||||
@@ -705,7 +705,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][2];
|
const cherryPickCommitID = [...commits.keys()][2];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('cherry-pick:A');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['cherry-pick:A']);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -721,7 +721,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][2];
|
const cherryPickCommitID = [...commits.keys()][2];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('MyTag');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['MyTag']);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -737,7 +737,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][2];
|
const cherryPickCommitID = [...commits.keys()][2];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual([]);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
expect(commits.get(cherryPickCommitID).branch).toBe('main');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][4];
|
const cherryPickCommitID = [...commits.keys()][4];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('cherry-pick:M|parent:B');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['cherry-pick:M|parent:B']);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -779,7 +779,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][4];
|
const cherryPickCommitID = [...commits.keys()][4];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('v1.0');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['v1.0']);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -802,7 +802,7 @@ describe('when parsing a gitGraph', function () {
|
|||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][5];
|
const cherryPickCommitID = [...commits.keys()][5];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('v2.1:ZERO');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual(['v2.1:ZERO']);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -827,8 +827,8 @@ describe('when parsing a gitGraph', function () {
|
|||||||
const commits = parser.yy.getCommits();
|
const commits = parser.yy.getCommits();
|
||||||
const cherryPickCommitID = [...commits.keys()][5];
|
const cherryPickCommitID = [...commits.keys()][5];
|
||||||
const cherryPickCommitID2 = [...commits.keys()][7];
|
const cherryPickCommitID2 = [...commits.keys()][7];
|
||||||
expect(commits.get(cherryPickCommitID).tag).toBe('');
|
expect(commits.get(cherryPickCommitID).tags).toStrictEqual([]);
|
||||||
expect(commits.get(cherryPickCommitID2).tag).toBe('');
|
expect(commits.get(cherryPickCommitID2).tags).toStrictEqual([]);
|
||||||
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
expect(commits.get(cherryPickCommitID).branch).toBe('release');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -414,59 +414,83 @@ const drawCommits = (svg, commits, modifyGraph) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (commit.tag) {
|
if (commit.tags.length > 0) {
|
||||||
const rect = gLabels.insert('polygon');
|
let yOffset = 0;
|
||||||
const hole = gLabels.append('circle');
|
let maxTagBboxWidth = 0;
|
||||||
const tag = gLabels
|
let maxTagBboxHeight = 0;
|
||||||
.append('text')
|
const tagElements = [];
|
||||||
// Note that we are delaying setting the x position until we know the width of the text
|
|
||||||
.attr('y', y - 16)
|
|
||||||
.attr('class', 'tag-label')
|
|
||||||
.text(commit.tag);
|
|
||||||
let tagBbox = tag.node().getBBox();
|
|
||||||
tag.attr('x', posWithOffset - tagBbox.width / 2);
|
|
||||||
|
|
||||||
const h2 = tagBbox.height / 2;
|
for (const tagValue of commit.tags.reverse()) {
|
||||||
const ly = y - 19.2;
|
const rect = gLabels.insert('polygon');
|
||||||
rect.attr('class', 'tag-label-bkg').attr(
|
const hole = gLabels.append('circle');
|
||||||
'points',
|
const tag = gLabels
|
||||||
`
|
.append('text')
|
||||||
${pos - tagBbox.width / 2 - px / 2},${ly + py}
|
// Note that we are delaying setting the x position until we know the width of the text
|
||||||
${pos - tagBbox.width / 2 - px / 2},${ly - py}
|
.attr('y', y - 16 - yOffset)
|
||||||
${posWithOffset - tagBbox.width / 2 - px},${ly - h2 - py}
|
.attr('class', 'tag-label')
|
||||||
${posWithOffset + tagBbox.width / 2 + px},${ly - h2 - py}
|
.text(tagValue);
|
||||||
${posWithOffset + tagBbox.width / 2 + px},${ly + h2 + py}
|
let tagBbox = tag.node().getBBox();
|
||||||
${posWithOffset - tagBbox.width / 2 - px},${ly + h2 + py}`
|
maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width);
|
||||||
);
|
maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height);
|
||||||
|
|
||||||
hole
|
// We don't use the max over here to center the text within the tags
|
||||||
.attr('cx', pos - tagBbox.width / 2 + px / 2)
|
tag.attr('x', posWithOffset - tagBbox.width / 2);
|
||||||
.attr('cy', ly)
|
|
||||||
.attr('r', 1.5)
|
tagElements.push({
|
||||||
.attr('class', 'tag-hole');
|
tag,
|
||||||
|
hole,
|
||||||
|
rect,
|
||||||
|
yOffset,
|
||||||
|
});
|
||||||
|
|
||||||
|
yOffset += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { tag, hole, rect, yOffset } of tagElements) {
|
||||||
|
const h2 = maxTagBboxHeight / 2;
|
||||||
|
const ly = y - 19.2 - yOffset;
|
||||||
|
rect.attr('class', 'tag-label-bkg').attr(
|
||||||
|
'points',
|
||||||
|
`
|
||||||
|
${pos - maxTagBboxWidth / 2 - px / 2},${ly + py}
|
||||||
|
${pos - maxTagBboxWidth / 2 - px / 2},${ly - py}
|
||||||
|
${posWithOffset - maxTagBboxWidth / 2 - px},${ly - h2 - py}
|
||||||
|
${posWithOffset + maxTagBboxWidth / 2 + px},${ly - h2 - py}
|
||||||
|
${posWithOffset + maxTagBboxWidth / 2 + px},${ly + h2 + py}
|
||||||
|
${posWithOffset - maxTagBboxWidth / 2 - px},${ly + h2 + py}`
|
||||||
|
);
|
||||||
|
|
||||||
if (dir === 'TB' || dir === 'BT') {
|
|
||||||
rect
|
|
||||||
.attr('class', 'tag-label-bkg')
|
|
||||||
.attr(
|
|
||||||
'points',
|
|
||||||
`
|
|
||||||
${x},${pos + py}
|
|
||||||
${x},${pos - py}
|
|
||||||
${x + layoutOffset},${pos - h2 - py}
|
|
||||||
${x + layoutOffset + tagBbox.width + px},${pos - h2 - py}
|
|
||||||
${x + layoutOffset + tagBbox.width + px},${pos + h2 + py}
|
|
||||||
${x + layoutOffset},${pos + h2 + py}`
|
|
||||||
)
|
|
||||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
|
||||||
hole
|
hole
|
||||||
.attr('cx', x + px / 2)
|
.attr('cy', ly)
|
||||||
.attr('cy', pos)
|
.attr('cx', pos - maxTagBboxWidth / 2 + px / 2)
|
||||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
.attr('r', 1.5)
|
||||||
tag
|
.attr('class', 'tag-hole');
|
||||||
.attr('x', x + 5)
|
|
||||||
.attr('y', pos + 3)
|
if (dir === 'TB' || dir === 'BT') {
|
||||||
.attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')');
|
const yOrigin = pos + yOffset;
|
||||||
|
|
||||||
|
rect
|
||||||
|
.attr('class', 'tag-label-bkg')
|
||||||
|
.attr(
|
||||||
|
'points',
|
||||||
|
`
|
||||||
|
${x},${yOrigin + py}
|
||||||
|
${x},${yOrigin - py}
|
||||||
|
${x + layoutOffset},${yOrigin - h2 - py}
|
||||||
|
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin - h2 - py}
|
||||||
|
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin + h2 + py}
|
||||||
|
${x + layoutOffset},${yOrigin + h2 + py}`
|
||||||
|
)
|
||||||
|
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
||||||
|
hole
|
||||||
|
.attr('cx', x + px / 2)
|
||||||
|
.attr('cy', yOrigin)
|
||||||
|
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
||||||
|
tag
|
||||||
|
.attr('x', x + 5)
|
||||||
|
.attr('y', yOrigin + 3)
|
||||||
|
.attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,122 +112,105 @@ branchStatement
|
|||||||
cherryPickStatement
|
cherryPickStatement
|
||||||
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
|
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
|
||||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
|
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
|
||||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR {yy.cherryPick($3, '', $5)}
|
| CHERRY_PICK COMMIT_ID STR commitTags {yy.cherryPick($3, '', $4)}
|
||||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG STR {yy.cherryPick($3, '', $7,$5)}
|
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR commitTags {yy.cherryPick($3, '', $6,$5)}
|
||||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR PARENT_COMMIT STR {yy.cherryPick($3, '', $5,$7)}
|
| CHERRY_PICK COMMIT_ID STR commitTags PARENT_COMMIT STR {yy.cherryPick($3, '', $4,$6)}
|
||||||
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR {yy.cherryPick($5, '', $3)}
|
| CHERRY_PICK commitTags COMMIT_ID STR {yy.cherryPick($4, '', $2)}
|
||||||
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR {yy.cherryPick($5, '', '')}
|
| CHERRY_PICK commitTags COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($4, '', $2,$6)}
|
||||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '')}
|
|
||||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '',$5)}
|
|
||||||
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR PARENT_COMMIT STR {yy.cherryPick($3, '', '',$7)}
|
|
||||||
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($5, '', $3,$7)}
|
|
||||||
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR PARENT_COMMIT STR{yy.cherryPick($5, '', '',$7)}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
mergeStatement
|
mergeStatement
|
||||||
: MERGE ref {yy.merge($2,'','','')}
|
: MERGE ref {yy.merge($2,'','', undefined)}
|
||||||
| MERGE ref COMMIT_ID STR {yy.merge($2, $4,'','')}
|
| MERGE ref COMMIT_ID STR {yy.merge($2, $4,'', undefined)}
|
||||||
| MERGE ref COMMIT_TYPE commitType {yy.merge($2,'', $4,'')}
|
| MERGE ref COMMIT_TYPE commitType {yy.merge($2,'', $4, undefined)}
|
||||||
| MERGE ref COMMIT_TAG STR {yy.merge($2, '','',$4)}
|
| MERGE ref commitTags {yy.merge($2, '','',$3)}
|
||||||
| MERGE ref COMMIT_TAG STR COMMIT_ID STR {yy.merge($2, $6,'', $4)}
|
| MERGE ref commitTags COMMIT_ID STR {yy.merge($2, $5,'', $3)}
|
||||||
| MERGE ref COMMIT_TAG STR COMMIT_TYPE commitType {yy.merge($2, '',$6, $4)}
|
| MERGE ref commitTags COMMIT_TYPE commitType {yy.merge($2, '',$5, $3)}
|
||||||
| MERGE ref COMMIT_TYPE commitType COMMIT_TAG STR {yy.merge($2, '',$4, $6)}
|
| MERGE ref COMMIT_TYPE commitType commitTags {yy.merge($2, '',$4, $5)}
|
||||||
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $4, $6, '')}
|
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $4, $6, undefined)}
|
||||||
| MERGE ref COMMIT_ID STR COMMIT_TAG STR {yy.merge($2, $4, '', $6)}
|
| MERGE ref COMMIT_ID STR commitTags {yy.merge($2, $4, '', $5)}
|
||||||
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $6,$4, '')}
|
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $6,$4, undefined)}
|
||||||
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.merge($2, $4, $6, $8)}
|
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.merge($2, $4, $6, $7)}
|
||||||
| MERGE ref COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_ID STR {yy.merge($2, $8, $4, $6)}
|
| MERGE ref COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.merge($2, $7, $4, $5)}
|
||||||
| MERGE ref COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.merge($2, $4, $8, $6)}
|
| MERGE ref COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.merge($2, $4, $7, $5)}
|
||||||
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR COMMIT_TAG STR {yy.merge($2, $6, $4, $8)}
|
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.merge($2, $6, $4, $7)}
|
||||||
| MERGE ref COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $8, $6, $4)}
|
| MERGE ref commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $7, $5, $3)}
|
||||||
| MERGE ref COMMIT_TAG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $6, $8, $4)}
|
| MERGE ref commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $5, $7, $3)}
|
||||||
;
|
;
|
||||||
|
|
||||||
commitStatement
|
commitStatement
|
||||||
: COMMIT commit_arg {yy.commit($2)}
|
: COMMIT commit_arg {yy.commit($2)}
|
||||||
| COMMIT COMMIT_TAG STR {yy.commit('','',yy.commitType.NORMAL,$3)}
|
| COMMIT commitTags {yy.commit('','',yy.commitType.NORMAL,$2)}
|
||||||
| COMMIT COMMIT_TYPE commitType {yy.commit('','',$3,'')}
|
| COMMIT COMMIT_TYPE commitType {yy.commit('','',$3, undefined)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit('','',$5,$3)}
|
| COMMIT commitTags COMMIT_TYPE commitType {yy.commit('','',$4,$2)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit('','',$3,$5)}
|
| COMMIT COMMIT_TYPE commitType commitTags {yy.commit('','',$3,$4)}
|
||||||
| COMMIT COMMIT_ID STR {yy.commit('',$3,yy.commitType.NORMAL,'')}
|
| COMMIT COMMIT_ID STR {yy.commit('',$3,yy.commitType.NORMAL, undefined)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TAG STR {yy.commit('',$3,yy.commitType.NORMAL,$5)}
|
| COMMIT COMMIT_ID STR commitTags {yy.commit('',$3,yy.commitType.NORMAL,$4)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_ID STR {yy.commit('',$5,yy.commitType.NORMAL,$3)}
|
| COMMIT commitTags COMMIT_ID STR {yy.commit('',$4,yy.commitType.NORMAL,$2)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$3,$5,'')}
|
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$3,$5, undefined)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$5,$3,'')}
|
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$5,$3, undefined)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit('',$3,$5,$7)}
|
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit('',$3,$5,$6)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit('',$3,$7,$5)}
|
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit('',$3,$6,$4)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_TAG STR {yy.commit('',$5,$3,$7)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit('',$5,$3,$6)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_ID STR {yy.commit('',$7,$3,$5)}
|
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit('',$6,$3,$4)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$7,$5,$3)}
|
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$6,$4,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$5,$7,$3)}
|
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$4,$6,$2)}
|
||||||
| COMMIT COMMIT_MSG STR {yy.commit($3,'',yy.commitType.NORMAL,'')}
|
| COMMIT COMMIT_MSG STR {yy.commit($3,'',yy.commitType.NORMAL, undefined)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_MSG STR {yy.commit($5,'',yy.commitType.NORMAL,$3)}
|
| COMMIT commitTags COMMIT_MSG STR {yy.commit($4,'',yy.commitType.NORMAL,$2)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TAG STR {yy.commit($3,'',yy.commitType.NORMAL,$5)}
|
| COMMIT COMMIT_MSG STR commitTags {yy.commit($3,'',yy.commitType.NORMAL,$4)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($3,'',$5,'')}
|
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($3,'',$5, undefined)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($5,'',$3,'')}
|
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($5,'',$3, undefined)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR {yy.commit($5,$3,yy.commitType.NORMAL,'')}
|
| COMMIT COMMIT_ID STR COMMIT_MSG STR {yy.commit($5,$3,yy.commitType.NORMAL, undefined)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR {yy.commit($3,$5,yy.commitType.NORMAL,'')}
|
| COMMIT COMMIT_MSG STR COMMIT_ID STR {yy.commit($3,$5,yy.commitType.NORMAL, undefined)}
|
||||||
|
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit($3,'',$5,$7)}
|
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($3,'',$5,$6)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit($3,'',$7,$5)}
|
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($3,'',$6,$4)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_TAG STR {yy.commit($5,'',$3,$7)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($5,'',$3,$6)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_MSG STR {yy.commit($7,'',$3,$5)}
|
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($6,'',$3,$4)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($7,'',$5,$3)}
|
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($6,'',$4,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($5,'',$7,$3)}
|
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($4,'',$6,$2)}
|
||||||
|
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$7,$5,'')}
|
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$7,$5, undefined)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$5,$7,'')}
|
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$5,$7, undefined)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($5,$7,$3,'')}
|
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($5,$7,$3, undefined)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($7,$5,$3,'')}
|
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($7,$5,$3, undefined)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($7,$3,$5,'')}
|
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($7,$3,$5, undefined)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($5,$3,$7,'')}
|
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($5,$3,$7, undefined)}
|
||||||
|
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TAG STR COMMIT_ID STR {yy.commit($3,$7,yy.commitType.NORMAL,$5)}
|
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($3,$6,yy.commitType.NORMAL,$4)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TAG STR {yy.commit($3,$5,yy.commitType.NORMAL,$7)}
|
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($3,$5,yy.commitType.NORMAL,$6)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_MSG STR COMMIT_ID STR {yy.commit($5,$7,yy.commitType.NORMAL,$3)}
|
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($4,$6,yy.commitType.NORMAL,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_ID STR COMMIT_MSG STR {yy.commit($7,$5,yy.commitType.NORMAL,$3)}
|
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($6,$4,yy.commitType.NORMAL,$2)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TAG STR COMMIT_MSG STR {yy.commit($7,$3,yy.commitType.NORMAL,$5)}
|
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($6,$3,yy.commitType.NORMAL,$4)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TAG STR {yy.commit($5,$3,yy.commitType.NORMAL,$7)}
|
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($5,$3,yy.commitType.NORMAL,$6)}
|
||||||
|
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit($3,$5,$7,$9)}
|
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit($3,$5,$7,$8)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit($3,$5,$9,$7)}
|
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit($3,$5,$8,$6)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR COMMIT_TAG STR {yy.commit($3,$7,$5,$9)}
|
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit($3,$7,$5,$8)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_ID STR {yy.commit($3,$9,$5,$7)}
|
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit($3,$8,$5,$6)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TAG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$7,$9,$5)}
|
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$6,$8,$4)}
|
||||||
| COMMIT COMMIT_MSG STR COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$9,$7,$5)}
|
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$8,$6,$4)}
|
||||||
|
|
||||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit($5,$3,$7,$9)}
|
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($5,$3,$7,$8)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit($5,$3,$9,$7)}
|
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($5,$3,$8,$6)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_TAG STR {yy.commit($7,$3,$5,$9)}
|
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($7,$3,$5,$8)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_MSG STR {yy.commit($9,$3,$5,$7)}
|
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($8,$3,$5,$6)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TAG STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($7,$3,$9,$5)}
|
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$3,$8,$4)}
|
||||||
| COMMIT COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($9,$3,$7,$5)}
|
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$3,$6,$4)}
|
||||||
|
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($9,$5,$7,$3)}
|
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$4,$6,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($7,$5,$9,$3)}
|
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$4,$8,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($9,$7,$5,$3)}
|
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$4,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($7,$9,$5,$3)}
|
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$4,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($5,$7,$9,$3)}
|
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($4,$6,$8,$2)}
|
||||||
| COMMIT COMMIT_TAG STR COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($5,$9,$7,$3)}
|
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($4,$8,$6,$2)}
|
||||||
|
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR COMMIT_TAG STR {yy.commit($7,$5,$3,$9)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($7,$5,$3,$8)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_TAG STR COMMIT_MSG STR {yy.commit($9,$5,$3,$7)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($8,$5,$3,$6)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_MSG STR COMMIT_ID STR {yy.commit($7,$9,$3,$5)}
|
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$3,$4)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_TAG STR COMMIT_ID STR COMMIT_MSG STR {yy.commit($9,$7,$3,$5)}
|
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$3,$4)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR COMMIT_TAG STR {yy.commit($5,$7,$3,$9)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($5,$7,$3,$8)}
|
||||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_TAG STR COMMIT_ID STR {yy.commit($5,$9,$3,$7)}
|
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($5,$8,$3,$6)}
|
||||||
|
|
||||||
|
|
||||||
// | COMMIT COMMIT_ID STR {yy.commit('',$3,yy.commitType.NORMAL,'')}
|
|
||||||
// | COMMIT COMMIT_TYPE commitType {yy.commit('','',$3,'')}
|
|
||||||
// | COMMIT COMMIT_TAG STR {yy.commit('','',yy.commitType.NORMAL,$3)}
|
|
||||||
// | COMMIT COMMIT_MSG STR {yy.commit($3,'',yy.commitType.NORMAL,'')}
|
|
||||||
// | COMMIT COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit('','',$5,$3)}
|
|
||||||
// | COMMIT COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit('','',$3,$5)}
|
|
||||||
// | COMMIT COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$3,$5,'')}
|
|
||||||
// | COMMIT COMMIT_ID STR COMMIT_TAG STR {yy.commit('',$3,yy.commitType.NORMAL,$5)}
|
|
||||||
// | COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_TAG STR {yy.commit('',$3,$5,$7)}
|
|
||||||
// | COMMIT COMMIT_ID STR COMMIT_TAG STR COMMIT_TYPE commitType {yy.commit('',$3,$7,$5)}
|
|
||||||
;
|
;
|
||||||
commit_arg
|
commit_arg
|
||||||
: /* empty */ {$$ = ""}
|
: /* empty */ {$$ = ""}
|
||||||
@@ -238,6 +221,12 @@ commitType
|
|||||||
| REVERSE { $$=yy.commitType.REVERSE;}
|
| REVERSE { $$=yy.commitType.REVERSE;}
|
||||||
| HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;}
|
| HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;}
|
||||||
;
|
;
|
||||||
|
commitTags
|
||||||
|
: COMMIT_TAG STR {$$=[$2]}
|
||||||
|
| COMMIT_TAG EMPTYSTR {$$=['']}
|
||||||
|
| commitTags COMMIT_TAG STR {$commitTags.push($3); $$=$commitTags;}
|
||||||
|
| commitTags COMMIT_TAG EMPTYSTR {$commitTags.push(''); $$=$commitTags;}
|
||||||
|
;
|
||||||
|
|
||||||
ref
|
ref
|
||||||
: ID
|
: ID
|
||||||
|
|||||||
@@ -16,18 +16,20 @@ import defaultConfig from '../../defaultConfig.js';
|
|||||||
// Inject the layout algorithm into cytoscape
|
// Inject the layout algorithm into cytoscape
|
||||||
cytoscape.use(coseBilkent);
|
cytoscape.use(coseBilkent);
|
||||||
|
|
||||||
function drawNodes(
|
async function drawNodes(
|
||||||
db: MindmapDB,
|
db: MindmapDB,
|
||||||
svg: D3Element,
|
svg: D3Element,
|
||||||
mindmap: FilledMindMapNode,
|
mindmap: FilledMindMapNode,
|
||||||
section: number,
|
section: number,
|
||||||
conf: MermaidConfig
|
conf: MermaidConfig
|
||||||
) {
|
) {
|
||||||
drawNode(db, svg, mindmap, section, conf);
|
await drawNode(db, svg, mindmap, section, conf);
|
||||||
if (mindmap.children) {
|
if (mindmap.children) {
|
||||||
mindmap.children.forEach((child, index) => {
|
await Promise.all(
|
||||||
drawNodes(db, svg, child, section < 0 ? index : section, conf);
|
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');
|
edgesElem.attr('class', 'mindmap-edges');
|
||||||
const nodesElem = svg.append('g');
|
const nodesElem = svg.append('g');
|
||||||
nodesElem.attr('class', 'mindmap-nodes');
|
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
|
// Next step is to layout the mindmap, giving each node a position
|
||||||
|
|
||||||
|
|||||||
@@ -174,13 +174,13 @@ const roundedRectBkg: ShapeFunction = function (db, elem, node) {
|
|||||||
* @param conf - The configuration object
|
* @param conf - The configuration object
|
||||||
* @returns The height nodes dom element
|
* @returns The height nodes dom element
|
||||||
*/
|
*/
|
||||||
export const drawNode = function (
|
export const drawNode = async function (
|
||||||
db: MindmapDB,
|
db: MindmapDB,
|
||||||
elem: D3Element,
|
elem: D3Element,
|
||||||
node: FilledMindMapNode,
|
node: FilledMindMapNode,
|
||||||
fullSection: number,
|
fullSection: number,
|
||||||
conf: MermaidConfig
|
conf: MermaidConfig
|
||||||
): number {
|
): Promise<number> {
|
||||||
const htmlLabels = conf.htmlLabels;
|
const htmlLabels = conf.htmlLabels;
|
||||||
const section = fullSection % (MAX_SECTIONS - 1);
|
const section = fullSection % (MAX_SECTIONS - 1);
|
||||||
const nodeElem = elem.append('g');
|
const nodeElem = elem.append('g');
|
||||||
@@ -195,7 +195,7 @@ export const drawNode = function (
|
|||||||
// Create the wrapped text element
|
// Create the wrapped text element
|
||||||
const textElem = nodeElem.append('g');
|
const textElem = nodeElem.append('g');
|
||||||
const description = node.descr.replace(/(<br\/*>)/g, '\n');
|
const description = node.descr.replace(/(<br\/*>)/g, '\n');
|
||||||
createText(
|
await createText(
|
||||||
textElem,
|
textElem,
|
||||||
description,
|
description,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1088,6 +1088,19 @@ export const draw = async function (_text: string, id: string, _version: string,
|
|||||||
|
|
||||||
const { bounds: box } = bounds.getBounds();
|
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.
|
// Make sure the height of the diagram supports long menus.
|
||||||
let boxHeight = box.stopy - box.starty;
|
let boxHeight = box.stopy - box.starty;
|
||||||
if (boxHeight < requiredBoxSize.maxHeight) {
|
if (boxHeight < requiredBoxSize.maxHeight) {
|
||||||
|
|||||||
411
packages/mermaid/src/diagrams/state/dataFetcher.js
Normal file
411
packages/mermaid/src/diagrams/state/dataFetcher.js
Normal file
@@ -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 === '</join></fork>' || nodeData.id === '</choice>') {
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,6 +27,13 @@
|
|||||||
%x CLASSDEFID
|
%x CLASSDEFID
|
||||||
%x CLASS
|
%x CLASS
|
||||||
%x CLASS_STYLE
|
%x CLASS_STYLE
|
||||||
|
|
||||||
|
// Style statement states
|
||||||
|
%x STYLE
|
||||||
|
%x STYLE_IDS
|
||||||
|
%x STYLEDEF_STYLES
|
||||||
|
%x STYLEDEF_STYLEOPTS
|
||||||
|
|
||||||
%x NOTE
|
%x NOTE
|
||||||
%x NOTE_ID
|
%x NOTE_ID
|
||||||
%x NOTE_TEXT
|
%x NOTE_TEXT
|
||||||
@@ -75,6 +82,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
|||||||
<CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
|
<CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
|
||||||
<CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' }
|
<CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' }
|
||||||
|
|
||||||
|
<INITIAL,struct>"style"\s+ { this.pushState('STYLE'); return 'style'; }
|
||||||
|
<STYLE>[\w,]+\s+ { this.popState(); this.pushState('STYLEDEF_STYLES'); return 'STYLE_IDS' }
|
||||||
|
<STYLEDEF_STYLES>[^\n]* { this.popState(); return 'STYLEDEF_STYLEOPTS' }
|
||||||
|
|
||||||
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
|
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
|
||||||
<SCALE>\d+ return 'WIDTH';
|
<SCALE>\d+ return 'WIDTH';
|
||||||
<SCALE>\s+"width" {this.popState();}
|
<SCALE>\s+"width" {this.popState();}
|
||||||
@@ -168,6 +179,7 @@ line
|
|||||||
|
|
||||||
statement
|
statement
|
||||||
: classDefStatement
|
: classDefStatement
|
||||||
|
| styleStatement
|
||||||
| cssClassStatement
|
| cssClassStatement
|
||||||
| idStatement { /* console.log('got id', $1); */
|
| idStatement { /* console.log('got id', $1); */
|
||||||
$$=$1;
|
$$=$1;
|
||||||
@@ -246,6 +258,12 @@ classDefStatement
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
styleStatement
|
||||||
|
: style STYLE_IDS STYLEDEF_STYLEOPTS {
|
||||||
|
$$ = { stmt: 'style', id: $2.trim(), styleClass: $3.trim() };
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
cssClassStatement
|
cssClassStatement
|
||||||
: class CLASSENTITY_IDS STYLECLASS {
|
: class CLASSENTITY_IDS STYLECLASS {
|
||||||
//console.log('apply class: id(s): ',$2, ' style class: ', $3);
|
//console.log('apply class: id(s): ',$2, ' style class: ', $3);
|
||||||
|
|||||||
@@ -14,19 +14,90 @@ export const STMT_STATE = 'state';
|
|||||||
export const STMT_RELATION = 'relation';
|
export const STMT_RELATION = 'relation';
|
||||||
// parsed statement type for a classDef
|
// parsed statement type for a classDef
|
||||||
export const STMT_CLASSDEF = 'classDef';
|
export const STMT_CLASSDEF = 'classDef';
|
||||||
|
export const STMT_STYLEDEF = 'style';
|
||||||
// parsed statement type for applyClass
|
// parsed statement type for applyClass
|
||||||
export const STMT_APPLYCLASS = 'applyClass';
|
export const STMT_APPLYCLASS = 'applyClass';
|
||||||
|
|
||||||
export const DEFAULT_STATE_TYPE = 'default';
|
export const DEFAULT_STATE_TYPE = 'default';
|
||||||
export const DIVIDER_TYPE = 'divider';
|
export const DIVIDER_TYPE = 'divider';
|
||||||
|
|
||||||
|
// Graph edge settings
|
||||||
|
export const G_EDGE_STYLE = 'fill:none';
|
||||||
|
export const G_EDGE_ARROWHEADSTYLE = 'fill: #333';
|
||||||
|
export const G_EDGE_LABELPOS = 'c';
|
||||||
|
export const G_EDGE_LABELTYPE = 'text';
|
||||||
|
export const G_EDGE_THICKNESS = 'normal';
|
||||||
|
|
||||||
|
export const SHAPE_STATE = 'rect';
|
||||||
|
export const SHAPE_STATE_WITH_DESC = 'rectWithTitle';
|
||||||
|
export const SHAPE_START = 'stateStart';
|
||||||
|
export const SHAPE_END = 'stateEnd';
|
||||||
|
export const SHAPE_DIVIDER = 'divider';
|
||||||
|
export const SHAPE_GROUP = 'roundedWithTitle';
|
||||||
|
export const SHAPE_NOTE = 'note';
|
||||||
|
export const SHAPE_NOTEGROUP = 'noteGroup';
|
||||||
|
|
||||||
|
// CSS classes
|
||||||
|
export const CSS_DIAGRAM = 'statediagram';
|
||||||
|
export const CSS_STATE = 'state';
|
||||||
|
export const CSS_DIAGRAM_STATE = `${CSS_DIAGRAM}-${CSS_STATE}`;
|
||||||
|
export const CSS_EDGE = 'transition';
|
||||||
|
export const CSS_NOTE = 'note';
|
||||||
|
export const CSS_NOTE_EDGE = 'note-edge';
|
||||||
|
export const CSS_EDGE_NOTE_EDGE = `${CSS_EDGE} ${CSS_NOTE_EDGE}`;
|
||||||
|
export const CSS_DIAGRAM_NOTE = `${CSS_DIAGRAM}-${CSS_NOTE}`;
|
||||||
|
export const CSS_CLUSTER = 'cluster';
|
||||||
|
export const CSS_DIAGRAM_CLUSTER = `${CSS_DIAGRAM}-${CSS_CLUSTER}`;
|
||||||
|
export const CSS_CLUSTER_ALT = 'cluster-alt';
|
||||||
|
export const CSS_DIAGRAM_CLUSTER_ALT = `${CSS_DIAGRAM}-${CSS_CLUSTER_ALT}`;
|
||||||
|
|
||||||
|
export const PARENT = 'parent';
|
||||||
|
export const NOTE = 'note';
|
||||||
|
export const DOMID_STATE = 'state';
|
||||||
|
export const DOMID_TYPE_SPACER = '----';
|
||||||
|
export const NOTE_ID = `${DOMID_TYPE_SPACER}${NOTE}`;
|
||||||
|
export const PARENT_ID = `${DOMID_TYPE_SPACER}${PARENT}`;
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
DEFAULT_DIAGRAM_DIRECTION,
|
DEFAULT_DIAGRAM_DIRECTION,
|
||||||
DEFAULT_NESTED_DOC_DIR,
|
DEFAULT_NESTED_DOC_DIR,
|
||||||
STMT_STATE,
|
STMT_STATE,
|
||||||
STMT_RELATION,
|
STMT_RELATION,
|
||||||
STMT_CLASSDEF,
|
STMT_CLASSDEF,
|
||||||
|
STMT_STYLEDEF,
|
||||||
STMT_APPLYCLASS,
|
STMT_APPLYCLASS,
|
||||||
DEFAULT_STATE_TYPE,
|
DEFAULT_STATE_TYPE,
|
||||||
DIVIDER_TYPE,
|
DIVIDER_TYPE,
|
||||||
|
G_EDGE_STYLE,
|
||||||
|
G_EDGE_ARROWHEADSTYLE,
|
||||||
|
G_EDGE_LABELPOS,
|
||||||
|
G_EDGE_LABELTYPE,
|
||||||
|
G_EDGE_THICKNESS,
|
||||||
|
CSS_EDGE,
|
||||||
|
CSS_DIAGRAM,
|
||||||
|
SHAPE_STATE,
|
||||||
|
SHAPE_STATE_WITH_DESC,
|
||||||
|
SHAPE_START,
|
||||||
|
SHAPE_END,
|
||||||
|
SHAPE_DIVIDER,
|
||||||
|
SHAPE_GROUP,
|
||||||
|
SHAPE_NOTE,
|
||||||
|
SHAPE_NOTEGROUP,
|
||||||
|
CSS_STATE,
|
||||||
|
CSS_DIAGRAM_STATE,
|
||||||
|
CSS_NOTE,
|
||||||
|
CSS_NOTE_EDGE,
|
||||||
|
CSS_EDGE_NOTE_EDGE,
|
||||||
|
CSS_DIAGRAM_NOTE,
|
||||||
|
CSS_CLUSTER,
|
||||||
|
CSS_DIAGRAM_CLUSTER,
|
||||||
|
CSS_CLUSTER_ALT,
|
||||||
|
CSS_DIAGRAM_CLUSTER_ALT,
|
||||||
|
PARENT,
|
||||||
|
NOTE,
|
||||||
|
DOMID_STATE,
|
||||||
|
DOMID_TYPE_SPACER,
|
||||||
|
NOTE_ID,
|
||||||
|
PARENT_ID,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ import {
|
|||||||
setDiagramTitle,
|
setDiagramTitle,
|
||||||
getDiagramTitle,
|
getDiagramTitle,
|
||||||
} from '../common/commonDb.js';
|
} from '../common/commonDb.js';
|
||||||
|
import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js';
|
||||||
|
import { getDir } from './stateRenderer-v3-unified.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_DIAGRAM_DIRECTION,
|
DEFAULT_DIAGRAM_DIRECTION,
|
||||||
STMT_STATE,
|
STMT_STATE,
|
||||||
STMT_RELATION,
|
STMT_RELATION,
|
||||||
STMT_CLASSDEF,
|
STMT_CLASSDEF,
|
||||||
|
STMT_STYLEDEF,
|
||||||
STMT_APPLYCLASS,
|
STMT_APPLYCLASS,
|
||||||
DEFAULT_STATE_TYPE,
|
DEFAULT_STATE_TYPE,
|
||||||
DIVIDER_TYPE,
|
DIVIDER_TYPE,
|
||||||
@@ -43,10 +46,15 @@ function newClassesList() {
|
|||||||
return new Map();
|
return new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nodes = [];
|
||||||
|
let edges = [];
|
||||||
|
|
||||||
let direction = DEFAULT_DIAGRAM_DIRECTION;
|
let direction = DEFAULT_DIAGRAM_DIRECTION;
|
||||||
let rootDoc = [];
|
let rootDoc = [];
|
||||||
let classes = newClassesList(); // style classes defined by a classDef
|
let classes = newClassesList(); // style classes defined by a classDef
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
const newDoc = () => {
|
const newDoc = () => {
|
||||||
return {
|
return {
|
||||||
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */
|
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */
|
||||||
@@ -165,9 +173,10 @@ const extract = (_doc) => {
|
|||||||
log.info(doc);
|
log.info(doc);
|
||||||
clear(true);
|
clear(true);
|
||||||
|
|
||||||
log.info('Extract', doc);
|
log.info('Extract initial document:', doc);
|
||||||
|
|
||||||
doc.forEach((item) => {
|
doc.forEach((item) => {
|
||||||
|
log.warn('Statement', item.stmt);
|
||||||
switch (item.stmt) {
|
switch (item.stmt) {
|
||||||
case STMT_STATE:
|
case STMT_STATE:
|
||||||
addState(
|
addState(
|
||||||
@@ -187,11 +196,47 @@ const extract = (_doc) => {
|
|||||||
case STMT_CLASSDEF:
|
case STMT_CLASSDEF:
|
||||||
addStyleClass(item.id.trim(), item.classes);
|
addStyleClass(item.id.trim(), item.classes);
|
||||||
break;
|
break;
|
||||||
|
case STMT_STYLEDEF:
|
||||||
|
{
|
||||||
|
const ids = item.id.trim().split(',');
|
||||||
|
const styles = item.styleClass.split(',');
|
||||||
|
ids.forEach((id) => {
|
||||||
|
let foundState = getState(id);
|
||||||
|
if (foundState === undefined) {
|
||||||
|
const trimmedId = id.trim();
|
||||||
|
addState(trimmedId);
|
||||||
|
foundState = getState(trimmedId);
|
||||||
|
}
|
||||||
|
foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case STMT_APPLYCLASS:
|
case STMT_APPLYCLASS:
|
||||||
setCssClass(item.id.trim(), item.styleClass);
|
setCssClass(item.id.trim(), item.styleClass);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const diagramStates = getStates();
|
||||||
|
const config = getConfig();
|
||||||
|
const look = config.look;
|
||||||
|
resetDataFetching();
|
||||||
|
dataFetcher(undefined, getRootDocV2(), diagramStates, nodes, edges, true, look, classes);
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (Array.isArray(node.label)) {
|
||||||
|
// add the rest as description
|
||||||
|
node.description = node.label.slice(1);
|
||||||
|
if (node.isGroup && node.description.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
'Group nodes can only have label. Remove the additional description for node [' +
|
||||||
|
node.id +
|
||||||
|
']'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// add first description as label
|
||||||
|
node.label = node.label[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,6 +321,8 @@ export const addState = function (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const clear = function (saveCommon) {
|
export const clear = function (saveCommon) {
|
||||||
|
nodes = [];
|
||||||
|
edges = [];
|
||||||
documents = {
|
documents = {
|
||||||
root: newDoc(),
|
root: newDoc(),
|
||||||
};
|
};
|
||||||
@@ -516,7 +563,7 @@ export const setCssClass = function (itemIds, cssClassName) {
|
|||||||
export const setStyle = function (itemId, styleText) {
|
export const setStyle = function (itemId, styleText) {
|
||||||
const item = getState(itemId);
|
const item = getState(itemId);
|
||||||
if (item !== undefined) {
|
if (item !== undefined) {
|
||||||
item.textStyles.push(styleText);
|
item.styles.push(styleText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -540,8 +587,14 @@ const setDirection = (dir) => {
|
|||||||
|
|
||||||
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
|
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
|
||||||
|
|
||||||
|
export const getData = () => {
|
||||||
|
const config = getConfig();
|
||||||
|
return { nodes, edges, other: {}, config, direction: getDir(getRootDocV2()) };
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getConfig: () => getConfig().state,
|
getConfig: () => getConfig().state,
|
||||||
|
getData,
|
||||||
addState,
|
addState,
|
||||||
clear,
|
clear,
|
||||||
getState,
|
getState,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
|||||||
import parser from './parser/stateDiagram.jison';
|
import parser from './parser/stateDiagram.jison';
|
||||||
import db from './stateDb.js';
|
import db from './stateDb.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
import renderer from './stateRenderer-v2.js';
|
import renderer from './stateRenderer-v3-unified.js';
|
||||||
|
|
||||||
export const diagram: DiagramDefinition = {
|
export const diagram: DiagramDefinition = {
|
||||||
parser,
|
parser,
|
||||||
|
|||||||
@@ -1,479 +0,0 @@
|
|||||||
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
|
||||||
import { select } from 'd3';
|
|
||||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
|
||||||
import { render } from '../../dagre-wrapper/index.js';
|
|
||||||
import { log } from '../../logger.js';
|
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
|
||||||
import common from '../common/common.js';
|
|
||||||
import utils, { getEdgeId } from '../../utils.js';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DEFAULT_DIAGRAM_DIRECTION,
|
|
||||||
DEFAULT_NESTED_DOC_DIR,
|
|
||||||
STMT_STATE,
|
|
||||||
STMT_RELATION,
|
|
||||||
DEFAULT_STATE_TYPE,
|
|
||||||
DIVIDER_TYPE,
|
|
||||||
} from './stateCommon.js';
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
// Shapes
|
|
||||||
const SHAPE_STATE = 'rect';
|
|
||||||
const SHAPE_STATE_WITH_DESC = 'rectWithTitle';
|
|
||||||
const SHAPE_START = 'start';
|
|
||||||
const SHAPE_END = 'end';
|
|
||||||
const SHAPE_DIVIDER = 'divider';
|
|
||||||
const SHAPE_GROUP = 'roundedWithTitle';
|
|
||||||
const SHAPE_NOTE = 'note';
|
|
||||||
const SHAPE_NOTEGROUP = 'noteGroup';
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
// CSS classes
|
|
||||||
const CSS_DIAGRAM = 'statediagram';
|
|
||||||
const CSS_STATE = 'state';
|
|
||||||
const CSS_DIAGRAM_STATE = `${CSS_DIAGRAM}-${CSS_STATE}`;
|
|
||||||
const CSS_EDGE = 'transition';
|
|
||||||
const CSS_NOTE = 'note';
|
|
||||||
const CSS_NOTE_EDGE = 'note-edge';
|
|
||||||
const CSS_EDGE_NOTE_EDGE = `${CSS_EDGE} ${CSS_NOTE_EDGE}`;
|
|
||||||
const CSS_DIAGRAM_NOTE = `${CSS_DIAGRAM}-${CSS_NOTE}`;
|
|
||||||
const CSS_CLUSTER = 'cluster';
|
|
||||||
const CSS_DIAGRAM_CLUSTER = `${CSS_DIAGRAM}-${CSS_CLUSTER}`;
|
|
||||||
const CSS_CLUSTER_ALT = 'cluster-alt';
|
|
||||||
const CSS_DIAGRAM_CLUSTER_ALT = `${CSS_DIAGRAM}-${CSS_CLUSTER_ALT}`;
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
// DOM and element IDs
|
|
||||||
const PARENT = 'parent';
|
|
||||||
const NOTE = 'note';
|
|
||||||
const DOMID_STATE = 'state';
|
|
||||||
const DOMID_TYPE_SPACER = '----';
|
|
||||||
const NOTE_ID = `${DOMID_TYPE_SPACER}${NOTE}`;
|
|
||||||
const PARENT_ID = `${DOMID_TYPE_SPACER}${PARENT}`;
|
|
||||||
// --------------------------------------
|
|
||||||
// Graph edge settings
|
|
||||||
const G_EDGE_STYLE = 'fill:none';
|
|
||||||
const G_EDGE_ARROWHEADSTYLE = 'fill: #333';
|
|
||||||
const G_EDGE_LABELPOS = 'c';
|
|
||||||
const G_EDGE_LABELTYPE = 'text';
|
|
||||||
const G_EDGE_THICKNESS = 'normal';
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
// List of nodes created from the parsed diagram statement items
|
|
||||||
let nodeDb = {};
|
|
||||||
|
|
||||||
let graphItemCount = 0; // used to construct ids, etc.
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
const conf = {};
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
export const setConf = function (cnf) {
|
|
||||||
const keys = Object.keys(cnf);
|
|
||||||
for (const key of keys) {
|
|
||||||
conf[key] = cnf[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the all the classdef styles (a.k.a. classes) from classDef statements in the graph definition.
|
|
||||||
*
|
|
||||||
* @param {string} text - the diagram text to be parsed
|
|
||||||
* @param diagramObj
|
|
||||||
* @returns {Map<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles (a Map with keys = strings, values = )
|
|
||||||
*/
|
|
||||||
export const getClasses = function (text, diagramObj) {
|
|
||||||
diagramObj.db.extract(diagramObj.db.getRootDocV2());
|
|
||||||
return diagramObj.db.getClasses();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
return dbInfoItem.classes.join(' ');
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a graph node based on the statement information
|
|
||||||
*
|
|
||||||
* @param g - graph
|
|
||||||
* @param {object} parent
|
|
||||||
* @param {object} parsedItem - parsed statement item
|
|
||||||
* @param {Map<string, object>} diagramStates - the list of all known states for the diagram
|
|
||||||
* @param {object} diagramDb
|
|
||||||
* @param {boolean} altFlag - for clusters, add the "statediagram-cluster-alt" CSS class
|
|
||||||
*/
|
|
||||||
const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) => {
|
|
||||||
const itemId = parsedItem.id;
|
|
||||||
const classStr = getClassesFromDbInfo(diagramStates.get(itemId));
|
|
||||||
|
|
||||||
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[itemId]) {
|
|
||||||
nodeDb[itemId] = {
|
|
||||||
id: itemId,
|
|
||||||
shape,
|
|
||||||
description: common.sanitizeText(itemId, getConfig()),
|
|
||||||
classes: `${classStr} ${CSS_DIAGRAM_STATE}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const newNode = nodeDb[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) {
|
|
||||||
newNode.shape = SHAPE_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// group
|
|
||||||
if (!newNode.type && parsedItem.doc) {
|
|
||||||
log.info('Setting cluster for ', itemId, getDir(parsedItem));
|
|
||||||
newNode.type = 'group';
|
|
||||||
newNode.dir = getDir(parsedItem);
|
|
||||||
newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP;
|
|
||||||
newNode.classes =
|
|
||||||
newNode.classes +
|
|
||||||
' ' +
|
|
||||||
CSS_DIAGRAM_CLUSTER +
|
|
||||||
' ' +
|
|
||||||
(altFlag ? CSS_DIAGRAM_CLUSTER_ALT : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is what will be added to the graph
|
|
||||||
const nodeData = {
|
|
||||||
labelStyle: '',
|
|
||||||
shape: newNode.shape,
|
|
||||||
labelText: newNode.description,
|
|
||||||
// typeof newNode.description === 'object'
|
|
||||||
// ? newNode.description[0]
|
|
||||||
// : newNode.description,
|
|
||||||
classes: newNode.classes,
|
|
||||||
style: '', //styles.style,
|
|
||||||
id: itemId,
|
|
||||||
dir: newNode.dir,
|
|
||||||
domId: stateDomId(itemId, graphItemCount),
|
|
||||||
type: newNode.type,
|
|
||||||
padding: 15, //getConfig().flowchart.padding
|
|
||||||
};
|
|
||||||
// if (useHtmlLabels) {
|
|
||||||
nodeData.centerLabel = true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (parsedItem.note) {
|
|
||||||
// Todo: set random id
|
|
||||||
const noteData = {
|
|
||||||
labelStyle: '',
|
|
||||||
shape: SHAPE_NOTE,
|
|
||||||
labelText: parsedItem.note.text,
|
|
||||||
classes: CSS_DIAGRAM_NOTE,
|
|
||||||
// useHtmlLabels: false,
|
|
||||||
style: '', // styles.style,
|
|
||||||
id: itemId + NOTE_ID + '-' + graphItemCount,
|
|
||||||
domId: stateDomId(itemId, graphItemCount, NOTE),
|
|
||||||
type: newNode.type,
|
|
||||||
padding: 15, //getConfig().flowchart.padding
|
|
||||||
};
|
|
||||||
const groupData = {
|
|
||||||
labelStyle: '',
|
|
||||||
shape: SHAPE_NOTEGROUP,
|
|
||||||
labelText: parsedItem.note.text,
|
|
||||||
classes: newNode.classes,
|
|
||||||
style: '', // styles.style,
|
|
||||||
id: itemId + PARENT_ID,
|
|
||||||
domId: stateDomId(itemId, graphItemCount, PARENT),
|
|
||||||
type: 'group',
|
|
||||||
padding: 0, //getConfig().flowchart.padding
|
|
||||||
};
|
|
||||||
|
|
||||||
const parentNodeId = itemId + PARENT_ID;
|
|
||||||
g.setNode(parentNodeId, groupData);
|
|
||||||
|
|
||||||
g.setNode(noteData.id, noteData);
|
|
||||||
g.setNode(itemId, nodeData);
|
|
||||||
|
|
||||||
g.setParent(itemId, parentNodeId);
|
|
||||||
g.setParent(noteData.id, parentNodeId);
|
|
||||||
|
|
||||||
let from = itemId;
|
|
||||||
let to = noteData.id;
|
|
||||||
|
|
||||||
if (parsedItem.note.position === 'left of') {
|
|
||||||
from = noteData.id;
|
|
||||||
to = itemId;
|
|
||||||
}
|
|
||||||
|
|
||||||
g.setEdge(from, to, {
|
|
||||||
arrowhead: 'none',
|
|
||||||
arrowType: '',
|
|
||||||
style: G_EDGE_STYLE,
|
|
||||||
labelStyle: '',
|
|
||||||
id: getEdgeId(from, to, {
|
|
||||||
counter: graphItemCount,
|
|
||||||
}),
|
|
||||||
classes: CSS_EDGE_NOTE_EDGE,
|
|
||||||
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
|
|
||||||
labelpos: G_EDGE_LABELPOS,
|
|
||||||
labelType: G_EDGE_LABELTYPE,
|
|
||||||
thickness: G_EDGE_THICKNESS,
|
|
||||||
});
|
|
||||||
|
|
||||||
graphItemCount++;
|
|
||||||
} else {
|
|
||||||
g.setNode(itemId, nodeData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parent && parent.id !== 'root') {
|
|
||||||
log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
|
|
||||||
g.setParent(itemId, parent.id);
|
|
||||||
}
|
|
||||||
if (parsedItem.doc) {
|
|
||||||
log.trace('Adding nodes children ');
|
|
||||||
setupDoc(g, parsedItem, parsedItem.doc, diagramStates, diagramDb, !altFlag);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turn parsed statements (item.stmt) into nodes, relationships, etc. for a document.
|
|
||||||
* (A document may be nested within others.)
|
|
||||||
*
|
|
||||||
* @param g
|
|
||||||
* @param parentParsedItem - parsed Item that is the parent of this document (doc)
|
|
||||||
* @param doc - the document to set up; it is a list of parsed statements
|
|
||||||
* @param {Map<string, object>} diagramStates - the list of all known states for the diagram
|
|
||||||
* @param diagramDb
|
|
||||||
* @param {boolean} altFlag
|
|
||||||
* @todo This duplicates some of what is done in stateDb.js extract method
|
|
||||||
*/
|
|
||||||
const setupDoc = (g, parentParsedItem, doc, diagramStates, diagramDb, altFlag) => {
|
|
||||||
// graphItemCount = 0;
|
|
||||||
log.trace('items', doc);
|
|
||||||
doc.forEach((item) => {
|
|
||||||
switch (item.stmt) {
|
|
||||||
case STMT_STATE:
|
|
||||||
setupNode(g, parentParsedItem, item, diagramStates, diagramDb, altFlag);
|
|
||||||
break;
|
|
||||||
case DEFAULT_STATE_TYPE:
|
|
||||||
setupNode(g, parentParsedItem, item, diagramStates, diagramDb, altFlag);
|
|
||||||
break;
|
|
||||||
case STMT_RELATION:
|
|
||||||
{
|
|
||||||
setupNode(g, parentParsedItem, item.state1, diagramStates, diagramDb, altFlag);
|
|
||||||
setupNode(g, parentParsedItem, item.state2, diagramStates, diagramDb, altFlag);
|
|
||||||
const edgeData = {
|
|
||||||
id: getEdgeId(item.state1.id, item.state2.id, {
|
|
||||||
counter: graphItemCount,
|
|
||||||
}),
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount);
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a state diagram in the tag with id: id based on the graph definition in text.
|
|
||||||
*
|
|
||||||
* @param {any} text
|
|
||||||
* @param {any} id
|
|
||||||
* @param _version
|
|
||||||
* @param diag
|
|
||||||
*/
|
|
||||||
export const draw = async function (text, id, _version, diag) {
|
|
||||||
log.info('Drawing state diagram (v2)', id);
|
|
||||||
nodeDb = {};
|
|
||||||
// Fetch the default direction, use TD if none was found
|
|
||||||
let dir = diag.db.getDirection();
|
|
||||||
if (dir === undefined) {
|
|
||||||
dir = DEFAULT_DIAGRAM_DIRECTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { securityLevel, state: conf } = getConfig();
|
|
||||||
const nodeSpacing = conf.nodeSpacing || 50;
|
|
||||||
const rankSpacing = conf.rankSpacing || 50;
|
|
||||||
|
|
||||||
log.info(diag.db.getRootDocV2());
|
|
||||||
|
|
||||||
// This parses the diagram text and sets the classes, relations, styles, classDefs, etc.
|
|
||||||
diag.db.extract(diag.db.getRootDocV2());
|
|
||||||
log.info(diag.db.getRootDocV2());
|
|
||||||
|
|
||||||
const diagramStates = diag.db.getStates();
|
|
||||||
|
|
||||||
// Create the input mermaid.graph
|
|
||||||
const g = new graphlib.Graph({
|
|
||||||
multigraph: true,
|
|
||||||
compound: true,
|
|
||||||
})
|
|
||||||
.setGraph({
|
|
||||||
rankdir: getDir(diag.db.getRootDocV2()),
|
|
||||||
nodesep: nodeSpacing,
|
|
||||||
ranksep: rankSpacing,
|
|
||||||
marginx: 8,
|
|
||||||
marginy: 8,
|
|
||||||
})
|
|
||||||
.setDefaultEdgeLabel(function () {
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
|
|
||||||
setupNode(g, undefined, diag.db.getRootDocV2(), diagramStates, diag.db, true);
|
|
||||||
|
|
||||||
// Set up an SVG group so that we can translate the final graph.
|
|
||||||
let sandboxElement;
|
|
||||||
if (securityLevel === 'sandbox') {
|
|
||||||
sandboxElement = select('#i' + id);
|
|
||||||
}
|
|
||||||
const root =
|
|
||||||
securityLevel === 'sandbox'
|
|
||||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
|
||||||
: select('body');
|
|
||||||
const svg = root.select(`[id="${id}"]`);
|
|
||||||
|
|
||||||
// Run the renderer. This is what draws the final graph.
|
|
||||||
|
|
||||||
const element = root.select('#' + id + ' g');
|
|
||||||
await render(element, g, ['barb'], CSS_DIAGRAM, id);
|
|
||||||
|
|
||||||
const padding = 8;
|
|
||||||
|
|
||||||
utils.insertTitle(svg, 'statediagramTitleText', conf.titleTopMargin, diag.db.getDiagramTitle());
|
|
||||||
|
|
||||||
const bounds = svg.node().getBBox();
|
|
||||||
const width = bounds.width + padding * 2;
|
|
||||||
const height = bounds.height + padding * 2;
|
|
||||||
|
|
||||||
// Zoom in a bit
|
|
||||||
svg.attr('class', CSS_DIAGRAM);
|
|
||||||
|
|
||||||
const svgBounds = svg.node().getBBox();
|
|
||||||
|
|
||||||
configureSvgSize(svg, height, width, conf.useMaxWidth);
|
|
||||||
|
|
||||||
// Ensure the viewBox includes the whole svgBounds area with extra space for padding
|
|
||||||
const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`;
|
|
||||||
log.debug(`viewBox ${vBox}`);
|
|
||||||
svg.attr('viewBox', vBox);
|
|
||||||
|
|
||||||
// Add label rects for non html labels
|
|
||||||
// if (!evaluate(conf.htmlLabels) || true) {
|
|
||||||
const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label');
|
|
||||||
for (const label of labels) {
|
|
||||||
// Get dimensions of label
|
|
||||||
const dim = label.getBBox();
|
|
||||||
|
|
||||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', SHAPE_STATE);
|
|
||||||
rect.setAttribute('rx', 0);
|
|
||||||
rect.setAttribute('ry', 0);
|
|
||||||
rect.setAttribute('width', dim.width);
|
|
||||||
rect.setAttribute('height', dim.height);
|
|
||||||
|
|
||||||
label.insertBefore(rect, label.firstChild);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setConf,
|
|
||||||
getClasses,
|
|
||||||
draw,
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { expectTypeOf } from 'vitest';
|
|
||||||
|
|
||||||
import { parser } from './parser/stateDiagram.jison';
|
|
||||||
import stateDb from './stateDb.js';
|
|
||||||
import stateRendererV2 from './stateRenderer-v2.js';
|
|
||||||
|
|
||||||
// Can use this instead of having to register diagrams and load/orchestrate them, etc.
|
|
||||||
class FauxDiagramObj {
|
|
||||||
db = stateDb;
|
|
||||||
parser = parser;
|
|
||||||
renderer = stateRendererV2;
|
|
||||||
|
|
||||||
constructor(options = { db: stateDb, parser: parser, renderer: stateRendererV2 }) {
|
|
||||||
this.db = options.db;
|
|
||||||
this.parser = options.parser;
|
|
||||||
this.renderer = options.renderer;
|
|
||||||
this.parser.yy = this.db;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('stateRenderer-v2', () => {
|
|
||||||
describe('getClasses', () => {
|
|
||||||
const diagramText = 'statediagram-v2\n';
|
|
||||||
const fauxStateDiagram = new FauxDiagramObj();
|
|
||||||
|
|
||||||
it('returns a {}', () => {
|
|
||||||
const result = stateRendererV2.getClasses(diagramText, fauxStateDiagram);
|
|
||||||
expectTypeOf(result).toBeObject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
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 { CSS_DIAGRAM, DEFAULT_NESTED_DOC_DIR } from './stateCommon.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 parsedItem - the parsed statement item to look through
|
||||||
|
* @param defaultDir - the direction to use if none is found
|
||||||
|
* @returns The direction to use
|
||||||
|
*/
|
||||||
|
export const getDir = (parsedItem: any, defaultDir = DEFAULT_NESTED_DOC_DIR) => {
|
||||||
|
if (!parsedItem.doc) {
|
||||||
|
return defaultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dir = defaultDir;
|
||||||
|
|
||||||
|
for (const parsedItemDoc of parsedItem.doc) {
|
||||||
|
if (parsedItemDoc.stmt === 'dir') {
|
||||||
|
dir = parsedItemDoc.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClasses = function (
|
||||||
|
text: string,
|
||||||
|
diagramObj: any
|
||||||
|
): Map<string, DiagramStyleClassDef> {
|
||||||
|
diagramObj.db.extract(diagramObj.db.getRootDocV2());
|
||||||
|
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, state: conf, layout } = getConfig();
|
||||||
|
// Extracting the data from the parsed structure into a more usable form
|
||||||
|
// Not related to the refactoring, but this is the first step in the rendering process
|
||||||
|
diag.db.extract(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
//const DIR = getDir(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
|
||||||
|
// into the Layout data format
|
||||||
|
const data4Layout = diag.db.getData() as LayoutData;
|
||||||
|
|
||||||
|
// Create the root SVG - the element is the div containing the SVG element
|
||||||
|
const { element, svg } = getDiagramElements(id, securityLevel);
|
||||||
|
|
||||||
|
data4Layout.type = diag.type;
|
||||||
|
data4Layout.layoutAlgorithm = layout;
|
||||||
|
|
||||||
|
// TODO: Should we move these two to baseConfig? These types are not there in StateConfig.
|
||||||
|
|
||||||
|
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
|
||||||
|
data4Layout.rankSpacing = conf?.rankSpacing || 50;
|
||||||
|
data4Layout.markers = ['barb'];
|
||||||
|
data4Layout.diagramId = id;
|
||||||
|
// console.log('REF1:', data4Layout);
|
||||||
|
await render(data4Layout, svg, element);
|
||||||
|
const padding = 8;
|
||||||
|
utils.insertTitle(
|
||||||
|
element,
|
||||||
|
'statediagramTitleText',
|
||||||
|
conf?.titleTopMargin ?? 25,
|
||||||
|
diag.db.getDiagramTitle()
|
||||||
|
);
|
||||||
|
setupViewPortForSVG(svg, padding, CSS_DIAGRAM, conf?.useMaxWidth ?? true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getClasses,
|
||||||
|
draw,
|
||||||
|
getDir,
|
||||||
|
};
|
||||||
@@ -68,6 +68,18 @@ g.stateGroup line {
|
|||||||
fill: ${options.labelBackgroundColor};
|
fill: ${options.labelBackgroundColor};
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
.edgeLabel {
|
||||||
|
background-color: ${options.edgeLabelBackground};
|
||||||
|
p {
|
||||||
|
background-color: ${options.edgeLabelBackground};
|
||||||
|
}
|
||||||
|
rect {
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: ${options.edgeLabelBackground};
|
||||||
|
fill: ${options.edgeLabelBackground};
|
||||||
|
}
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.edgeLabel .label text {
|
.edgeLabel .label text {
|
||||||
fill: ${options.transitionLabelColor || options.tertiaryTextColor};
|
fill: ${options.transitionLabelColor || options.tertiaryTextColor};
|
||||||
}
|
}
|
||||||
@@ -124,6 +136,7 @@ g.stateGroup line {
|
|||||||
|
|
||||||
.cluster-label, .nodeLabel {
|
.cluster-label, .nodeLabel {
|
||||||
color: ${options.stateLabelColor};
|
color: ${options.stateLabelColor};
|
||||||
|
// line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statediagram-cluster rect.outer {
|
.statediagram-cluster rect.outer {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ Cardinality is a property that describes how many elements of another entity can
|
|||||||
| 1+ | 1+ | One or more |
|
| 1+ | 1+ | One or more |
|
||||||
| zero or more | zero or more | Zero or more |
|
| zero or more | zero or more | Zero or more |
|
||||||
| zero or many | zero or many | Zero or more |
|
| zero or many | zero or many | Zero or more |
|
||||||
| many(0) | many(1) | Zero or more |
|
| many(0) | many(0) | Zero or more |
|
||||||
| 0+ | 0+ | Zero or more |
|
| 0+ | 0+ | Zero or more |
|
||||||
| only one | only one | Exactly one |
|
| only one | only one | Exactly one |
|
||||||
| 1 | 1 | Exactly one |
|
| 1 | 1 | Exactly one |
|
||||||
|
|||||||
31
packages/mermaid/src/internals.ts
Normal file
31
packages/mermaid/src/internals.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { getConfig } from './config.js';
|
||||||
|
import common from './diagrams/common/common.js';
|
||||||
|
import { log } from './logger.js';
|
||||||
|
import { insertCluster } from './rendering-util/rendering-elements/clusters.js';
|
||||||
|
import {
|
||||||
|
insertEdge,
|
||||||
|
insertEdgeLabel,
|
||||||
|
positionEdgeLabel,
|
||||||
|
} from './rendering-util/rendering-elements/edges.js';
|
||||||
|
import insertMarkers from './rendering-util/rendering-elements/markers.js';
|
||||||
|
import { insertNode } from './rendering-util/rendering-elements/nodes.js';
|
||||||
|
import { labelHelper } from './rendering-util/rendering-elements/shapes/util.js';
|
||||||
|
import { interpolateToCurve } from './utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal helpers for mermaid
|
||||||
|
* @deprecated - This should not be used by external packages, as the definitions will change without notice.
|
||||||
|
*/
|
||||||
|
export const internalHelpers = {
|
||||||
|
common,
|
||||||
|
getConfig,
|
||||||
|
insertCluster,
|
||||||
|
insertEdge,
|
||||||
|
insertEdgeLabel,
|
||||||
|
insertMarkers,
|
||||||
|
insertNode,
|
||||||
|
interpolateToCurve,
|
||||||
|
labelHelper,
|
||||||
|
log,
|
||||||
|
positionEdgeLabel,
|
||||||
|
};
|
||||||
@@ -18,6 +18,10 @@ import type { UnknownDiagramError } from './errors.js';
|
|||||||
import type { IconLibrary, IconResolver } from './rendering-util/svgRegister.js';
|
import type { IconLibrary, IconResolver } from './rendering-util/svgRegister.js';
|
||||||
import { createIcon } from './rendering-util/svgRegister.js';
|
import { createIcon } from './rendering-util/svgRegister.js';
|
||||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||||
|
import { registerLayoutLoaders } from './rendering-util/render.js';
|
||||||
|
import type { LayoutLoaderDefinition } from './rendering-util/render.js';
|
||||||
|
import { internalHelpers } from './internals.js';
|
||||||
|
import type { LayoutData } from './rendering-util/types.js';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
MermaidConfig,
|
MermaidConfig,
|
||||||
@@ -30,6 +34,8 @@ export type {
|
|||||||
UnknownDiagramError,
|
UnknownDiagramError,
|
||||||
IconLibrary,
|
IconLibrary,
|
||||||
IconResolver,
|
IconResolver,
|
||||||
|
LayoutLoaderDefinition,
|
||||||
|
LayoutData,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { createIcon };
|
export { createIcon };
|
||||||
@@ -426,11 +432,17 @@ export interface Mermaid {
|
|||||||
*/
|
*/
|
||||||
init: typeof init;
|
init: typeof init;
|
||||||
run: typeof run;
|
run: typeof run;
|
||||||
|
registerLayoutLoaders: typeof registerLayoutLoaders;
|
||||||
registerExternalDiagrams: typeof registerExternalDiagrams;
|
registerExternalDiagrams: typeof registerExternalDiagrams;
|
||||||
initialize: typeof initialize;
|
initialize: typeof initialize;
|
||||||
contentLoaded: typeof contentLoaded;
|
contentLoaded: typeof contentLoaded;
|
||||||
setParseErrorHandler: typeof setParseErrorHandler;
|
setParseErrorHandler: typeof setParseErrorHandler;
|
||||||
detectType: typeof detectType;
|
detectType: typeof detectType;
|
||||||
|
/**
|
||||||
|
* Internal helpers for mermaid
|
||||||
|
* @deprecated - This should not be used by external packages, as the definitions will change without notice.
|
||||||
|
*/
|
||||||
|
internalHelpers: typeof internalHelpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mermaid: Mermaid = {
|
const mermaid: Mermaid = {
|
||||||
@@ -441,11 +453,13 @@ const mermaid: Mermaid = {
|
|||||||
init,
|
init,
|
||||||
run,
|
run,
|
||||||
registerExternalDiagrams,
|
registerExternalDiagrams,
|
||||||
|
registerLayoutLoaders,
|
||||||
initialize,
|
initialize,
|
||||||
parseError: undefined,
|
parseError: undefined,
|
||||||
contentLoaded,
|
contentLoaded,
|
||||||
setParseErrorHandler,
|
setParseErrorHandler,
|
||||||
detectType,
|
detectType,
|
||||||
|
internalHelpers,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mermaid;
|
export default mermaid;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ vi.mock('./diagrams/packet/renderer.js');
|
|||||||
vi.mock('./diagrams/xychart/xychartRenderer.js');
|
vi.mock('./diagrams/xychart/xychartRenderer.js');
|
||||||
vi.mock('./diagrams/requirement/requirementRenderer.js');
|
vi.mock('./diagrams/requirement/requirementRenderer.js');
|
||||||
vi.mock('./diagrams/sequence/sequenceRenderer.js');
|
vi.mock('./diagrams/sequence/sequenceRenderer.js');
|
||||||
vi.mock('./diagrams/state/stateRenderer-v2.js');
|
|
||||||
|
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,11 @@ export const createCssStyles = (
|
|||||||
}
|
}
|
||||||
// create the css styles for the tspan element and the text styles (only if there are textStyles)
|
// create the css styles for the tspan element and the text styles (only if there are textStyles)
|
||||||
if (!isEmpty(styleClassDef.textStyles)) {
|
if (!isEmpty(styleClassDef.textStyles)) {
|
||||||
cssStyles += cssImportantStyles(styleClassDef.id, 'tspan', styleClassDef.textStyles);
|
cssStyles += cssImportantStyles(
|
||||||
|
styleClassDef.id,
|
||||||
|
'tspan',
|
||||||
|
(styleClassDef?.textStyles || []).map((s) => s.replace('color', 'fill'))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
// @ts-nocheck TODO: Fix types
|
// @ts-nocheck TODO: Fix types
|
||||||
import type { MermaidConfig } from '../config.type.js';
|
import type { MermaidConfig } from '../config.type.js';
|
||||||
import type { Group } from '../diagram-api/types.js';
|
import type { Group } from '../diagram-api/types.js';
|
||||||
|
import { select } from 'd3';
|
||||||
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
||||||
import { log } from '../logger.js';
|
import { log } from '../logger.js';
|
||||||
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
||||||
import { decodeEntities } from '../utils.js';
|
import { decodeEntities } from '../utils.js';
|
||||||
import { splitLineToFitWidth } from './splitText.js';
|
import { splitLineToFitWidth } from './splitText.js';
|
||||||
import type { MarkdownLine, MarkdownWord } from './types.js';
|
import type { MarkdownLine, MarkdownWord } from './types.js';
|
||||||
|
import common, { hasKatex, renderKatex } from '$root/diagrams/common/common.js';
|
||||||
|
import { getConfig } from '$root/diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
function applyStyle(dom, styleFn) {
|
function applyStyle(dom, styleFn) {
|
||||||
if (styleFn) {
|
if (styleFn) {
|
||||||
@@ -15,11 +18,13 @@ function applyStyle(dom, styleFn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
async function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
||||||
const fo = element.append('foreignObject');
|
const fo = element.append('foreignObject');
|
||||||
const div = fo.append('xhtml:div');
|
const div = fo.append('xhtml:div');
|
||||||
|
let label = node.label;
|
||||||
const label = node.label;
|
if (node.label && hasKatex(node.label)) {
|
||||||
|
label = await renderKatex(node.label.replace(common.lineBreakRegex, '\n'), getConfig());
|
||||||
|
}
|
||||||
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
|
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
|
||||||
const span = div.append('span');
|
const span = div.append('span');
|
||||||
span.html(label);
|
span.html(label);
|
||||||
@@ -29,6 +34,7 @@ function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
|||||||
applyStyle(div, node.labelStyle);
|
applyStyle(div, node.labelStyle);
|
||||||
div.style('display', 'table-cell');
|
div.style('display', 'table-cell');
|
||||||
div.style('white-space', 'nowrap');
|
div.style('white-space', 'nowrap');
|
||||||
|
div.style('line-height', '1.5');
|
||||||
div.style('max-width', width + 'px');
|
div.style('max-width', width + 'px');
|
||||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||||
if (addBackground) {
|
if (addBackground) {
|
||||||
@@ -43,8 +49,8 @@ function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
|||||||
bbox = div.node().getBoundingClientRect();
|
bbox = div.node().getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
fo.style('width', bbox.width);
|
// fo.style('width', bbox.width);
|
||||||
fo.style('height', bbox.height);
|
// fo.style('height', bbox.height);
|
||||||
|
|
||||||
return fo.node();
|
return fo.node();
|
||||||
}
|
}
|
||||||
@@ -107,7 +113,7 @@ function createFormattedText(
|
|||||||
) {
|
) {
|
||||||
const lineHeight = 1.1;
|
const lineHeight = 1.1;
|
||||||
const labelGroup = g.append('g');
|
const labelGroup = g.append('g');
|
||||||
const bkg = labelGroup.insert('rect').attr('class', 'background');
|
const bkg = labelGroup.insert('rect').attr('class', 'background').attr('style', 'stroke: none');
|
||||||
const textElement = labelGroup.append('text').attr('y', '-10.1');
|
const textElement = labelGroup.append('text').attr('y', '-10.1');
|
||||||
let lineIndex = 0;
|
let lineIndex = 0;
|
||||||
for (const line of structuredText) {
|
for (const line of structuredText) {
|
||||||
@@ -180,7 +186,7 @@ export function replaceIconSubstring(text: string) {
|
|||||||
|
|
||||||
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'
|
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'
|
||||||
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
|
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
|
||||||
export const createText = (
|
export const createText = async (
|
||||||
el,
|
el,
|
||||||
text = '',
|
text = '',
|
||||||
{
|
{
|
||||||
@@ -194,22 +200,77 @@ export const createText = (
|
|||||||
} = {},
|
} = {},
|
||||||
config: MermaidConfig
|
config: MermaidConfig
|
||||||
) => {
|
) => {
|
||||||
log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
|
log.info(
|
||||||
|
'XYZ createText',
|
||||||
|
text,
|
||||||
|
style,
|
||||||
|
isTitle,
|
||||||
|
classes,
|
||||||
|
useHtmlLabels,
|
||||||
|
isNode,
|
||||||
|
'addSvgBackground: ',
|
||||||
|
addSvgBackground
|
||||||
|
);
|
||||||
if (useHtmlLabels) {
|
if (useHtmlLabels) {
|
||||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
|
|
||||||
const htmlText = markdownToHTML(text, config);
|
const htmlText = markdownToHTML(text, config);
|
||||||
const decodedReplacedText = replaceIconSubstring(decodeEntities(htmlText));
|
const decodedReplacedText = replaceIconSubstring(decodeEntities(htmlText));
|
||||||
|
|
||||||
|
//for Katex the text could contain escaped characters, \\relax that should be transformed to \relax
|
||||||
|
const inputForKatex = text.replace(/\\\\/g, '\\');
|
||||||
|
|
||||||
const node = {
|
const node = {
|
||||||
isNode,
|
isNode,
|
||||||
label: decodedReplacedText,
|
label: hasKatex(text) ? inputForKatex : decodedReplacedText,
|
||||||
labelStyle: style.replace('fill:', 'color:'),
|
labelStyle: style.replace('fill:', 'color:'),
|
||||||
};
|
};
|
||||||
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
|
const vertexNode = await addHtmlSpan(el, node, width, classes, addSvgBackground);
|
||||||
return vertexNode;
|
return vertexNode;
|
||||||
} else {
|
} else {
|
||||||
const structuredText = markdownToLines(text, config);
|
//sometimes the user might add br tags with 1 or more spaces in between, so we need to replace them with <br/>
|
||||||
const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
|
const sanitizeBR = text.replace(/<br\s*\/?>/g, '<br/>');
|
||||||
|
const structuredText = markdownToLines(sanitizeBR.replace('<br>', '<br/>'), config);
|
||||||
|
const svgLabel = createFormattedText(
|
||||||
|
width,
|
||||||
|
el,
|
||||||
|
structuredText,
|
||||||
|
text ? addSvgBackground : false
|
||||||
|
);
|
||||||
|
if (isNode) {
|
||||||
|
if (/stroke:/.exec(style)) {
|
||||||
|
style = style.replace('stroke:', 'lineColor:');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeLabelTextStyle = style
|
||||||
|
.replace(/stroke:[^;]+;?/g, '')
|
||||||
|
.replace(/stroke-width:[^;]+;?/g, '')
|
||||||
|
.replace(/fill:[^;]+;?/g, '')
|
||||||
|
.replace(/color:/g, 'fill:');
|
||||||
|
select(svgLabel).attr('style', nodeLabelTextStyle);
|
||||||
|
// svgLabel.setAttribute('style', style);
|
||||||
|
} else {
|
||||||
|
//On style, assume `stroke`, `stroke-width` are used for edge path, so remove them
|
||||||
|
// remove `fill`
|
||||||
|
// use `background` as `fill` for label rect,
|
||||||
|
|
||||||
|
const edgeLabelRectStyle = style
|
||||||
|
.replace(/stroke:[^;]+;?/g, '')
|
||||||
|
.replace(/stroke-width:[^;]+;?/g, '')
|
||||||
|
.replace(/fill:[^;]+;?/g, '')
|
||||||
|
.replace(/background:/g, 'fill:');
|
||||||
|
select(svgLabel)
|
||||||
|
.select('rect')
|
||||||
|
.attr('style', edgeLabelRectStyle.replace(/background:/g, 'fill:'));
|
||||||
|
|
||||||
|
// for text, update fill color with `color`
|
||||||
|
const edgeLabelTextStyle = style
|
||||||
|
.replace(/stroke:[^;]+;?/g, '')
|
||||||
|
.replace(/stroke-width:[^;]+;?/g, '')
|
||||||
|
.replace(/fill:[^;]+;?/g, '')
|
||||||
|
.replace(/color:/g, 'fill:');
|
||||||
|
select(svgLabel).select('text').attr('style', edgeLabelTextStyle);
|
||||||
|
}
|
||||||
return svgLabel;
|
return svgLabel;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import type { MermaidConfig } from '../config.type.js';
|
|||||||
* @returns processed markdown
|
* @returns processed markdown
|
||||||
*/
|
*/
|
||||||
function preprocessMarkdown(markdown: string, { markdownAutoWrap }: MermaidConfig): string {
|
function preprocessMarkdown(markdown: string, { markdownAutoWrap }: MermaidConfig): string {
|
||||||
|
//Replace <br/>with \n
|
||||||
|
const withoutBR = markdown.replace(/<br\/>/g, '\n');
|
||||||
// Replace multiple newlines with a single newline
|
// Replace multiple newlines with a single newline
|
||||||
const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, '\n');
|
const withoutMultipleNewlines = withoutBR.replace(/\n{2,}/g, '\n');
|
||||||
// Remove extra spaces at the beginning of each line
|
// Remove extra spaces at the beginning of each line
|
||||||
const withoutExtraSpaces = dedent(withoutMultipleNewlines);
|
const withoutExtraSpaces = dedent(withoutMultipleNewlines);
|
||||||
if (markdownAutoWrap === false) {
|
if (markdownAutoWrap === false) {
|
||||||
@@ -46,6 +48,8 @@ export function markdownToLines(markdown: string, config: MermaidConfig = {}): M
|
|||||||
node.tokens.forEach((contentNode) => {
|
node.tokens.forEach((contentNode) => {
|
||||||
processNode(contentNode as MarkedToken, node.type);
|
processNode(contentNode as MarkedToken, node.type);
|
||||||
});
|
});
|
||||||
|
} else if (node.type === 'html') {
|
||||||
|
lines[currentLine].push({ content: node.text, type: 'normal' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +58,8 @@ export function markdownToLines(markdown: string, config: MermaidConfig = {}): M
|
|||||||
treeNode.tokens?.forEach((contentNode) => {
|
treeNode.tokens?.forEach((contentNode) => {
|
||||||
processNode(contentNode as MarkedToken);
|
processNode(contentNode as MarkedToken);
|
||||||
});
|
});
|
||||||
|
} else if (treeNode.type === 'html') {
|
||||||
|
lines[currentLine].push({ content: treeNode.text, type: 'normal' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,6 +81,10 @@ export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidCo
|
|||||||
return `<em>${node.tokens?.map(output).join('')}</em>`;
|
return `<em>${node.tokens?.map(output).join('')}</em>`;
|
||||||
} else if (node.type === 'paragraph') {
|
} else if (node.type === 'paragraph') {
|
||||||
return `<p>${node.tokens?.map(output).join('')}</p>`;
|
return `<p>${node.tokens?.map(output).join('')}</p>`;
|
||||||
|
} else if (node.type === 'space') {
|
||||||
|
return '';
|
||||||
|
} else if (node.type === 'html') {
|
||||||
|
return `${node.text}`;
|
||||||
}
|
}
|
||||||
return `Unsupported markdown: ${node.type}`;
|
return `Unsupported markdown: ${node.type}`;
|
||||||
}
|
}
|
||||||
|
|||||||
54
packages/mermaid/src/rendering-util/insertElementsForSize.js
Normal file
54
packages/mermaid/src/rendering-util/insertElementsForSize.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { select } from 'd3';
|
||||||
|
import { insertNode } from '../dagre-wrapper/nodes.js';
|
||||||
|
|
||||||
|
export const getDiagramElements = (id, securityLevel) => {
|
||||||
|
let sandboxElement;
|
||||||
|
if (securityLevel === 'sandbox') {
|
||||||
|
sandboxElement = select('#i' + id);
|
||||||
|
}
|
||||||
|
const root =
|
||||||
|
securityLevel === 'sandbox'
|
||||||
|
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||||
|
: select('body');
|
||||||
|
|
||||||
|
const svg = root.select(`[id="${id}"]`);
|
||||||
|
|
||||||
|
// Run the renderer. This is what draws the final graph.
|
||||||
|
|
||||||
|
// @ts-ignore todo: fix this
|
||||||
|
const element = root.select('#' + id + ' g');
|
||||||
|
return { svg, element };
|
||||||
|
};
|
||||||
|
|
||||||
|
export function insertElementsForSize(el, data) {
|
||||||
|
const nodesElem = el.insert('g').attr('class', 'nodes');
|
||||||
|
el.insert('g').attr('class', 'edges');
|
||||||
|
data.nodes.forEach(async (item) => {
|
||||||
|
item.shape = 'rect';
|
||||||
|
await insertNode(nodesElem, {
|
||||||
|
...item,
|
||||||
|
class: 'default flowchart-label',
|
||||||
|
labelStyle: '',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
rx: 0,
|
||||||
|
ry: 0,
|
||||||
|
height: 100,
|
||||||
|
shape: 'rect',
|
||||||
|
padding: 8,
|
||||||
|
});
|
||||||
|
// Create a new DOM element
|
||||||
|
// const element = document.createElement('div');
|
||||||
|
|
||||||
|
// // Set the content of the element to the name of the item
|
||||||
|
// element.textContent = item.name;
|
||||||
|
|
||||||
|
// // Set the size of the element to the size of the item
|
||||||
|
// element.style.width = `${item.size}px`;
|
||||||
|
// element.style.height = `${item.size}px`;
|
||||||
|
|
||||||
|
// Append the element to the body of the document
|
||||||
|
// document.body.appendChild(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';
|
||||||
|
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
|
||||||
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||||
|
import insertMarkers from '../../rendering-elements/markers.js';
|
||||||
|
import { updateNodeBounds } from '../../rendering-elements/shapes/util.js';
|
||||||
|
import {
|
||||||
|
clear as clearGraphlib,
|
||||||
|
clusterDb,
|
||||||
|
adjustClustersAndEdges,
|
||||||
|
findNonClusterChild,
|
||||||
|
sortNodesByHierarchy,
|
||||||
|
} from './mermaid-graphlib.js';
|
||||||
|
import {
|
||||||
|
insertNode,
|
||||||
|
positionNode,
|
||||||
|
clear as clearNodes,
|
||||||
|
setNodeElem,
|
||||||
|
} from '../../rendering-elements/nodes.js';
|
||||||
|
import { insertCluster, clear as clearClusters } from '../../rendering-elements/clusters.js';
|
||||||
|
import {
|
||||||
|
insertEdgeLabel,
|
||||||
|
positionEdgeLabel,
|
||||||
|
insertEdge,
|
||||||
|
clear as clearEdges,
|
||||||
|
} from '../../rendering-elements/edges.js';
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
||||||
|
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
|
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
||||||
|
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
|
||||||
|
const dir = graph.graph().rankdir;
|
||||||
|
log.trace('Dir in recursive render - dir:', dir);
|
||||||
|
|
||||||
|
const elem = _elem.insert('g').attr('class', 'root');
|
||||||
|
if (!graph.nodes()) {
|
||||||
|
log.info('No nodes found for', graph);
|
||||||
|
} else {
|
||||||
|
log.info('Recursive render XXX', graph.nodes());
|
||||||
|
}
|
||||||
|
if (graph.edges().length > 0) {
|
||||||
|
log.info('Recursive edges', graph.edge(graph.edges()[0]));
|
||||||
|
}
|
||||||
|
const clusters = elem.insert('g').attr('class', 'clusters');
|
||||||
|
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
|
||||||
|
const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
|
||||||
|
const nodes = elem.insert('g').attr('class', 'nodes');
|
||||||
|
|
||||||
|
// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
|
||||||
|
// to the abstract node and is later used by dagre for the layout
|
||||||
|
await Promise.all(
|
||||||
|
graph.nodes().map(async function (v) {
|
||||||
|
const node = graph.node(v);
|
||||||
|
if (parentCluster !== undefined) {
|
||||||
|
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
|
||||||
|
// data.clusterPositioning = true;
|
||||||
|
log.trace(
|
||||||
|
'Setting data for parent cluster XXX\n Node.id = ',
|
||||||
|
v,
|
||||||
|
'\n data=',
|
||||||
|
data.height,
|
||||||
|
'\nParent cluster',
|
||||||
|
parentCluster.height
|
||||||
|
);
|
||||||
|
graph.setNode(parentCluster.id, data);
|
||||||
|
if (!graph.parent(v)) {
|
||||||
|
log.trace('Setting parent', v, parentCluster.id);
|
||||||
|
graph.setParent(v, parentCluster.id, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
|
||||||
|
if (node?.clusterNode) {
|
||||||
|
// const children = graph.children(v);
|
||||||
|
log.info('Cluster identified XBX', v, node.width, graph.node(v));
|
||||||
|
|
||||||
|
// `node.graph.setGraph` applies the graph configurations such as nodeSpacing to subgraphs as without this the default values would be used
|
||||||
|
// We override only the `ranksep` and `nodesep` configurations to allow for setting subgraph spacing while avoiding overriding other properties
|
||||||
|
const { ranksep, nodesep } = graph.graph();
|
||||||
|
node.graph.setGraph({
|
||||||
|
...node.graph.graph(),
|
||||||
|
ranksep: ranksep + 25,
|
||||||
|
nodesep,
|
||||||
|
});
|
||||||
|
|
||||||
|
// "o" will contain the full cluster not just the children
|
||||||
|
const o = await recursiveRender(
|
||||||
|
nodes,
|
||||||
|
node.graph,
|
||||||
|
diagramType,
|
||||||
|
id,
|
||||||
|
graph.node(v),
|
||||||
|
siteConfig
|
||||||
|
);
|
||||||
|
const newEl = o.elem;
|
||||||
|
updateNodeBounds(node, newEl);
|
||||||
|
// node.height = o.diff;
|
||||||
|
node.diff = o.diff || 0;
|
||||||
|
log.info(
|
||||||
|
'New compound node after recursive render XAX',
|
||||||
|
v,
|
||||||
|
'width',
|
||||||
|
// node,
|
||||||
|
node.width,
|
||||||
|
'height',
|
||||||
|
node.height
|
||||||
|
// node.x,
|
||||||
|
// node.y
|
||||||
|
);
|
||||||
|
setNodeElem(newEl, node);
|
||||||
|
} else {
|
||||||
|
if (graph.children(v).length > 0) {
|
||||||
|
// This is a cluster but not to be rendered recursively
|
||||||
|
// Render as before
|
||||||
|
log.info(
|
||||||
|
'Cluster - the non recursive path XBX',
|
||||||
|
v,
|
||||||
|
node.id,
|
||||||
|
node,
|
||||||
|
node.width,
|
||||||
|
'Graph:',
|
||||||
|
graph
|
||||||
|
);
|
||||||
|
log.info(findNonClusterChild(node.id, graph));
|
||||||
|
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
|
||||||
|
// insertCluster(clusters, graph.node(v));
|
||||||
|
} else {
|
||||||
|
log.trace('Node - the non recursive path XAX', v, node.id, node);
|
||||||
|
await insertNode(nodes, graph.node(v), dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const processEdges = async () => {
|
||||||
|
const edgePromises = graph.edges().map(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.get(e.v),
|
||||||
|
clusterDb.get(e.w)
|
||||||
|
);
|
||||||
|
await insertEdgeLabel(edgeLabels, edge);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(edgePromises);
|
||||||
|
};
|
||||||
|
|
||||||
|
await processEdges();
|
||||||
|
|
||||||
|
log.info('Graph before layout:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
|
|
||||||
|
log.info('############################################# XXX');
|
||||||
|
log.info('### Layout ### XXX');
|
||||||
|
log.info('############################################# XXX');
|
||||||
|
|
||||||
|
dagreLayout(graph);
|
||||||
|
|
||||||
|
log.info('Graph after layout:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
|
// Move the nodes to the correct place
|
||||||
|
let diff = 0;
|
||||||
|
let { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||||
|
await Promise.all(
|
||||||
|
sortNodesByHierarchy(graph).map(async function (v) {
|
||||||
|
const node = graph.node(v);
|
||||||
|
log.info(
|
||||||
|
'Position XBX => ' + v + ': (' + node.x,
|
||||||
|
',' + node.y,
|
||||||
|
') width: ',
|
||||||
|
node.width,
|
||||||
|
' height: ',
|
||||||
|
node.height
|
||||||
|
);
|
||||||
|
if (node?.clusterNode) {
|
||||||
|
// Adjust for padding when on root level
|
||||||
|
node.y += subGraphTitleTotalMargin;
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
'A tainted cluster node XBX1',
|
||||||
|
v,
|
||||||
|
node.id,
|
||||||
|
node.width,
|
||||||
|
node.height,
|
||||||
|
node.x,
|
||||||
|
node.y,
|
||||||
|
graph.parent(v)
|
||||||
|
);
|
||||||
|
clusterDb.get(node.id).node = node;
|
||||||
|
positionNode(node);
|
||||||
|
} else {
|
||||||
|
// A tainted cluster node
|
||||||
|
if (graph.children(v).length > 0) {
|
||||||
|
log.info(
|
||||||
|
'A pure cluster node XBX1',
|
||||||
|
v,
|
||||||
|
node.id,
|
||||||
|
node.x,
|
||||||
|
node.y,
|
||||||
|
node.width,
|
||||||
|
node.height,
|
||||||
|
graph.parent(v)
|
||||||
|
);
|
||||||
|
node.height += subGraphTitleTotalMargin;
|
||||||
|
graph.node(node.parentId);
|
||||||
|
const halfPadding = node?.padding / 2 || 0;
|
||||||
|
const labelHeight = node?.labelBBox?.height || 0;
|
||||||
|
const offsetY = labelHeight - halfPadding || 0;
|
||||||
|
log.debug('OffsetY', offsetY, 'labelHeight', labelHeight, 'halfPadding', halfPadding);
|
||||||
|
await insertCluster(clusters, node);
|
||||||
|
|
||||||
|
// A cluster in the non-recursive way
|
||||||
|
clusterDb.get(node.id).node = node;
|
||||||
|
} else {
|
||||||
|
// Regular node
|
||||||
|
const parent = graph.node(node.parentId);
|
||||||
|
node.y += subGraphTitleTotalMargin / 2;
|
||||||
|
log.info(
|
||||||
|
'A regular node XBX1 - using the padding',
|
||||||
|
node.id,
|
||||||
|
'parent',
|
||||||
|
node.parentId,
|
||||||
|
node.width,
|
||||||
|
node.height,
|
||||||
|
node.x,
|
||||||
|
node.y,
|
||||||
|
'offsetY',
|
||||||
|
node.offsetY,
|
||||||
|
'parent',
|
||||||
|
parent,
|
||||||
|
parent?.offsetY,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
|
||||||
|
positionNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move the edge labels to the correct place after layout
|
||||||
|
graph.edges().forEach(function (e) {
|
||||||
|
const edge = graph.edge(e);
|
||||||
|
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
|
||||||
|
|
||||||
|
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
|
||||||
|
const startNode = graph.node(e.v);
|
||||||
|
var endNode = graph.node(e.w);
|
||||||
|
const paths = insertEdge(edgePaths, edge, clusterDb, diagramType, startNode, endNode, id);
|
||||||
|
positionEdgeLabel(edge, paths);
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.nodes().forEach(function (v) {
|
||||||
|
const n = graph.node(v);
|
||||||
|
log.info(v, n.type, n.diff);
|
||||||
|
if (n.isGroup) {
|
||||||
|
diff = n.diff;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.warn('Returning from recursive render XAX', elem, diff);
|
||||||
|
return { elem, diff };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const render = async (data4Layout, svg, element) => {
|
||||||
|
const graph = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true,
|
||||||
|
})
|
||||||
|
.setGraph({
|
||||||
|
rankdir: data4Layout.direction,
|
||||||
|
nodesep:
|
||||||
|
data4Layout.config?.nodeSpacing ||
|
||||||
|
data4Layout.config?.flowchart?.nodeSpacing ||
|
||||||
|
data4Layout.nodeSpacing,
|
||||||
|
ranksep:
|
||||||
|
data4Layout.config?.rankSpacing ||
|
||||||
|
data4Layout.config?.flowchart?.rankSpacing ||
|
||||||
|
data4Layout.rankSpacing,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8,
|
||||||
|
})
|
||||||
|
.setDefaultEdgeLabel(function () {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||||
|
clearNodes();
|
||||||
|
clearEdges();
|
||||||
|
clearClusters();
|
||||||
|
clearGraphlib();
|
||||||
|
|
||||||
|
data4Layout.nodes.forEach((node) => {
|
||||||
|
graph.setNode(node.id, { ...node });
|
||||||
|
if (node.parentId) {
|
||||||
|
graph.setParent(node.id, node.parentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug('Edges:', data4Layout.edges);
|
||||||
|
data4Layout.edges.forEach((edge) => {
|
||||||
|
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
|
adjustClustersAndEdges(graph);
|
||||||
|
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
|
const siteConfig = getConfig();
|
||||||
|
await recursiveRender(
|
||||||
|
element,
|
||||||
|
graph,
|
||||||
|
data4Layout.type,
|
||||||
|
data4Layout.diagramId,
|
||||||
|
undefined,
|
||||||
|
siteConfig
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
/** Decorates with functions required by mermaids dagre-wrapper. */
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||||
|
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
|
||||||
|
|
||||||
|
export let clusterDb = new Map();
|
||||||
|
let descendants = new Map();
|
||||||
|
let parents = new Map();
|
||||||
|
|
||||||
|
export const clear = () => {
|
||||||
|
descendants.clear();
|
||||||
|
parents.clear();
|
||||||
|
clusterDb.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDescendant = (id, ancestorId) => {
|
||||||
|
const ancestorDescendants = descendants.get(ancestorId) || [];
|
||||||
|
log.trace('In isDescendant', ancestorId, ' ', id, ' = ', ancestorDescendants.includes(id));
|
||||||
|
return ancestorDescendants.includes(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const edgeInCluster = (edge, clusterId) => {
|
||||||
|
const clusterDescendants = descendants.get(clusterId) || [];
|
||||||
|
log.info('Descendants of ', clusterId, ' is ', clusterDescendants);
|
||||||
|
log.info('Edge is ', edge);
|
||||||
|
if (edge.v === clusterId || edge.w === clusterId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clusterDescendants) {
|
||||||
|
log.debug('Tilt, ', clusterId, ',not in descendants');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
clusterDescendants.includes(edge.v) ||
|
||||||
|
isDescendant(edge.v, clusterId) ||
|
||||||
|
isDescendant(edge.w, clusterId) ||
|
||||||
|
clusterDescendants.includes(edge.w)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copy = (clusterId, graph, newGraph, rootId) => {
|
||||||
|
log.warn(
|
||||||
|
'Copying children of ',
|
||||||
|
clusterId,
|
||||||
|
'root',
|
||||||
|
rootId,
|
||||||
|
'data',
|
||||||
|
graph.node(clusterId),
|
||||||
|
rootId
|
||||||
|
);
|
||||||
|
const nodes = graph.children(clusterId) || [];
|
||||||
|
|
||||||
|
if (clusterId !== rootId) {
|
||||||
|
nodes.push(clusterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn('Copying (nodes) clusterId', clusterId, 'nodes', nodes);
|
||||||
|
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
if (graph.children(node).length > 0) {
|
||||||
|
copy(node, graph, newGraph, rootId);
|
||||||
|
} else {
|
||||||
|
const data = graph.node(node);
|
||||||
|
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId);
|
||||||
|
newGraph.setNode(node, data);
|
||||||
|
if (rootId !== graph.parent(node)) {
|
||||||
|
log.warn('Setting parent', node, graph.parent(node));
|
||||||
|
newGraph.setParent(node, graph.parent(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clusterId !== rootId && node !== clusterId) {
|
||||||
|
log.debug('Setting parent', node, clusterId);
|
||||||
|
newGraph.setParent(node, clusterId);
|
||||||
|
} else {
|
||||||
|
log.info('In copy ', clusterId, 'root', rootId, 'data', graph.node(clusterId), rootId);
|
||||||
|
log.debug(
|
||||||
|
'Not Setting parent for node=',
|
||||||
|
node,
|
||||||
|
'cluster!==rootId',
|
||||||
|
clusterId !== rootId,
|
||||||
|
'node!==clusterId',
|
||||||
|
node !== clusterId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const edges = graph.edges(node);
|
||||||
|
log.debug('Copying Edges', edges);
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
log.info('Edge', edge);
|
||||||
|
const data = graph.edge(edge.v, edge.w, edge.name);
|
||||||
|
log.info('Edge data', data, rootId);
|
||||||
|
try {
|
||||||
|
if (edgeInCluster(edge, rootId)) {
|
||||||
|
log.info('Copying as ', edge.v, edge.w, data, edge.name);
|
||||||
|
newGraph.setEdge(edge.v, edge.w, data, edge.name);
|
||||||
|
log.info('newGraph edges ', newGraph.edges(), newGraph.edge(newGraph.edges()[0]));
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
'Skipping copy of edge ',
|
||||||
|
edge.v,
|
||||||
|
'-->',
|
||||||
|
edge.w,
|
||||||
|
' rootId: ',
|
||||||
|
rootId,
|
||||||
|
' clusterId:',
|
||||||
|
clusterId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log.debug('Removing node', node);
|
||||||
|
graph.removeNode(node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractDescendants = (id, graph) => {
|
||||||
|
const children = graph.children(id);
|
||||||
|
let res = [...children];
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
parents.set(child, id);
|
||||||
|
res = [...res, ...extractDescendants(child, graph)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validate = (graph) => {
|
||||||
|
const edges = graph.edges();
|
||||||
|
log.trace('Edges: ', edges);
|
||||||
|
for (const edge of edges) {
|
||||||
|
if (graph.children(edge.v).length > 0) {
|
||||||
|
log.trace('The node ', edge.v, ' is part of and edge even though it has children');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (graph.children(edge.w).length > 0) {
|
||||||
|
log.trace('The node ', edge.w, ' is part of and edge even though it has children');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findCommonEdges = (graph, id1, id2) => {
|
||||||
|
const edges1 = graph.edges().filter((edge) => edge.v === id1 || edge.w === id1);
|
||||||
|
const edges2 = graph.edges().filter((edge) => edge.v === id2 || edge.w === id2);
|
||||||
|
const edges1Prim = edges1.map((edge) => {
|
||||||
|
return { v: edge.v === id1 ? id2 : edge.v, w: edge.w === id1 ? id1 : edge.w };
|
||||||
|
});
|
||||||
|
const edges2Prim = edges2.map((edge) => {
|
||||||
|
return { v: edge.v, w: edge.w };
|
||||||
|
});
|
||||||
|
const result = edges1Prim.filter((edgeIn1) => {
|
||||||
|
return edges2Prim.some((edge) => edgeIn1.v === edge.v && edgeIn1.w === edge.w);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findNonClusterChild = (id, graph, clusterId) => {
|
||||||
|
const children = graph.children(id);
|
||||||
|
log.trace('Searching children of id ', id, children);
|
||||||
|
if (children.length < 1) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
let reserve;
|
||||||
|
for (const child of children) {
|
||||||
|
const _id = findNonClusterChild(child, graph, clusterId);
|
||||||
|
|
||||||
|
const commonEdges = findCommonEdges(graph, clusterId, _id);
|
||||||
|
|
||||||
|
if (_id) {
|
||||||
|
if (commonEdges.length > 0) {
|
||||||
|
reserve = _id;
|
||||||
|
} else {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reserve;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAnchorId = (id) => {
|
||||||
|
if (!clusterDb.has(id)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
if (!clusterDb.get(id).externalConnections) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clusterDb.has(id)) {
|
||||||
|
return clusterDb.get(id).id;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adjustClustersAndEdges = (graph, depth) => {
|
||||||
|
if (!graph || depth > 10) {
|
||||||
|
log.debug('Opting out, no graph ');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.debug('Opting in, graph ');
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.nodes().forEach(function (id) {
|
||||||
|
const children = graph.children(id);
|
||||||
|
if (children.length > 0) {
|
||||||
|
log.warn(
|
||||||
|
'Cluster identified',
|
||||||
|
id,
|
||||||
|
' Replacement id in edges: ',
|
||||||
|
findNonClusterChild(id, graph, id)
|
||||||
|
);
|
||||||
|
descendants.set(id, extractDescendants(id, graph));
|
||||||
|
clusterDb.set(id, { id: findNonClusterChild(id, graph, id), clusterData: graph.node(id) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.nodes().forEach(function (id) {
|
||||||
|
const children = graph.children(id);
|
||||||
|
const edges = graph.edges();
|
||||||
|
if (children.length > 0) {
|
||||||
|
log.debug('Cluster identified', id, descendants);
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
const d1 = isDescendant(edge.v, id);
|
||||||
|
const d2 = isDescendant(edge.w, id);
|
||||||
|
|
||||||
|
if (d1 ^ d2) {
|
||||||
|
log.warn('Edge: ', edge, ' leaves cluster ', id);
|
||||||
|
log.warn('Descendants of XXX ', id, ': ', descendants.get(id));
|
||||||
|
clusterDb.get(id).externalConnections = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.debug('Not a cluster ', id, descendants);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let id of clusterDb.keys()) {
|
||||||
|
const nonClusterChild = clusterDb.get(id).id;
|
||||||
|
const parent = graph.parent(nonClusterChild);
|
||||||
|
|
||||||
|
if (parent !== id && clusterDb.has(parent) && !clusterDb.get(parent).externalConnections) {
|
||||||
|
clusterDb.get(id).id = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.edges().forEach(function (e) {
|
||||||
|
const edge = graph.edge(e);
|
||||||
|
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
|
||||||
|
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e)));
|
||||||
|
|
||||||
|
let v = e.v;
|
||||||
|
let w = e.w;
|
||||||
|
log.warn(
|
||||||
|
'Fix XXX',
|
||||||
|
clusterDb,
|
||||||
|
'ids:',
|
||||||
|
e.v,
|
||||||
|
e.w,
|
||||||
|
'Translating: ',
|
||||||
|
clusterDb.get(e.v),
|
||||||
|
' --- ',
|
||||||
|
clusterDb.get(e.w)
|
||||||
|
);
|
||||||
|
if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
|
||||||
|
log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
|
||||||
|
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
||||||
|
v = getAnchorId(e.v);
|
||||||
|
w = getAnchorId(e.w);
|
||||||
|
graph.removeEdge(e.v, e.w, e.name);
|
||||||
|
const specialId1 = e.w + '---' + e.v + '---1';
|
||||||
|
const specialId2 = e.w + '---' + e.v + '---2';
|
||||||
|
graph.setNode(specialId1, {
|
||||||
|
domId: specialId1,
|
||||||
|
id: specialId1,
|
||||||
|
labelStyle: '',
|
||||||
|
label: '',
|
||||||
|
padding: 0,
|
||||||
|
shape: 'labelRect',
|
||||||
|
style: '',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
});
|
||||||
|
graph.setNode(specialId2, {
|
||||||
|
domId: specialId2,
|
||||||
|
id: specialId2,
|
||||||
|
labelStyle: '',
|
||||||
|
padding: 0,
|
||||||
|
shape: 'labelRect',
|
||||||
|
style: '',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
});
|
||||||
|
const edge1 = structuredClone(edge);
|
||||||
|
const edgeMid = structuredClone(edge);
|
||||||
|
const edge2 = structuredClone(edge);
|
||||||
|
edge1.label = '';
|
||||||
|
edge1.arrowTypeEnd = 'none';
|
||||||
|
edge1.id = e.name + '-cyclic-special-1';
|
||||||
|
edgeMid.arrowTypeEnd = 'none';
|
||||||
|
edgeMid.id = e.name + '-cyclic-special-mid';
|
||||||
|
edge2.label = '';
|
||||||
|
edge1.fromCluster = e.v;
|
||||||
|
edge2.toCluster = e.v;
|
||||||
|
edge2.id = e.name + '-cyclic-special-2';
|
||||||
|
graph.setEdge(v, specialId1, edge1, e.name + '-cyclic-special-0');
|
||||||
|
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
|
||||||
|
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
|
||||||
|
} else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
|
||||||
|
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
||||||
|
v = getAnchorId(e.v);
|
||||||
|
w = getAnchorId(e.w);
|
||||||
|
graph.removeEdge(e.v, e.w, e.name);
|
||||||
|
if (v !== e.v) {
|
||||||
|
const parent = graph.parent(v);
|
||||||
|
clusterDb.get(parent).externalConnections = true;
|
||||||
|
edge.fromCluster = e.v;
|
||||||
|
}
|
||||||
|
if (w !== e.w) {
|
||||||
|
const parent = graph.parent(w);
|
||||||
|
clusterDb.get(parent).externalConnections = true;
|
||||||
|
edge.toCluster = e.w;
|
||||||
|
}
|
||||||
|
log.warn('Fix Replacing with XXX', v, w, e.name);
|
||||||
|
graph.setEdge(v, w, edge, e.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.warn('Adjusted Graph', graphlibJson.write(graph));
|
||||||
|
extractor(graph, 0);
|
||||||
|
|
||||||
|
log.trace(clusterDb);
|
||||||
|
|
||||||
|
// Remove references to extracted cluster
|
||||||
|
// graph.edges().forEach((edge) => {
|
||||||
|
// if (isDescendant(edge.v, clusterId) || isDescendant(edge.w, clusterId)) {
|
||||||
|
// graph.removeEdge(edge);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractor = (graph, depth) => {
|
||||||
|
log.warn('extractor - ', depth, graphlibJson.write(graph), graph.children('D'));
|
||||||
|
if (depth > 10) {
|
||||||
|
log.error('Bailing out');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let nodes = graph.nodes();
|
||||||
|
let hasChildren = false;
|
||||||
|
for (const node of nodes) {
|
||||||
|
const children = graph.children(node);
|
||||||
|
hasChildren = hasChildren || children.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChildren) {
|
||||||
|
log.debug('Done, no node has children', graph.nodes());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug('Nodes = ', nodes, depth);
|
||||||
|
for (const node of nodes) {
|
||||||
|
log.debug(
|
||||||
|
'Extracting node',
|
||||||
|
node,
|
||||||
|
clusterDb,
|
||||||
|
clusterDb.has(node) && !clusterDb.get(node).externalConnections,
|
||||||
|
!graph.parent(node),
|
||||||
|
graph.node(node),
|
||||||
|
graph.children('D'),
|
||||||
|
' Depth ',
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
if (!clusterDb.has(node)) {
|
||||||
|
log.debug('Not a cluster', node, depth);
|
||||||
|
} else if (
|
||||||
|
!clusterDb.get(node).externalConnections &&
|
||||||
|
graph.children(node) &&
|
||||||
|
graph.children(node).length > 0
|
||||||
|
) {
|
||||||
|
log.warn(
|
||||||
|
'Cluster without external connections, without a parent and with children',
|
||||||
|
node,
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
|
||||||
|
const graphSettings = graph.graph();
|
||||||
|
let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB';
|
||||||
|
if (clusterDb.get(node)?.clusterData?.dir) {
|
||||||
|
dir = clusterDb.get(node).clusterData.dir;
|
||||||
|
log.warn('Fixing dir', clusterDb.get(node).clusterData.dir, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clusterGraph = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true,
|
||||||
|
})
|
||||||
|
.setGraph({
|
||||||
|
rankdir: dir,
|
||||||
|
nodesep: 50,
|
||||||
|
ranksep: 50,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8,
|
||||||
|
})
|
||||||
|
.setDefaultEdgeLabel(function () {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
log.warn('Old graph before copy', graphlibJson.write(graph));
|
||||||
|
copy(node, graph, clusterGraph, node);
|
||||||
|
graph.setNode(node, {
|
||||||
|
clusterNode: true,
|
||||||
|
id: node,
|
||||||
|
clusterData: clusterDb.get(node).clusterData,
|
||||||
|
label: clusterDb.get(node).label,
|
||||||
|
graph: clusterGraph,
|
||||||
|
});
|
||||||
|
log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph));
|
||||||
|
log.debug('Old graph after copy', graphlibJson.write(graph));
|
||||||
|
} else {
|
||||||
|
log.warn(
|
||||||
|
'Cluster ** ',
|
||||||
|
node,
|
||||||
|
' **not meeting the criteria !externalConnections:',
|
||||||
|
!clusterDb.get(node).externalConnections,
|
||||||
|
' no parent: ',
|
||||||
|
!graph.parent(node),
|
||||||
|
' children ',
|
||||||
|
graph.children(node) && graph.children(node).length > 0,
|
||||||
|
graph.children('D'),
|
||||||
|
depth
|
||||||
|
);
|
||||||
|
log.debug(clusterDb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes = graph.nodes();
|
||||||
|
log.warn('New list of nodes', nodes);
|
||||||
|
for (const node of nodes) {
|
||||||
|
const data = graph.node(node);
|
||||||
|
log.warn(' Now next level', node, data);
|
||||||
|
if (data.clusterNode) {
|
||||||
|
extractor(data.graph, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sorter = (graph, nodes) => {
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let result = Object.assign([], nodes);
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const children = graph.children(node);
|
||||||
|
const sorted = sorter(graph, children);
|
||||||
|
result = [...result, ...sorted];
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortNodesByHierarchy = (graph) => sorter(graph, graph.children());
|
||||||
@@ -0,0 +1,508 @@
|
|||||||
|
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
|
||||||
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
||||||
|
import {
|
||||||
|
validate,
|
||||||
|
adjustClustersAndEdges,
|
||||||
|
extractDescendants,
|
||||||
|
sortNodesByHierarchy,
|
||||||
|
} from './mermaid-graphlib.js';
|
||||||
|
import { setLogLevel, log } from '$root/logger.js';
|
||||||
|
|
||||||
|
describe('Graphlib decorations', () => {
|
||||||
|
let g;
|
||||||
|
beforeEach(function () {
|
||||||
|
setLogLevel(1);
|
||||||
|
g = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true,
|
||||||
|
});
|
||||||
|
g.setGraph({
|
||||||
|
rankdir: 'TB',
|
||||||
|
nodesep: 10,
|
||||||
|
ranksep: 10,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8,
|
||||||
|
});
|
||||||
|
g.setDefaultEdgeLabel(function () {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validate', function () {
|
||||||
|
it('Validate should detect edges between clusters', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a --> b
|
||||||
|
end
|
||||||
|
subgraph C2
|
||||||
|
c
|
||||||
|
end
|
||||||
|
C1 --> C2
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setParent('b', 'C1');
|
||||||
|
g.setParent('c', 'C2');
|
||||||
|
g.setEdge('a', 'b');
|
||||||
|
g.setEdge('C1', 'C2');
|
||||||
|
|
||||||
|
expect(validate(g)).toBe(false);
|
||||||
|
});
|
||||||
|
it('Validate should not detect edges between clusters after adjustment', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a --> b
|
||||||
|
end
|
||||||
|
subgraph C2
|
||||||
|
c
|
||||||
|
end
|
||||||
|
C1 --> C2
|
||||||
|
*/
|
||||||
|
g.setNode('a', {});
|
||||||
|
g.setNode('b', {});
|
||||||
|
g.setNode('c', {});
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setParent('b', 'C1');
|
||||||
|
g.setParent('c', 'C2');
|
||||||
|
g.setEdge('a', 'b');
|
||||||
|
g.setEdge('C1', 'C2');
|
||||||
|
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.info(g.edges());
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Validate should detect edges between clusters and transform clusters GLB4', function () {
|
||||||
|
/*
|
||||||
|
a --> b
|
||||||
|
subgraph C1
|
||||||
|
subgraph C2
|
||||||
|
a
|
||||||
|
end
|
||||||
|
b
|
||||||
|
end
|
||||||
|
C1 --> c
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setNode('C1', { data: 4 });
|
||||||
|
g.setNode('C2', { data: 5 });
|
||||||
|
g.setParent('a', 'C2');
|
||||||
|
g.setParent('b', 'C1');
|
||||||
|
g.setParent('C2', 'C1');
|
||||||
|
g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
||||||
|
g.setEdge('C1', 'c', { name: 'C1-external-link' });
|
||||||
|
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.info(g.nodes());
|
||||||
|
expect(g.nodes().length).toBe(2);
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
});
|
||||||
|
it('Validate should detect edges between clusters and transform clusters GLB5', function () {
|
||||||
|
/*
|
||||||
|
a --> b
|
||||||
|
subgraph C1
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph C2
|
||||||
|
b
|
||||||
|
end
|
||||||
|
C1 -->
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setParent('b', 'C2');
|
||||||
|
// g.setEdge('a', 'b', { name: 'C1-internal-link' });
|
||||||
|
g.setEdge('C1', 'C2', { name: 'C1-external-link' });
|
||||||
|
|
||||||
|
log.info(g.nodes());
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.info(g.nodes());
|
||||||
|
expect(g.nodes().length).toBe(2);
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges GLB6', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a
|
||||||
|
end
|
||||||
|
C1 --> b
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('C1', { data: 3 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||||
|
|
||||||
|
// log.info(g.edges())
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.info(g.edges());
|
||||||
|
expect(g.nodes()).toEqual(['b', 'C1']);
|
||||||
|
expect(g.edges().length).toBe(1);
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
expect(g.node('C1').clusterNode).toBe(true);
|
||||||
|
|
||||||
|
const C1Graph = g.node('C1').graph;
|
||||||
|
expect(C1Graph.nodes()).toEqual(['a']);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges GLB7', function () {
|
||||||
|
/*
|
||||||
|
subgraph C1
|
||||||
|
a
|
||||||
|
end
|
||||||
|
C1 --> b
|
||||||
|
C1 --> c
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'C1');
|
||||||
|
g.setNode('C1', { data: 4 });
|
||||||
|
g.setEdge('C1', 'b', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('C1', 'c', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
log.info(g.node('C1'));
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.info(g.edges());
|
||||||
|
expect(g.nodes()).toEqual(['b', 'c', 'C1']);
|
||||||
|
expect(g.nodes().length).toBe(3);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
const edgeData = g.edge(g.edges()[1]);
|
||||||
|
expect(edgeData.data).toBe('link2');
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
|
||||||
|
const C1Graph = g.node('C1').graph;
|
||||||
|
expect(C1Graph.nodes()).toEqual(['a']);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges GLB8', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
c
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('c', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
// log.info(g.edges())
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
expect(g.nodes()).toEqual(['A', 'B', 'C']);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
const edgeData = g.edge(g.edges()[1]);
|
||||||
|
expect(edgeData.data).toBe('link2');
|
||||||
|
expect(validate(g)).toBe(true);
|
||||||
|
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
expect(CGraph.nodes()).toEqual(['c']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB10', function () {
|
||||||
|
/*
|
||||||
|
subgraph C
|
||||||
|
subgraph D
|
||||||
|
d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.setNode('C', { data: 1 });
|
||||||
|
g.setNode('D', { data: 2 });
|
||||||
|
g.setNode('d', { data: 3 });
|
||||||
|
g.setParent('d', 'D');
|
||||||
|
g.setParent('D', 'C');
|
||||||
|
|
||||||
|
// log.info('Graph before', g.node('D'))
|
||||||
|
// log.info('Graph before', graphlibJson.write(g))
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
// log.info('Graph after', graphlibJson.write(g), g.node('C').graph)
|
||||||
|
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
const DGraph = CGraph.node('D').graph;
|
||||||
|
|
||||||
|
expect(CGraph.nodes()).toEqual(['D']);
|
||||||
|
expect(DGraph.nodes()).toEqual(['d']);
|
||||||
|
|
||||||
|
expect(g.nodes()).toEqual(['C']);
|
||||||
|
expect(g.nodes().length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct data GLB11', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
subgraph D
|
||||||
|
d
|
||||||
|
end
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.setNode('C', { data: 1 });
|
||||||
|
g.setNode('D', { data: 2 });
|
||||||
|
g.setNode('d', { data: 3 });
|
||||||
|
g.setNode('B', { data: 4 });
|
||||||
|
g.setNode('b', { data: 5 });
|
||||||
|
g.setNode('A', { data: 6 });
|
||||||
|
g.setNode('a', { data: 7 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('d', 'D');
|
||||||
|
g.setParent('D', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
log.info('Graph before', g.node('D'));
|
||||||
|
log.info('Graph before', graphlibJson.write(g));
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
log.trace('Graph after', graphlibJson.write(g));
|
||||||
|
expect(g.nodes()).toEqual(['C', 'B', 'A']);
|
||||||
|
expect(g.nodes().length).toBe(3);
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
const AGraph = g.node('A').graph;
|
||||||
|
const BGraph = g.node('B').graph;
|
||||||
|
const CGraph = g.node('C').graph;
|
||||||
|
// log.info(CGraph.nodes());
|
||||||
|
const DGraph = CGraph.node('D').graph;
|
||||||
|
// log.info('DG', CGraph.children('D'));
|
||||||
|
|
||||||
|
log.info('A', AGraph.nodes());
|
||||||
|
expect(AGraph.nodes().length).toBe(1);
|
||||||
|
expect(AGraph.nodes()).toEqual(['a']);
|
||||||
|
log.trace('Nodes', BGraph.nodes());
|
||||||
|
expect(BGraph.nodes().length).toBe(1);
|
||||||
|
expect(BGraph.nodes()).toEqual(['b']);
|
||||||
|
expect(CGraph.nodes()).toEqual(['D']);
|
||||||
|
expect(CGraph.nodes().length).toEqual(1);
|
||||||
|
|
||||||
|
expect(AGraph.edges().length).toBe(0);
|
||||||
|
expect(BGraph.edges().length).toBe(0);
|
||||||
|
expect(CGraph.edges().length).toBe(0);
|
||||||
|
expect(DGraph.nodes()).toEqual(['d']);
|
||||||
|
expect(DGraph.edges().length).toBe(0);
|
||||||
|
// expect(CGraph.node('D')).toEqual({ data: 2 });
|
||||||
|
expect(g.edges().length).toBe(2);
|
||||||
|
|
||||||
|
// expect(g.edges().length).toBe(2);
|
||||||
|
// const edgeData = g.edge(g.edges()[1]);
|
||||||
|
// expect(edgeData.data).toBe('link2');
|
||||||
|
// expect(validate(g)).toBe(true);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB20', function () {
|
||||||
|
/*
|
||||||
|
a --> b
|
||||||
|
subgraph b [Test]
|
||||||
|
c --> d -->e
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setNode('d', { data: 3 });
|
||||||
|
g.setNode('e', { data: 3 });
|
||||||
|
g.setParent('c', 'b');
|
||||||
|
g.setParent('d', 'b');
|
||||||
|
g.setParent('e', 'b');
|
||||||
|
g.setEdge('a', 'b', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('c', 'd', { data: 'link2' }, '2');
|
||||||
|
g.setEdge('d', 'e', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
log.info('Graph before', graphlibJson.write(g));
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
const bGraph = g.node('b').graph;
|
||||||
|
// log.trace('Graph after', graphlibJson.write(g))
|
||||||
|
log.info('Graph after', graphlibJson.write(bGraph));
|
||||||
|
expect(bGraph.nodes().length).toBe(3);
|
||||||
|
expect(bGraph.edges().length).toBe(2);
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges the extracted graphs shall contain the correct links GLB21', function () {
|
||||||
|
/*
|
||||||
|
state a {
|
||||||
|
state b {
|
||||||
|
state c {
|
||||||
|
e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setNode('e', { data: 3 });
|
||||||
|
g.setParent('b', 'a');
|
||||||
|
g.setParent('c', 'b');
|
||||||
|
g.setParent('e', 'c');
|
||||||
|
|
||||||
|
log.info('Graph before', graphlibJson.write(g));
|
||||||
|
adjustClustersAndEdges(g);
|
||||||
|
const aGraph = g.node('a').graph;
|
||||||
|
const bGraph = aGraph.node('b').graph;
|
||||||
|
log.info('Graph after', graphlibJson.write(aGraph));
|
||||||
|
const cGraph = bGraph.node('c').graph;
|
||||||
|
// log.trace('Graph after', graphlibJson.write(g))
|
||||||
|
expect(aGraph.nodes().length).toBe(1);
|
||||||
|
expect(bGraph.nodes().length).toBe(1);
|
||||||
|
expect(cGraph.nodes().length).toBe(1);
|
||||||
|
expect(bGraph.edges().length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('adjustClustersAndEdges should handle nesting GLB77', function () {
|
||||||
|
/*
|
||||||
|
flowchart TB
|
||||||
|
subgraph A
|
||||||
|
b-->B
|
||||||
|
a-->c
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
|
||||||
|
const exportedGraph = JSON.parse(
|
||||||
|
'{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"cssClass":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","cssClasses":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","cssClasses":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}'
|
||||||
|
);
|
||||||
|
const gr = graphlibJson.read(exportedGraph);
|
||||||
|
|
||||||
|
log.info('Graph before', graphlibJson.write(gr));
|
||||||
|
adjustClustersAndEdges(gr);
|
||||||
|
const aGraph = gr.node('A').graph;
|
||||||
|
const bGraph = aGraph.node('B').graph;
|
||||||
|
log.info('Graph after', graphlibJson.write(aGraph));
|
||||||
|
// log.trace('Graph after', graphlibJson.write(g))
|
||||||
|
expect(aGraph.parent('c')).toBe('B');
|
||||||
|
expect(aGraph.parent('B')).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('extractDescendants', function () {
|
||||||
|
let g;
|
||||||
|
beforeEach(function () {
|
||||||
|
setLogLevel(1);
|
||||||
|
g = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true,
|
||||||
|
});
|
||||||
|
g.setGraph({
|
||||||
|
rankdir: 'TB',
|
||||||
|
nodesep: 10,
|
||||||
|
ranksep: 10,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8,
|
||||||
|
});
|
||||||
|
g.setDefaultEdgeLabel(function () {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Simple case of one level descendants GLB9', function () {
|
||||||
|
/*
|
||||||
|
subgraph A
|
||||||
|
a
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph C
|
||||||
|
c
|
||||||
|
end
|
||||||
|
A --> B
|
||||||
|
A --> C
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setNode('c', { data: 3 });
|
||||||
|
g.setParent('a', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('c', 'C');
|
||||||
|
g.setEdge('A', 'B', { data: 'link1' }, '1');
|
||||||
|
g.setEdge('A', 'C', { data: 'link2' }, '2');
|
||||||
|
|
||||||
|
// log.info(g.edges())
|
||||||
|
const d1 = extractDescendants('A', g);
|
||||||
|
const d2 = extractDescendants('B', g);
|
||||||
|
const d3 = extractDescendants('C', g);
|
||||||
|
|
||||||
|
expect(d1).toEqual(['a']);
|
||||||
|
expect(d2).toEqual(['b']);
|
||||||
|
expect(d3).toEqual(['c']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('sortNodesByHierarchy', function () {
|
||||||
|
let g;
|
||||||
|
beforeEach(function () {
|
||||||
|
setLogLevel(1);
|
||||||
|
g = new graphlib.Graph({
|
||||||
|
multigraph: true,
|
||||||
|
compound: true,
|
||||||
|
});
|
||||||
|
g.setGraph({
|
||||||
|
rankdir: 'TB',
|
||||||
|
nodesep: 10,
|
||||||
|
ranksep: 10,
|
||||||
|
marginx: 8,
|
||||||
|
marginy: 8,
|
||||||
|
});
|
||||||
|
g.setDefaultEdgeLabel(function () {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should sort proper en nodes are in reverse order', function () {
|
||||||
|
/*
|
||||||
|
a -->b
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph A
|
||||||
|
B
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setParent('B', 'A');
|
||||||
|
g.setEdge('a', 'b', '1');
|
||||||
|
expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
|
||||||
|
});
|
||||||
|
it('should sort proper en nodes are in correct order', function () {
|
||||||
|
/*
|
||||||
|
a -->b
|
||||||
|
subgraph B
|
||||||
|
b
|
||||||
|
end
|
||||||
|
subgraph A
|
||||||
|
B
|
||||||
|
end
|
||||||
|
*/
|
||||||
|
g.setNode('a', { data: 1 });
|
||||||
|
g.setParent('B', 'A');
|
||||||
|
g.setParent('b', 'B');
|
||||||
|
g.setNode('b', { data: 2 });
|
||||||
|
g.setEdge('a', 'b', '1');
|
||||||
|
expect(sortNodesByHierarchy(g)).toEqual(['a', 'A', 'B', 'b']);
|
||||||
|
});
|
||||||
|
});
|
||||||
44
packages/mermaid/src/rendering-util/render.ts
Normal file
44
packages/mermaid/src/rendering-util/render.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
export interface LayoutAlgorithm {
|
||||||
|
render(data4Layout: any, svg: any, element: any, algorithm?: string): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LayoutLoader = () => Promise<LayoutAlgorithm>;
|
||||||
|
export interface LayoutLoaderDefinition {
|
||||||
|
name: string;
|
||||||
|
loader: LayoutLoader;
|
||||||
|
algorithm?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutAlgorithms: Record<string, LayoutLoaderDefinition> = {};
|
||||||
|
|
||||||
|
export const registerLayoutLoaders = (loaders: LayoutLoaderDefinition[]) => {
|
||||||
|
for (const loader of loaders) {
|
||||||
|
layoutAlgorithms[loader.name] = loader;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Should we load dagre without lazy loading?
|
||||||
|
const registerDefaultLayoutLoaders = () => {
|
||||||
|
registerLayoutLoaders([
|
||||||
|
{
|
||||||
|
name: 'dagre',
|
||||||
|
loader: async () => await import('./layout-algorithms/dagre/index.js'),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: 'elk',
|
||||||
|
// loader: async () => await import('../../../mermaid-layout-elk/src/render.js'),
|
||||||
|
// },
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerDefaultLayoutLoaders();
|
||||||
|
|
||||||
|
export const render = async (data4Layout: any, svg: any, element: any) => {
|
||||||
|
if (!(data4Layout.layoutAlgorithm in layoutAlgorithms)) {
|
||||||
|
throw new Error(`Unknown layout algorithm: ${data4Layout.layoutAlgorithm}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutDefinition = layoutAlgorithms[data4Layout.layoutAlgorithm];
|
||||||
|
const layoutRenderer = await layoutDefinition.loader();
|
||||||
|
return layoutRenderer.render(data4Layout, svg, element, layoutDefinition.algorithm);
|
||||||
|
};
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
import { getConfig } from '$root/diagram-api/diagramAPI.js';
|
||||||
|
import { evaluate } from '$root/diagrams/common/common.js';
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import { getSubGraphTitleMargins } from '$root/utils/subGraphTitleMargins.js';
|
||||||
|
import { select } from 'd3';
|
||||||
|
import rough from 'roughjs';
|
||||||
|
import { createText } from '../createText.ts';
|
||||||
|
import intersectRect from '../rendering-elements/intersect/intersect-rect.js';
|
||||||
|
import createLabel from './createLabel.js';
|
||||||
|
import { createRoundedRectPathD } from './shapes/roundedRectPath.ts';
|
||||||
|
import {
|
||||||
|
styles2String,
|
||||||
|
userNodeOverrides,
|
||||||
|
} from '$root/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js';
|
||||||
|
|
||||||
|
const rect = async (parent, node) => {
|
||||||
|
log.info('Creating subgraph rect for ', node.id, node);
|
||||||
|
const siteConfig = getConfig();
|
||||||
|
const { themeVariables, handDrawnSeed } = siteConfig;
|
||||||
|
const { clusterBkg, clusterBorder } = themeVariables;
|
||||||
|
|
||||||
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
|
|
||||||
|
// Add outer g element
|
||||||
|
const shapeSvg = parent
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', 'cluster ' + node.cssClasses)
|
||||||
|
.attr('id', node.id)
|
||||||
|
.attr('data-look', node.look);
|
||||||
|
|
||||||
|
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
|
||||||
|
|
||||||
|
// Create the label and insert it after the rect
|
||||||
|
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label ');
|
||||||
|
|
||||||
|
const text = await createText(labelEl, node.label, {
|
||||||
|
style: node.labelStyle,
|
||||||
|
useHtmlLabels,
|
||||||
|
isNode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the size of the label
|
||||||
|
let bbox = text.getBBox();
|
||||||
|
|
||||||
|
if (evaluate(siteConfig.flowchart.htmlLabels)) {
|
||||||
|
const div = text.children[0];
|
||||||
|
const dv = select(text);
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width;
|
||||||
|
if (node.width <= bbox.width + node.padding) {
|
||||||
|
node.diff = (width - node.width) / 2 - node.padding;
|
||||||
|
} else {
|
||||||
|
node.diff = -node.padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = node.height;
|
||||||
|
const x = node.x - width / 2;
|
||||||
|
const y = node.y - height / 2;
|
||||||
|
|
||||||
|
log.trace('Data ', node, JSON.stringify(node));
|
||||||
|
let rect;
|
||||||
|
if (node.look === 'handDrawn') {
|
||||||
|
// @ts-ignore TODO: Fix rough typings
|
||||||
|
const rc = rough.svg(shapeSvg);
|
||||||
|
const options = userNodeOverrides(node, {
|
||||||
|
roughness: 0.7,
|
||||||
|
fill: clusterBkg,
|
||||||
|
// fill: 'red',
|
||||||
|
stroke: clusterBorder,
|
||||||
|
fillWeight: 3,
|
||||||
|
seed: handDrawnSeed,
|
||||||
|
});
|
||||||
|
const roughNode = rc.path(createRoundedRectPathD(x, y, width, height, 0), options);
|
||||||
|
rect = shapeSvg.insert(() => {
|
||||||
|
log.debug('Rough node insert CXC', roughNode);
|
||||||
|
return roughNode;
|
||||||
|
}, ':first-child');
|
||||||
|
} else {
|
||||||
|
// add the rect
|
||||||
|
rect = shapeSvg.insert('rect', ':first-child');
|
||||||
|
// center the rect around its coordinate
|
||||||
|
rect
|
||||||
|
.attr('style', nodeStyles)
|
||||||
|
.attr('rx', node.rx)
|
||||||
|
.attr('ry', node.ry)
|
||||||
|
.attr('x', x)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height);
|
||||||
|
}
|
||||||
|
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
|
||||||
|
labelEl.attr(
|
||||||
|
'transform',
|
||||||
|
// This puts the label on top of the box instead of inside it
|
||||||
|
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (labelStyles) {
|
||||||
|
const span = labelEl.select('span');
|
||||||
|
if (span) {
|
||||||
|
span.attr('style', labelStyles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Center the label
|
||||||
|
|
||||||
|
const rectBox = rect.node().getBBox();
|
||||||
|
node.offsetX = 0;
|
||||||
|
node.width = rectBox.width;
|
||||||
|
node.height = rectBox.height;
|
||||||
|
// Used by layout engine to position subgraph in parent
|
||||||
|
node.offsetY = bbox.height - node.padding / 2;
|
||||||
|
|
||||||
|
node.intersect = function (point) {
|
||||||
|
return intersectRect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { cluster: shapeSvg, labelBBox: bbox };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non visible cluster where the note is group with its
|
||||||
|
*
|
||||||
|
* @param {any} parent
|
||||||
|
* @param {any} node
|
||||||
|
* @returns {any} ShapeSvg
|
||||||
|
*/
|
||||||
|
const noteGroup = (parent, node) => {
|
||||||
|
// Add outer g element
|
||||||
|
const shapeSvg = parent.insert('g').attr('class', 'note-cluster').attr('id', node.id);
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
const rect = shapeSvg.insert('rect', ':first-child');
|
||||||
|
|
||||||
|
const padding = 0 * node.padding;
|
||||||
|
const halfPadding = padding / 2;
|
||||||
|
|
||||||
|
// center the rect around its coordinate
|
||||||
|
rect
|
||||||
|
.attr('rx', node.rx)
|
||||||
|
.attr('ry', node.ry)
|
||||||
|
.attr('x', node.x - node.width / 2 - halfPadding)
|
||||||
|
.attr('y', node.y - node.height / 2 - halfPadding)
|
||||||
|
.attr('width', node.width + padding)
|
||||||
|
.attr('height', node.height + padding)
|
||||||
|
.attr('fill', 'none');
|
||||||
|
|
||||||
|
const rectBox = rect.node().getBBox();
|
||||||
|
node.width = rectBox.width;
|
||||||
|
node.height = rectBox.height;
|
||||||
|
|
||||||
|
node.intersect = function (point) {
|
||||||
|
return intersectRect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { cluster: shapeSvg, labelBBox: { width: 0, height: 0 } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const roundedWithTitle = async (parent, node) => {
|
||||||
|
const siteConfig = getConfig();
|
||||||
|
|
||||||
|
const { themeVariables, handDrawnSeed } = siteConfig;
|
||||||
|
const { altBackground, compositeBackground, compositeTitleBackground, nodeBorder } =
|
||||||
|
themeVariables;
|
||||||
|
|
||||||
|
// Add outer g element
|
||||||
|
const shapeSvg = parent
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', node.cssClasses)
|
||||||
|
.attr('id', node.id)
|
||||||
|
.attr('data-id', node.id)
|
||||||
|
.attr('data-look', node.look);
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
const outerRectG = shapeSvg.insert('g', ':first-child');
|
||||||
|
|
||||||
|
// Create the label and insert it after the rect
|
||||||
|
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
|
||||||
|
let innerRect = shapeSvg.append('rect');
|
||||||
|
|
||||||
|
const text = label
|
||||||
|
.node()
|
||||||
|
.appendChild(await createLabel(node.label, node.labelStyle, undefined, true));
|
||||||
|
|
||||||
|
// Get the size of the label
|
||||||
|
let bbox = text.getBBox();
|
||||||
|
|
||||||
|
if (evaluate(siteConfig.flowchart.htmlLabels)) {
|
||||||
|
const div = text.children[0];
|
||||||
|
const dv = select(text);
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rounded With Title
|
||||||
|
const padding = 0 * node.padding;
|
||||||
|
const halfPadding = padding / 2;
|
||||||
|
|
||||||
|
const width =
|
||||||
|
(node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width) + padding;
|
||||||
|
if (node.width <= bbox.width + node.padding) {
|
||||||
|
node.diff = (width - node.width) / 2 - node.padding;
|
||||||
|
} else {
|
||||||
|
node.diff = -node.padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = node.height + padding;
|
||||||
|
// const height = node.height + padding;
|
||||||
|
const innerHeight = node.height + padding - bbox.height - 6;
|
||||||
|
const x = node.x - width / 2;
|
||||||
|
const y = node.y - height / 2;
|
||||||
|
node.width = width;
|
||||||
|
const innerY = node.y - node.height / 2 - halfPadding + bbox.height + 2;
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
let rect;
|
||||||
|
if (node.look === 'handDrawn') {
|
||||||
|
const isAlt = node.cssClasses.includes('statediagram-cluster-alt');
|
||||||
|
const rc = rough.svg(shapeSvg);
|
||||||
|
const roughOuterNode =
|
||||||
|
node.rx || node.ry
|
||||||
|
? rc.path(createRoundedRectPathD(x, y, width, height, 10), {
|
||||||
|
roughness: 0.7,
|
||||||
|
fill: compositeTitleBackground,
|
||||||
|
fillStyle: 'solid',
|
||||||
|
stroke: nodeBorder,
|
||||||
|
seed: handDrawnSeed,
|
||||||
|
})
|
||||||
|
: rc.rectangle(x, y, width, height, { seed: handDrawnSeed });
|
||||||
|
|
||||||
|
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
|
||||||
|
const roughInnerNode = rc.rectangle(x, innerY, width, innerHeight, {
|
||||||
|
fill: isAlt ? altBackground : compositeBackground,
|
||||||
|
fillStyle: isAlt ? 'hachure' : 'solid',
|
||||||
|
stroke: nodeBorder,
|
||||||
|
seed: handDrawnSeed,
|
||||||
|
});
|
||||||
|
|
||||||
|
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
|
||||||
|
innerRect = shapeSvg.insert(() => roughInnerNode);
|
||||||
|
} else {
|
||||||
|
rect = outerRectG.insert('rect', ':first-child');
|
||||||
|
const outerRectClass = 'outer';
|
||||||
|
|
||||||
|
// center the rect around its coordinate
|
||||||
|
rect
|
||||||
|
.attr('class', outerRectClass)
|
||||||
|
.attr('x', x)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height)
|
||||||
|
.attr('data-look', node.look);
|
||||||
|
innerRect
|
||||||
|
.attr('class', 'inner')
|
||||||
|
.attr('x', x)
|
||||||
|
.attr('y', innerY)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
label.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${node.x - bbox.width / 2}, ${y + 1 - (evaluate(siteConfig.flowchart.htmlLabels) ? 0 : 3)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const rectBox = rect.node().getBBox();
|
||||||
|
node.height = rectBox.height;
|
||||||
|
node.offsetX = 0;
|
||||||
|
// Used by layout engine to position subgraph in parent
|
||||||
|
node.offsetY = bbox.height - node.padding / 2;
|
||||||
|
node.labelBBox = bbox;
|
||||||
|
|
||||||
|
node.intersect = function (point) {
|
||||||
|
return intersectRect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { cluster: shapeSvg, labelBBox: bbox };
|
||||||
|
};
|
||||||
|
const divider = (parent, node) => {
|
||||||
|
const siteConfig = getConfig();
|
||||||
|
|
||||||
|
const { themeVariables, handDrawnSeed } = siteConfig;
|
||||||
|
const { nodeBorder } = themeVariables;
|
||||||
|
|
||||||
|
// Add outer g element
|
||||||
|
const shapeSvg = parent
|
||||||
|
.insert('g')
|
||||||
|
.attr('class', node.cssClasses)
|
||||||
|
.attr('id', node.id)
|
||||||
|
.attr('data-look', node.look);
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
const outerRectG = shapeSvg.insert('g', ':first-child');
|
||||||
|
|
||||||
|
const padding = 0 * node.padding;
|
||||||
|
|
||||||
|
const width = node.width + padding;
|
||||||
|
|
||||||
|
node.diff = -node.padding;
|
||||||
|
|
||||||
|
const height = node.height + padding;
|
||||||
|
// const height = node.height + padding;
|
||||||
|
const x = node.x - width / 2;
|
||||||
|
const y = node.y - height / 2;
|
||||||
|
node.width = width;
|
||||||
|
|
||||||
|
// add the rect
|
||||||
|
let rect;
|
||||||
|
if (node.look === 'handDrawn') {
|
||||||
|
const rc = rough.svg(shapeSvg);
|
||||||
|
const roughOuterNode = rc.rectangle(x, y, width, height, {
|
||||||
|
fill: 'lightgrey',
|
||||||
|
roughness: 0.5,
|
||||||
|
strokeLineDash: [5],
|
||||||
|
stroke: nodeBorder,
|
||||||
|
seed: handDrawnSeed,
|
||||||
|
});
|
||||||
|
|
||||||
|
rect = shapeSvg.insert(() => roughOuterNode, ':first-child');
|
||||||
|
} else {
|
||||||
|
rect = outerRectG.insert('rect', ':first-child');
|
||||||
|
const outerRectClass = 'divider';
|
||||||
|
|
||||||
|
// center the rect around its coordinate
|
||||||
|
rect
|
||||||
|
.attr('class', outerRectClass)
|
||||||
|
.attr('x', x)
|
||||||
|
.attr('y', y)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', height)
|
||||||
|
.attr('data-look', node.look);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rectBox = rect.node().getBBox();
|
||||||
|
node.height = rectBox.height;
|
||||||
|
node.offsetX = 0;
|
||||||
|
// Used by layout engine to position subgraph in parent
|
||||||
|
node.offsetY = 0;
|
||||||
|
|
||||||
|
node.intersect = function (point) {
|
||||||
|
return intersectRect(node, point);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { cluster: shapeSvg, labelBBox: {} };
|
||||||
|
};
|
||||||
|
|
||||||
|
const squareRect = rect;
|
||||||
|
const shapes = {
|
||||||
|
rect,
|
||||||
|
squareRect,
|
||||||
|
roundedWithTitle,
|
||||||
|
noteGroup,
|
||||||
|
divider,
|
||||||
|
};
|
||||||
|
|
||||||
|
let clusterElems = new Map();
|
||||||
|
|
||||||
|
export const insertCluster = async (elem, node) => {
|
||||||
|
const shape = node.shape || 'rect';
|
||||||
|
const cluster = await shapes[shape](elem, node);
|
||||||
|
clusterElems.set(node.id, cluster);
|
||||||
|
return cluster;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClusterTitleWidth = (elem, node) => {
|
||||||
|
const label = createLabel(node.label, node.labelStyle, undefined, true);
|
||||||
|
elem.node().appendChild(label);
|
||||||
|
const width = label.getBBox().width;
|
||||||
|
elem.node().removeChild(label);
|
||||||
|
return width;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clear = () => {
|
||||||
|
clusterElems = new Map();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const positionCluster = (node) => {
|
||||||
|
log.info(
|
||||||
|
'Position cluster (' +
|
||||||
|
node.id +
|
||||||
|
', ' +
|
||||||
|
node.x +
|
||||||
|
', ' +
|
||||||
|
node.y +
|
||||||
|
') (' +
|
||||||
|
node?.width +
|
||||||
|
', ' +
|
||||||
|
node?.height +
|
||||||
|
')',
|
||||||
|
clusterElems.get(node.id)
|
||||||
|
);
|
||||||
|
const el = clusterElems.get(node.id);
|
||||||
|
el.cluster.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
|
||||||
|
};
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { select } from 'd3';
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import { getConfig } from '$root/diagram-api/diagramAPI.js';
|
||||||
|
import common, { evaluate, renderKatex, hasKatex } from '$root/diagrams/common/common.js';
|
||||||
|
import { decodeEntities } from '$root/utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dom
|
||||||
|
* @param styleFn
|
||||||
|
*/
|
||||||
|
function applyStyle(dom, styleFn) {
|
||||||
|
if (styleFn) {
|
||||||
|
dom.attr('style', styleFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} node
|
||||||
|
* @returns {Promise<SVGForeignObjectElement>} Node
|
||||||
|
*/
|
||||||
|
async function addHtmlLabel(node) {
|
||||||
|
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
|
||||||
|
const div = fo.append('xhtml:div');
|
||||||
|
|
||||||
|
let label = node.label;
|
||||||
|
if (node.label && hasKatex(node.label)) {
|
||||||
|
label = await renderKatex(node.label.replace(common.lineBreakRegex, '\n'), getConfig());
|
||||||
|
}
|
||||||
|
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
|
||||||
|
div.html(
|
||||||
|
'<span class="' +
|
||||||
|
labelClass +
|
||||||
|
'" ' +
|
||||||
|
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') + // codeql [js/html-constructed-from-input] : false positive
|
||||||
|
'>' +
|
||||||
|
label +
|
||||||
|
'</span>'
|
||||||
|
);
|
||||||
|
|
||||||
|
applyStyle(div, node.labelStyle);
|
||||||
|
div.style('display', 'inline-block');
|
||||||
|
div.style('padding-right', '1px');
|
||||||
|
// Fix for firefox
|
||||||
|
div.style('white-space', 'nowrap');
|
||||||
|
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||||
|
return fo.node();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param _vertexText
|
||||||
|
* @param style
|
||||||
|
* @param isTitle
|
||||||
|
* @param isNode
|
||||||
|
* @deprecated svg-util/createText instead
|
||||||
|
*/
|
||||||
|
const createLabel = async (_vertexText, style, isTitle, isNode) => {
|
||||||
|
let vertexText = _vertexText || '';
|
||||||
|
if (typeof vertexText === 'object') {
|
||||||
|
vertexText = vertexText[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
||||||
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
|
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
|
||||||
|
log.info('vertexText' + vertexText);
|
||||||
|
const node = {
|
||||||
|
isNode,
|
||||||
|
label: decodeEntities(vertexText).replace(
|
||||||
|
/fa[blrs]?:fa-[\w-]+/g,
|
||||||
|
(s) => `<i class='${s.replace(':', ' ')}'></i>`
|
||||||
|
),
|
||||||
|
labelStyle: style ? style.replace('fill:', 'color:') : style,
|
||||||
|
};
|
||||||
|
let vertexNode = await addHtmlLabel(node);
|
||||||
|
// vertexNode.parentNode.removeChild(vertexNode);
|
||||||
|
return vertexNode;
|
||||||
|
} else {
|
||||||
|
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
|
svgLabel.setAttribute('style', style.replace('color:', 'fill:'));
|
||||||
|
let rows = [];
|
||||||
|
if (typeof vertexText === 'string') {
|
||||||
|
rows = vertexText.split(/\\n|\n|<br\s*\/?>/gi);
|
||||||
|
} else if (Array.isArray(vertexText)) {
|
||||||
|
rows = vertexText;
|
||||||
|
} else {
|
||||||
|
rows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||||
|
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||||
|
tspan.setAttribute('dy', '1em');
|
||||||
|
tspan.setAttribute('x', '0');
|
||||||
|
if (isTitle) {
|
||||||
|
tspan.setAttribute('class', 'title-row');
|
||||||
|
} else {
|
||||||
|
tspan.setAttribute('class', 'row');
|
||||||
|
}
|
||||||
|
tspan.textContent = row.trim();
|
||||||
|
svgLabel.appendChild(tspan);
|
||||||
|
}
|
||||||
|
return svgLabel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createLabel;
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
import type { SVG } from '$root/diagram-api/types.js';
|
||||||
|
import type { Mocked } from 'vitest';
|
||||||
|
import { addEdgeMarkers } from './edgeMarker.js';
|
||||||
|
|
||||||
|
describe('addEdgeMarker', () => {
|
||||||
|
const svgPath = {
|
||||||
|
attr: vitest.fn(),
|
||||||
|
} as unknown as Mocked<SVG>;
|
||||||
|
const url = 'http://example.com';
|
||||||
|
const id = 'test';
|
||||||
|
const diagramType = 'test';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
svgPath.attr.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add markers for arrow_cross:arrow_point', () => {
|
||||||
|
const arrowTypeStart = 'arrow_cross';
|
||||||
|
const arrowTypeEnd = 'arrow_point';
|
||||||
|
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-start',
|
||||||
|
`url(${url}#${id}_${diagramType}-crossStart)`
|
||||||
|
);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-end',
|
||||||
|
`url(${url}#${id}_${diagramType}-pointEnd)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add markers for aggregation:arrow_point', () => {
|
||||||
|
const arrowTypeStart = 'aggregation';
|
||||||
|
const arrowTypeEnd = 'arrow_point';
|
||||||
|
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-start',
|
||||||
|
`url(${url}#${id}_${diagramType}-aggregationStart)`
|
||||||
|
);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-end',
|
||||||
|
`url(${url}#${id}_${diagramType}-pointEnd)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add markers for arrow_point:aggregation', () => {
|
||||||
|
const arrowTypeStart = 'arrow_point';
|
||||||
|
const arrowTypeEnd = 'aggregation';
|
||||||
|
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-start',
|
||||||
|
`url(${url}#${id}_${diagramType}-pointStart)`
|
||||||
|
);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-end',
|
||||||
|
`url(${url}#${id}_${diagramType}-aggregationEnd)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add markers for aggregation:composition', () => {
|
||||||
|
const arrowTypeStart = 'aggregation';
|
||||||
|
const arrowTypeEnd = 'composition';
|
||||||
|
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-start',
|
||||||
|
`url(${url}#${id}_${diagramType}-aggregationStart)`
|
||||||
|
);
|
||||||
|
expect(svgPath.attr).toHaveBeenCalledWith(
|
||||||
|
'marker-end',
|
||||||
|
`url(${url}#${id}_${diagramType}-compositionEnd)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add invalid markers', () => {
|
||||||
|
const arrowTypeStart = 'this is an invalid marker';
|
||||||
|
const arrowTypeEnd = ') url(https://my-malicious-site.example)';
|
||||||
|
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
|
||||||
|
expect(svgPath.attr).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { SVG } from '$root/diagram-api/types.js';
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import type { EdgeData } from '$root/types.js';
|
||||||
|
/**
|
||||||
|
* Adds SVG markers to a path element based on the arrow types specified in the edge.
|
||||||
|
*
|
||||||
|
* @param svgPath - The SVG path element to add markers to.
|
||||||
|
* @param edge - The edge data object containing the arrow types.
|
||||||
|
* @param url - The URL of the SVG marker definitions.
|
||||||
|
* @param id - The ID prefix for the SVG marker definitions.
|
||||||
|
* @param diagramType - The type of diagram being rendered.
|
||||||
|
*/
|
||||||
|
export const addEdgeMarkers = (
|
||||||
|
svgPath: SVG,
|
||||||
|
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
|
||||||
|
url: string,
|
||||||
|
id: string,
|
||||||
|
diagramType: string
|
||||||
|
) => {
|
||||||
|
if (edge.arrowTypeStart) {
|
||||||
|
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
|
||||||
|
}
|
||||||
|
if (edge.arrowTypeEnd) {
|
||||||
|
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const arrowTypesMap = {
|
||||||
|
arrow_cross: 'cross',
|
||||||
|
arrow_point: 'point',
|
||||||
|
arrow_barb: 'barb',
|
||||||
|
arrow_circle: 'circle',
|
||||||
|
aggregation: 'aggregation',
|
||||||
|
extension: 'extension',
|
||||||
|
composition: 'composition',
|
||||||
|
dependency: 'dependency',
|
||||||
|
lollipop: 'lollipop',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const addEdgeMarker = (
|
||||||
|
svgPath: SVG,
|
||||||
|
position: 'start' | 'end',
|
||||||
|
arrowType: string,
|
||||||
|
url: string,
|
||||||
|
id: string,
|
||||||
|
diagramType: string
|
||||||
|
) => {
|
||||||
|
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
|
||||||
|
|
||||||
|
if (!endMarkerType) {
|
||||||
|
log.warn(`Unknown arrow type: ${arrowType}`);
|
||||||
|
return; // unknown arrow type, ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffix = position === 'start' ? 'Start' : 'End';
|
||||||
|
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
|
||||||
|
};
|
||||||
562
packages/mermaid/src/rendering-util/rendering-elements/edges.js
Normal file
562
packages/mermaid/src/rendering-util/rendering-elements/edges.js
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
import { getConfig } from '$root/diagram-api/diagramAPI.js';
|
||||||
|
import { evaluate } from '$root/diagrams/common/common.js';
|
||||||
|
import { log } from '$root/logger.js';
|
||||||
|
import { createText } from '$root/rendering-util/createText.ts';
|
||||||
|
import utils from '$root/utils.js';
|
||||||
|
import { getLineFunctionsWithOffset } from '$root/utils/lineWithOffset.js';
|
||||||
|
import { getSubGraphTitleMargins } from '$root/utils/subGraphTitleMargins.js';
|
||||||
|
import { curveBasis, line, select } from 'd3';
|
||||||
|
import rough from 'roughjs';
|
||||||
|
import createLabel from './createLabel.js';
|
||||||
|
import { addEdgeMarkers } from './edgeMarker.ts';
|
||||||
|
|
||||||
|
const edgeLabels = new Map();
|
||||||
|
const terminalLabels = new Map();
|
||||||
|
|
||||||
|
export const clear = () => {
|
||||||
|
edgeLabels.clear();
|
||||||
|
terminalLabels.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLabelStyles = (styleArray) => {
|
||||||
|
let styles = styleArray ? styleArray.reduce((acc, style) => acc + ';' + style, '') : '';
|
||||||
|
return styles;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const insertEdgeLabel = async (elem, edge) => {
|
||||||
|
let useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
||||||
|
|
||||||
|
const labelElement = await createText(elem, edge.label, {
|
||||||
|
style: getLabelStyles(edge.labelStyle),
|
||||||
|
useHtmlLabels,
|
||||||
|
addSvgBackground: true,
|
||||||
|
isNode: false,
|
||||||
|
});
|
||||||
|
log.info('abc82', edge, edge.labelType);
|
||||||
|
|
||||||
|
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||||
|
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
|
||||||
|
|
||||||
|
// Create inner g, label, this will be positioned now for centering the text
|
||||||
|
const label = edgeLabel.insert('g').attr('class', 'label');
|
||||||
|
label.node().appendChild(labelElement);
|
||||||
|
|
||||||
|
// Center the label
|
||||||
|
let bbox = labelElement.getBBox();
|
||||||
|
if (useHtmlLabels) {
|
||||||
|
const div = labelElement.children[0];
|
||||||
|
const dv = select(labelElement);
|
||||||
|
bbox = div.getBoundingClientRect();
|
||||||
|
dv.attr('width', bbox.width);
|
||||||
|
dv.attr('height', bbox.height);
|
||||||
|
}
|
||||||
|
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
|
||||||
|
|
||||||
|
// Make element accessible by id for positioning
|
||||||
|
edgeLabels.set(edge.id, edgeLabel);
|
||||||
|
|
||||||
|
// Update the abstract data of the edge with the new information about its width and height
|
||||||
|
edge.width = bbox.width;
|
||||||
|
edge.height = bbox.height;
|
||||||
|
|
||||||
|
let fo;
|
||||||
|
if (edge.startLabelLeft) {
|
||||||
|
// Create the actual text element
|
||||||
|
const startLabelElement = await createLabel(
|
||||||
|
edge.startLabelLeft,
|
||||||
|
getLabelStyles(edge.labelStyle)
|
||||||
|
);
|
||||||
|
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
|
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
|
||||||
|
fo = inner.node().appendChild(startLabelElement);
|
||||||
|
const slBox = startLabelElement.getBBox();
|
||||||
|
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
|
||||||
|
if (!terminalLabels.get(edge.id)) {
|
||||||
|
terminalLabels.set(edge.id, {});
|
||||||
|
}
|
||||||
|
terminalLabels.get(edge.id).startLeft = startEdgeLabelLeft;
|
||||||
|
setTerminalWidth(fo, edge.startLabelLeft);
|
||||||
|
}
|
||||||
|
if (edge.startLabelRight) {
|
||||||
|
// Create the actual text element
|
||||||
|
const startLabelElement = await createLabel(
|
||||||
|
edge.startLabelRight,
|
||||||
|
getLabelStyles(edge.labelStyle)
|
||||||
|
);
|
||||||
|
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
|
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
|
||||||
|
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
|
||||||
|
inner.node().appendChild(startLabelElement);
|
||||||
|
const slBox = startLabelElement.getBBox();
|
||||||
|
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
|
||||||
|
|
||||||
|
if (!terminalLabels.get(edge.id)) {
|
||||||
|
terminalLabels.set(edge.id, {});
|
||||||
|
}
|
||||||
|
terminalLabels.get(edge.id).startRight = startEdgeLabelRight;
|
||||||
|
setTerminalWidth(fo, edge.startLabelRight);
|
||||||
|
}
|
||||||
|
if (edge.endLabelLeft) {
|
||||||
|
// Create the actual text element
|
||||||
|
const endLabelElement = await createLabel(edge.endLabelLeft, getLabelStyles(edge.labelStyle));
|
||||||
|
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
|
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
|
||||||
|
fo = inner.node().appendChild(endLabelElement);
|
||||||
|
const slBox = endLabelElement.getBBox();
|
||||||
|
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
|
||||||
|
|
||||||
|
endEdgeLabelLeft.node().appendChild(endLabelElement);
|
||||||
|
|
||||||
|
if (!terminalLabels.get(edge.id)) {
|
||||||
|
terminalLabels.set(edge.id, {});
|
||||||
|
}
|
||||||
|
terminalLabels.get(edge.id).endLeft = endEdgeLabelLeft;
|
||||||
|
setTerminalWidth(fo, edge.endLabelLeft);
|
||||||
|
}
|
||||||
|
if (edge.endLabelRight) {
|
||||||
|
// Create the actual text element
|
||||||
|
const endLabelElement = await createLabel(edge.endLabelRight, getLabelStyles(edge.labelStyle));
|
||||||
|
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
|
||||||
|
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
|
||||||
|
|
||||||
|
fo = inner.node().appendChild(endLabelElement);
|
||||||
|
const slBox = endLabelElement.getBBox();
|
||||||
|
inner.attr('transform', 'translate(' + -slBox.width / 2 + ', ' + -slBox.height / 2 + ')');
|
||||||
|
|
||||||
|
endEdgeLabelRight.node().appendChild(endLabelElement);
|
||||||
|
if (!terminalLabels.get(edge.id)) {
|
||||||
|
terminalLabels.set(edge.id, {});
|
||||||
|
}
|
||||||
|
terminalLabels.get(edge.id).endRight = endEdgeLabelRight;
|
||||||
|
setTerminalWidth(fo, edge.endLabelRight);
|
||||||
|
}
|
||||||
|
return labelElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} fo
|
||||||
|
* @param {any} value
|
||||||
|
*/
|
||||||
|
function setTerminalWidth(fo, value) {
|
||||||
|
if (getConfig().flowchart.htmlLabels && fo) {
|
||||||
|
fo.style.width = value.length * 9 + 'px';
|
||||||
|
fo.style.height = '12px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const positionEdgeLabel = (edge, paths) => {
|
||||||
|
log.debug('Moving label abc88 ', edge.id, edge.label, edgeLabels.get(edge.id), paths);
|
||||||
|
let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
|
||||||
|
const siteConfig = getConfig();
|
||||||
|
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
|
||||||
|
if (edge.label) {
|
||||||
|
const el = edgeLabels.get(edge.id);
|
||||||
|
let x = edge.x;
|
||||||
|
let y = edge.y;
|
||||||
|
if (path) {
|
||||||
|
const pos = utils.calcLabelPosition(path);
|
||||||
|
log.debug(
|
||||||
|
'Moving label ' + edge.label + ' from (',
|
||||||
|
x,
|
||||||
|
',',
|
||||||
|
y,
|
||||||
|
') to (',
|
||||||
|
pos.x,
|
||||||
|
',',
|
||||||
|
pos.y,
|
||||||
|
') abc88'
|
||||||
|
);
|
||||||
|
if (paths.updatedPath) {
|
||||||
|
x = pos.x;
|
||||||
|
y = pos.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge.startLabelLeft) {
|
||||||
|
const el = terminalLabels.get(edge.id).startLeft;
|
||||||
|
let x = edge.x;
|
||||||
|
let y = edge.y;
|
||||||
|
if (path) {
|
||||||
|
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeStart ? 10 : 0, 'start_left', path);
|
||||||
|
x = pos.x;
|
||||||
|
y = pos.y;
|
||||||
|
}
|
||||||
|
el.attr('transform', `translate(${x}, ${y})`);
|
||||||
|
}
|
||||||
|
if (edge.startLabelRight) {
|
||||||
|
const el = terminalLabels.get(edge.id).startRight;
|
||||||
|
let x = edge.x;
|
||||||
|
let y = edge.y;
|
||||||
|
if (path) {
|
||||||
|
const pos = utils.calcTerminalLabelPosition(
|
||||||
|
edge.arrowTypeStart ? 10 : 0,
|
||||||
|
'start_right',
|
||||||
|
path
|
||||||
|
);
|
||||||
|
x = pos.x;
|
||||||
|
y = pos.y;
|
||||||
|
}
|
||||||
|
el.attr('transform', `translate(${x}, ${y})`);
|
||||||
|
}
|
||||||
|
if (edge.endLabelLeft) {
|
||||||
|
const el = terminalLabels.get(edge.id).endLeft;
|
||||||
|
let x = edge.x;
|
||||||
|
let y = edge.y;
|
||||||
|
if (path) {
|
||||||
|
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_left', path);
|
||||||
|
x = pos.x;
|
||||||
|
y = pos.y;
|
||||||
|
}
|
||||||
|
el.attr('transform', `translate(${x}, ${y})`);
|
||||||
|
}
|
||||||
|
if (edge.endLabelRight) {
|
||||||
|
const el = terminalLabels.get(edge.id).endRight;
|
||||||
|
let x = edge.x;
|
||||||
|
let y = edge.y;
|
||||||
|
if (path) {
|
||||||
|
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_right', path);
|
||||||
|
x = pos.x;
|
||||||
|
y = pos.y;
|
||||||
|
}
|
||||||
|
el.attr('transform', `translate(${x}, ${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;
|
||||||
|
return dx >= w || dy >= h;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
let 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 top/bottom calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
// Intersection on sides of rect
|
||||||
|
if (insidePoint.x < outsidePoint.x) {
|
||||||
|
r = outsidePoint.x - w - x;
|
||||||
|
} else {
|
||||||
|
r = x - w - outsidePoint.x;
|
||||||
|
}
|
||||||
|
let q = (Q * r) / R;
|
||||||
|
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + 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 cutPathAtIntersect = (_points, boundaryNode) => {
|
||||||
|
log.warn('abc88 cutPathAtIntersect', _points, boundaryNode);
|
||||||
|
let points = [];
|
||||||
|
let lastPointOutside = _points[0];
|
||||||
|
let isInside = false;
|
||||||
|
_points.forEach((point) => {
|
||||||
|
log.info('abc88 checking point', point, boundaryNode);
|
||||||
|
|
||||||
|
if (!outsideNode(boundaryNode, point) && !isInside) {
|
||||||
|
const inter = intersection(boundaryNode, lastPointOutside, point);
|
||||||
|
log.debug('abc88 inside', point, lastPointOutside, inter);
|
||||||
|
log.debug('abc88 intersection', inter, boundaryNode);
|
||||||
|
|
||||||
|
let pointPresent = false;
|
||||||
|
points.forEach((p) => {
|
||||||
|
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
|
||||||
|
points.push(inter);
|
||||||
|
} else {
|
||||||
|
log.warn('abc88 no intersect', inter, points);
|
||||||
|
}
|
||||||
|
isInside = true;
|
||||||
|
} else {
|
||||||
|
log.warn('abc88 outside', point, lastPointOutside);
|
||||||
|
lastPointOutside = point;
|
||||||
|
if (!isInside) {
|
||||||
|
points.push(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.debug('returning points', points);
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
|
function extractCornerPoints(points) {
|
||||||
|
const cornerPoints = [];
|
||||||
|
const cornerPointPositions = [];
|
||||||
|
for (let i = 1; i < points.length - 1; i++) {
|
||||||
|
const prev = points[i - 1];
|
||||||
|
const curr = points[i];
|
||||||
|
const next = points[i + 1];
|
||||||
|
if (
|
||||||
|
prev.x === curr.x &&
|
||||||
|
curr.y === next.y &&
|
||||||
|
Math.abs(curr.x - next.x) > 5 &&
|
||||||
|
Math.abs(curr.y - prev.y) > 5
|
||||||
|
) {
|
||||||
|
cornerPoints.push(curr);
|
||||||
|
cornerPointPositions.push(i);
|
||||||
|
} else if (
|
||||||
|
prev.y === curr.y &&
|
||||||
|
curr.x === next.x &&
|
||||||
|
Math.abs(curr.x - prev.x) > 5 &&
|
||||||
|
Math.abs(curr.y - next.y) > 5
|
||||||
|
) {
|
||||||
|
cornerPoints.push(curr);
|
||||||
|
cornerPointPositions.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { cornerPoints, cornerPointPositions };
|
||||||
|
}
|
||||||
|
|
||||||
|
const findAdjacentPoint = function (pointA, pointB, distance) {
|
||||||
|
const xDiff = pointB.x - pointA.x;
|
||||||
|
const yDiff = pointB.y - pointA.y;
|
||||||
|
const length = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
|
||||||
|
const ratio = distance / length;
|
||||||
|
return { x: pointB.x - ratio * xDiff, y: pointB.y - ratio * yDiff };
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixCorners = function (lineData) {
|
||||||
|
const { cornerPointPositions } = extractCornerPoints(lineData);
|
||||||
|
const newLineData = [];
|
||||||
|
for (let i = 0; i < lineData.length; i++) {
|
||||||
|
if (cornerPointPositions.includes(i)) {
|
||||||
|
const prevPoint = lineData[i - 1];
|
||||||
|
const nextPoint = lineData[i + 1];
|
||||||
|
const cornerPoint = lineData[i];
|
||||||
|
|
||||||
|
const newPrevPoint = findAdjacentPoint(prevPoint, cornerPoint, 5);
|
||||||
|
const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5);
|
||||||
|
|
||||||
|
const xDiff = newNextPoint.x - newPrevPoint.x;
|
||||||
|
const yDiff = newNextPoint.y - newPrevPoint.y;
|
||||||
|
newLineData.push(newPrevPoint);
|
||||||
|
|
||||||
|
const a = Math.sqrt(2) * 2;
|
||||||
|
let newCornerPoint = { x: cornerPoint.x, y: cornerPoint.y };
|
||||||
|
if (Math.abs(nextPoint.x - prevPoint.x) > 10 && Math.abs(nextPoint.y - prevPoint.y) >= 10) {
|
||||||
|
log.debug(
|
||||||
|
'Corner point fixing',
|
||||||
|
Math.abs(nextPoint.x - prevPoint.x),
|
||||||
|
Math.abs(nextPoint.y - prevPoint.y)
|
||||||
|
);
|
||||||
|
const r = 5;
|
||||||
|
if (cornerPoint.x === newPrevPoint.x) {
|
||||||
|
newCornerPoint = {
|
||||||
|
x: xDiff < 0 ? newPrevPoint.x - r + a : newPrevPoint.x + r - a,
|
||||||
|
y: yDiff < 0 ? newPrevPoint.y - a : newPrevPoint.y + a,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newCornerPoint = {
|
||||||
|
x: xDiff < 0 ? newPrevPoint.x - a : newPrevPoint.x + a,
|
||||||
|
y: yDiff < 0 ? newPrevPoint.y - r + a : newPrevPoint.y + r - a,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug(
|
||||||
|
'Corner point skipping fixing',
|
||||||
|
Math.abs(nextPoint.x - prevPoint.x),
|
||||||
|
Math.abs(nextPoint.y - prevPoint.y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
newLineData.push(newCornerPoint, newNextPoint);
|
||||||
|
} else {
|
||||||
|
newLineData.push(lineData[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLineData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const insertEdge = function (elem, edge, clusterDb, diagramType, startNode, endNode, id) {
|
||||||
|
const { handDrawnSeed } = getConfig();
|
||||||
|
let points = edge.points;
|
||||||
|
let pointsHasChanged = false;
|
||||||
|
const tail = startNode;
|
||||||
|
var head = endNode;
|
||||||
|
|
||||||
|
if (head.intersect && tail.intersect) {
|
||||||
|
points = points.slice(1, edge.points.length - 1);
|
||||||
|
points.unshift(tail.intersect(points[0]));
|
||||||
|
log.debug(
|
||||||
|
'Last point APA12',
|
||||||
|
edge.start,
|
||||||
|
'-->',
|
||||||
|
edge.end,
|
||||||
|
points[points.length - 1],
|
||||||
|
head,
|
||||||
|
head.intersect(points[points.length - 1])
|
||||||
|
);
|
||||||
|
points.push(head.intersect(points[points.length - 1]));
|
||||||
|
}
|
||||||
|
if (edge.toCluster) {
|
||||||
|
log.info('to cluster abc88', clusterDb.get(edge.toCluster));
|
||||||
|
points = cutPathAtIntersect(edge.points, clusterDb.get(edge.toCluster).node);
|
||||||
|
|
||||||
|
pointsHasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge.fromCluster) {
|
||||||
|
log.debug(
|
||||||
|
'from cluster abc88',
|
||||||
|
clusterDb.get(edge.fromCluster),
|
||||||
|
JSON.stringify(points, null, 2)
|
||||||
|
);
|
||||||
|
points = cutPathAtIntersect(points.reverse(), clusterDb.get(edge.fromCluster).node).reverse();
|
||||||
|
|
||||||
|
pointsHasChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineData = points.filter((p) => !Number.isNaN(p.y));
|
||||||
|
lineData = fixCorners(lineData);
|
||||||
|
let lastPoint = lineData[lineData.length - 1];
|
||||||
|
if (lineData.length > 1) {
|
||||||
|
lastPoint = lineData[lineData.length - 1];
|
||||||
|
const secondLastPoint = lineData[lineData.length - 2];
|
||||||
|
const diffX = (lastPoint.x - secondLastPoint.x) / 2;
|
||||||
|
const diffY = (lastPoint.y - secondLastPoint.y) / 2;
|
||||||
|
const midPoint = { x: secondLastPoint.x + diffX, y: secondLastPoint.y + diffY };
|
||||||
|
lineData.splice(-1, 0, midPoint);
|
||||||
|
}
|
||||||
|
let curve = curveBasis;
|
||||||
|
if (edge.curve) {
|
||||||
|
curve = edge.curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { x, y } = getLineFunctionsWithOffset(edge);
|
||||||
|
const lineFunction = line().x(x).y(y).curve(curve);
|
||||||
|
|
||||||
|
let strokeClasses;
|
||||||
|
switch (edge.thickness) {
|
||||||
|
case 'normal':
|
||||||
|
strokeClasses = 'edge-thickness-normal';
|
||||||
|
break;
|
||||||
|
case 'thick':
|
||||||
|
strokeClasses = 'edge-thickness-thick';
|
||||||
|
break;
|
||||||
|
case 'invisible':
|
||||||
|
strokeClasses = 'edge-thickness-invisible';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strokeClasses = 'edge-thickness-normal';
|
||||||
|
}
|
||||||
|
switch (edge.pattern) {
|
||||||
|
case 'solid':
|
||||||
|
strokeClasses += ' edge-pattern-solid';
|
||||||
|
break;
|
||||||
|
case 'dotted':
|
||||||
|
strokeClasses += ' edge-pattern-dotted';
|
||||||
|
break;
|
||||||
|
case 'dashed':
|
||||||
|
strokeClasses += ' edge-pattern-dashed';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
strokeClasses += ' edge-pattern-solid';
|
||||||
|
}
|
||||||
|
let svgPath;
|
||||||
|
let linePath = lineFunction(lineData);
|
||||||
|
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
|
||||||
|
if (edge.look === 'handDrawn') {
|
||||||
|
const rc = rough.svg(elem);
|
||||||
|
Object.assign([], lineData);
|
||||||
|
|
||||||
|
const svgPathNode = rc.path(linePath, {
|
||||||
|
roughness: 0.3,
|
||||||
|
seed: handDrawnSeed,
|
||||||
|
});
|
||||||
|
|
||||||
|
strokeClasses += ' transition';
|
||||||
|
|
||||||
|
svgPath = select(svgPathNode)
|
||||||
|
.select('path')
|
||||||
|
.attr('id', edge.id)
|
||||||
|
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
|
||||||
|
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
|
||||||
|
let d = svgPath.attr('d');
|
||||||
|
svgPath.attr('d', d);
|
||||||
|
elem.node().appendChild(svgPath.node());
|
||||||
|
} else {
|
||||||
|
svgPath = elem
|
||||||
|
.append('path')
|
||||||
|
.attr('d', linePath)
|
||||||
|
.attr('id', edge.id)
|
||||||
|
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
|
||||||
|
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = '';
|
||||||
|
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
|
||||||
|
url =
|
||||||
|
window.location.protocol +
|
||||||
|
'//' +
|
||||||
|
window.location.host +
|
||||||
|
window.location.pathname +
|
||||||
|
window.location.search;
|
||||||
|
url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
|
||||||
|
}
|
||||||
|
log.info('arrowTypeStart', edge.arrowTypeStart);
|
||||||
|
log.info('arrowTypeEnd', edge.arrowTypeEnd);
|
||||||
|
|
||||||
|
addEdgeMarkers(svgPath, edge, url, id, diagramType);
|
||||||
|
|
||||||
|
let paths = {};
|
||||||
|
if (pointsHasChanged) {
|
||||||
|
paths.updatedPath = points;
|
||||||
|
}
|
||||||
|
paths.originalPath = edge.points;
|
||||||
|
return paths;
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Borrowed with love from from dagre-d3. Many thanks to cpettitt!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import node from './intersect-node.js';
|
||||||
|
import circle from './intersect-circle.js';
|
||||||
|
import ellipse from './intersect-ellipse.js';
|
||||||
|
import polygon from './intersect-polygon.js';
|
||||||
|
import rect from './intersect-rect.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
node,
|
||||||
|
circle,
|
||||||
|
ellipse,
|
||||||
|
polygon,
|
||||||
|
rect,
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import intersectEllipse from './intersect-ellipse.js';
|
||||||
|
|
||||||
|
function intersectCircle(node, rx, point) {
|
||||||
|
return intersectEllipse(node, rx, rx, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default intersectCircle;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user