mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-23 10:16:43 +02:00
Compare commits
21 Commits
knsv/mindm
...
ipsep-cola
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f06f1bacd3 | ||
![]() |
e12036ed6c | ||
![]() |
4ea4a7b3fe | ||
![]() |
09c239fc25 | ||
![]() |
b01a0d7dcd | ||
![]() |
3e99158c04 | ||
![]() |
339927d7e5 | ||
![]() |
2785ca47a0 | ||
![]() |
b70abed096 | ||
![]() |
1a5a846d71 | ||
![]() |
20ddc12b42 | ||
![]() |
5e037ee315 | ||
![]() |
70eedd840d | ||
![]() |
bb0aa4009c | ||
![]() |
75f940bf9e | ||
![]() |
a46de01885 | ||
![]() |
a7e9f4f926 | ||
![]() |
99c8224dfa | ||
![]() |
13f8549f81 | ||
![]() |
cfd25ed33e | ||
![]() |
3a8952ebe0 |
@@ -33,11 +33,6 @@ export const packageOptions = {
|
||||
packageName: 'mermaid-layout-elk',
|
||||
file: 'layouts.ts',
|
||||
},
|
||||
'mermaid-layout-tidy-tree': {
|
||||
name: 'mermaid-layout-tidy-tree',
|
||||
packageName: 'mermaid-layout-tidy-tree',
|
||||
file: 'index.ts',
|
||||
},
|
||||
examples: {
|
||||
name: 'mermaid-examples',
|
||||
packageName: 'examples',
|
||||
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
chore: Remove the "-beta" suffix from the XYChart, Block, Sankey diagrams to reflect their stable status
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Position the edge label in state diagram correctly relative to the edge
|
5
.changeset/cute-crews-shake.md
Normal file
5
.changeset/cute-crews-shake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
---
|
||||
|
||||
feat: Added IpSepCoLa algorithm for layout
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Apply correct dateFormat in Gantt chart to show only day when specified
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: handle exclude dates properly in Gantt charts when using dateFormat: 'YYYY-MM-DD HH:mm:ss'
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: fixed connection gaps in flowchart for roundedRect, stadium and diamond shape
|
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
'@mermaid-js/layout-tidy-tree': minor
|
||||
'@mermaid-js/layout-elk': minor
|
||||
---
|
||||
|
||||
feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Allow equals sign in sequenceDiagram labels
|
@@ -1,9 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
Add validation for negative values in pie charts:
|
||||
|
||||
Prevents crashes during parsing by validating values post-parsing.
|
||||
|
||||
Provides clearer, user-friendly error messages for invalid negative inputs.
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: node border style for handdrawn shapes
|
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: correctly render non-directional lines for '---' in block diagrams
|
@@ -12,6 +12,7 @@ gantt
|
||||
gitgraph
|
||||
gzipped
|
||||
handDrawn
|
||||
ipsepcola
|
||||
kanban
|
||||
marginx
|
||||
marginy
|
||||
|
2
.github/workflows/e2e-timings.yml
vendored
2
.github/workflows/e2e-timings.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and create pull request
|
||||
uses: peter-evans/create-pull-request@1310d7dab503600742045e6fd4b84dda64352858
|
||||
uses: peter-evans/create-pull-request@07cbaebb4bfc9c5d7db426ea5a5f585df29dd0a0
|
||||
with:
|
||||
add-paths: |
|
||||
cypress/timings.json
|
||||
|
2
.github/workflows/validate-lockfile.yml
vendored
2
.github/workflows/validate-lockfile.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
# 2) No unwanted vitepress paths
|
||||
if grep -qF 'packages/mermaid/src/vitepress' pnpm-lock.yaml; then
|
||||
issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run \`rm -rf packages/mermaid/src/vitepress && pnpm install\` to regenerate.")
|
||||
issues+=("• Disallowed path 'packages/mermaid/src/vitepress' present. Run `rm -rf packages/mermaid/src/vitepress && pnpm install` to regenerate.")
|
||||
fi
|
||||
|
||||
# 3) Lockfile only changes when package.json changes
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,7 +4,6 @@ node_modules/
|
||||
coverage/
|
||||
.idea/
|
||||
.pnpm-store/
|
||||
.instructions/
|
||||
|
||||
dist
|
||||
v8-compile-cache-0
|
||||
|
@@ -16,7 +16,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL2: should handle columns statement in sub-blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
id1["Hello"]
|
||||
block
|
||||
columns 3
|
||||
@@ -32,7 +32,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL3: should align block widths and handle columns statement in sub-blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
block
|
||||
columns 1
|
||||
id1
|
||||
@@ -48,7 +48,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL4: should align block widths and handle columns statements in deeper sub-blocks then 1 level', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 1
|
||||
block
|
||||
columns 1
|
||||
@@ -68,7 +68,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL5: should align block widths and handle columns statements in deeper sub-blocks then 1 level (alt)', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 1
|
||||
block
|
||||
id1
|
||||
@@ -87,7 +87,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL6: should handle block arrows and spece statements', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
space:3
|
||||
ida idb idc
|
||||
@@ -106,7 +106,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL7: should handle different types of edges', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
A space:5
|
||||
A --o B
|
||||
@@ -119,7 +119,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL8: should handle sub-blocks without columns statements', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 2
|
||||
C A B
|
||||
block
|
||||
@@ -133,7 +133,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL9: should handle edges from blocks in sub blocks to other blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
B space
|
||||
block
|
||||
@@ -147,7 +147,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL10: should handle edges from composite blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
B space
|
||||
block BL
|
||||
@@ -161,7 +161,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL11: should handle edges to composite blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
B space
|
||||
block BL
|
||||
@@ -175,7 +175,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL12: edges should handle labels', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A
|
||||
space
|
||||
A -- "apa" --> E
|
||||
@@ -186,7 +186,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL13: should handle block arrows in different directions', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
space blockArrowId1<["down"]>(down) space
|
||||
blockArrowId2<["right"]>(right) blockArrowId3<["Sync"]>(x, y) blockArrowId4<["left"]>(left)
|
||||
@@ -199,7 +199,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL14: should style statements and class statements', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A
|
||||
B
|
||||
classDef blue fill:#66f,stroke:#333,stroke-width:2px;
|
||||
@@ -212,7 +212,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL15: width alignment - D and E should share available space', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
block
|
||||
D
|
||||
E
|
||||
@@ -225,7 +225,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL16: width alignment - C should be as wide as the composite block', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
block
|
||||
A("This is the text")
|
||||
B
|
||||
@@ -238,7 +238,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL17: width alignment - blocks should be equal in width', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A("This is the text")
|
||||
B
|
||||
C
|
||||
@@ -249,7 +249,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL18: block types 1 - square, rounded and circle', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A["square"]
|
||||
B("rounded")
|
||||
C(("circle"))
|
||||
@@ -260,7 +260,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL19: block types 2 - odd, diamond and hexagon', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A>"rect_left_inv_arrow"]
|
||||
B{"diamond"}
|
||||
C{{"hexagon"}}
|
||||
@@ -271,7 +271,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL20: block types 3 - stadium', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A(["stadium"])
|
||||
`,
|
||||
{}
|
||||
@@ -280,7 +280,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL21: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A[/"lean right"/]
|
||||
B[\"lean left"\]
|
||||
C[/"trapezoid"\]
|
||||
@@ -292,7 +292,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL22: block types 1 - square, rounded and circle', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A["square"]
|
||||
B("rounded")
|
||||
C(("circle"))
|
||||
@@ -303,7 +303,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL23: sizing - it should be possible to make a block wider', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
A("rounded"):2
|
||||
B:2
|
||||
C
|
||||
@@ -314,7 +314,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL24: sizing - it should be possible to make a composite block wider', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
block:2
|
||||
A
|
||||
end
|
||||
@@ -326,7 +326,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL25: block in the middle with space on each side', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
space
|
||||
middle["In the middle"]
|
||||
@@ -337,7 +337,7 @@ describe('Block diagram', () => {
|
||||
});
|
||||
it('BL26: space and an edge', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 5
|
||||
A space B
|
||||
A --x B
|
||||
@@ -347,7 +347,7 @@ describe('Block diagram', () => {
|
||||
});
|
||||
it('BL27: block sizes for regular blocks', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
a["A wide one"] b:2 c:2 d
|
||||
`,
|
||||
@@ -356,7 +356,7 @@ describe('Block diagram', () => {
|
||||
});
|
||||
it('BL28: composite block with a set width - f should use the available space', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
a:3
|
||||
block:e:3
|
||||
@@ -370,7 +370,7 @@ describe('Block diagram', () => {
|
||||
|
||||
it('BL29: composite block with a set width - f and g should split the available space', () => {
|
||||
imgSnapshotTest(
|
||||
`block
|
||||
`block-beta
|
||||
columns 3
|
||||
a:3
|
||||
block:e:3
|
||||
@@ -393,17 +393,6 @@ describe('Block diagram', () => {
|
||||
overflow:3
|
||||
short:1
|
||||
also_overflow:2
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('BL31: edge without arrow syntax should render with no arrowheads', () => {
|
||||
imgSnapshotTest(
|
||||
`block-beta
|
||||
a
|
||||
b
|
||||
a --- b
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
@@ -1113,37 +1113,6 @@ end
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Flowchart Node Shape Rendering', () => {
|
||||
it('should render a stadium-shaped node', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart TB
|
||||
A(["Start"]) --> n1["Untitled Node"]
|
||||
A --> n2["Untitled Node"]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a diamond-shaped node using shape config', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart BT
|
||||
n2["Untitled Node"] --> n1["Diamond"]
|
||||
n1@{ shape: diam}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a rounded rectangle and a normal rectangle', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart BT
|
||||
n2["Untitled Node"] --> n1["Rounded Rectangle"]
|
||||
n3["Untitled Node"] --> n1
|
||||
n1@{ shape: rounded}
|
||||
n3@{ shape: rect}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('6617: Per Link Curve Styling using edge Ids', () => {
|
||||
imgSnapshotTest(
|
||||
|
@@ -565,18 +565,6 @@ describe('Gantt diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render only the day when using dateFormat D', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title Test
|
||||
dateFormat D
|
||||
A :a, 1, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: fix it
|
||||
//
|
||||
// This test is skipped deliberately
|
||||
@@ -659,49 +647,6 @@ describe('Gantt diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram excluding a specific date in YYYY-MM-DD HH:mm:ss format', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD HH:mm:ss
|
||||
excludes 2025-07-07
|
||||
section Section
|
||||
A task :a1, 2025-07-04 20:30:30, 2025-07-08 10:30:30
|
||||
Another task:after a1, 20h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram excluding saturday and sunday in YYYY-MM-DD HH:mm:ss format', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD HH:mm:ss
|
||||
excludes weekends
|
||||
weekend saturday
|
||||
section Section
|
||||
A task :a1, 2025-07-04 20:30:30, 2025-07-08 10:30:30
|
||||
Another task:after a1, 20h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a gantt diagram excluding friday and saturday in YYYY-MM-DD HH:mm:ss format', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD HH:mm:ss
|
||||
excludes weekends
|
||||
weekend friday
|
||||
section Section
|
||||
A task :a1, 2025-07-04 20:30:30, 2025-07-08 10:30:30
|
||||
Another task:after a1, 20h
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it("should render when there's a semicolon in the title", () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
153
cypress/integration/rendering/ipsepCola.spec.ts
Normal file
153
cypress/integration/rendering/ipsepCola.spec.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Flowchart IPSepCoLa', () => {
|
||||
it('1-ipsepCola: should render a simple flowchart', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('2-ipsepCola: handle bidirectional edges', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart TD
|
||||
subgraph D
|
||||
A --> B
|
||||
A --> B
|
||||
B --> A
|
||||
B --> A
|
||||
end
|
||||
`
|
||||
);
|
||||
});
|
||||
it('3-ipsepCola: handle multiple self loops', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart
|
||||
a --> a
|
||||
a --> a
|
||||
a --> a
|
||||
a --> a
|
||||
`
|
||||
);
|
||||
});
|
||||
it('4-ipsepCola: handle state diagram example', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('5-ipsepCola: handle multiple subgraphs with edges between them', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart LR
|
||||
c1-->a2
|
||||
subgraph one
|
||||
a1-->a2
|
||||
end
|
||||
subgraph two
|
||||
b1-->b2
|
||||
end
|
||||
subgraph three
|
||||
c1-->c2
|
||||
end
|
||||
one --> two
|
||||
three --> two
|
||||
two --> c2
|
||||
`
|
||||
);
|
||||
});
|
||||
it('6-ipsepCola: handle class diagram example', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
classDiagram
|
||||
class AuthService {
|
||||
+login(username: string, password: string): boolean
|
||||
+logout(): void
|
||||
+register(): void
|
||||
}
|
||||
|
||||
class User {
|
||||
-username: string
|
||||
-password: string
|
||||
-role: Role
|
||||
+changePassword(): void
|
||||
}
|
||||
|
||||
class Role {
|
||||
-name: string
|
||||
-permissions: string[]
|
||||
+hasPermission(): boolean
|
||||
}
|
||||
|
||||
AuthService --> User
|
||||
User --> Role
|
||||
`
|
||||
);
|
||||
});
|
||||
it('7-ipsepCola: should render a decision flowchart', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart TD
|
||||
Start([Start]) --> Prep[Preparation Step]
|
||||
Prep --> Split{Ready to Process?}
|
||||
Split -->|Yes| T1[Task A]
|
||||
Split -->|Yes| T2[Task B]
|
||||
T1 --> Merge
|
||||
T2 --> Merge
|
||||
Merge((Join Results)) --> Finalize[Finalize Process]
|
||||
Finalize --> End([End])
|
||||
`
|
||||
);
|
||||
});
|
||||
it('8-ipsepCola: handle nested subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart LR
|
||||
subgraph main
|
||||
subgraph subcontainer
|
||||
subcontainer-child
|
||||
end
|
||||
subcontainer-child--> subcontainer-sibling
|
||||
end
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
@@ -1,79 +0,0 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Mindmap Tidy Tree', () => {
|
||||
it('1-tidy-tree: should render a simple mindmap without children', () => {
|
||||
imgSnapshotTest(
|
||||
` ---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
`
|
||||
);
|
||||
});
|
||||
it('2-tidy-tree: should render a simple mindmap', () => {
|
||||
imgSnapshotTest(
|
||||
` ---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
`
|
||||
);
|
||||
});
|
||||
it('3-tidy-tree: should render a mindmap with different shapes', () => {
|
||||
imgSnapshotTest(
|
||||
` ---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
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
|
||||
id)I am a cloud(
|
||||
id))I am a bang((
|
||||
Tools
|
||||
`
|
||||
);
|
||||
});
|
||||
it('4-tidy-tree: should render a mindmap with children', () => {
|
||||
imgSnapshotTest(
|
||||
` ---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
((This is a mindmap))
|
||||
child1
|
||||
grandchild 1
|
||||
grandchild 2
|
||||
child2
|
||||
grandchild 3
|
||||
grandchild 4
|
||||
child3
|
||||
grandchild 5
|
||||
grandchild 6
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
@@ -159,10 +159,12 @@ root
|
||||
});
|
||||
it('square shape', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
`
|
||||
mindmap
|
||||
root[
|
||||
The root
|
||||
]`,
|
||||
]
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
@@ -170,10 +172,12 @@ root
|
||||
});
|
||||
it('rounded rect shape', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
`
|
||||
mindmap
|
||||
root((
|
||||
The root
|
||||
))`,
|
||||
))
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
@@ -181,10 +185,12 @@ root
|
||||
});
|
||||
it('circle shape', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
`
|
||||
mindmap
|
||||
root(
|
||||
The root
|
||||
)`,
|
||||
)
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
@@ -192,8 +198,10 @@ root
|
||||
});
|
||||
it('default shape', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
The root`,
|
||||
`
|
||||
mindmap
|
||||
The root
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
@@ -201,10 +209,12 @@ root
|
||||
});
|
||||
it('adding children', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
`
|
||||
mindmap
|
||||
The root
|
||||
child1
|
||||
child2`,
|
||||
child2
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
@@ -212,11 +222,13 @@ root
|
||||
});
|
||||
it('adding grand children', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
`
|
||||
mindmap
|
||||
The root
|
||||
child1
|
||||
child2
|
||||
child3`,
|
||||
child3
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
|
@@ -82,13 +82,4 @@ describe('pie chart', () => {
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should render pie slices only for non-zero values but shows all legends', () => {
|
||||
imgSnapshotTest(
|
||||
` pie title Pets adopted by volunteers
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 1
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -15,7 +15,7 @@ describe('Sankey Diagram', () => {
|
||||
describe('when given a linkColor', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.wrap(
|
||||
`sankey
|
||||
`sankey-beta
|
||||
a,b,10
|
||||
`
|
||||
).as('graph');
|
||||
@@ -62,7 +62,7 @@ describe('Sankey Diagram', () => {
|
||||
this.beforeAll(() => {
|
||||
cy.wrap(
|
||||
`
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
a,b,8
|
||||
b,c,8
|
||||
|
@@ -602,231 +602,6 @@ State1 --> [*]
|
||||
--
|
||||
55
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render edge labels correctly', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
title: On The Way To Something Something DarkSide
|
||||
config:
|
||||
look: default
|
||||
theme: default
|
||||
---
|
||||
|
||||
stateDiagram-v2
|
||||
|
||||
state State1_____________
|
||||
{
|
||||
c0
|
||||
}
|
||||
|
||||
state State2_____________
|
||||
{
|
||||
c1
|
||||
}
|
||||
|
||||
state State3_____________
|
||||
{
|
||||
c7
|
||||
}
|
||||
|
||||
state State4_____________
|
||||
{
|
||||
c2
|
||||
}
|
||||
|
||||
state State5_____________
|
||||
{
|
||||
c3
|
||||
}
|
||||
|
||||
state State6_____________
|
||||
{
|
||||
c4
|
||||
}
|
||||
|
||||
state State7_____________
|
||||
{
|
||||
c5
|
||||
}
|
||||
|
||||
state State8_____________
|
||||
{
|
||||
c6
|
||||
}
|
||||
|
||||
|
||||
[*] --> State1_____________
|
||||
State1_____________ --> State2_____________ : Transition1_____
|
||||
State2_____________ --> State4_____________ : Transition2_____
|
||||
State2_____________ --> State3_____________ : Transition3_____
|
||||
State3_____________ --> State2_____________
|
||||
State4_____________ --> State2_____________ : Transition5_____
|
||||
State4_____________ --> State5_____________ : Transition6_____
|
||||
State5_____________ --> State6_____________ : Transition7_____
|
||||
State6_____________ --> State4_____________ : Transition8_____
|
||||
State2_____________ --> State7_____________ : Transition4_____
|
||||
State4_____________ --> State7_____________ : Transition4_____
|
||||
State5_____________ --> State7_____________ : Transition4_____
|
||||
State6_____________ --> State7_____________ : Transition4_____
|
||||
State7_____________ --> State1_____________ : Transition9_____
|
||||
State5_____________ --> State8_____________ : Transition10____
|
||||
State8_____________ --> State5_____________ : Transition11____
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render edge labels correctly with multiple transitions', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
title: Multiple Transitions
|
||||
config:
|
||||
look: default
|
||||
theme: default
|
||||
---
|
||||
|
||||
stateDiagram-v2
|
||||
|
||||
state State1_____________
|
||||
{
|
||||
c0
|
||||
}
|
||||
|
||||
state State2_____________
|
||||
{
|
||||
c1
|
||||
}
|
||||
|
||||
state State3_____________
|
||||
{
|
||||
c7
|
||||
}
|
||||
|
||||
state State4_____________
|
||||
{
|
||||
c2
|
||||
}
|
||||
|
||||
state State5_____________
|
||||
{
|
||||
c3
|
||||
}
|
||||
|
||||
state State6_____________
|
||||
{
|
||||
c4
|
||||
}
|
||||
|
||||
state State7_____________
|
||||
{
|
||||
c5
|
||||
}
|
||||
|
||||
state State8_____________
|
||||
{
|
||||
c6
|
||||
}
|
||||
|
||||
state State9_____________
|
||||
{
|
||||
c9
|
||||
}
|
||||
|
||||
[*] --> State1_____________
|
||||
State1_____________ --> State2_____________ : Transition1_____
|
||||
State2_____________ --> State4_____________ : Transition2_____
|
||||
State2_____________ --> State3_____________ : Transition3_____
|
||||
State3_____________ --> State2_____________
|
||||
State4_____________ --> State2_____________ : Transition5_____
|
||||
State4_____________ --> State5_____________ : Transition6_____
|
||||
State5_____________ --> State6_____________ : Transition7_____
|
||||
State6_____________ --> State4_____________ : Transition8_____
|
||||
State2_____________ --> State7_____________ : Transition4_____
|
||||
State4_____________ --> State7_____________ : Transition4_____
|
||||
State5_____________ --> State7_____________ : Transition4_____
|
||||
State6_____________ --> State7_____________ : Transition4_____
|
||||
State7_____________ --> State1_____________ : Transition9_____
|
||||
State5_____________ --> State8_____________ : Transition10____
|
||||
State8_____________ --> State5_____________ : Transition11____
|
||||
State9_____________ --> State8_____________ : Transition12____
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render edge labels correctly with multiple states', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
title: Multiple States
|
||||
config:
|
||||
look: default
|
||||
theme: default
|
||||
---
|
||||
|
||||
stateDiagram-v2
|
||||
|
||||
state State1_____________
|
||||
{
|
||||
c0
|
||||
}
|
||||
|
||||
state State2_____________
|
||||
{
|
||||
c1
|
||||
}
|
||||
|
||||
state State3_____________
|
||||
{
|
||||
c7
|
||||
}
|
||||
|
||||
state State4_____________
|
||||
{
|
||||
c2
|
||||
}
|
||||
|
||||
state State5_____________
|
||||
{
|
||||
c3
|
||||
}
|
||||
|
||||
state State6_____________
|
||||
{
|
||||
c4
|
||||
}
|
||||
|
||||
state State7_____________
|
||||
{
|
||||
c5
|
||||
}
|
||||
|
||||
state State8_____________
|
||||
{
|
||||
c6
|
||||
}
|
||||
|
||||
state State9_____________
|
||||
{
|
||||
c9
|
||||
}
|
||||
|
||||
state State10_____________
|
||||
{
|
||||
c10
|
||||
}
|
||||
|
||||
[*] --> State1_____________
|
||||
State1_____________ --> State2_____________ : Transition1_____
|
||||
State2_____________ --> State3_____________ : Transition2_____
|
||||
State3_____________ --> State4_____________ : Transition3_____
|
||||
State4_____________ --> State5_____________ : Transition4_____
|
||||
State5_____________ --> State6_____________ : Transition5_____
|
||||
State6_____________ --> State7_____________ : Transition6_____
|
||||
State7_____________ --> State8_____________ : Transition7_____
|
||||
State8_____________ --> State9_____________ : Transition8_____
|
||||
State9_____________ --> State10_____________ : Transition9_____
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('XY Chart', () => {
|
||||
it('should render the simplest possible xy-beta chart', () => {
|
||||
it('should render the simplest possible chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart-beta
|
||||
@@ -10,19 +10,10 @@ describe('XY Chart', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render the simplest possible xy chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
line [10, 30, 20]
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Should render a complete chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -35,7 +26,7 @@ describe('XY Chart', () => {
|
||||
it('Should render a chart without title', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
@@ -47,7 +38,7 @@ describe('XY Chart', () => {
|
||||
it('y-axis title not required', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
@@ -59,7 +50,7 @@ describe('XY Chart', () => {
|
||||
it('Should render a chart without y-axis with different range', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 14000, 3200, 9200, 9900, 3400, 6000]
|
||||
line [2000, 7000, 6500, 9200, 9500, 7500, 11000, 10200, 3200, 8500, 7000, 8800]
|
||||
@@ -70,7 +61,7 @@ describe('XY Chart', () => {
|
||||
it('x axis title not required', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 14000, 3200, 9200, 9900, 3400, 6000]
|
||||
line [2000, 7000, 6500, 9200, 9500, 7500, 11000, 10200, 3200, 8500, 7000, 8800]
|
||||
@@ -81,7 +72,7 @@ describe('XY Chart', () => {
|
||||
it('Multiple plots can be rendered', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
line [23, 46, 77, 34]
|
||||
line [45, 32, 33, 12]
|
||||
bar [87, 54, 99, 85]
|
||||
@@ -95,7 +86,7 @@ describe('XY Chart', () => {
|
||||
it('Decimals and negative numbers are supported', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
y-axis -2.4 --> 3.5
|
||||
line [+1.3, .6, 2.4, -.34]
|
||||
`,
|
||||
@@ -113,7 +104,7 @@ describe('XY Chart', () => {
|
||||
height: 20
|
||||
plotReservedSpacePercent: 100
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
line [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
|
||||
`,
|
||||
{}
|
||||
@@ -139,7 +130,7 @@ describe('XY Chart', () => {
|
||||
showTick: false
|
||||
showAxisLine: false
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
bar [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
|
||||
`,
|
||||
{}
|
||||
@@ -149,7 +140,7 @@ describe('XY Chart', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {"xyChart": {"width": 1000, "height": 600, "titlePadding": 5, "titleFontSize": 10, "xAxis": {"labelFontSize": "20", "labelPadding": 10, "titleFontSize": 30, "titlePadding": 20, "tickLength": 10, "tickWidth": 5}, "yAxis": {"labelFontSize": "20", "labelPadding": 10, "titleFontSize": 30, "titlePadding": 20, "tickLength": 10, "tickWidth": 5}, "plotBorderWidth": 5, "chartOrientation": "horizontal", "plotReservedSpacePercent": 60 }}}%%
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -190,7 +181,7 @@ describe('XY Chart', () => {
|
||||
plotReservedSpacePercent: 60
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -211,7 +202,7 @@ describe('XY Chart', () => {
|
||||
yAxis:
|
||||
showTitle: false
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -232,7 +223,7 @@ describe('XY Chart', () => {
|
||||
yAxis:
|
||||
showLabel: false
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -253,7 +244,7 @@ describe('XY Chart', () => {
|
||||
yAxis:
|
||||
showTick: false
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -274,7 +265,7 @@ describe('XY Chart', () => {
|
||||
yAxis:
|
||||
showAxisLine: false
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -303,7 +294,7 @@ describe('XY Chart', () => {
|
||||
xAxisLineColor: "#87ceeb"
|
||||
plotColorPalette: "#008000, #faba63"
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -316,7 +307,7 @@ describe('XY Chart', () => {
|
||||
it('should use the correct distances between data points', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
x-axis 0 --> 2
|
||||
line [0, 1, 0, 1]
|
||||
bar [1, 0, 1, 0]
|
||||
@@ -334,7 +325,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -353,7 +344,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -366,7 +357,7 @@ describe('XY Chart', () => {
|
||||
it('should render vertical bar chart without labels by default', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -384,7 +375,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -402,7 +393,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Multiple Bar Plots"
|
||||
x-axis Categories [A, B, C]
|
||||
y-axis "Values" 0 --> 100
|
||||
@@ -421,7 +412,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Multiple Bar Plots"
|
||||
x-axis Categories [A, B, C]
|
||||
y-axis "Values" 0 --> 100
|
||||
@@ -439,7 +430,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Single Bar Chart"
|
||||
x-axis Categories [A]
|
||||
y-axis "Value" 0 --> 100
|
||||
@@ -458,7 +449,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Single Bar Chart"
|
||||
x-axis Categories [A]
|
||||
y-axis "Value" 0 --> 100
|
||||
@@ -476,7 +467,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Decimal and Negative Values"
|
||||
x-axis Categories [A, B, C]
|
||||
y-axis -10 --> 10
|
||||
@@ -495,7 +486,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Decimal and Negative Values"
|
||||
x-axis Categories [A, B, C]
|
||||
y-axis -10 --> 10
|
||||
@@ -513,7 +504,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan,b,c]
|
||||
y-axis "Revenue (in $)" 4000 --> 12000
|
||||
@@ -570,7 +561,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan,b,c]
|
||||
y-axis "Revenue (in $)" 4000 --> 12000
|
||||
@@ -624,7 +615,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
|
||||
y-axis "Revenue (in $)" 4000 --> 12000
|
||||
@@ -681,7 +672,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
|
||||
y-axis "Revenue (in $)" 4000 --> 12000
|
||||
@@ -735,7 +726,7 @@ describe('XY Chart', () => {
|
||||
xyChart:
|
||||
showDataLabel: true
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan]
|
||||
y-axis "Revenue (in $)" 3000 --> 12000
|
||||
@@ -792,7 +783,7 @@ describe('XY Chart', () => {
|
||||
showDataLabel: true
|
||||
chartOrientation: horizontal
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan]
|
||||
y-axis "Revenue (in $)" 3000 --> 12000
|
||||
|
@@ -1,35 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Pie chart demos</h1>
|
||||
<pre class="mermaid">
|
||||
pie title Default text position: Animal adoption
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"dogs" : -60.67
|
||||
"rats" : 40.12
|
||||
</pre>
|
||||
|
||||
<hr />
|
||||
<script type="module">
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
601
cypress/platform/ipsepcola_sample.html
Normal file
601
cypress/platform/ipsepcola_sample.html
Normal file
@@ -0,0 +1,601 @@
|
||||
<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/6.7.2/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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.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 {
|
||||
/* background: #333; */
|
||||
font-family: 'Arial';
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.mermaid2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
border: 3px solid #300;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
A --> B
|
||||
subgraph hello
|
||||
C --> D
|
||||
end
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
A --> B
|
||||
A --> B
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
A[hello] --> A
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
subgraph C
|
||||
c
|
||||
end
|
||||
A --> C
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
subgraph C
|
||||
c
|
||||
end
|
||||
A --> c
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart TD
|
||||
subgraph D
|
||||
A --> B
|
||||
A --> B
|
||||
B --> A
|
||||
B --> A
|
||||
end
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
subgraph B2
|
||||
A --> B --> C
|
||||
B --> D
|
||||
end
|
||||
|
||||
B2 --> X
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
classDiagram
|
||||
Animal <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class Duck{
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart TD
|
||||
P1
|
||||
P1 -->P1.5
|
||||
subgraph P1.5
|
||||
P2
|
||||
P2.5(( A ))
|
||||
P3
|
||||
end
|
||||
P2 --> P4
|
||||
P3 --> P6
|
||||
P1.5 --> P5
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% Length of edges
|
||||
flowchart TD
|
||||
L1 --- L2
|
||||
L2 --- C
|
||||
M1 ---> C
|
||||
R1 .-> R2
|
||||
R2 <.-> C
|
||||
C -->|Label 1| E1
|
||||
C <-- Label 2 ---> E2
|
||||
C ----> E3
|
||||
C <-...-> E4
|
||||
C ======> E5
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% Stadium shape
|
||||
flowchart TD
|
||||
A([stadium shape test])
|
||||
A -->|Get money| B([Go shopping])
|
||||
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
|
||||
C -->|One| D([Laptop])
|
||||
C -->|Two| E([iPhone])
|
||||
C -->|Three| F([Car<br/>wroom wroom])
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
class C someclass;
|
||||
</pre>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% should render escaped without html labels
|
||||
flowchart TD
|
||||
a["<strong>Haiya</strong>"]---->b
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs in reverse order
|
||||
flowchart LR
|
||||
a -->b
|
||||
subgraph A
|
||||
B
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs in several levels
|
||||
flowchart LR
|
||||
b-->B
|
||||
a-->c
|
||||
subgraph O
|
||||
A
|
||||
end
|
||||
subgraph B
|
||||
c
|
||||
end
|
||||
subgraph A
|
||||
a
|
||||
b
|
||||
B
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with edges in and out
|
||||
flowchart LR
|
||||
internet
|
||||
nat
|
||||
routeur
|
||||
lb1
|
||||
lb2
|
||||
compute1
|
||||
compute2
|
||||
subgraph project
|
||||
routeur
|
||||
nat
|
||||
subgraph subnet1
|
||||
compute1
|
||||
lb1
|
||||
end
|
||||
subgraph subnet2
|
||||
compute2
|
||||
lb2
|
||||
end
|
||||
end
|
||||
internet --> routeur
|
||||
routeur --> subnet1 & subnet2
|
||||
subnet1 & subnet2 --> nat --> internet
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links
|
||||
flowchart LR
|
||||
subgraph main
|
||||
subgraph subcontainer
|
||||
subcontainer-child
|
||||
end
|
||||
subcontainer-child--> subcontainer-sibling
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with ingoing links
|
||||
flowchart LR
|
||||
subgraph one[One]
|
||||
subgraph sub_one[Sub One]
|
||||
_sub_one
|
||||
end
|
||||
subgraph sub_two[Sub Two]
|
||||
_sub_two
|
||||
end
|
||||
_one
|
||||
end
|
||||
|
||||
%% here, either the first or the second one
|
||||
sub_one --> sub_two
|
||||
_one --> b
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links 3
|
||||
flowchart LR
|
||||
subgraph container_Beta
|
||||
process_C-->Process_D
|
||||
end
|
||||
subgraph container_Alpha
|
||||
process_A-->process_B
|
||||
process_A-->|messages|process_C
|
||||
end
|
||||
process_B-->|via_AWSBatch|container_Beta
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links 4
|
||||
flowchart LR
|
||||
subgraph A
|
||||
a -->b
|
||||
end
|
||||
subgraph B
|
||||
b
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links 2
|
||||
flowchart LR
|
||||
c1-->a2
|
||||
subgraph one
|
||||
a1-->a2
|
||||
end
|
||||
subgraph two
|
||||
b1-->b2
|
||||
end
|
||||
subgraph three
|
||||
c1-->c2
|
||||
end
|
||||
one --> two
|
||||
three --> two
|
||||
two --> c2
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links 5
|
||||
flowchart LR
|
||||
subgraph container_Beta
|
||||
process_C-->Process_D
|
||||
end
|
||||
subgraph container_Alpha
|
||||
process_A-->process_B
|
||||
process_B-->|via_AWSBatch|container_Beta
|
||||
process_A-->|messages|process_C
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% More subgraphs
|
||||
flowchart LR
|
||||
subgraph two
|
||||
b1
|
||||
end
|
||||
subgraph three
|
||||
c2
|
||||
end
|
||||
|
||||
three --> two
|
||||
two --> c2
|
||||
note[There are two links in this diagram]
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% nested subgraphs with outgoing links 5
|
||||
flowchart LR
|
||||
A[red text] -->|default style| B(blue text)
|
||||
C([red text]) -->|default style| D[[blue text]]
|
||||
E[(red text)] -->|default style| F((blue text))
|
||||
G>red text] -->|default style| H{blue text}
|
||||
I{{red text}} -->|default style| J[/blue text/]
|
||||
K[\\ red text\\] -->|default style| L[/blue text\\]
|
||||
M[\\ red text/] -->|default style| N[blue text];
|
||||
O(((red text))) -->|default style| P(((blue text)));
|
||||
linkStyle default color:Sienna;
|
||||
style A stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style B stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style C stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style D stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style E stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style F stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style G stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style H stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style I stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style J stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style K stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style L stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style M stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style N stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
style O stroke:#ff0000,fill:#ffcccc,color:#ff0000;
|
||||
style P stroke:#0000ff,fill:#ccccff,color:#0000ff;
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% labels on edges in a cluster
|
||||
flowchart RL
|
||||
subgraph one
|
||||
a1 -- I am a long label --> a2
|
||||
a1 -- Another long label --> a2
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% labels on edges in a cluster
|
||||
flowchart RL
|
||||
subgraph one
|
||||
a1[Iam a node with a super long label] -- I am a long label --> a2[I am another node with a mega long label]
|
||||
a1 -- Another long label --> a2
|
||||
a3 --> a1 & a2 & a3 & a4
|
||||
a1 --> a4
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% labels on edges in a cluster
|
||||
flowchart RL
|
||||
subgraph one
|
||||
a1[Iam a node with a super long label]
|
||||
a2[I am another node with a mega long label]
|
||||
a3[I am a node with a super long label]
|
||||
a4[I am another node with a mega long label]
|
||||
a1 -- Another long label --> a2
|
||||
a3 --> a1 & a2 & a3 & a4
|
||||
a1 --> a4
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
%% labels on edges in a cluster
|
||||
flowchart RL
|
||||
a1[I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. I am a node with a super long label. ]
|
||||
a2[I am another node with a mega long label]
|
||||
a3[I am a node with a super long label]
|
||||
a4[I am another node with a mega long label]
|
||||
a1 & a2 & a3 & a4 --> a5 & a6 & a7 & a8 & a9 & a10
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
subgraph Z
|
||||
subgraph X
|
||||
a --> b
|
||||
end
|
||||
subgraph Y
|
||||
c --> d
|
||||
end
|
||||
end
|
||||
Y --> X
|
||||
X --> P
|
||||
P --> Y
|
||||
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart
|
||||
|
||||
a --> b
|
||||
b --> c
|
||||
b --> d
|
||||
c --> a
|
||||
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre id="diagram3" class="mermaid">
|
||||
flowchart TD
|
||||
Start([Start]) --> Prep[Preparation Step]
|
||||
Prep --> Split{Ready to Process?}
|
||||
Split -->|Yes| T1[Task A]
|
||||
Split -->|Yes| T2[Task B]
|
||||
T1 --> Merge
|
||||
T2 --> Merge
|
||||
Merge((Join Results)) --> Finalize[Finalize Process]
|
||||
Finalize --> End([End])
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
flowchart TD
|
||||
A[Start Build] --> B[Compile Source]
|
||||
B --> C[Test Suite]
|
||||
C --> D{Tests Passed?}
|
||||
D -->|No| E[Notify Developer]
|
||||
E --> A
|
||||
D -->|Yes| F[Build Docker Image]
|
||||
|
||||
subgraph Deploy Pipeline
|
||||
F --> G[Deploy to Staging]
|
||||
G --> H[Run Integration Tests]
|
||||
H --> I{Tests Passed?}
|
||||
I -->|No| J[Rollback & Alert]
|
||||
I -->|Yes| K[Deploy to Production]
|
||||
end
|
||||
|
||||
K --> L([Success])
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
class Controller {
|
||||
+handleRequest(): void
|
||||
}
|
||||
|
||||
class View {
|
||||
+render(): void
|
||||
}
|
||||
|
||||
class Model {
|
||||
+getData(): any
|
||||
+setData(data: any): void
|
||||
}
|
||||
|
||||
Controller --> Model
|
||||
Controller --> View
|
||||
Model --> View : notifyChange()
|
||||
</pre
|
||||
>
|
||||
|
||||
<pre class="mermaid">
|
||||
classDiagram
|
||||
class AuthService {
|
||||
+login(username: string, password: string): boolean
|
||||
+logout(): void
|
||||
+register(): void
|
||||
}
|
||||
|
||||
class User {
|
||||
-username: string
|
||||
-password: string
|
||||
-role: Role
|
||||
+changePassword(): void
|
||||
}
|
||||
|
||||
class Role {
|
||||
-name: string
|
||||
-permissions: string[]
|
||||
+hasPermission(): boolean
|
||||
}
|
||||
|
||||
AuthService --> User
|
||||
User --> Role
|
||||
</pre
|
||||
>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||
|
||||
const staticBellIconPack = {
|
||||
prefix: 'fa6-regular',
|
||||
icons: {
|
||||
bell: {
|
||||
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
|
||||
width: 448,
|
||||
},
|
||||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
};
|
||||
|
||||
mermaid.registerIconPacks([
|
||||
{
|
||||
name: 'logos',
|
||||
loader: () =>
|
||||
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
|
||||
},
|
||||
{
|
||||
name: 'fa',
|
||||
loader: () => staticBellIconPack,
|
||||
},
|
||||
]);
|
||||
mermaid.registerLayoutLoaders(layouts);
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('Mermaid error: ', err);
|
||||
};
|
||||
window.callback = function () {
|
||||
alert('A callback was triggered');
|
||||
};
|
||||
function callback() {
|
||||
alert('It worked');
|
||||
}
|
||||
await mermaid.initialize({
|
||||
theme: 'redux-dark',
|
||||
// theme: 'default',
|
||||
// theme: 'forest',
|
||||
handDrawnSeed: 12,
|
||||
look: 'classic ',
|
||||
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
|
||||
// layout: 'dagre',
|
||||
layout: 'ipsepCola',
|
||||
// layout: 'elk',
|
||||
// layout: 'sugiyama',
|
||||
// htmlLabels: false,
|
||||
flowchart: { titleTopMargin: 10 },
|
||||
|
||||
// fontFamily: 'Caveat',
|
||||
// fontFamily: 'Kalam',
|
||||
// fontFamily: 'courier',
|
||||
fontFamily: 'arial',
|
||||
sequence: {
|
||||
actorFontFamily: 'courier',
|
||||
noteFontFamily: 'courier',
|
||||
messageFontFamily: 'courier',
|
||||
},
|
||||
kanban: {
|
||||
htmlLabels: false,
|
||||
},
|
||||
fontSize: 12,
|
||||
logLevel: 0,
|
||||
securityLevel: 'loose',
|
||||
callback,
|
||||
});
|
||||
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('In parse error:');
|
||||
console.error(err);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -130,76 +130,6 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
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>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram4" class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
c
|
||||
d
|
||||
B
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
D
|
||||
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
</pre>
|
||||
|
||||
<pre id="diagram4" class="mermaid">
|
||||
treemap
|
||||
"Section 1"
|
||||
@@ -245,145 +175,8 @@ treemap
|
||||
"Item B2": 25
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
flowchart TB
|
||||
A --> n0["1"]
|
||||
A --> n1["2"]
|
||||
A --> n2["3"]
|
||||
A --> n3["4"] --> Q & R & S & T
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
flowchart TB
|
||||
A --> n0["1"]
|
||||
A --> n1["2"]
|
||||
A --> n2["3"]
|
||||
A --> n3["4"] --> Q & R & S & T
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
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>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
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>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
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>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
flowchart LR
|
||||
root{mindmap} --- Origins --- Europe
|
||||
Origins --> Asia
|
||||
root --- Background --- Rich
|
||||
Background --- Poor
|
||||
subgraph apa
|
||||
Background
|
||||
Poor
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
flowchart LR
|
||||
root{mindmap} --- Origins --- Europe
|
||||
Origins --> Asia
|
||||
root --- Background --- Rich
|
||||
Background --- Poor
|
||||
|
||||
|
||||
|
||||
|
||||
AB["apa@apa@"] --> B(("`apa@apa`"))
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
flowchart
|
||||
@@ -465,44 +258,6 @@ config:
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
flowchart LR
|
||||
a
|
||||
subgraph s0["APA"]
|
||||
subgraph s8["APA"]
|
||||
subgraph s1["APA"]
|
||||
D{"X"}
|
||||
E[Q]
|
||||
end
|
||||
subgraph s3["BAPA"]
|
||||
F[Q]
|
||||
I
|
||||
end
|
||||
D --> I
|
||||
D --> I
|
||||
D --> I
|
||||
|
||||
I{"X"}
|
||||
end
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
flowchart LR
|
||||
a
|
||||
D{"Use the editor"}
|
||||
|
||||
D -- Mermaid js --> I{"fa:fa-code Text"}
|
||||
D-->I
|
||||
D-->I
|
||||
</pre>
|
||||
<pre id="diagram4" class="mermaid2">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
|
@@ -1,376 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
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
|
||||
id)I am a cloud(
|
||||
id))I am a bang((
|
||||
Tools
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
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
|
||||
id)I am a cloud(
|
||||
id))I am a bang((
|
||||
Tools
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
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
|
||||
id)I am a cloud(
|
||||
id))I am a bang((
|
||||
Tools
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
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
|
||||
id)I am a cloud(
|
||||
id))I am a bang((
|
||||
Tools
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
c
|
||||
d
|
||||
B
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
D
|
||||
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
c
|
||||
d
|
||||
B
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
D
|
||||
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
c
|
||||
d
|
||||
B
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
D
|
||||
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
a
|
||||
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
b
|
||||
c
|
||||
d
|
||||
B
|
||||
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
D
|
||||
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
((This is a mindmap))
|
||||
child1
|
||||
grandchild 1
|
||||
grandchild 2
|
||||
child2
|
||||
grandchild 3
|
||||
grandchild 4
|
||||
child3
|
||||
grandchild 5
|
||||
grandchild 6
|
||||
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
---
|
||||
mindmap
|
||||
((This is a mindmap))
|
||||
child1
|
||||
grandchild 1
|
||||
grandchild 2
|
||||
child2
|
||||
grandchild 3
|
||||
grandchild 4
|
||||
child3
|
||||
grandchild 5
|
||||
grandchild 6
|
||||
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
mindmap
|
||||
((This is a mindmap))
|
||||
child1
|
||||
grandchild 1
|
||||
grandchild 2
|
||||
child2
|
||||
grandchild 3
|
||||
grandchild 4
|
||||
child3
|
||||
grandchild 5
|
||||
grandchild 6
|
||||
|
||||
</pre>
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
layout: cose-bilkent
|
||||
---
|
||||
mindmap
|
||||
((This is a mindmap))
|
||||
child1
|
||||
grandchild 1
|
||||
grandchild 2
|
||||
child2
|
||||
grandchild 3
|
||||
grandchild 4
|
||||
child3
|
||||
grandchild 5
|
||||
grandchild 6
|
||||
|
||||
</pre>
|
||||
|
||||
<hr />
|
||||
<script type="module">
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
import tidytree from '/mermaid-layout-tidy-tree.esm.mjs';
|
||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||
mermaid.registerLayoutLoaders(layouts);
|
||||
mermaid.registerLayoutLoaders(tidytree);
|
||||
mermaid.initialize({
|
||||
theme: 'default',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,6 +1,5 @@
|
||||
import externalExample from './mermaid-example-diagram.esm.mjs';
|
||||
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||
import tidyTree from './mermaid-layout-tidy-tree.esm.mjs';
|
||||
import zenUml from './mermaid-zenuml.esm.mjs';
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
|
||||
@@ -66,7 +65,6 @@ const contentLoaded = async function () {
|
||||
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
|
||||
|
||||
mermaid.registerLayoutLoaders(layouts);
|
||||
mermaid.registerLayoutLoaders(tidyTree);
|
||||
mermaid.initialize(graphObj.mermaid);
|
||||
/**
|
||||
* CC-BY-4.0
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<body>
|
||||
<h1>Block diagram demos</h1>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
@@ -26,7 +26,7 @@ columns 1
|
||||
style B fill:#f9F,stroke:#333,stroke-width:4px
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
A1["square"]
|
||||
B1("rounded")
|
||||
C1(("circle"))
|
||||
@@ -36,7 +36,7 @@ block
|
||||
</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
A1(["stadium"])
|
||||
A2[["subroutine"]]
|
||||
B1[("cylinder")]
|
||||
@@ -48,7 +48,7 @@ block
|
||||
</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
block:e:4
|
||||
columns 2
|
||||
f
|
||||
@@ -57,7 +57,7 @@ block
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
block:e:4
|
||||
columns 2
|
||||
f
|
||||
@@ -67,7 +67,7 @@ block
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a:3
|
||||
block:e:3
|
||||
@@ -80,7 +80,7 @@ block
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 4
|
||||
a b c d
|
||||
block:e:4
|
||||
@@ -97,19 +97,19 @@ flowchart LR
|
||||
X-- "a label" -->z
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 5
|
||||
A space B
|
||||
A --x B
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a["A wide one"] b:2 c:2 d
|
||||
</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a b c
|
||||
e:3
|
||||
@@ -117,7 +117,7 @@ columns 3
|
||||
</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
block
|
||||
block-beta
|
||||
|
||||
A1:3
|
||||
A2:1
|
||||
|
@@ -20,14 +20,12 @@
|
||||
width: 800
|
||||
nodeAlignment: left
|
||||
---
|
||||
sankey
|
||||
a,b,8
|
||||
b,c,8
|
||||
c,d,8
|
||||
d,e,8
|
||||
|
||||
x,c,4
|
||||
c,y,4
|
||||
sankey-beta
|
||||
Revenue,Expenses,10
|
||||
Revenue,Profit,10
|
||||
Expenses,Manufacturing,5
|
||||
Expenses,Tax,3
|
||||
Expenses,Research,2
|
||||
</pre>
|
||||
|
||||
<h2>Energy flow</h2>
|
||||
@@ -42,7 +40,7 @@
|
||||
linkColor: gradient
|
||||
nodeAlignment: justify
|
||||
---
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<body>
|
||||
<h1>XY Charts demos</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue (in $)"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -26,7 +26,7 @@
|
||||
<hr />
|
||||
<h1>XY Charts horizontal</h1>
|
||||
<pre class="mermaid">
|
||||
xychart horizontal
|
||||
xychart-beta horizontal
|
||||
title "Basic xychart"
|
||||
x-axis "this is x axis" [category1, "category 2", category3, category4]
|
||||
y-axis yaxisText 10 --> 150
|
||||
@@ -36,7 +36,7 @@
|
||||
<hr />
|
||||
<h1>XY Charts only lines and bar</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
line [23, 46, 77, 34]
|
||||
line [45, 32, 33, 12]
|
||||
line [87, 54, 99, 85]
|
||||
@@ -48,13 +48,13 @@
|
||||
<hr />
|
||||
<h1>XY Charts with +ve and -ve numbers</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
line [+1.3, .6, 2.4, -.34]
|
||||
</pre>
|
||||
|
||||
<h1>XY Charts Bar with multiple category</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Basic xychart with many categories"
|
||||
x-axis "this is x axis" [category1, "category 2", category3, category4, category5, category6, category7]
|
||||
y-axis yaxisText 10 --> 150
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
<h1>XY Charts line with multiple category</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Line chart with many category"
|
||||
x-axis "this is x axis" [category1, "category 2", category3, category4, category5, category6, category7]
|
||||
y-axis yaxisText 10 --> 150
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<h1>XY Charts category with large text</h1>
|
||||
<pre class="mermaid">
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Basic xychart with many categories with category overlap"
|
||||
x-axis "this is x axis" [category1, "Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", category3, category4, category5, category6, category7]
|
||||
y-axis yaxisText 10 --> 150
|
||||
@@ -89,7 +89,7 @@ config:
|
||||
height: 20
|
||||
plotReservedSpacePercent: 100
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
line [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
|
||||
</pre>
|
||||
|
||||
@@ -103,7 +103,7 @@ config:
|
||||
height: 20
|
||||
plotReservedSpacePercent: 100
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
bar [5000, 9000, 7500, 6200, 9500, 5500, 11000, 8200, 9200, 9500, 7000, 8800]
|
||||
</pre>
|
||||
|
||||
@@ -136,7 +136,7 @@ config:
|
||||
chartOrientation: horizontal
|
||||
plotReservedSpacePercent: 60
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -162,7 +162,7 @@ config:
|
||||
xAxisLineColor: "#87ceeb"
|
||||
plotColorPalette: "#008000, #faba63"
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
|
@@ -29,8 +29,7 @@ In GitHub, you first [**fork a mermaid repository**](https://github.com/mermaid-
|
||||
|
||||
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
|
||||
|
||||
> **💡 Tip**
|
||||
> [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
|
||||
> **💡 Tip** > [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
|
||||
|
||||
```bash
|
||||
git clone git@github.com/your-fork/mermaid
|
||||
|
@@ -33,8 +33,7 @@ mindmap
|
||||
|
||||
## Join the Development
|
||||
|
||||
> **💡 Tip**
|
||||
> **Check out our** [**detailed contribution guide**](./contributing.md).
|
||||
> **💡 Tip** > **Check out our** [**detailed contribution guide**](./contributing.md).
|
||||
|
||||
Where to start:
|
||||
|
||||
@@ -48,8 +47,7 @@ Where to start:
|
||||
|
||||
## A Question Or a Suggestion?
|
||||
|
||||
> **💡 Tip**
|
||||
> **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
|
||||
> **💡 Tip** > **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
|
||||
|
||||
If you have faced a vulnerability [report it to us](./security.md).
|
||||
|
||||
|
@@ -22,6 +22,7 @@ While directives allow you to change most of the default configuration settings,
|
||||
Mermaid basically supports two types of configuration options to be overridden by directives.
|
||||
|
||||
1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are:
|
||||
|
||||
- theme
|
||||
- fontFamily
|
||||
- logLevel
|
||||
|
@@ -10,6 +10,10 @@
|
||||
|
||||
# mermaid
|
||||
|
||||
## Classes
|
||||
|
||||
- [UnknownDiagramError](classes/UnknownDiagramError.md)
|
||||
|
||||
## Interfaces
|
||||
|
||||
- [DetailedError](interfaces/DetailedError.md)
|
||||
@@ -23,7 +27,6 @@
|
||||
- [RenderOptions](interfaces/RenderOptions.md)
|
||||
- [RenderResult](interfaces/RenderResult.md)
|
||||
- [RunOptions](interfaces/RunOptions.md)
|
||||
- [UnknownDiagramError](interfaces/UnknownDiagramError.md)
|
||||
|
||||
## Type Aliases
|
||||
|
||||
|
159
docs/config/setup/mermaid/classes/UnknownDiagramError.md
Normal file
159
docs/config/setup/mermaid/classes/UnknownDiagramError.md
Normal file
@@ -0,0 +1,159 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md).
|
||||
|
||||
[**mermaid**](../../README.md)
|
||||
|
||||
---
|
||||
|
||||
# Class: UnknownDiagramError
|
||||
|
||||
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
|
||||
|
||||
## Extends
|
||||
|
||||
- `Error`
|
||||
|
||||
## Constructors
|
||||
|
||||
### new UnknownDiagramError()
|
||||
|
||||
> **new UnknownDiagramError**(`message`): [`UnknownDiagramError`](UnknownDiagramError.md)
|
||||
|
||||
Defined in: [packages/mermaid/src/errors.ts:2](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L2)
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### message
|
||||
|
||||
`string`
|
||||
|
||||
#### Returns
|
||||
|
||||
[`UnknownDiagramError`](UnknownDiagramError.md)
|
||||
|
||||
#### Overrides
|
||||
|
||||
`Error.constructor`
|
||||
|
||||
## Properties
|
||||
|
||||
### cause?
|
||||
|
||||
> `optional` **cause**: `unknown`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.cause`
|
||||
|
||||
---
|
||||
|
||||
### message
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.message`
|
||||
|
||||
---
|
||||
|
||||
### name
|
||||
|
||||
> **name**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.name`
|
||||
|
||||
---
|
||||
|
||||
### stack?
|
||||
|
||||
> `optional` **stack**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.stack`
|
||||
|
||||
---
|
||||
|
||||
### prepareStackTrace()?
|
||||
|
||||
> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any`
|
||||
|
||||
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:143
|
||||
|
||||
Optional override for formatting stack traces
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### err
|
||||
|
||||
`Error`
|
||||
|
||||
##### stackTraces
|
||||
|
||||
`CallSite`\[]
|
||||
|
||||
#### Returns
|
||||
|
||||
`any`
|
||||
|
||||
#### See
|
||||
|
||||
<https://v8.dev/docs/stack-trace-api#customizing-stack-traces>
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.prepareStackTrace`
|
||||
|
||||
---
|
||||
|
||||
### stackTraceLimit
|
||||
|
||||
> `static` **stackTraceLimit**: `number`
|
||||
|
||||
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:145
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.stackTraceLimit`
|
||||
|
||||
## Methods
|
||||
|
||||
### captureStackTrace()
|
||||
|
||||
> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void`
|
||||
|
||||
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:136
|
||||
|
||||
Create .stack property on a target object
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### targetObject
|
||||
|
||||
`object`
|
||||
|
||||
##### constructorOpt?
|
||||
|
||||
`Function`
|
||||
|
||||
#### Returns
|
||||
|
||||
`void`
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.captureStackTrace`
|
@@ -10,7 +10,7 @@
|
||||
|
||||
# Interface: LayoutData
|
||||
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:178](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L178)
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L153)
|
||||
|
||||
## Indexable
|
||||
|
||||
@@ -22,7 +22,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:178](https://github.co
|
||||
|
||||
> **config**: [`MermaidConfig`](MermaidConfig.md)
|
||||
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L181)
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L156)
|
||||
|
||||
---
|
||||
|
||||
@@ -30,7 +30,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:181](https://github.co
|
||||
|
||||
> **edges**: `Edge`\[]
|
||||
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:180](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L180)
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L155)
|
||||
|
||||
---
|
||||
|
||||
@@ -38,4 +38,4 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:180](https://github.co
|
||||
|
||||
> **nodes**: `Node`\[]
|
||||
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:179](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L179)
|
||||
Defined in: [packages/mermaid/src/rendering-util/types.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L154)
|
||||
|
@@ -32,7 +32,7 @@ page.
|
||||
|
||||
### detectType()
|
||||
|
||||
> **detectType**: (`text`, `config?`) => `string`
|
||||
> **detectType**: (`text`, `config`?) => `string`
|
||||
|
||||
Defined in: [packages/mermaid/src/mermaid.ts:449](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L449)
|
||||
|
||||
@@ -105,7 +105,7 @@ An array of objects with the id of the diagram.
|
||||
|
||||
### ~~init()~~
|
||||
|
||||
> **init**: (`config?`, `nodes?`, `callback?`) => `Promise`<`void`>
|
||||
> **init**: (`config`?, `nodes`?, `callback`?) => `Promise`<`void`>
|
||||
|
||||
Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
|
||||
|
||||
@@ -117,7 +117,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/
|
||||
|
||||
[`MermaidConfig`](MermaidConfig.md)
|
||||
|
||||
**Deprecated**, please set configuration in [initialize](#initialize).
|
||||
**Deprecated**, please set configuration in [initialize](Mermaid.md#initialize).
|
||||
|
||||
##### nodes?
|
||||
|
||||
@@ -141,13 +141,13 @@ Called once for each rendered diagram's id.
|
||||
|
||||
#### Deprecated
|
||||
|
||||
Use [initialize](#initialize) and [run](#run) instead.
|
||||
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
|
||||
|
||||
Renders the mermaid diagrams
|
||||
|
||||
#### Deprecated
|
||||
|
||||
Use [initialize](#initialize) and [run](#run) instead.
|
||||
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
|
||||
|
||||
---
|
||||
|
||||
@@ -176,7 +176,7 @@ Configuration object for mermaid.
|
||||
|
||||
### ~~mermaidAPI~~
|
||||
|
||||
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }; `render`: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
|
||||
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>; `render`: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
|
||||
|
||||
Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
|
||||
|
||||
@@ -184,81 +184,73 @@ Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/
|
||||
|
||||
#### Deprecated
|
||||
|
||||
Use [parse](#parse) and [render](#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
|
||||
Use [parse](Mermaid.md#parse) and [render](Mermaid.md#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
|
||||
|
||||
---
|
||||
|
||||
### parse()
|
||||
|
||||
> **parse**: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }
|
||||
> **parse**: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>
|
||||
|
||||
Defined in: [packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
|
||||
|
||||
#### Call Signature
|
||||
|
||||
> (`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>
|
||||
|
||||
Parse the text and validate the syntax.
|
||||
|
||||
##### Parameters
|
||||
#### Parameters
|
||||
|
||||
###### text
|
||||
##### text
|
||||
|
||||
`string`
|
||||
|
||||
The mermaid diagram definition.
|
||||
|
||||
###### parseOptions
|
||||
##### parseOptions
|
||||
|
||||
[`ParseOptions`](ParseOptions.md) & `object`
|
||||
|
||||
Options for parsing.
|
||||
|
||||
##### Returns
|
||||
#### Returns
|
||||
|
||||
`Promise`<`false` | [`ParseResult`](ParseResult.md)>
|
||||
|
||||
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||
|
||||
##### See
|
||||
#### See
|
||||
|
||||
[ParseOptions](ParseOptions.md)
|
||||
|
||||
##### Throws
|
||||
#### Throws
|
||||
|
||||
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||
|
||||
#### Call Signature
|
||||
|
||||
> (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>
|
||||
|
||||
Parse the text and validate the syntax.
|
||||
|
||||
##### Parameters
|
||||
#### Parameters
|
||||
|
||||
###### text
|
||||
##### text
|
||||
|
||||
`string`
|
||||
|
||||
The mermaid diagram definition.
|
||||
|
||||
###### parseOptions?
|
||||
##### parseOptions?
|
||||
|
||||
[`ParseOptions`](ParseOptions.md)
|
||||
|
||||
Options for parsing.
|
||||
|
||||
##### Returns
|
||||
#### Returns
|
||||
|
||||
`Promise`<[`ParseResult`](ParseResult.md)>
|
||||
|
||||
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
|
||||
|
||||
##### See
|
||||
#### See
|
||||
|
||||
[ParseOptions](ParseOptions.md)
|
||||
|
||||
##### Throws
|
||||
#### Throws
|
||||
|
||||
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
|
||||
|
||||
@@ -340,7 +332,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:444](https://github.com/mermaid-js/
|
||||
|
||||
### render()
|
||||
|
||||
> **render**: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>
|
||||
> **render**: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>
|
||||
|
||||
Defined in: [packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)
|
||||
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
# Interface: ParseOptions
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76)
|
||||
Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L72)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mer
|
||||
|
||||
> `optional` **suppressErrors**: `boolean`
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L81)
|
||||
Defined in: [packages/mermaid/src/types.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L77)
|
||||
|
||||
If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
|
||||
The `parseError` function will not be called.
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
# Interface: ParseResult
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
|
||||
Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mer
|
||||
|
||||
> **config**: [`MermaidConfig`](MermaidConfig.md)
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92)
|
||||
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
|
||||
|
||||
The config passed as YAML frontmatter or directives
|
||||
|
||||
@@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives
|
||||
|
||||
> **diagramType**: `string`
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
|
||||
Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
|
||||
|
||||
The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
# Interface: RenderResult
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102)
|
||||
Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/me
|
||||
|
||||
> `optional` **bindFunctions**: (`element`) => `void`
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:120](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L120)
|
||||
Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116)
|
||||
|
||||
Bind function to be called after the svg has been inserted into the DOM.
|
||||
This is necessary for adding event listeners to the elements in the svg.
|
||||
@@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
|
||||
> **diagramType**: `string`
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110)
|
||||
Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106)
|
||||
|
||||
The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
@@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
|
||||
|
||||
> **svg**: `string`
|
||||
|
||||
Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106)
|
||||
Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102)
|
||||
|
||||
The svg code for the rendered graph.
|
||||
|
@@ -1,65 +0,0 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md).
|
||||
|
||||
[**mermaid**](../../README.md)
|
||||
|
||||
---
|
||||
|
||||
# Interface: UnknownDiagramError
|
||||
|
||||
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
|
||||
|
||||
## Extends
|
||||
|
||||
- `Error`
|
||||
|
||||
## Properties
|
||||
|
||||
### cause?
|
||||
|
||||
> `optional` **cause**: `unknown`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.cause`
|
||||
|
||||
---
|
||||
|
||||
### message
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.message`
|
||||
|
||||
---
|
||||
|
||||
### name
|
||||
|
||||
> **name**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.name`
|
||||
|
||||
---
|
||||
|
||||
### stack?
|
||||
|
||||
> `optional` **stack**: `string`
|
||||
|
||||
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
|
||||
|
||||
#### Inherited from
|
||||
|
||||
`Error.stack`
|
@@ -10,6 +10,6 @@
|
||||
|
||||
# Type Alias: InternalHelpers
|
||||
|
||||
> **InternalHelpers** = _typeof_ `internalHelpers`
|
||||
> **InternalHelpers**: _typeof_ `internalHelpers`
|
||||
|
||||
Defined in: [packages/mermaid/src/internals.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/internals.ts#L33)
|
||||
|
@@ -10,7 +10,7 @@
|
||||
|
||||
# Type Alias: ParseErrorFunction()
|
||||
|
||||
> **ParseErrorFunction** = (`err`, `hash?`) => `void`
|
||||
> **ParseErrorFunction**: (`err`, `hash`?) => `void`
|
||||
|
||||
Defined in: [packages/mermaid/src/Diagram.ts:10](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/Diagram.ts#L10)
|
||||
|
||||
|
@@ -10,6 +10,6 @@
|
||||
|
||||
# Type Alias: SVG
|
||||
|
||||
> **SVG** = `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||
> **SVG**: `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||
|
||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L126)
|
||||
|
@@ -10,6 +10,6 @@
|
||||
|
||||
# Type Alias: SVGGroup
|
||||
|
||||
> **SVGGroup** = `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||
> **SVGGroup**: `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
|
||||
|
||||
Defined in: [packages/mermaid/src/diagram-api/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L128)
|
||||
|
@@ -1,63 +0,0 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/tidy-tree.md](../../packages/mermaid/src/docs/config/tidy-tree.md).
|
||||
|
||||
# Tidy-tree Layout Instructions
|
||||
|
||||
Instructions to use the Tidy-tree layout algorithm.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install non-layered-tidy-tree-layout
|
||||
# or
|
||||
yarn add non-layered-tidy-tree-layout
|
||||
|
||||
```
|
||||
|
||||
There's also a built version: `dist/non-layered-tidy-tree-layout.js` for use with browser `<script>` tag, or as a Javascript module.
|
||||
|
||||
## Tidy tree Layouts
|
||||
|
||||
Mermaid also supports a Tidy Tree layout for mindmaps.
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
```
|
||||
|
||||
### With bundlers
|
||||
|
||||
```sh
|
||||
npm install @mermaid-js/layout-tidy-tree
|
||||
```
|
||||
|
||||
```ts
|
||||
import mermaid from 'mermaid';
|
||||
import tidyTreeLayouts from '@mermaid-js/layout-tidy-tree';
|
||||
|
||||
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||
```
|
||||
|
||||
### With CDN
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
import tidyTreeLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-tidy-tree@0/dist/mermaid-layout-tidy-tree.esm.min.mjs';
|
||||
|
||||
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||
</script>
|
||||
```
|
@@ -29,6 +29,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
- **Plugins** - A plugin system for extending the functionality of Mermaid.
|
||||
|
||||
Official Mermaid Chart plugins:
|
||||
|
||||
- [Mermaid Chart GPT](https://chatgpt.com/g/g-684cc36f30208191b21383b88650a45d-mermaid-chart-diagrams-and-charts)
|
||||
- [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud)
|
||||
|
156
docs/layouts/development.md
Normal file
156
docs/layouts/development.md
Normal file
@@ -0,0 +1,156 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/development.md](../../packages/mermaid/src/docs/layouts/development.md).
|
||||
|
||||
# 🛠️ How to Create a New Layout Algorithm in Mermaid
|
||||
|
||||
Mermaid supports pluggable layout engines, and contributors can add custom layout algorithms to support specialized rendering needs such as clustered layouts, nested structures, or domain-specific visualizations.
|
||||
|
||||
This guide outlines the steps required to **create and integrate a new layout algorithm** into the Mermaid codebase.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Prerequisites
|
||||
|
||||
Before starting, ensure the following:
|
||||
|
||||
- You have [Node.js](https://nodejs.org/) installed.
|
||||
- You have [pnpm](https://pnpm.io/) installed globally:
|
||||
```bash
|
||||
npm install -g pnpm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Step-by-Step Integration
|
||||
|
||||
### Refer [Mermaid Contributing Guide](../community/contributing.md)
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Implementing Your Custom Layout Algorithm
|
||||
|
||||
### 1. Create Your Layout Folder
|
||||
|
||||
Navigate to the relevant source directory and create a folder for your new algorithm:
|
||||
|
||||
```bash
|
||||
cd packages/mermaid/src/layout
|
||||
mkdir myCustomLayout
|
||||
touch myCustomLayout/index.ts
|
||||
```
|
||||
|
||||
> 📁 You can organize supporting files, utils, and types inside this folder.
|
||||
|
||||
### 2. Register the Layout Algorithm
|
||||
|
||||
Open the file:
|
||||
|
||||
```
|
||||
packages/mermaid/src/rendering-util/render.ts
|
||||
```
|
||||
|
||||
Inside the function `registerDefaultLayoutLoaders`, find the `layoutLoaders` array. Add your layout here:
|
||||
|
||||
```ts
|
||||
registerDefaultLayoutLoaders([
|
||||
...,
|
||||
{
|
||||
id: 'myCustomLayout',
|
||||
loader: () => import('../layout/myCustomLayout'),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
This tells Mermaid how to load your layout dynamically by name (`id`).
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Your Algorithm
|
||||
|
||||
### 3. Create a Test File
|
||||
|
||||
To visually test your layout implementation, create a test HTML file in:
|
||||
|
||||
```
|
||||
cypress/platform/
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
touch cypress/platform/myCustomLayoutTest.html
|
||||
```
|
||||
|
||||
Inside the file, load your diagram like this:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module">
|
||||
import mermaid from '/dist/mermaid.esm.mjs';
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
theme: 'default',
|
||||
layout: 'myCustomLayout', // Use your layout here
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="mermaid">graph TD A[Node A] --> B[Node B] B --> C[Node C]</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 4. Open in Browser
|
||||
|
||||
After running `pnpm dev`, open your test in the browser:
|
||||
|
||||
```
|
||||
http://localhost:9000/myCustomLayoutTest.html
|
||||
```
|
||||
|
||||
You should see your diagram rendered using your new layout engine.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Tips
|
||||
|
||||
- Keep your layout algorithm modular and readable.
|
||||
- Use TypeScript types and helper functions for better structure.
|
||||
- Add comments and constraints where necessary.
|
||||
- If applicable, create a unit test and add a visual test for Cypress.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Example File Structure
|
||||
|
||||
```
|
||||
packages/
|
||||
└── mermaid/
|
||||
└── src/
|
||||
└── layout/
|
||||
└── myCustomLayout/
|
||||
├── index.ts
|
||||
├── utils.ts
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Final Checklist
|
||||
|
||||
- [ ] All dependencies installed via `pnpm i`
|
||||
- [ ] Layout folder and files created under `src/layout/`
|
||||
- [ ] Entry registered in `registerDefaultLayoutLoaders`
|
||||
- [ ] HTML test file added under `cypress/platform/`
|
||||
- [ ] Diagram renders as expected at `localhost:9000`
|
||||
- [ ] Code is linted and documented
|
||||
|
||||
---
|
||||
|
||||
> 💡 You’re now ready to build advanced layout algorithms and contribute to Mermaid's growing visualization capabilities!
|
138
docs/layouts/introduction.md
Normal file
138
docs/layouts/introduction.md
Normal file
@@ -0,0 +1,138 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/introduction.md](../../packages/mermaid/src/docs/layouts/introduction.md).
|
||||
|
||||
# 📊 Layout Algorithms in Mermaid
|
||||
|
||||
Mermaid is a popular JavaScript-based diagramming tool that supports auto-layout for graphs using pluggable layout engines. Layout algorithms play a critical role in rendering nodes and edges in a clean, readable, and meaningful way. Mermaid currently uses engines like **Dagre** and **ELK**, and will soon introduce a powerful new layout engine: **IPSep-CoLa**.
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Dagre Layout
|
||||
|
||||
**Dagre** is a layout engine inspired by the **Sugiyama algorithm**, optimized for directed acyclic graphs (DAGs). It arranges nodes in layers and computes edge routing to minimize crossings and improve readability.
|
||||
|
||||
### Key Features:
|
||||
|
||||
- **Layered (Sugiyama-style) layout**: Ideal for top-down or left-to-right flow.
|
||||
- **Edge routing**: Attempts to reduce edge crossings and bends.
|
||||
- **Ranking**: Vertices are assigned ranks to group related elements into the same level.
|
||||
- **Lightweight and fast**: Suitable for small to medium-sized graphs.
|
||||
|
||||
### Technical Overview:
|
||||
|
||||
- Works in four stages:
|
||||
1. **Cycle Removal**
|
||||
2. **Layer Assignment**
|
||||
3. **Node Ordering**
|
||||
4. **Coordinate Assignment**
|
||||
- Outputs crisp layouts where edge direction is clear and logical.
|
||||
|
||||
### Limitations:
|
||||
|
||||
- No native support for **grouped or nested structures**.
|
||||
- Not ideal for graphs with **non-hierarchical** or **dense cyclic connections**.
|
||||
- Limited edge label placement capabilities.
|
||||
|
||||
---
|
||||
|
||||
## 🔸 ELK (Eclipse Layout Kernel)
|
||||
|
||||
**ELK** is a modular, extensible layout framework developed as part of the Eclipse ecosystem. It supports a wide variety of graph types and layout strategies.
|
||||
|
||||
### Key Features:
|
||||
|
||||
- **Multiple layout styles**: Hierarchical, force-based, layered, orthogonal, etc.
|
||||
- **Support for ports**: Allows fine-grained edge anchoring on specific sides of nodes.
|
||||
- **Group and hierarchy awareness**: Ideal for nested and compartmentalized diagrams.
|
||||
- **Rich configuration**: Offers control over spacing, edge routing, direction, padding, and more.
|
||||
|
||||
### Technical Overview:
|
||||
|
||||
- Uses a **model-driven approach** with a well-defined intermediate representation (ELK Graph Model).
|
||||
- Different engines are plugged in depending on the chosen layout strategy.
|
||||
- Works well with large, complex, and deeply nested graphs.
|
||||
|
||||
### Limitations:
|
||||
|
||||
- Requires verbose configuration for best results.
|
||||
- Can be slower than Dagre for small or simple diagrams.
|
||||
- More complex to integrate and control dynamically.
|
||||
|
||||
---
|
||||
|
||||
## 🆕 IPSep-CoLa
|
||||
|
||||
### 🌐 Introduction
|
||||
|
||||
**IPSep-CoLa** stands for **Incremental Procedure for Separation Constraint Layout**, a next-generation layout algorithm tailored for **grouped, nested, and labeled graphs**. It is an enhancement over standard force-directed layouts, offering constraint enforcement and iterative refinement.
|
||||
|
||||
It is particularly useful for diagrams where:
|
||||
|
||||
- **Group integrity** is important (e.g., modules, clusters).
|
||||
- **Edge labels** need smart placement.
|
||||
- **Overlaps** must be prevented even under tight space constraints.
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ How IPSep-CoLa Works
|
||||
|
||||
#### 1. **Constraint-Based Force Simulation**:
|
||||
|
||||
It builds on top of standard force-directed approaches (like CoLa), but adds **constraints** to influence the final positions of nodes:
|
||||
|
||||
- **Separation constraints**: Minimum distances between nodes, edge labels, and groups.
|
||||
- **Containment constraints**: Child nodes must stay within the bounds of parent groups.
|
||||
- **Alignment constraints**: Nodes can be aligned in rows or columns if desired.
|
||||
|
||||
#### 2. **Incremental Refinement**:
|
||||
|
||||
Unlike one-pass algorithms, IPSep-CoLa works in **phases**:
|
||||
|
||||
- Initial layout is produced using a base force simulation.
|
||||
- The layout is iteratively adjusted using **constraint solvers**.
|
||||
- Additional forces (spring, collision avoidance, containment) are incrementally added.
|
||||
|
||||
#### 3. **Edge Label Handling**:
|
||||
|
||||
One of the distinguishing features of IPSep-CoLa is its support for **multi-segment edge routing with mid-edge label positioning**, ensuring labels do not clutter or overlap.
|
||||
|
||||
---
|
||||
|
||||
### 📌 Use Cases
|
||||
|
||||
IPSep-CoLa is ideal for:
|
||||
|
||||
- **Hierarchical graphs** with complex nesting (e.g., software architecture, UML diagrams).
|
||||
- **Clustered views** (e.g., social network groupings).
|
||||
- **Diagrams with heavy labeling** where label placement affects readability.
|
||||
- **Diagrams with strict visual structure** needs — maintaining boundaries, margins, or padding.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Comparison Table
|
||||
|
||||
| Feature | Dagre | ELK | IPSep-CoLa |
|
||||
| ------------------------- | ----------- | ------------------- | ------------------------------ |
|
||||
| Layout Type | Layered DAG | Modular (varied) | Constraint-driven force layout |
|
||||
| Edge Labeling | ⚠️ Basic | ✅ Yes | ✅ Smart Placement |
|
||||
| Overlap Avoidance | ⚠️ Partial | ✅ Configurable | ✅ Automatic |
|
||||
| Layout Performance | ✅ Fast | ⚠️ Medium | ⚠️ Medium |
|
||||
| Customization Flexibility | ⚠️ Limited | ✅ Extensive | ✅ Moderate to High |
|
||||
| Best For | Simple DAGs | Complex hierarchies | Grouped and labeled graphs |
|
||||
|
||||
---
|
||||
|
||||
## 🧾 Summary
|
||||
|
||||
Each layout engine in Mermaid serves a different purpose:
|
||||
|
||||
- **Dagre** is best for fast, simple, and readable DAGs.
|
||||
- **ELK** is powerful for modular, layered, or port-based diagrams with a need for rich customization.
|
||||
- **IPSep-CoLa** will soon offer a flexible, constraint-respecting layout engine that excels at **visual clarity in grouped and complex diagrams**.
|
||||
|
||||
The addition of IPSep-CoLa to Mermaid's layout stack represents a significant leap forward in layout control and quality — making it easier than ever to visualize rich, structured, and annotated graphs.
|
||||
|
||||
---
|
46
docs/layouts/ipsepcola/implementation.md
Normal file
46
docs/layouts/ipsepcola/implementation.md
Normal file
@@ -0,0 +1,46 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/ipsepcola/implementation.md](../../../packages/mermaid/src/docs/layouts/ipsepcola/implementation.md).
|
||||
|
||||
## IPSEPCOLA Documentation :
|
||||
|
||||
IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs
|
||||
|
||||
## How IPSep-CoLa built :
|
||||
|
||||
IPSep-CoLa follows a multi-stage process to compute a well-structured layout:
|
||||
|
||||
1. Layer Assignment :
|
||||
The layer assignment algorithm organizes nodes into hierarchical layers to create a structured layout for directed graphs. It begins by detecting and temporarily removing cyclic edges using a depth-first search (DFS) approach, ensuring the graph becomes a Directed Acyclic Graph (DAG) for proper layering. The algorithm then performs a topological sort using Kahn's method, calculating node ranks (layers) based on in-degree counts. Each node's layer is determined by its position in the topological order, with parent nodes always appearing in higher layers than their children to maintain proper flow direction.
|
||||
|
||||
The implementation handles special cases like nested nodes by considering parent-child relationships when calculating layers. Nodes without dependencies are placed in layer 0, while subsequent nodes are assigned to layers one level below their nearest parent. The algorithm efficiently processes nodes using a queue system, decrementing in-degrees as it progresses, and ultimately stores the layer information directly in the node objects. Though cyclic edges are removed during processing, they could potentially be reintroduced after layer assignment if needed for visualization purposes.
|
||||
|
||||
2. Node ordering:
|
||||
After assigning layers to nodes, this step organizes nodes horizontally within each layer to minimize edge crossings and create a clean, readable layout. It uses the barycenter method—a technique that positions each node based on the average position of its connected neighbors (either incoming or outgoing). Nodes with no connections are pushed to the end of their layer.
|
||||
|
||||
The algorithm works in multiple passes (iterations) to refine the order: first adjusting nodes based on their incoming connections (from the layer above), then outgoing connections (to the layer below). Group nodes (like containers) are handled separately—their position is determined by averaging the positions of their children, ensuring they stay properly aligned with their contents. This approach keeps the layout structured while reducing visual clutter.
|
||||
|
||||
3. AssignInitial positions to node :
|
||||
This step calculates the starting (x, y) positions for each node based on its assigned layer (vertical level) and order (horizontal position). Nodes are spaced evenly—horizontally using nodeSpacing and vertically using layerHeight. For example, a node in layer 2 with order 3 will be placed at (3 \_ nodeSpacing, 2 \_ layerHeight). This creates a grid-like structure where nodes align neatly in rows (layers) and columns (orders).
|
||||
|
||||
The initial positioning is simple but crucial—it provides a structured starting point before more advanced adjustments (like reducing edge crossings or compacting the layout) are applied. Group nodes follow the same logic, ensuring they align with their children. This method ensures a readable, organized foundation for further refinement.
|
||||
|
||||
4. Force-Directed Simulation with Constraints :
|
||||
|
||||
- Spring Forces: Attracts connected nodes to maintain desired edge lengths.
|
||||
- Repulsion Forces: Pushes nodes apart to prevent overlaps.
|
||||
- Group Constraints: Ensures child nodes stay near their parent groups.
|
||||
- Cooling Factor: Gradually reduces movement to stabilize the layout.
|
||||
|
||||
5. Incremental Refinement :
|
||||
|
||||
- Overlap Resolution: Iteratively adjusts node positions to eliminate overlaps.
|
||||
- Edge Routing: Computes smooth paths for edges, including curved paths for parallel edges and self-loops.
|
||||
- Group Boundary Adjustment: Dynamically resizes group containers to fit nested elements.
|
||||
|
||||
6. Adjusting the Final Layout :
|
||||
This step takes the calculated node positions and applies them to the visual elements of the graph. Nodes are placed at their assigned (x, y) coordinates—regular nodes are positioned directly, while group nodes (clusters) are rendered as containers that may include other nodes. Edges (connections between nodes) are drawn based on their start and end points, ensuring they follow the structured layout.
|
||||
|
||||
The adjustment phase bridges the mathematical layout with the actual rendering, updating the SVG or canvas elements to reflect the computed positions. This ensures that the graph is not only logically organized but also visually coherent, with proper spacing, alignment, and connections. The result is a clean, readable diagram ready for display.
|
186
docs/layouts/ipsepcola/overview.md
Normal file
186
docs/layouts/ipsepcola/overview.md
Normal file
@@ -0,0 +1,186 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/layouts/ipsepcola/overview.md](../../../packages/mermaid/src/docs/layouts/ipsepcola/overview.md).
|
||||
|
||||
## IPSEPCOLA Documentation :
|
||||
|
||||
IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs
|
||||
|
||||
## Introduction :
|
||||
|
||||
IPSep-CoLa (Incremental Procedure for Separation Constraint Layout) is an advanced graph layout algorithm designed to handle complex diagrams with separation constraints, such as grouped nodes, edge labels, and hierarchical structures. Unlike traditional force-directed algorithms, IPSep-CoLa incrementally refines node positions while enforcing geometric constraints to prevent overlaps, maintain group cohesion, and optimize edge routing.
|
||||
|
||||
The algorithm is particularly effective for visualizing nested and clustered graphs, where maintaining clear separation between elements is crucial. It combines techniques from force-directed layout, constraint satisfaction, and incremental refinement to produce readable and aesthetically pleasing diagrams.
|
||||
|
||||
## How IPSep-CoLa Works :
|
||||
|
||||
IPSep-CoLa follows a multi-stage process to compute a well-structured layout:
|
||||
|
||||
1. Graph Preprocessing :
|
||||
Cycle Removal: Detects and temporarily removes cyclic dependencies to enable proper layering.
|
||||
Layer Assignment: Assigns nodes to hierarchical layers using topological sorting.
|
||||
Node Ordering: Uses the barycenter heuristic to minimize edge crossings within layers.
|
||||
|
||||
2. Force-Directed Simulation with Constraints :
|
||||
Spring Forces: Attracts connected nodes to maintain desired edge lengths.
|
||||
Repulsion Forces: Pushes nodes apart to prevent overlaps.
|
||||
Group Constraints: Ensures child nodes stay near their parent groups.
|
||||
Cooling Factor: Gradually reduces movement to stabilize the layout.
|
||||
|
||||
3. Incremental Refinement :
|
||||
Overlap Resolution: Iteratively adjusts node positions to eliminate overlaps.
|
||||
Edge Routing: Computes smooth paths for edges, including curved paths for parallel edges and self-loops.
|
||||
Group Boundary Adjustment: Dynamically resizes group containers to fit nested elements.
|
||||
|
||||
## Key Features :
|
||||
|
||||
1. Group-Aware Layout: Maintains separation between nested structures.
|
||||
2. Edge Label Placement: Uses edge labels as virtual nodes and automatically positions labels inside their parent groups.
|
||||
3. Stable Convergence: Uses cooling factors and incremental updates for smooth refinement.
|
||||
4. Support for Self-Loops & Parallel Edges: Avoids visual clutter with intelligent edge routing.
|
||||
|
||||
## Use Cases :
|
||||
|
||||
1. Hierarchical Diagrams (org charts, flowcharts, decision trees)
|
||||
2. Network Visualization (dependency graphs, data pipelines)
|
||||
3. Interactive Graph Editors (real-time layout adjustments)
|
||||
4. Clustered Data Visualization (UML diagrams, biological networks)
|
||||
|
||||
## **Examples**
|
||||
|
||||
### **Example 1**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
|
||||
flowchart TD
|
||||
CEO --> MKT["Marketing Head"]
|
||||
CEO --> ENG["Engineering Head"]
|
||||
ENG --> DEV["Developer"]
|
||||
ENG --> QA["QA Tester"]
|
||||
```
|
||||
|
||||
### **Example 2**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
|
||||
flowchart TD
|
||||
Start["Start"] --> Red{"Is it red?"}
|
||||
Red -- Yes --> Round{"Is it round?"}
|
||||
Red -- No --> NotApple["❌ Not an Apple"]
|
||||
Round -- Yes --> Apple["✅ It's an Apple"]
|
||||
Round -- No --> NotApple2["❌ Not an Apple"]
|
||||
```
|
||||
|
||||
### **Example 3**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
|
||||
flowchart TD
|
||||
A[Module A] --> B[Module B]
|
||||
A --> C[Module C]
|
||||
B --> D[Module D]
|
||||
C --> D
|
||||
D --> E[Module E]
|
||||
```
|
||||
|
||||
### **Example 4**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
|
||||
flowchart TD
|
||||
Source1["📦 Raw Data (CSV)"]
|
||||
Source2["🌐 API Data"]
|
||||
|
||||
Source1 --> Clean["🧹 Clean & Format"]
|
||||
Source2 --> Clean
|
||||
|
||||
Clean --> Transform["🔄 Transform Data"]
|
||||
Transform --> Load["📥 Load into Data Warehouse"]
|
||||
Load --> BI["📊 BI Dashboard"]
|
||||
```
|
||||
|
||||
### **Example 5**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
classDiagram
|
||||
class Person {
|
||||
-String name
|
||||
-int age
|
||||
+greet(): void
|
||||
}
|
||||
|
||||
class Employee {
|
||||
-int employeeId
|
||||
+calculateSalary(): float
|
||||
}
|
||||
|
||||
class Manager {
|
||||
-String department
|
||||
+assignTask(): void
|
||||
}
|
||||
|
||||
Person <|-- Employee
|
||||
Employee <|-- Manager
|
||||
```
|
||||
|
||||
### **Example 6**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart TD
|
||||
Sunlight["☀️ Sunlight"] --> Leaf["🌿 Leaf"]
|
||||
Leaf --> Glucose["🍬 Glucose"]
|
||||
Leaf --> Oxygen["💨 Oxygen"]
|
||||
```
|
||||
|
||||
### **Example 7**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: ipsepCola
|
||||
---
|
||||
flowchart TD
|
||||
Internet["🌐 Internet"] --> Router["📡 Router"]
|
||||
Router --> Server1["🖥️ Server A"]
|
||||
Router --> Server2["🖥️ Server B"]
|
||||
Router --> Laptop["💻 Laptop"]
|
||||
|
||||
%% New device joins
|
||||
Router --> Mobile["📱 Mobile"]
|
||||
```
|
||||
|
||||
## Limitations :
|
||||
|
||||
1. Computational Cost: More iterations may be needed for large graphs (>1000 nodes).
|
||||
2. Parameter Tuning: Requires adjustments for different graph types.
|
||||
3. Non-Determinism: Small variations may occur between runs due to force simulation.
|
||||
|
||||
## Conclusion :
|
||||
|
||||
IPSep-CoLa provides a robust solution for constraint-based graph layout, particularly for structured and clustered diagrams. By combining incremental refinement with separation constraints, it achieves readable and well-organized visualizations. Future improvements could include GPU acceleration and adaptive parameter tuning for large-scale graphs.
|
@@ -35,11 +35,13 @@ The Mermaid Chart team is excited to introduce a new Visual Editor for Flowchart
|
||||
Learn more:
|
||||
|
||||
- Visual Editor For Flowcharts
|
||||
|
||||
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-releases-new-visual-editor-for-flowcharts)
|
||||
|
||||
- [Demo video](https://www.youtube.com/watch?v=5aja0gijoO0)
|
||||
|
||||
- Visual Editor For Sequence diagrams
|
||||
|
||||
- [Blog post](https://www.mermaidchart.com/blog/posts/mermaid-chart-unveils-visual-editor-for-sequence-diagrams)
|
||||
|
||||
- [Demo video](https://youtu.be/imc2u5_N6Dc)
|
||||
|
@@ -9,7 +9,7 @@
|
||||
## Introduction to Block Diagrams
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
@@ -26,7 +26,7 @@ columns 1
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
@@ -80,12 +80,12 @@ At its core, a block diagram consists of blocks representing different entities
|
||||
To create a simple block diagram with three blocks labeled 'a', 'b', and 'c', the syntax is as follows:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
a b c
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
a b c
|
||||
```
|
||||
|
||||
@@ -101,13 +101,13 @@ While simple block diagrams are linear and straightforward, more complex systems
|
||||
In scenarios where you need to distribute blocks across multiple columns, you can specify the number of columns and arrange the blocks accordingly. Here's how to create a block diagram with three columns and four blocks, where the fourth block appears in a second row:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a b c d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a b c d
|
||||
```
|
||||
@@ -130,13 +130,13 @@ In more complex diagrams, you may need blocks that span multiple columns to emph
|
||||
To create a block diagram where one block spans across two columns, you can specify the desired width for each block:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a["A label"] b:2 c:2 d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a["A label"] b:2 c:2 d
|
||||
```
|
||||
@@ -153,7 +153,7 @@ Composite blocks, or blocks within blocks, are an advanced feature in Mermaid's
|
||||
Creating a composite block involves defining a parent block and then nesting other blocks within it. Here's how to define a composite block with nested elements:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
block
|
||||
D
|
||||
end
|
||||
@@ -161,7 +161,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
block
|
||||
D
|
||||
end
|
||||
@@ -180,7 +180,7 @@ Mermaid also allows for dynamic adjustment of column widths based on the content
|
||||
In diagrams with varying block sizes, Mermaid automatically adjusts the column widths to fit the largest block in each column. Here's an example:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a:3
|
||||
block:group1:2
|
||||
@@ -195,7 +195,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a:3
|
||||
block:group1:2
|
||||
@@ -215,7 +215,7 @@ This example demonstrates how Mermaid dynamically adjusts the width of the colum
|
||||
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
@@ -223,7 +223,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
@@ -247,12 +247,12 @@ Mermaid supports a range of block shapes to suit different diagramming needs, fr
|
||||
To create a block with round edges, which can be used to represent a softer or more flexible component:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1("This is the text in the box")
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1("This is the text in the box")
|
||||
```
|
||||
|
||||
@@ -261,12 +261,12 @@ block
|
||||
A stadium-shaped block, resembling an elongated circle, can be used for components that are process-oriented:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1(["This is the text in the box"])
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1(["This is the text in the box"])
|
||||
```
|
||||
|
||||
@@ -275,12 +275,12 @@ block
|
||||
For representing subroutines or contained processes, a block with double vertical lines is useful:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1[["This is the text in the box"]]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1[["This is the text in the box"]]
|
||||
```
|
||||
|
||||
@@ -289,12 +289,12 @@ block
|
||||
The cylindrical shape is ideal for representing databases or storage components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1[("Database")]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1[("Database")]
|
||||
```
|
||||
|
||||
@@ -303,12 +303,12 @@ block
|
||||
A circle can be used for centralized or pivotal components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1(("This is the text in the circle"))
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1(("This is the text in the circle"))
|
||||
```
|
||||
|
||||
@@ -319,36 +319,36 @@ For decision points, use a rhombus, and for unique or specialized processes, asy
|
||||
**Asymmetric**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1>"This is the text in the box"]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1>"This is the text in the box"]
|
||||
```
|
||||
|
||||
**Rhombus**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1{"This is the text in the box"}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1{"This is the text in the box"}
|
||||
```
|
||||
|
||||
**Hexagon**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1{{"This is the text in the box"}}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1{{"This is the text in the box"}}
|
||||
```
|
||||
|
||||
@@ -357,7 +357,7 @@ block
|
||||
Parallelogram and trapezoid shapes are perfect for inputs/outputs and transitional processes:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1[/"This is the text in the box"/]
|
||||
id2[\"This is the text in the box"\]
|
||||
A[/"Christmas"\]
|
||||
@@ -365,7 +365,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1[/"This is the text in the box"/]
|
||||
id2[\"This is the text in the box"\]
|
||||
A[/"Christmas"\]
|
||||
@@ -377,12 +377,12 @@ block
|
||||
For highlighting critical or high-priority components, a double circle can be effective:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1((("This is the text in the circle")))
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1((("This is the text in the circle")))
|
||||
```
|
||||
|
||||
@@ -395,7 +395,7 @@ Mermaid also offers unique shapes like block arrows and space blocks for directi
|
||||
Block arrows can visually indicate direction or flow within a process:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
blockArrowId<["Label"]>(right)
|
||||
blockArrowId2<["Label"]>(left)
|
||||
blockArrowId3<["Label"]>(up)
|
||||
@@ -406,7 +406,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
blockArrowId<["Label"]>(right)
|
||||
blockArrowId2<["Label"]>(left)
|
||||
blockArrowId3<["Label"]>(up)
|
||||
@@ -421,14 +421,14 @@ block
|
||||
Space blocks can be used to create intentional empty spaces in the diagram, which is useful for layout and readability:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a space b
|
||||
c d e
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
a space b
|
||||
c d e
|
||||
@@ -437,12 +437,12 @@ block
|
||||
or
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
ida space:3 idb idc
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
ida space:3 idb idc
|
||||
```
|
||||
|
||||
@@ -467,13 +467,13 @@ The most fundamental aspect of connecting blocks is the use of arrows or links.
|
||||
A simple link with an arrow can be created to show direction or flow from one block to another:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A-->B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A-->B
|
||||
```
|
||||
@@ -490,13 +490,13 @@ Example - Text with Links
|
||||
To add text to a link, the syntax includes the text within the link definition:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A space:2 B
|
||||
A-- "X" -->B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A space:2 B
|
||||
A-- "X" -->B
|
||||
```
|
||||
@@ -506,7 +506,7 @@ This example show how to add descriptive text to the links, enhancing the inform
|
||||
Example - Edges and Styles:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
@@ -523,7 +523,7 @@ columns 1
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
@@ -552,7 +552,7 @@ Mermaid enables detailed styling of individual blocks, allowing you to apply var
|
||||
To apply custom styles to a block, you can use the `style` keyword followed by the block identifier and the desired CSS properties:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
id1 space id2
|
||||
id1("Start")-->id2("Stop")
|
||||
style id1 fill:#636,stroke:#333,stroke-width:4px
|
||||
@@ -560,7 +560,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
id1 space id2
|
||||
id1("Start")-->id2("Stop")
|
||||
style id1 fill:#636,stroke:#333,stroke-width:4px
|
||||
@@ -574,7 +574,7 @@ Mermaid enables applying styling to classes, which could make styling easier if
|
||||
#### Example - Styling a Single Class
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A-->B
|
||||
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
|
||||
@@ -583,7 +583,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A-->B
|
||||
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
|
||||
@@ -608,7 +608,7 @@ Combining the elements of structure, linking, and styling, we can create compreh
|
||||
Illustrating a simple software system architecture with interconnected components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
Frontend blockArrowId6<[" "]>(right) Backend
|
||||
space:2 down<[" "]>(down)
|
||||
@@ -621,7 +621,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
Frontend blockArrowId6<[" "]>(right) Backend
|
||||
space:2 down<[" "]>(down)
|
||||
@@ -640,7 +640,7 @@ This example shows a basic architecture with a frontend, backend, and database.
|
||||
Representing a business process flow with decision points and multiple stages:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
Start(("Start")) space:2
|
||||
down<[" "]>(down) space:2
|
||||
@@ -653,7 +653,7 @@ block
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
columns 3
|
||||
Start(("Start")) space:2
|
||||
down<[" "]>(down) space:2
|
||||
@@ -682,7 +682,7 @@ Understanding and avoiding common syntax errors is key to a smooth experience wi
|
||||
A common mistake is incorrect linking syntax, which can lead to unexpected results or broken diagrams:
|
||||
|
||||
```
|
||||
block
|
||||
block-beta
|
||||
A - B
|
||||
```
|
||||
|
||||
@@ -690,13 +690,13 @@ block
|
||||
Ensure that links between blocks are correctly specified with arrows (--> or ---) to define the direction and type of connection. Also remember that one of the fundamentals for block diagram is to give the author full control of where the boxes are positioned so in the example you need to add a space between the boxes:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A --> B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A space B
|
||||
A --> B
|
||||
```
|
||||
@@ -706,13 +706,13 @@ block
|
||||
Applying styles in the wrong context or with incorrect syntax can lead to blocks not being styled as intended:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A
|
||||
style A fill#969;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A
|
||||
style A fill#969;
|
||||
```
|
||||
@@ -721,14 +721,14 @@ Applying styles in the wrong context or with incorrect syntax can lead to blocks
|
||||
Correct the syntax by ensuring proper separation of style properties with commas and using the correct CSS property format:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block-beta
|
||||
A
|
||||
style A fill:#969,stroke:#333;
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block-beta
|
||||
A
|
||||
style A fill:#969,stroke:#333;
|
||||
|
||||
|
@@ -139,6 +139,7 @@ The following unfinished features are not supported in the short term.
|
||||
- [ ] Legend
|
||||
|
||||
- [x] System Context
|
||||
|
||||
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Person_Ext
|
||||
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
@@ -152,6 +153,7 @@ The following unfinished features are not supported in the short term.
|
||||
- [x] System_Boundary
|
||||
|
||||
- [x] Container diagram
|
||||
|
||||
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ContainerDb
|
||||
- [x] ContainerQueue
|
||||
@@ -161,6 +163,7 @@ The following unfinished features are not supported in the short term.
|
||||
- [x] Container_Boundary(alias, label, ?tags, $link)
|
||||
|
||||
- [x] Component diagram
|
||||
|
||||
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ComponentDb
|
||||
- [x] ComponentQueue
|
||||
@@ -169,15 +172,18 @@ The following unfinished features are not supported in the short term.
|
||||
- [x] ComponentQueue_Ext
|
||||
|
||||
- [x] Dynamic diagram
|
||||
|
||||
- [x] RelIndex(index, from, to, label, ?tags, $link)
|
||||
|
||||
- [x] Deployment diagram
|
||||
|
||||
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
|
||||
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
|
||||
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
|
||||
|
||||
- [x] Relationship Types
|
||||
|
||||
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] BiRel (bidirectional relationship)
|
||||
- [x] Rel_U, Rel_Up
|
||||
|
@@ -324,56 +324,53 @@ This syntax creates a node A as a rectangle. It renders in the same way as `A["A
|
||||
|
||||
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
|
||||
|
||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| --------------------------------- | ---------------------- | ----------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||
| Bang | Bang | `bang` | Bang | `bang` |
|
||||
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||
| Cloud | Cloud | `cloud` | cloud | `cloud` |
|
||||
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
||||
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
||||
| Comment Right | Curly Brace | `brace-r` | Adds a comment | |
|
||||
| Comment with braces on both sides | Curly Braces | `braces` | Adds a comment | |
|
||||
| Data Input/Output | Lean Right | `lean-r` | Represents input or output | `in-out`, `lean-right` |
|
||||
| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
|
||||
| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` |
|
||||
| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` |
|
||||
| Default Mindmap Node | defaultMindmapNode | `default-mindmap` | defaultMindmapNode | `default-mindmap`, `defaultMindmapNode` |
|
||||
| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
|
||||
| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
|
||||
| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
|
||||
| Display | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
|
||||
| Divided Process | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-process`, `divided-rectangle` |
|
||||
| Document | Document | `doc` | Represents a document | `doc`, `document` |
|
||||
| Event | Rounded Rectangle | `rounded` | Represents an event | `event` |
|
||||
| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` |
|
||||
| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
|
||||
| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
|
||||
| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` |
|
||||
| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` |
|
||||
| Lined/Shaded Process | Lined Rectangle | `lin-rect` | Lined process shape | `lin-proc`, `lined-process`, `lined-rectangle`, `shaded-process` |
|
||||
| Loop Limit | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
|
||||
| Manual File | Flipped Triangle | `flip-tri` | Manual file operation | `flipped-triangle`, `manual-file` |
|
||||
| Manual Input | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
|
||||
| Manual Operation | Trapezoid Base Top | `trap-t` | Represents a manual task | `inv-trapezoid`, `manual`, `trapezoid-top` |
|
||||
| Multi-Document | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
|
||||
| Multi-Process | Stacked Rectangle | `st-rect` | Multiple processes | `processes`, `procs`, `stacked-rectangle` |
|
||||
| Odd | Odd | `odd` | Odd shape | |
|
||||
| Paper Tape | Flag | `flag` | Paper tape | `paper-tape` |
|
||||
| Prepare Conditional | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
|
||||
| Priority Action | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid`, `trapezoid-bottom` |
|
||||
| Process | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
|
||||
| Start | Circle | `circle` | Starting point | `circ` |
|
||||
| Start | Small Circle | `sm-circ` | Small starting point | `small-circle`, `start` |
|
||||
| Stop | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
|
||||
| Stop | Framed Circle | `fr-circ` | Stop point | `framed-circle`, `stop` |
|
||||
| Stored Data | Bow Tie Rectangle | `bow-rect` | Stored data | `bow-tie-rectangle`, `stored-data` |
|
||||
| Subprocess | Framed Rectangle | `fr-rect` | Subprocess | `framed-rectangle`, `subproc`, `subprocess`, `subroutine` |
|
||||
| Summary | Crossed Circle | `cross-circ` | Summary | `crossed-circle`, `summary` |
|
||||
| Tagged Document | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
|
||||
| Tagged Process | Tagged Rectangle | `tag-rect` | Tagged process | `tag-proc`, `tagged-process`, `tagged-rectangle` |
|
||||
| Terminal Point | Stadium | `stadium` | Terminal point | `pill`, `terminal` |
|
||||
| Text Block | Text Block | `text` | Text block | |
|
||||
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
|
||||
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
|
||||
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
|
||||
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
|
||||
| Comment Right | Curly Brace | `brace-r` | Adds a comment | |
|
||||
| Comment with braces on both sides | Curly Braces | `braces` | Adds a comment | |
|
||||
| Data Input/Output | Lean Right | `lean-r` | Represents input or output | `in-out`, `lean-right` |
|
||||
| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
|
||||
| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` |
|
||||
| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` |
|
||||
| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
|
||||
| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
|
||||
| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
|
||||
| Display | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
|
||||
| Divided Process | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-process`, `divided-rectangle` |
|
||||
| Document | Document | `doc` | Represents a document | `doc`, `document` |
|
||||
| Event | Rounded Rectangle | `rounded` | Represents an event | `event` |
|
||||
| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` |
|
||||
| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
|
||||
| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
|
||||
| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` |
|
||||
| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` |
|
||||
| Lined/Shaded Process | Lined Rectangle | `lin-rect` | Lined process shape | `lin-proc`, `lined-process`, `lined-rectangle`, `shaded-process` |
|
||||
| Loop Limit | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
|
||||
| Manual File | Flipped Triangle | `flip-tri` | Manual file operation | `flipped-triangle`, `manual-file` |
|
||||
| Manual Input | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
|
||||
| Manual Operation | Trapezoid Base Top | `trap-t` | Represents a manual task | `inv-trapezoid`, `manual`, `trapezoid-top` |
|
||||
| Multi-Document | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
|
||||
| Multi-Process | Stacked Rectangle | `st-rect` | Multiple processes | `processes`, `procs`, `stacked-rectangle` |
|
||||
| Odd | Odd | `odd` | Odd shape | |
|
||||
| Paper Tape | Flag | `flag` | Paper tape | `paper-tape` |
|
||||
| Prepare Conditional | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
|
||||
| Priority Action | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid`, `trapezoid-bottom` |
|
||||
| Process | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
|
||||
| Start | Circle | `circle` | Starting point | `circ` |
|
||||
| Start | Small Circle | `sm-circ` | Small starting point | `small-circle`, `start` |
|
||||
| Stop | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
|
||||
| Stop | Framed Circle | `fr-circ` | Stop point | `framed-circle`, `stop` |
|
||||
| Stored Data | Bow Tie Rectangle | `bow-rect` | Stored data | `bow-tie-rectangle`, `stored-data` |
|
||||
| Subprocess | Framed Rectangle | `fr-rect` | Subprocess | `framed-rectangle`, `subproc`, `subprocess`, `subroutine` |
|
||||
| Summary | Crossed Circle | `cross-circ` | Summary | `crossed-circle`, `summary` |
|
||||
| Tagged Document | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
|
||||
| Tagged Process | Tagged Rectangle | `tag-rect` | Tagged process | `tag-proc`, `tagged-process`, `tagged-rectangle` |
|
||||
| Terminal Point | Stadium | `stadium` | Terminal point | `pill`, `terminal` |
|
||||
| Text Block | Text Block | `text` | Text block | |
|
||||
|
||||
### Example Flowchart with New Shapes
|
||||
|
||||
|
@@ -360,8 +360,7 @@ gantt
|
||||
weekday monday
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
> `millisecond` and `second` support was added in v10.3.0
|
||||
> **Warning** > `millisecond` and `second` support was added in v10.3.0
|
||||
|
||||
## Output in compact mode
|
||||
|
||||
|
@@ -314,22 +314,3 @@ You can also refer the [implementation in the live editor](https://github.com/me
|
||||
cspell:locale en,en-gb
|
||||
cspell:ignore Buzan
|
||||
--->
|
||||
|
||||
## Layouts
|
||||
|
||||
Mermaid also supports a Tidy Tree layout for mindmaps.
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
```
|
||||
|
||||
Instructions to add and register tidy-tree layout are present in [Tidy Tree Configuration](/config/tidy-tree)
|
||||
|
@@ -37,11 +37,6 @@ Drawing a pie chart is really simple in mermaid.
|
||||
- Followed by `:` colon as separator
|
||||
- Followed by `positive numeric value` (supported up to two decimal places)
|
||||
|
||||
**Note:**
|
||||
|
||||
> Pie chart values must be **positive numbers greater than zero**.\
|
||||
> **Negative values are not allowed** and will result in an error.
|
||||
|
||||
\[pie] \[showData] (OPTIONAL)
|
||||
\[title] \[titlevalue] (OPTIONAL)
|
||||
"\[datakey1]" : \[dataValue1]
|
||||
|
@@ -23,7 +23,7 @@ config:
|
||||
sankey:
|
||||
showValues: false
|
||||
---
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
@@ -101,7 +101,7 @@ config:
|
||||
sankey:
|
||||
showValues: false
|
||||
---
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
@@ -175,7 +175,7 @@ Wind,Electricity grid,289.366
|
||||
|
||||
## Syntax
|
||||
|
||||
The idea behind syntax is that a user types `sankey` keyword first, then pastes raw CSV below and get the result.
|
||||
The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result.
|
||||
|
||||
It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**:
|
||||
|
||||
@@ -187,7 +187,7 @@ It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.
|
||||
It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
@@ -196,7 +196,7 @@ Electricity grid,H2 conversion,27.14
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
@@ -209,7 +209,7 @@ Electricity grid,H2 conversion,27.14
|
||||
CSV does not support empty lines without comma delimiters by default. But you can add them if needed:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
@@ -219,7 +219,7 @@ Bio-conversion,Gas,81.144
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
@@ -233,14 +233,14 @@ Bio-conversion,Gas,81.144
|
||||
If you need to have a comma, wrap it in double quotes:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
@@ -251,14 +251,14 @@ Pumped heat,"Heating and cooling, commercial",70.672
|
||||
If you need to have double quote, put a pair of them inside quoted string:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
|
@@ -13,7 +13,7 @@
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -22,7 +22,7 @@ xychart
|
||||
```
|
||||
|
||||
```mermaid
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -40,7 +40,7 @@ xychart
|
||||
The chart can be drawn horizontal or vertical, default value is vertical.
|
||||
|
||||
```
|
||||
xychart horizontal
|
||||
xychart-beta horizontal
|
||||
...
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ The title is a short description of the chart and it will always render on top o
|
||||
#### Example
|
||||
|
||||
```
|
||||
xychart
|
||||
xychart-beta
|
||||
title "This is a simple example"
|
||||
...
|
||||
```
|
||||
@@ -98,10 +98,10 @@ A bar chart offers the capability to graphically depict bars.
|
||||
|
||||
#### Simplest example
|
||||
|
||||
The only two things required are the chart name (`xychart`) and one data set. So you will be able to draw a chart with a simple config like
|
||||
The only two things required are the chart name (`xychart-beta`) and one data set. So you will be able to draw a chart with a simple config like
|
||||
|
||||
```
|
||||
xychart
|
||||
xychart-beta
|
||||
line [+1.3, .6, 2.4, -.34]
|
||||
```
|
||||
|
||||
@@ -176,7 +176,7 @@ config:
|
||||
xyChart:
|
||||
titleColor: "#ff0000"
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
@@ -195,7 +195,7 @@ config:
|
||||
xyChart:
|
||||
titleColor: "#ff0000"
|
||||
---
|
||||
xychart
|
||||
xychart-beta
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
|
@@ -17,7 +17,6 @@ export default tseslint.config(
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
{
|
||||
ignores: [
|
||||
'**/*.d.ts',
|
||||
'**/dist/',
|
||||
'**/node_modules/',
|
||||
'.git/',
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
This package provides a layout engine for Mermaid based on the [ELK](https://www.eclipse.org/elk/) layout engine.
|
||||
|
||||
> [!NOTE]
|
||||
> [!NOTE]
|
||||
> The ELK Layout engine will not be available in all providers that support mermaid by default.
|
||||
> The websites will have to install the `@mermaid-js/layout-elk` package to use the ELK layout engine.
|
||||
|
||||
@@ -69,4 +69,4 @@ mermaid.registerLayoutLoaders(elkLayouts);
|
||||
- `elk.mrtree`: Multi-root tree layout
|
||||
- `elk.sporeOverlap`: Spore overlap layout
|
||||
|
||||
<!-- TODO: Add images for these layouts, as GitHub doesn't support natively. -->
|
||||
<!-- TODO: Add images for these layouts, as GitHub doesn't support natively -->
|
||||
|
@@ -1,9 +0,0 @@
|
||||
export interface TreeData {
|
||||
parentById: Record<string, string>;
|
||||
childrenById: Record<string, string[]>;
|
||||
}
|
||||
export declare const findCommonAncestor: (
|
||||
id1: string,
|
||||
id2: string,
|
||||
{ parentById }: TreeData
|
||||
) => string;
|
@@ -4,8 +4,7 @@ import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from '
|
||||
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
|
||||
|
||||
type Node = LayoutData['nodes'][number];
|
||||
// Used to calculate distances in order to avoid floating number rounding issues when comparing floating numbers
|
||||
const epsilon = 0.0001;
|
||||
|
||||
interface LabelData {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -14,20 +13,11 @@ interface LabelData {
|
||||
}
|
||||
|
||||
interface NodeWithVertex extends Omit<Node, 'domId'> {
|
||||
children?: LayoutData['nodes'];
|
||||
children?: unknown[];
|
||||
labelData?: LabelData;
|
||||
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
|
||||
}
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
function distance(p1?: Point, p2?: Point): number {
|
||||
if (!p1 || !p2) {
|
||||
return 0;
|
||||
}
|
||||
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
|
||||
}
|
||||
|
||||
export const render = async (
|
||||
data4Layout: LayoutData,
|
||||
svg: SVG,
|
||||
@@ -61,30 +51,15 @@ export const render = async (
|
||||
|
||||
// Add the element to the DOM
|
||||
if (!node.isGroup) {
|
||||
// Create a clean node object for ELK with only the properties it expects
|
||||
const child: NodeWithVertex = {
|
||||
id: node.id,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
// Store the original node data for later use
|
||||
label: node.label,
|
||||
isGroup: node.isGroup,
|
||||
shape: node.shape,
|
||||
padding: node.padding,
|
||||
cssClasses: node.cssClasses,
|
||||
cssStyles: node.cssStyles,
|
||||
look: node.look,
|
||||
// Include parentId for subgraph processing
|
||||
parentId: node.parentId,
|
||||
...node,
|
||||
};
|
||||
graph.children.push(child);
|
||||
nodeDb[node.id] = child;
|
||||
|
||||
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
|
||||
const boundingBox = childNodeEl.node()!.getBBox();
|
||||
// Store the domId separately for rendering, not in the ELK graph
|
||||
child.domId = childNodeEl;
|
||||
child.calcIntersect = node.calcIntersect;
|
||||
child.width = boundingBox.width;
|
||||
child.height = boundingBox.height;
|
||||
} else {
|
||||
@@ -484,6 +459,302 @@ export const render = async (
|
||||
}
|
||||
}
|
||||
|
||||
function intersectLine(
|
||||
p1: { y: number; x: number },
|
||||
p2: { y: number; x: number },
|
||||
q1: { x: any; y: any },
|
||||
q2: { x: any; y: any }
|
||||
) {
|
||||
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;
|
||||
|
||||
const epsilon = 1e-6;
|
||||
|
||||
// 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 (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && 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: number, r2: number) {
|
||||
return r1 * r2 > 0;
|
||||
}
|
||||
const diamondIntersection = (
|
||||
bounds: { x: any; y: any; width: any; height: any },
|
||||
outsidePoint: { x: number; y: number },
|
||||
insidePoint: any
|
||||
) => {
|
||||
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(
|
||||
`APA16 diamondIntersection calc abc89:
|
||||
outsidePoint: ${JSON.stringify(outsidePoint)}
|
||||
insidePoint : ${JSON.stringify(insidePoint)}
|
||||
node-bounds : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
|
||||
JSON.stringify(polyPoints)
|
||||
);
|
||||
|
||||
const intersections = [];
|
||||
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
|
||||
polyPoints.forEach(function (entry) {
|
||||
minX = Math.min(minX, entry.x);
|
||||
minY = Math.min(minY, entry.y);
|
||||
});
|
||||
|
||||
const left = x1 - w / 2 - minX;
|
||||
const top = y1 - h / 2 - minY;
|
||||
|
||||
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: left + p1.x, y: top + p1.y },
|
||||
{ x: left + p2.x, y: top + 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];
|
||||
};
|
||||
|
||||
const intersection = (
|
||||
node: { x: any; y: any; width: number; height: number },
|
||||
outsidePoint: { x: number; y: number },
|
||||
insidePoint: { x: number; y: number }
|
||||
) => {
|
||||
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 on 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: { x: any; y: any; width: number; height: number },
|
||||
point: { x: number; y: number }
|
||||
) => {
|
||||
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: any[],
|
||||
bounds: { x: any; y: any; width: any; height: any; padding: any },
|
||||
isDiamond: boolean
|
||||
) => {
|
||||
log.debug('APA18 cutPathAtIntersect Points:', _points, 'node:', bounds, 'isDiamond', isDiamond);
|
||||
const points: any[] = [];
|
||||
let lastPointOutside = _points[0];
|
||||
let isInside = false;
|
||||
_points.forEach((point: any) => {
|
||||
// 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 and 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
return points;
|
||||
};
|
||||
|
||||
// @ts-ignore - ELK is not typed
|
||||
const elk = new ELK();
|
||||
const element = svg.select('g');
|
||||
@@ -595,16 +866,11 @@ export const render = async (
|
||||
delete node.height;
|
||||
}
|
||||
});
|
||||
log.debug('APA01 processing edges, count:', elkGraph.edges.length);
|
||||
elkGraph.edges.forEach((edge: any, index: number) => {
|
||||
log.debug('APA01 processing edge', index, ':', edge);
|
||||
elkGraph.edges.forEach((edge: any) => {
|
||||
const source = edge.sources[0];
|
||||
const target = edge.targets[0];
|
||||
log.debug('APA01 source:', source, 'target:', target);
|
||||
log.debug('APA01 nodeDb[source]:', nodeDb[source]);
|
||||
log.debug('APA01 nodeDb[target]:', nodeDb[target]);
|
||||
|
||||
if (nodeDb[source] && nodeDb[target] && nodeDb[source].parentId !== nodeDb[target].parentId) {
|
||||
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);
|
||||
@@ -612,37 +878,7 @@ export const render = async (
|
||||
}
|
||||
});
|
||||
|
||||
log.debug('APA01 before');
|
||||
log.debug('APA01 elkGraph structure:', JSON.stringify(elkGraph, null, 2));
|
||||
log.debug('APA01 elkGraph.children length:', elkGraph.children?.length);
|
||||
log.debug('APA01 elkGraph.edges length:', elkGraph.edges?.length);
|
||||
|
||||
// Validate that all edge references exist as nodes
|
||||
elkGraph.edges?.forEach((edge: any, index: number) => {
|
||||
log.debug(`APA01 validating edge ${index}:`, edge);
|
||||
if (edge.sources) {
|
||||
edge.sources.forEach((sourceId: any) => {
|
||||
const sourceExists = elkGraph.children?.some((child: any) => child.id === sourceId);
|
||||
log.debug(`APA01 source ${sourceId} exists:`, sourceExists);
|
||||
});
|
||||
}
|
||||
if (edge.targets) {
|
||||
edge.targets.forEach((targetId: any) => {
|
||||
const targetExists = elkGraph.children?.some((child: any) => child.id === targetId);
|
||||
log.debug(`APA01 target ${targetId} exists:`, targetExists);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let g;
|
||||
try {
|
||||
g = await elk.layout(elkGraph);
|
||||
log.debug('APA01 after - success');
|
||||
log.debug('APA01 layout result:', JSON.stringify(g, null, 2));
|
||||
} catch (error) {
|
||||
log.error('APA01 ELK layout error:', error);
|
||||
throw error;
|
||||
}
|
||||
const g = await elk.layout(elkGraph);
|
||||
|
||||
// debugger;
|
||||
await drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
|
||||
@@ -730,38 +966,43 @@ export const render = async (
|
||||
startNode.innerHTML
|
||||
);
|
||||
}
|
||||
|
||||
if (startNode.calcIntersect) {
|
||||
const intersection = startNode.calcIntersect(
|
||||
{
|
||||
x: startNode.offset.posX + startNode.width / 2,
|
||||
y: startNode.offset.posY + startNode.height / 2,
|
||||
width: startNode.width,
|
||||
height: startNode.height,
|
||||
},
|
||||
edge.points[0]
|
||||
);
|
||||
|
||||
if (distance(intersection, edge.points[0]) > epsilon) {
|
||||
edge.points.unshift(intersection);
|
||||
}
|
||||
if (startNode.shape === 'diamond' || startNode.shape === 'diam') {
|
||||
edge.points.unshift({
|
||||
x: startNode.offset.posX + startNode.width / 2,
|
||||
y: startNode.offset.posY + startNode.height / 2,
|
||||
});
|
||||
}
|
||||
if (endNode.calcIntersect) {
|
||||
const intersection = endNode.calcIntersect(
|
||||
{
|
||||
x: endNode.offset.posX + endNode.width / 2,
|
||||
y: endNode.offset.posY + endNode.height / 2,
|
||||
width: endNode.width,
|
||||
height: endNode.height,
|
||||
},
|
||||
edge.points[edge.points.length - 1]
|
||||
);
|
||||
|
||||
if (distance(intersection, edge.points[edge.points.length - 1]) > epsilon) {
|
||||
edge.points.push(intersection);
|
||||
}
|
||||
if (endNode.shape === 'diamond' || endNode.shape === 'diam') {
|
||||
edge.points.push({
|
||||
x: endNode.offset.posX + endNode.width / 2,
|
||||
y: endNode.offset.posY + endNode.height / 2,
|
||||
});
|
||||
}
|
||||
|
||||
edge.points = cutPathAtIntersect(
|
||||
edge.points.reverse(),
|
||||
{
|
||||
x: startNode.offset.posX + startNode.width / 2,
|
||||
y: startNode.offset.posY + startNode.height / 2,
|
||||
width: sw,
|
||||
height: startNode.height,
|
||||
padding: startNode.padding,
|
||||
},
|
||||
startNode.shape === 'diamond' || startNode.shape === 'diam'
|
||||
).reverse();
|
||||
|
||||
edge.points = cutPathAtIntersect(
|
||||
edge.points,
|
||||
{
|
||||
x: endNode.offset.posX + endNode.width / 2,
|
||||
y: endNode.offset.posY + endNode.height / 2,
|
||||
width: ew,
|
||||
height: endNode.height,
|
||||
padding: endNode.padding,
|
||||
},
|
||||
endNode.shape === 'diamond' || endNode.shape === 'diam'
|
||||
);
|
||||
|
||||
const paths = insertEdge(
|
||||
edgesEl,
|
||||
edge,
|
||||
@@ -771,6 +1012,7 @@ export const render = async (
|
||||
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;
|
||||
|
@@ -5,6 +5,6 @@
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/**/*.d.ts"],
|
||||
"include": ["./src/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
||||
|
@@ -1,59 +0,0 @@
|
||||
# @mermaid-js/layout-tidy-tree
|
||||
|
||||
This package provides a bidirectional tidy tree layout engine for Mermaid based on the non-layered-tidy-tree-layout algorithm.
|
||||
|
||||
> [!NOTE]
|
||||
> The Tidy Tree Layout engine will not be available in all providers that support mermaid by default.
|
||||
> The websites will have to install the @mermaid-js/layout-tidy-tree package to use the Tidy Tree layout engine.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
A
|
||||
B
|
||||
```
|
||||
|
||||
### With bundlers
|
||||
|
||||
```sh
|
||||
npm install @mermaid-js/layout-tidy-tree
|
||||
```
|
||||
|
||||
```ts
|
||||
import mermaid from 'mermaid';
|
||||
import tidyTreeLayouts from '@mermaid-js/layout-tidy-tree';
|
||||
|
||||
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||
```
|
||||
|
||||
### With CDN
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
import tidyTreeLayouts from 'https://cdn.jsdelivr.net/npm/@mermaid-js/layout-tidy-tree@0/dist/mermaid-layout-tidy-tree.esm.min.mjs';
|
||||
|
||||
mermaid.registerLayoutLoaders(tidyTreeLayouts);
|
||||
</script>
|
||||
```
|
||||
|
||||
## Tidy Tree Layout Overview
|
||||
|
||||
tidy-tree: The bidirectional tidy tree layout
|
||||
|
||||
The bidirectional tidy tree layout algorithm creates two separate trees that grow horizontally in opposite directions from a central root node:
|
||||
Left tree: grows horizontally to the left (children alternate: 1st, 3rd, 5th...)
|
||||
Right tree: grows horizontally to the right (children alternate: 2nd, 4th, 6th...)
|
||||
|
||||
This creates a balanced, symmetric layout that is ideal for mindmaps, organizational charts, and other tree-based diagrams.
|
||||
|
||||
Layout Structure:
|
||||
[Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]
|
||||
↓ ↓ ↓ ↓
|
||||
[GrandChild] [GrandChild] [GrandChild] [GrandChild]
|
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-tidy-tree",
|
||||
"version": "0.1.0",
|
||||
"description": "Tidy-tree layout engine for mermaid",
|
||||
"module": "dist/mermaid-layout-tidy-tree.core.mjs",
|
||||
"types": "dist/layouts.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-layout-tidy-tree.core.mjs",
|
||||
"types": "./dist/layouts.d.ts"
|
||||
},
|
||||
"./": "./"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"tidy-tree",
|
||||
"mermaid",
|
||||
"layout"
|
||||
],
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
},
|
||||
"contributors": [
|
||||
"Knut Sveidqvist",
|
||||
"Sidharth Vinod"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3": "^7.9.0",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3",
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "^11.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Bidirectional Tidy-Tree Layout Algorithm for Generic Diagrams
|
||||
*
|
||||
* This module provides a layout algorithm implementation using the
|
||||
* non-layered-tidy-tree-layout algorithm for positioning nodes and edges
|
||||
* in tree structures with a bidirectional approach.
|
||||
*
|
||||
* The algorithm creates two separate trees that grow horizontally in opposite
|
||||
* directions from a central root node:
|
||||
* - Left tree: grows horizontally to the left (children alternate: 1st, 3rd, 5th...)
|
||||
* - Right tree: grows horizontally to the right (children alternate: 2nd, 4th, 6th...)
|
||||
*
|
||||
* This creates a balanced, symmetric layout that is ideal for mindmaps,
|
||||
* organizational charts, and other tree-based diagrams.
|
||||
*
|
||||
* The algorithm follows the unified rendering pattern and can be used
|
||||
* by any diagram type that provides compatible LayoutData.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Render function for the bidirectional tidy-tree layout algorithm
|
||||
*
|
||||
* This function follows the unified rendering pattern used by all layout algorithms.
|
||||
* It takes LayoutData, inserts nodes into DOM, runs the bidirectional tidy-tree layout algorithm,
|
||||
* and renders the positioned elements to the SVG.
|
||||
*
|
||||
* Features:
|
||||
* - Alternates root children between left and right trees
|
||||
* - Left tree grows horizontally to the left (rotated 90° counterclockwise)
|
||||
* - Right tree grows horizontally to the right (rotated 90° clockwise)
|
||||
* - Uses tidy-tree algorithm for optimal spacing within each tree
|
||||
* - Creates symmetric, balanced layouts
|
||||
* - Maintains proper edge connections between all nodes
|
||||
*
|
||||
* Layout Structure:
|
||||
* ```
|
||||
* [Child 3] ← [Child 1] ← [Root] → [Child 2] → [Child 4]
|
||||
* ↓ ↓ ↓ ↓
|
||||
* [GrandChild] [GrandChild] [GrandChild] [GrandChild]
|
||||
* ```
|
||||
*
|
||||
* @param layoutData - Layout data containing nodes, edges, and configuration
|
||||
* @param svg - SVG element to render to
|
||||
* @param helpers - Internal helper functions for rendering
|
||||
* @param options - Rendering options
|
||||
*/
|
||||
export { default } from './layouts.js';
|
||||
export * from './types.js';
|
||||
export * from './layout.js';
|
||||
export { render } from './render.js';
|
@@ -1,409 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { executeTidyTreeLayout, validateLayoutData } from './layout.js';
|
||||
import type { LayoutResult } from './types.js';
|
||||
import type { LayoutData, MermaidConfig } from 'mermaid';
|
||||
|
||||
// Mock non-layered-tidy-tree-layout
|
||||
vi.mock('non-layered-tidy-tree-layout', () => ({
|
||||
BoundingBox: vi.fn().mockImplementation(() => ({})),
|
||||
Layout: vi.fn().mockImplementation(() => ({
|
||||
layout: vi.fn().mockImplementation((treeData) => {
|
||||
const result = { ...treeData };
|
||||
|
||||
if (result.id?.toString().startsWith('virtual-root')) {
|
||||
result.x = 0;
|
||||
result.y = 0;
|
||||
} else {
|
||||
result.x = 100;
|
||||
result.y = 50;
|
||||
}
|
||||
|
||||
if (result.children) {
|
||||
result.children.forEach((child: any, index: number) => {
|
||||
child.x = 50 + index * 100;
|
||||
child.y = 100;
|
||||
|
||||
if (child.children) {
|
||||
child.children.forEach((grandchild: any, gIndex: number) => {
|
||||
grandchild.x = 25 + gIndex * 50;
|
||||
grandchild.y = 200;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
result,
|
||||
boundingBox: {
|
||||
left: 0,
|
||||
right: 200,
|
||||
top: 0,
|
||||
bottom: 250,
|
||||
},
|
||||
};
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('Tidy-Tree Layout Algorithm', () => {
|
||||
let mockConfig: MermaidConfig;
|
||||
let mockLayoutData: LayoutData;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConfig = {
|
||||
theme: 'default',
|
||||
} as MermaidConfig;
|
||||
|
||||
mockLayoutData = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'root',
|
||||
label: 'Root',
|
||||
isGroup: false,
|
||||
shape: 'rect',
|
||||
width: 100,
|
||||
height: 50,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'child1',
|
||||
label: 'Child 1',
|
||||
isGroup: false,
|
||||
shape: 'rect',
|
||||
width: 80,
|
||||
height: 40,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'child2',
|
||||
label: 'Child 2',
|
||||
isGroup: false,
|
||||
shape: 'rect',
|
||||
width: 80,
|
||||
height: 40,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'child3',
|
||||
label: 'Child 3',
|
||||
isGroup: false,
|
||||
shape: 'rect',
|
||||
width: 80,
|
||||
height: 40,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'child4',
|
||||
label: 'Child 4',
|
||||
isGroup: false,
|
||||
shape: 'rect',
|
||||
width: 80,
|
||||
height: 40,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'root_child1',
|
||||
start: 'root',
|
||||
end: 'child1',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
{
|
||||
id: 'root_child2',
|
||||
start: 'root',
|
||||
end: 'child2',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
{
|
||||
id: 'root_child3',
|
||||
start: 'root',
|
||||
end: 'child3',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
{
|
||||
id: 'root_child4',
|
||||
start: 'root',
|
||||
end: 'child4',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
],
|
||||
config: mockConfig,
|
||||
direction: 'TB',
|
||||
type: 'test',
|
||||
diagramId: 'test-diagram',
|
||||
markers: [],
|
||||
};
|
||||
});
|
||||
|
||||
describe('validateLayoutData', () => {
|
||||
it('should validate correct layout data', () => {
|
||||
expect(() => validateLayoutData(mockLayoutData)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for missing data', () => {
|
||||
expect(() => validateLayoutData(null as any)).toThrow('Layout data is required');
|
||||
});
|
||||
|
||||
it('should throw error for missing config', () => {
|
||||
const invalidData = { ...mockLayoutData, config: null as any };
|
||||
expect(() => validateLayoutData(invalidData)).toThrow('Configuration is required');
|
||||
});
|
||||
|
||||
it('should throw error for invalid nodes array', () => {
|
||||
const invalidData = { ...mockLayoutData, nodes: null as any };
|
||||
expect(() => validateLayoutData(invalidData)).toThrow('Nodes array is required');
|
||||
});
|
||||
|
||||
it('should throw error for invalid edges array', () => {
|
||||
const invalidData = { ...mockLayoutData, edges: null as any };
|
||||
expect(() => validateLayoutData(invalidData)).toThrow('Edges array is required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeTidyTreeLayout function', () => {
|
||||
it('should execute layout algorithm successfully', async () => {
|
||||
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.nodes).toBeDefined();
|
||||
expect(result.edges).toBeDefined();
|
||||
expect(Array.isArray(result.nodes)).toBe(true);
|
||||
expect(Array.isArray(result.edges)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return positioned nodes with coordinates', async () => {
|
||||
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||
|
||||
expect(result.nodes.length).toBeGreaterThan(0);
|
||||
result.nodes.forEach((node) => {
|
||||
expect(node.x).toBeDefined();
|
||||
expect(node.y).toBeDefined();
|
||||
expect(typeof node.x).toBe('number');
|
||||
expect(typeof node.y).toBe('number');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return positioned edges with coordinates', async () => {
|
||||
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
|
||||
|
||||
expect(result.edges.length).toBeGreaterThan(0);
|
||||
result.edges.forEach((edge) => {
|
||||
expect(edge.startX).toBeDefined();
|
||||
expect(edge.startY).toBeDefined();
|
||||
expect(edge.midX).toBeDefined();
|
||||
expect(edge.midY).toBeDefined();
|
||||
expect(edge.endX).toBeDefined();
|
||||
expect(edge.endY).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty layout data gracefully', async () => {
|
||||
const emptyData: LayoutData = {
|
||||
...mockLayoutData,
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
await expect(executeTidyTreeLayout(emptyData)).rejects.toThrow(
|
||||
'No nodes found in layout data'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for missing nodes', async () => {
|
||||
const invalidData = { ...mockLayoutData, nodes: [] };
|
||||
|
||||
await expect(executeTidyTreeLayout(invalidData)).rejects.toThrow(
|
||||
'No nodes found in layout data'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle empty edges (single node tree)', async () => {
|
||||
const singleNodeData = {
|
||||
...mockLayoutData,
|
||||
edges: [],
|
||||
nodes: [mockLayoutData.nodes[0]],
|
||||
};
|
||||
|
||||
const result = await executeTidyTreeLayout(singleNodeData);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.nodes).toHaveLength(1);
|
||||
expect(result.edges).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should create bidirectional dual-tree layout with alternating left/right children', async () => {
|
||||
const result = await executeTidyTreeLayout(mockLayoutData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.nodes).toHaveLength(5);
|
||||
|
||||
const rootNode = result.nodes.find((node) => node.id === 'root');
|
||||
expect(rootNode).toBeDefined();
|
||||
expect(rootNode!.x).toBe(0);
|
||||
expect(rootNode!.y).toBe(20);
|
||||
|
||||
const child1 = result.nodes.find((node) => node.id === 'child1');
|
||||
const child2 = result.nodes.find((node) => node.id === 'child2');
|
||||
const child3 = result.nodes.find((node) => node.id === 'child3');
|
||||
const child4 = result.nodes.find((node) => node.id === 'child4');
|
||||
|
||||
expect(child1).toBeDefined();
|
||||
expect(child2).toBeDefined();
|
||||
expect(child3).toBeDefined();
|
||||
expect(child4).toBeDefined();
|
||||
|
||||
expect(child1!.x).toBeLessThan(rootNode!.x);
|
||||
expect(child2!.x).toBeGreaterThan(rootNode!.x);
|
||||
expect(child3!.x).toBeLessThan(rootNode!.x);
|
||||
expect(child4!.x).toBeGreaterThan(rootNode!.x);
|
||||
|
||||
expect(child1!.x).toBeLessThan(-100);
|
||||
expect(child3!.x).toBeLessThan(-100);
|
||||
|
||||
expect(child2!.x).toBeGreaterThan(100);
|
||||
expect(child4!.x).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
it('should correctly transpose coordinates to prevent high nodes from covering nodes above them', async () => {
|
||||
const testData = {
|
||||
...mockLayoutData,
|
||||
nodes: [
|
||||
{
|
||||
id: 'root',
|
||||
label: 'Root',
|
||||
isGroup: false,
|
||||
shape: 'rect' as const,
|
||||
width: 100,
|
||||
height: 50,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'tall-child',
|
||||
label: 'Tall Child',
|
||||
isGroup: false,
|
||||
shape: 'rect' as const,
|
||||
width: 80,
|
||||
height: 120,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
{
|
||||
id: 'short-child',
|
||||
label: 'Short Child',
|
||||
isGroup: false,
|
||||
shape: 'rect' as const,
|
||||
width: 80,
|
||||
height: 30,
|
||||
padding: 10,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cssClasses: '',
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'root_tall',
|
||||
start: 'root',
|
||||
end: 'tall-child',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
{
|
||||
id: 'root_short',
|
||||
start: 'root',
|
||||
end: 'short-child',
|
||||
type: 'edge',
|
||||
classes: '',
|
||||
style: [],
|
||||
animate: false,
|
||||
arrowTypeEnd: 'arrow_point',
|
||||
arrowTypeStart: 'none',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = await executeTidyTreeLayout(testData);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.nodes).toHaveLength(3);
|
||||
|
||||
const rootNode = result.nodes.find((node) => node.id === 'root');
|
||||
const tallChild = result.nodes.find((node) => node.id === 'tall-child');
|
||||
const shortChild = result.nodes.find((node) => node.id === 'short-child');
|
||||
|
||||
expect(rootNode).toBeDefined();
|
||||
expect(tallChild).toBeDefined();
|
||||
expect(shortChild).toBeDefined();
|
||||
|
||||
expect(tallChild!.x).not.toBe(shortChild!.x);
|
||||
|
||||
expect(tallChild!.width).toBe(80);
|
||||
expect(tallChild!.height).toBe(120);
|
||||
expect(shortChild!.width).toBe(80);
|
||||
expect(shortChild!.height).toBe(30);
|
||||
|
||||
const yDifference = Math.abs(tallChild!.y - shortChild!.y);
|
||||
expect(yDifference).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,629 +0,0 @@
|
||||
import type { LayoutData } from 'mermaid';
|
||||
import type { Bounds, Point } from 'mermaid/src/types.js';
|
||||
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
|
||||
import type {
|
||||
Edge,
|
||||
LayoutResult,
|
||||
Node,
|
||||
PositionedEdge,
|
||||
PositionedNode,
|
||||
TidyTreeNode,
|
||||
} from './types.js';
|
||||
|
||||
/**
|
||||
* Execute the tidy-tree layout algorithm on generic layout data
|
||||
*
|
||||
* This function takes layout data and uses the non-layered-tidy-tree-layout
|
||||
* algorithm to calculate optimal node positions for tree structures.
|
||||
*
|
||||
* @param data - The layout data containing nodes, edges, and configuration
|
||||
* @param config - Mermaid configuration object
|
||||
* @returns Promise resolving to layout result with positioned nodes and edges
|
||||
*/
|
||||
export function executeTidyTreeLayout(data: LayoutData): Promise<LayoutResult> {
|
||||
let intersectionShift = 50;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (!data.nodes || !Array.isArray(data.nodes) || data.nodes.length === 0) {
|
||||
throw new Error('No nodes found in layout data');
|
||||
}
|
||||
|
||||
if (!data.edges || !Array.isArray(data.edges)) {
|
||||
data.edges = [];
|
||||
}
|
||||
|
||||
const { leftTree, rightTree, rootNode } = convertToDualTreeFormat(data);
|
||||
|
||||
const gap = 20;
|
||||
const bottomPadding = 40;
|
||||
intersectionShift = 30;
|
||||
|
||||
const bb = new BoundingBox(gap, bottomPadding);
|
||||
const layout = new Layout(bb);
|
||||
|
||||
let leftResult = null;
|
||||
let rightResult = null;
|
||||
|
||||
if (leftTree) {
|
||||
const leftLayoutResult = layout.layout(leftTree);
|
||||
leftResult = leftLayoutResult.result;
|
||||
}
|
||||
|
||||
if (rightTree) {
|
||||
const rightLayoutResult = layout.layout(rightTree);
|
||||
rightResult = rightLayoutResult.result;
|
||||
}
|
||||
|
||||
const positionedNodes = combineAndPositionTrees(rootNode, leftResult, rightResult);
|
||||
const positionedEdges = calculateEdgePositions(
|
||||
data.edges,
|
||||
positionedNodes,
|
||||
intersectionShift
|
||||
);
|
||||
resolve({
|
||||
nodes: positionedNodes,
|
||||
edges: positionedEdges,
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert LayoutData to dual-tree format (left and right trees)
|
||||
*
|
||||
* This function builds two separate tree structures from the nodes and edges,
|
||||
* alternating children between left and right trees.
|
||||
*/
|
||||
function convertToDualTreeFormat(data: LayoutData): {
|
||||
leftTree: TidyTreeNode | null;
|
||||
rightTree: TidyTreeNode | null;
|
||||
rootNode: TidyTreeNode;
|
||||
} {
|
||||
const { nodes, edges } = data;
|
||||
|
||||
const nodeMap = new Map<string, Node>();
|
||||
nodes.forEach((node) => nodeMap.set(node.id, node));
|
||||
|
||||
const children = new Map<string, string[]>();
|
||||
const parents = new Map<string, string>();
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const parentId = edge.start;
|
||||
const childId = edge.end;
|
||||
|
||||
if (parentId && childId) {
|
||||
if (!children.has(parentId)) {
|
||||
children.set(parentId, []);
|
||||
}
|
||||
children.get(parentId)!.push(childId);
|
||||
parents.set(childId, parentId);
|
||||
}
|
||||
});
|
||||
|
||||
const rootNodeData = nodes.find((node) => !parents.has(node.id));
|
||||
if (!rootNodeData && nodes.length === 0) {
|
||||
throw new Error('No nodes available to create tree');
|
||||
}
|
||||
|
||||
const actualRoot = rootNodeData ?? nodes[0];
|
||||
|
||||
const rootNode: TidyTreeNode = {
|
||||
id: actualRoot.id,
|
||||
width: actualRoot.width ?? 100,
|
||||
height: actualRoot.height ?? 50,
|
||||
_originalNode: actualRoot,
|
||||
};
|
||||
|
||||
const rootChildren = children.get(actualRoot.id) ?? [];
|
||||
const leftChildren: string[] = [];
|
||||
const rightChildren: string[] = [];
|
||||
|
||||
rootChildren.forEach((childId, index) => {
|
||||
if (index % 2 === 0) {
|
||||
leftChildren.push(childId);
|
||||
} else {
|
||||
rightChildren.push(childId);
|
||||
}
|
||||
});
|
||||
|
||||
const leftTree = leftChildren.length > 0 ? buildSubTree(leftChildren, children, nodeMap) : null;
|
||||
|
||||
const rightTree =
|
||||
rightChildren.length > 0 ? buildSubTree(rightChildren, children, nodeMap) : null;
|
||||
|
||||
return { leftTree, rightTree, rootNode };
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a subtree from a list of root children
|
||||
* For horizontal trees, we need to transpose width/height since the tree will be rotated 90°
|
||||
*/
|
||||
function buildSubTree(
|
||||
rootChildren: string[],
|
||||
children: Map<string, string[]>,
|
||||
nodeMap: Map<string, Node>
|
||||
): TidyTreeNode {
|
||||
const virtualRoot: TidyTreeNode = {
|
||||
id: `virtual-root-${Math.random()}`,
|
||||
width: 1,
|
||||
height: 1,
|
||||
children: rootChildren
|
||||
.map((childId) => nodeMap.get(childId))
|
||||
.filter((child): child is Node => child !== undefined)
|
||||
.map((child) => convertNodeToTidyTreeTransposed(child, children, nodeMap)),
|
||||
};
|
||||
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively convert a node and its children to tidy-tree format
|
||||
* This version transposes width/height for horizontal tree layout
|
||||
*/
|
||||
function convertNodeToTidyTreeTransposed(
|
||||
node: Node,
|
||||
children: Map<string, string[]>,
|
||||
nodeMap: Map<string, Node>
|
||||
): TidyTreeNode {
|
||||
const childIds = children.get(node.id) ?? [];
|
||||
const childNodes = childIds
|
||||
.map((childId) => nodeMap.get(childId))
|
||||
.filter((child): child is Node => child !== undefined)
|
||||
.map((child) => convertNodeToTidyTreeTransposed(child, children, nodeMap));
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
width: node.height ?? 50,
|
||||
height: node.width ?? 100,
|
||||
children: childNodes.length > 0 ? childNodes : undefined,
|
||||
_originalNode: node,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Combine and position the left and right trees around the root node
|
||||
* Creates a bidirectional layout where left tree grows left and right tree grows right
|
||||
*/
|
||||
function combineAndPositionTrees(
|
||||
rootNode: TidyTreeNode,
|
||||
leftResult: TidyTreeNode | null,
|
||||
rightResult: TidyTreeNode | null
|
||||
): PositionedNode[] {
|
||||
const positionedNodes: PositionedNode[] = [];
|
||||
|
||||
const rootX = 0;
|
||||
const rootY = 0;
|
||||
|
||||
const treeSpacing = rootNode.width / 2 + 30;
|
||||
const leftTreeNodes: PositionedNode[] = [];
|
||||
const rightTreeNodes: PositionedNode[] = [];
|
||||
|
||||
if (leftResult?.children) {
|
||||
positionLeftTreeBidirectional(leftResult.children, leftTreeNodes, rootX - treeSpacing, rootY);
|
||||
}
|
||||
|
||||
if (rightResult?.children) {
|
||||
positionRightTreeBidirectional(
|
||||
rightResult.children,
|
||||
rightTreeNodes,
|
||||
rootX + treeSpacing,
|
||||
rootY
|
||||
);
|
||||
}
|
||||
|
||||
let leftTreeCenterY = 0;
|
||||
let rightTreeCenterY = 0;
|
||||
|
||||
if (leftTreeNodes.length > 0) {
|
||||
const leftTreeXPositions = [...new Set(leftTreeNodes.map((node) => node.x))].sort(
|
||||
(a, b) => b - a
|
||||
);
|
||||
const firstLevelLeftX = leftTreeXPositions[0];
|
||||
const firstLevelLeftNodes = leftTreeNodes.filter((node) => node.x === firstLevelLeftX);
|
||||
|
||||
if (firstLevelLeftNodes.length > 0) {
|
||||
const leftMinY = Math.min(
|
||||
...firstLevelLeftNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||
);
|
||||
const leftMaxY = Math.max(
|
||||
...firstLevelLeftNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||
);
|
||||
leftTreeCenterY = (leftMinY + leftMaxY) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (rightTreeNodes.length > 0) {
|
||||
const rightTreeXPositions = [...new Set(rightTreeNodes.map((node) => node.x))].sort(
|
||||
(a, b) => a - b
|
||||
);
|
||||
const firstLevelRightX = rightTreeXPositions[0];
|
||||
const firstLevelRightNodes = rightTreeNodes.filter((node) => node.x === firstLevelRightX);
|
||||
|
||||
if (firstLevelRightNodes.length > 0) {
|
||||
const rightMinY = Math.min(
|
||||
...firstLevelRightNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||
);
|
||||
const rightMaxY = Math.max(
|
||||
...firstLevelRightNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||
);
|
||||
rightTreeCenterY = (rightMinY + rightMaxY) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
const leftTreeOffset = -leftTreeCenterY;
|
||||
const rightTreeOffset = -rightTreeCenterY;
|
||||
|
||||
positionedNodes.push({
|
||||
id: String(rootNode.id),
|
||||
x: rootX,
|
||||
y: rootY + 20,
|
||||
section: 'root',
|
||||
width: rootNode._originalNode?.width ?? rootNode.width,
|
||||
height: rootNode._originalNode?.height ?? rootNode.height,
|
||||
originalNode: rootNode._originalNode,
|
||||
});
|
||||
|
||||
const leftTreeNodesWithOffset = leftTreeNodes.map((node) => ({
|
||||
id: node.id,
|
||||
x: node.x - (node.width ?? 0) / 2,
|
||||
y: node.y + leftTreeOffset + (node.height ?? 0) / 2,
|
||||
section: 'left' as const,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
originalNode: node.originalNode,
|
||||
}));
|
||||
|
||||
const rightTreeNodesWithOffset = rightTreeNodes.map((node) => ({
|
||||
id: node.id,
|
||||
x: node.x + (node.width ?? 0) / 2,
|
||||
y: node.y + rightTreeOffset + (node.height ?? 0) / 2,
|
||||
section: 'right' as const,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
originalNode: node.originalNode,
|
||||
}));
|
||||
|
||||
positionedNodes.push(...leftTreeNodesWithOffset);
|
||||
positionedNodes.push(...rightTreeNodesWithOffset);
|
||||
|
||||
return positionedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position nodes from the left tree in a bidirectional layout (grows to the left)
|
||||
* Rotates the tree 90 degrees counterclockwise so it grows horizontally to the left
|
||||
*/
|
||||
function positionLeftTreeBidirectional(
|
||||
nodes: TidyTreeNode[],
|
||||
positionedNodes: PositionedNode[],
|
||||
offsetX: number,
|
||||
offsetY: number
|
||||
): void {
|
||||
nodes.forEach((node) => {
|
||||
const distanceFromRoot = node.y ?? 0;
|
||||
const verticalPosition = node.x ?? 0;
|
||||
|
||||
const originalWidth = node._originalNode?.width ?? 100;
|
||||
const originalHeight = node._originalNode?.height ?? 50;
|
||||
|
||||
const adjustedY = offsetY + verticalPosition;
|
||||
|
||||
positionedNodes.push({
|
||||
id: String(node.id),
|
||||
x: offsetX - distanceFromRoot,
|
||||
y: adjustedY,
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
originalNode: node._originalNode,
|
||||
});
|
||||
|
||||
if (node.children) {
|
||||
positionLeftTreeBidirectional(node.children, positionedNodes, offsetX, offsetY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Position nodes from the right tree in a bidirectional layout (grows to the right)
|
||||
* Rotates the tree 90 degrees clockwise so it grows horizontally to the right
|
||||
*/
|
||||
function positionRightTreeBidirectional(
|
||||
nodes: TidyTreeNode[],
|
||||
positionedNodes: PositionedNode[],
|
||||
offsetX: number,
|
||||
offsetY: number
|
||||
): void {
|
||||
nodes.forEach((node) => {
|
||||
const distanceFromRoot = node.y ?? 0;
|
||||
const verticalPosition = node.x ?? 0;
|
||||
|
||||
const originalWidth = node._originalNode?.width ?? 100;
|
||||
const originalHeight = node._originalNode?.height ?? 50;
|
||||
|
||||
const adjustedY = offsetY + verticalPosition;
|
||||
|
||||
positionedNodes.push({
|
||||
id: String(node.id),
|
||||
x: offsetX + distanceFromRoot,
|
||||
y: adjustedY,
|
||||
width: originalWidth,
|
||||
height: originalHeight,
|
||||
originalNode: node._originalNode,
|
||||
});
|
||||
|
||||
if (node.children) {
|
||||
positionRightTreeBidirectional(node.children, positionedNodes, offsetX, offsetY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the intersection point of a line with a circle
|
||||
* @param circle - Circle coordinates and radius
|
||||
* @param lineStart - Starting point of the line
|
||||
* @param lineEnd - Ending point of the line
|
||||
* @returns The intersection point
|
||||
*/
|
||||
function computeCircleEdgeIntersection(circle: Bounds, lineStart: Point, lineEnd: Point): Point {
|
||||
const radius = Math.min(circle.width, circle.height) / 2;
|
||||
|
||||
const dx = lineEnd.x - lineStart.x;
|
||||
const dy = lineEnd.y - lineStart.y;
|
||||
const length = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (length === 0) {
|
||||
return lineStart;
|
||||
}
|
||||
|
||||
const nx = dx / length;
|
||||
const ny = dy / length;
|
||||
|
||||
return {
|
||||
x: circle.x - nx * radius,
|
||||
y: circle.y - ny * radius,
|
||||
};
|
||||
}
|
||||
|
||||
function intersection(node: PositionedNode, outsidePoint: Point, insidePoint: Point): Point {
|
||||
const x = node.x;
|
||||
const y = node.y;
|
||||
|
||||
if (!node.width || !node.height) {
|
||||
return { x: outsidePoint.x, y: outsidePoint.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.
|
||||
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;
|
||||
}
|
||||
|
||||
return res;
|
||||
} else {
|
||||
if (insidePoint.x < outsidePoint.x) {
|
||||
r = outsidePoint.x - w - x;
|
||||
} else {
|
||||
r = x - w - outsidePoint.x;
|
||||
}
|
||||
const 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;
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate edge positions based on positioned nodes
|
||||
* Now includes tree membership and node dimensions for precise edge calculations
|
||||
* Edges now stop at shape boundaries instead of extending to centers
|
||||
*/
|
||||
function calculateEdgePositions(
|
||||
edges: Edge[],
|
||||
positionedNodes: PositionedNode[],
|
||||
intersectionShift: number
|
||||
): PositionedEdge[] {
|
||||
const nodeInfo = new Map<string, PositionedNode>();
|
||||
positionedNodes.forEach((node) => {
|
||||
nodeInfo.set(node.id, node);
|
||||
});
|
||||
|
||||
return edges.map((edge) => {
|
||||
const sourceNode = nodeInfo.get(edge.start ?? '');
|
||||
const targetNode = nodeInfo.get(edge.end ?? '');
|
||||
|
||||
if (!sourceNode || !targetNode) {
|
||||
return {
|
||||
id: edge.id,
|
||||
source: edge.start ?? '',
|
||||
target: edge.end ?? '',
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
midX: 0,
|
||||
midY: 0,
|
||||
endX: 0,
|
||||
endY: 0,
|
||||
points: [{ x: 0, y: 0 }],
|
||||
sourceSection: undefined,
|
||||
targetSection: undefined,
|
||||
sourceWidth: undefined,
|
||||
sourceHeight: undefined,
|
||||
targetWidth: undefined,
|
||||
targetHeight: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const sourceCenter = { x: sourceNode.x, y: sourceNode.y };
|
||||
const targetCenter = { x: targetNode.x, y: targetNode.y };
|
||||
|
||||
const isSourceRound = ['circle', 'cloud', 'bang'].includes(
|
||||
sourceNode.originalNode?.shape ?? ''
|
||||
);
|
||||
const isTargetRound = ['circle', 'cloud', 'bang'].includes(
|
||||
targetNode.originalNode?.shape ?? ''
|
||||
);
|
||||
|
||||
let startPos = isSourceRound
|
||||
? computeCircleEdgeIntersection(
|
||||
{
|
||||
x: sourceNode.x,
|
||||
y: sourceNode.y,
|
||||
width: sourceNode.width ?? 100,
|
||||
height: sourceNode.height ?? 100,
|
||||
},
|
||||
targetCenter,
|
||||
sourceCenter
|
||||
)
|
||||
: intersection(sourceNode, sourceCenter, targetCenter);
|
||||
|
||||
let endPos = isTargetRound
|
||||
? computeCircleEdgeIntersection(
|
||||
{
|
||||
x: targetNode.x,
|
||||
y: targetNode.y,
|
||||
width: targetNode.width ?? 100,
|
||||
height: targetNode.height ?? 100,
|
||||
},
|
||||
sourceCenter,
|
||||
targetCenter
|
||||
)
|
||||
: intersection(targetNode, targetCenter, sourceCenter);
|
||||
|
||||
const midX = (startPos.x + endPos.x) / 2;
|
||||
const midY = (startPos.y + endPos.y) / 2;
|
||||
|
||||
const points = [startPos];
|
||||
if (sourceNode.section === 'left') {
|
||||
points.push({
|
||||
x: sourceNode.x - (sourceNode.width ?? 0) / 2 - intersectionShift,
|
||||
y: sourceNode.y,
|
||||
});
|
||||
} else if (sourceNode.section === 'right') {
|
||||
points.push({
|
||||
x: sourceNode.x + (sourceNode.width ?? 0) / 2 + intersectionShift,
|
||||
y: sourceNode.y,
|
||||
});
|
||||
}
|
||||
if (targetNode.section === 'left') {
|
||||
points.push({
|
||||
x: targetNode.x + (targetNode.width ?? 0) / 2 + intersectionShift,
|
||||
y: targetNode.y,
|
||||
});
|
||||
} else if (targetNode.section === 'right') {
|
||||
points.push({
|
||||
x: targetNode.x - (targetNode.width ?? 0) / 2 - intersectionShift,
|
||||
y: targetNode.y,
|
||||
});
|
||||
}
|
||||
|
||||
points.push(endPos);
|
||||
|
||||
const secondPoint = points.length > 1 ? points[1] : targetCenter;
|
||||
startPos = isSourceRound
|
||||
? computeCircleEdgeIntersection(
|
||||
{
|
||||
x: sourceNode.x,
|
||||
y: sourceNode.y,
|
||||
width: sourceNode.width ?? 100,
|
||||
height: sourceNode.height ?? 100,
|
||||
},
|
||||
secondPoint,
|
||||
sourceCenter
|
||||
)
|
||||
: intersection(sourceNode, secondPoint, sourceCenter);
|
||||
points[0] = startPos;
|
||||
|
||||
const secondLastPoint = points.length > 1 ? points[points.length - 2] : sourceCenter;
|
||||
endPos = isTargetRound
|
||||
? computeCircleEdgeIntersection(
|
||||
{
|
||||
x: targetNode.x,
|
||||
y: targetNode.y,
|
||||
width: targetNode.width ?? 100,
|
||||
height: targetNode.height ?? 100,
|
||||
},
|
||||
secondLastPoint,
|
||||
targetCenter
|
||||
)
|
||||
: intersection(targetNode, secondLastPoint, targetCenter);
|
||||
points[points.length - 1] = endPos;
|
||||
|
||||
return {
|
||||
id: edge.id,
|
||||
source: edge.start ?? '',
|
||||
target: edge.end ?? '',
|
||||
startX: startPos.x,
|
||||
startY: startPos.y,
|
||||
midX,
|
||||
midY,
|
||||
endX: endPos.x,
|
||||
endY: endPos.y,
|
||||
points,
|
||||
sourceSection: sourceNode?.section,
|
||||
targetSection: targetNode?.section,
|
||||
sourceWidth: sourceNode?.width,
|
||||
sourceHeight: sourceNode?.height,
|
||||
targetWidth: targetNode?.width,
|
||||
targetHeight: targetNode?.height,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate layout data structure
|
||||
* @param data - The data to validate
|
||||
* @returns True if data is valid, throws error otherwise
|
||||
*/
|
||||
export function validateLayoutData(data: LayoutData): boolean {
|
||||
if (!data) {
|
||||
throw new Error('Layout data is required');
|
||||
}
|
||||
|
||||
if (!data.config) {
|
||||
throw new Error('Configuration is required in layout data');
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.nodes)) {
|
||||
throw new Error('Nodes array is required in layout data');
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.edges)) {
|
||||
throw new Error('Edges array is required in layout data');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@@ -1,13 +0,0 @@
|
||||
import type { LayoutLoaderDefinition } from 'mermaid';
|
||||
|
||||
const loader = async () => await import(`./render.js`);
|
||||
|
||||
const tidyTreeLayout: LayoutLoaderDefinition[] = [
|
||||
{
|
||||
name: 'tidy-tree',
|
||||
loader,
|
||||
algorithm: 'tidy-tree',
|
||||
},
|
||||
];
|
||||
|
||||
export default tidyTreeLayout;
|
@@ -1,18 +0,0 @@
|
||||
declare module 'non-layered-tidy-tree-layout' {
|
||||
export class BoundingBox {
|
||||
constructor(gap: number, bottomPadding: number);
|
||||
}
|
||||
|
||||
export class Layout {
|
||||
constructor(boundingBox: BoundingBox);
|
||||
layout(data: any): {
|
||||
result: any;
|
||||
boundingBox: {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,180 +0,0 @@
|
||||
import type { InternalHelpers, LayoutData, RenderOptions, SVG } from 'mermaid';
|
||||
import { executeTidyTreeLayout } from './layout.js';
|
||||
|
||||
interface NodeWithPosition {
|
||||
id: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
domId?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render function for bidirectional tidy-tree layout algorithm
|
||||
*
|
||||
* This follows the same pattern as ELK and dagre renderers:
|
||||
* 1. Insert nodes into DOM to get their actual dimensions
|
||||
* 2. Run the bidirectional tidy-tree layout algorithm to calculate positions
|
||||
* 3. Position the nodes and edges based on layout results
|
||||
*
|
||||
* The bidirectional layout creates two trees that grow horizontally in opposite
|
||||
* directions from a central root node:
|
||||
* - Left tree: grows horizontally to the left (children: 1st, 3rd, 5th...)
|
||||
* - Right tree: grows horizontally to the right (children: 2nd, 4th, 6th...)
|
||||
*/
|
||||
export const render = async (
|
||||
data4Layout: LayoutData,
|
||||
svg: SVG,
|
||||
{
|
||||
insertCluster,
|
||||
insertEdge,
|
||||
insertEdgeLabel,
|
||||
insertMarkers,
|
||||
insertNode,
|
||||
log,
|
||||
positionEdgeLabel,
|
||||
}: InternalHelpers,
|
||||
{ algorithm: _algorithm }: RenderOptions
|
||||
) => {
|
||||
const nodeDb: Record<string, NodeWithPosition> = {};
|
||||
const clusterDb: Record<string, any> = {};
|
||||
|
||||
const element = svg.select('g');
|
||||
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||
|
||||
const subGraphsEl = element.insert('g').attr('class', 'subgraphs');
|
||||
const edgePaths = element.insert('g').attr('class', 'edgePaths');
|
||||
const edgeLabels = element.insert('g').attr('class', 'edgeLabels');
|
||||
const nodes = element.insert('g').attr('class', 'nodes');
|
||||
// Step 1: Insert nodes into DOM to get their actual dimensions
|
||||
log.debug('Inserting nodes into DOM for dimension calculation');
|
||||
|
||||
await Promise.all(
|
||||
data4Layout.nodes.map(async (node) => {
|
||||
if (node.isGroup) {
|
||||
const clusterNode: NodeWithPosition = {
|
||||
...node,
|
||||
id: node.id,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
};
|
||||
clusterDb[node.id] = clusterNode;
|
||||
nodeDb[node.id] = clusterNode;
|
||||
|
||||
await insertCluster(subGraphsEl, node);
|
||||
} else {
|
||||
const nodeWithPosition: NodeWithPosition = {
|
||||
...node,
|
||||
id: node.id,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
};
|
||||
nodeDb[node.id] = nodeWithPosition;
|
||||
|
||||
const nodeEl = await insertNode(nodes, node, {
|
||||
config: data4Layout.config,
|
||||
dir: data4Layout.direction || 'TB',
|
||||
});
|
||||
|
||||
const boundingBox = nodeEl.node()!.getBBox();
|
||||
nodeWithPosition.width = boundingBox.width;
|
||||
nodeWithPosition.height = boundingBox.height;
|
||||
nodeWithPosition.domId = nodeEl;
|
||||
|
||||
log.debug(`Node ${node.id} dimensions: ${boundingBox.width}x${boundingBox.height}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
// Step 2: Run the bidirectional tidy-tree layout algorithm
|
||||
log.debug('Running bidirectional tidy-tree layout algorithm');
|
||||
|
||||
const updatedLayoutData = {
|
||||
...data4Layout,
|
||||
nodes: data4Layout.nodes.map((node) => {
|
||||
const nodeWithDimensions = nodeDb[node.id];
|
||||
return {
|
||||
...node,
|
||||
width: nodeWithDimensions.width ?? node.width ?? 100,
|
||||
height: nodeWithDimensions.height ?? node.height ?? 50,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const layoutResult = await executeTidyTreeLayout(updatedLayoutData);
|
||||
// Step 3: Position the nodes based on bidirectional layout results
|
||||
log.debug('Positioning nodes based on bidirectional layout results');
|
||||
|
||||
layoutResult.nodes.forEach((positionedNode) => {
|
||||
const node = nodeDb[positionedNode.id];
|
||||
if (node?.domId) {
|
||||
// Position the node at the calculated coordinates from bidirectional layout
|
||||
// The layout algorithm has already calculated positions for:
|
||||
// - Root node at center (0, 0)
|
||||
// - Left tree nodes with negative x coordinates (growing left)
|
||||
// - Right tree nodes with positive x coordinates (growing right)
|
||||
node.domId.attr('transform', `translate(${positionedNode.x}, ${positionedNode.y})`);
|
||||
// Store the final position
|
||||
node.x = positionedNode.x;
|
||||
node.y = positionedNode.y;
|
||||
// Step 3: Position the nodes based on bidirectional layout results
|
||||
log.debug(`Positioned node ${node.id} at (${positionedNode.x}, ${positionedNode.y})`);
|
||||
}
|
||||
});
|
||||
|
||||
log.debug('Inserting and positioning edges');
|
||||
|
||||
await Promise.all(
|
||||
data4Layout.edges.map(async (edge) => {
|
||||
await insertEdgeLabel(edgeLabels, edge);
|
||||
|
||||
const startNode = nodeDb[edge.start ?? ''];
|
||||
const endNode = nodeDb[edge.end ?? ''];
|
||||
|
||||
if (startNode && endNode) {
|
||||
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
||||
|
||||
if (positionedEdge) {
|
||||
log.debug('APA01 positionedEdge', positionedEdge);
|
||||
const edgeWithPath = {
|
||||
...edge,
|
||||
points: positionedEdge.points,
|
||||
};
|
||||
const paths = insertEdge(
|
||||
edgePaths,
|
||||
edgeWithPath,
|
||||
clusterDb,
|
||||
data4Layout.type,
|
||||
startNode,
|
||||
endNode,
|
||||
data4Layout.diagramId
|
||||
);
|
||||
|
||||
positionEdgeLabel(edgeWithPath, paths);
|
||||
} else {
|
||||
const edgeWithPath = {
|
||||
...edge,
|
||||
points: [
|
||||
{ x: startNode.x ?? 0, y: startNode.y ?? 0 },
|
||||
{ x: endNode.x ?? 0, y: endNode.y ?? 0 },
|
||||
],
|
||||
};
|
||||
|
||||
const paths = insertEdge(
|
||||
edgePaths,
|
||||
edgeWithPath,
|
||||
clusterDb,
|
||||
data4Layout.type,
|
||||
startNode,
|
||||
endNode,
|
||||
data4Layout.diagramId
|
||||
);
|
||||
positionEdgeLabel(edgeWithPath, paths);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
log.debug('Bidirectional tidy-tree rendering completed');
|
||||
};
|
@@ -1,69 +0,0 @@
|
||||
import type { LayoutData } from 'mermaid';
|
||||
|
||||
export type Node = LayoutData['nodes'][number];
|
||||
export type Edge = LayoutData['edges'][number];
|
||||
|
||||
/**
|
||||
* Positioned node after layout calculation
|
||||
*/
|
||||
export interface PositionedNode {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
section?: 'root' | 'left' | 'right';
|
||||
width?: number;
|
||||
height?: number;
|
||||
originalNode?: Node;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positioned edge after layout calculation
|
||||
*/
|
||||
export interface PositionedEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
startX: number;
|
||||
startY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
sourceSection?: 'root' | 'left' | 'right';
|
||||
targetSection?: 'root' | 'left' | 'right';
|
||||
sourceWidth?: number;
|
||||
sourceHeight?: number;
|
||||
targetWidth?: number;
|
||||
targetHeight?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of layout algorithm execution
|
||||
*/
|
||||
export interface LayoutResult {
|
||||
nodes: PositionedNode[];
|
||||
edges: PositionedEdge[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidy-tree node structure compatible with non-layered-tidy-tree-layout
|
||||
*/
|
||||
export interface TidyTreeNode {
|
||||
id: string | number;
|
||||
width: number;
|
||||
height: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
children?: TidyTreeNode[];
|
||||
_originalNode?: Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidy-tree layout configuration
|
||||
*/
|
||||
export interface TidyTreeLayoutConfig {
|
||||
gap: number;
|
||||
bottomPadding: number;
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/**/*.d.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
@@ -87,6 +87,7 @@
|
||||
### Minor Changes
|
||||
|
||||
- [#6408](https://github.com/mermaid-js/mermaid/pull/6408) [`ad65313`](https://github.com/mermaid-js/mermaid/commit/ad653138e16765d095613a6e5de86dc5e52ac8f0) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: restore curve type configuration functionality for flowcharts. This fixes the issue where curve type settings were not being applied when configured through any of the following methods:
|
||||
|
||||
- Config
|
||||
- Init directive (%%{ init: { 'flowchart': { 'curve': '...' } } }%%)
|
||||
- LinkStyle command (linkStyle default interpolate ...)
|
||||
@@ -105,12 +106,14 @@
|
||||
### Minor Changes
|
||||
|
||||
- [#6187](https://github.com/mermaid-js/mermaid/pull/6187) [`7809b5a`](https://github.com/mermaid-js/mermaid/commit/7809b5a93fae127f45727071f5ff14325222c518) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Flowchart new syntax for node metadata bugs
|
||||
|
||||
- Incorrect label mapping for nodes when using `&`
|
||||
- Syntax error when `}` with trailing spaces before new line
|
||||
|
||||
- [#6136](https://github.com/mermaid-js/mermaid/pull/6136) [`ec0d9c3`](https://github.com/mermaid-js/mermaid/commit/ec0d9c389aa6018043187654044c1e0b5aa4f600) Thanks [@knsv](https://github.com/knsv)! - Adding support for animation of flowchart edges
|
||||
|
||||
- [#6373](https://github.com/mermaid-js/mermaid/pull/6373) [`05bdf0e`](https://github.com/mermaid-js/mermaid/commit/05bdf0e20e2629fe77513218fbd4e28e65f75882) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - Upgrade Requirement and ER diagram to use the common renderer flow
|
||||
|
||||
- Added support for directions
|
||||
- Added support for hand drawn look
|
||||
|
||||
@@ -159,6 +162,7 @@
|
||||
- [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type
|
||||
|
||||
- [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes:
|
||||
|
||||
- Updates the class diagram to the new unified way of rendering.
|
||||
- Includes a new "classBox" shape to be used in diagrams
|
||||
- Other updates such as:
|
||||
|
@@ -82,7 +82,7 @@
|
||||
"katex": "^0.16.22",
|
||||
"khroma": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^15.0.7",
|
||||
"marked": "^16.0.0",
|
||||
"roughjs": "^4.6.6",
|
||||
"stylis": "^4.3.6",
|
||||
"ts-dedent": "^2.2.0",
|
||||
@@ -123,8 +123,8 @@
|
||||
"rimraf": "^6.0.1",
|
||||
"start-server-and-test": "^2.0.10",
|
||||
"type-fest": "^4.35.0",
|
||||
"typedoc": "^0.28.9",
|
||||
"typedoc-plugin-markdown": "^4.8.0",
|
||||
"typedoc": "^0.27.8",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typescript": "~5.7.3",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
|
@@ -169,56 +169,53 @@ This Markdown should be kept.
|
||||
describe('buildShapeDoc', () => {
|
||||
it('should build shapesTable based on the shapeDefs', () => {
|
||||
expect(buildShapeDoc()).toMatchInlineSnapshot(`
|
||||
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| --------------------------------- | ---------------------- | ----------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||
| Bang | Bang | \`bang\` | Bang | \`bang\` |
|
||||
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
||||
| Cloud | Cloud | \`cloud\` | cloud | \`cloud\` |
|
||||
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
||||
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
||||
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
||||
| Comment Right | Curly Brace | \`brace-r\` | Adds a comment | |
|
||||
| Comment with braces on both sides | Curly Braces | \`braces\` | Adds a comment | |
|
||||
| Data Input/Output | Lean Right | \`lean-r\` | Represents input or output | \`in-out\`, \`lean-right\` |
|
||||
| Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` |
|
||||
| Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` |
|
||||
| Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` |
|
||||
| Default Mindmap Node | defaultMindmapNode | \`default-mindmap\` | defaultMindmapNode | \`default-mindmap\`, \`defaultMindmapNode\` |
|
||||
| Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` |
|
||||
| Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` |
|
||||
| Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` |
|
||||
| Display | Curved Trapezoid | \`curv-trap\` | Represents a display | \`curved-trapezoid\`, \`display\` |
|
||||
| Divided Process | Divided Rectangle | \`div-rect\` | Divided process shape | \`div-proc\`, \`divided-process\`, \`divided-rectangle\` |
|
||||
| Document | Document | \`doc\` | Represents a document | \`doc\`, \`document\` |
|
||||
| Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` |
|
||||
| Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` |
|
||||
| Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` |
|
||||
| Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` |
|
||||
| Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` |
|
||||
| Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` |
|
||||
| Lined/Shaded Process | Lined Rectangle | \`lin-rect\` | Lined process shape | \`lin-proc\`, \`lined-process\`, \`lined-rectangle\`, \`shaded-process\` |
|
||||
| Loop Limit | Trapezoidal Pentagon | \`notch-pent\` | Loop limit step | \`loop-limit\`, \`notched-pentagon\` |
|
||||
| Manual File | Flipped Triangle | \`flip-tri\` | Manual file operation | \`flipped-triangle\`, \`manual-file\` |
|
||||
| Manual Input | Sloped Rectangle | \`sl-rect\` | Manual input step | \`manual-input\`, \`sloped-rectangle\` |
|
||||
| Manual Operation | Trapezoid Base Top | \`trap-t\` | Represents a manual task | \`inv-trapezoid\`, \`manual\`, \`trapezoid-top\` |
|
||||
| Multi-Document | Stacked Document | \`docs\` | Multiple documents | \`documents\`, \`st-doc\`, \`stacked-document\` |
|
||||
| Multi-Process | Stacked Rectangle | \`st-rect\` | Multiple processes | \`processes\`, \`procs\`, \`stacked-rectangle\` |
|
||||
| Odd | Odd | \`odd\` | Odd shape | |
|
||||
| Paper Tape | Flag | \`flag\` | Paper tape | \`paper-tape\` |
|
||||
| Prepare Conditional | Hexagon | \`hex\` | Preparation or condition step | \`hexagon\`, \`prepare\` |
|
||||
| Priority Action | Trapezoid Base Bottom | \`trap-b\` | Priority action | \`priority\`, \`trapezoid\`, \`trapezoid-bottom\` |
|
||||
| Process | Rectangle | \`rect\` | Standard process shape | \`proc\`, \`process\`, \`rectangle\` |
|
||||
| Start | Circle | \`circle\` | Starting point | \`circ\` |
|
||||
| Start | Small Circle | \`sm-circ\` | Small starting point | \`small-circle\`, \`start\` |
|
||||
| Stop | Double Circle | \`dbl-circ\` | Represents a stop point | \`double-circle\` |
|
||||
| Stop | Framed Circle | \`fr-circ\` | Stop point | \`framed-circle\`, \`stop\` |
|
||||
| Stored Data | Bow Tie Rectangle | \`bow-rect\` | Stored data | \`bow-tie-rectangle\`, \`stored-data\` |
|
||||
| Subprocess | Framed Rectangle | \`fr-rect\` | Subprocess | \`framed-rectangle\`, \`subproc\`, \`subprocess\`, \`subroutine\` |
|
||||
| Summary | Crossed Circle | \`cross-circ\` | Summary | \`crossed-circle\`, \`summary\` |
|
||||
| Tagged Document | Tagged Document | \`tag-doc\` | Tagged document | \`tag-doc\`, \`tagged-document\` |
|
||||
| Tagged Process | Tagged Rectangle | \`tag-rect\` | Tagged process | \`tag-proc\`, \`tagged-process\`, \`tagged-rectangle\` |
|
||||
| Terminal Point | Stadium | \`stadium\` | Terminal point | \`pill\`, \`terminal\` |
|
||||
| Text Block | Text Block | \`text\` | Text block | |
|
||||
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
|
||||
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
|
||||
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
|
||||
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
|
||||
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
|
||||
| Comment Right | Curly Brace | \`brace-r\` | Adds a comment | |
|
||||
| Comment with braces on both sides | Curly Braces | \`braces\` | Adds a comment | |
|
||||
| Data Input/Output | Lean Right | \`lean-r\` | Represents input or output | \`in-out\`, \`lean-right\` |
|
||||
| Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` |
|
||||
| Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` |
|
||||
| Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` |
|
||||
| Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` |
|
||||
| Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` |
|
||||
| Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` |
|
||||
| Display | Curved Trapezoid | \`curv-trap\` | Represents a display | \`curved-trapezoid\`, \`display\` |
|
||||
| Divided Process | Divided Rectangle | \`div-rect\` | Divided process shape | \`div-proc\`, \`divided-process\`, \`divided-rectangle\` |
|
||||
| Document | Document | \`doc\` | Represents a document | \`doc\`, \`document\` |
|
||||
| Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` |
|
||||
| Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` |
|
||||
| Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` |
|
||||
| Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` |
|
||||
| Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` |
|
||||
| Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` |
|
||||
| Lined/Shaded Process | Lined Rectangle | \`lin-rect\` | Lined process shape | \`lin-proc\`, \`lined-process\`, \`lined-rectangle\`, \`shaded-process\` |
|
||||
| Loop Limit | Trapezoidal Pentagon | \`notch-pent\` | Loop limit step | \`loop-limit\`, \`notched-pentagon\` |
|
||||
| Manual File | Flipped Triangle | \`flip-tri\` | Manual file operation | \`flipped-triangle\`, \`manual-file\` |
|
||||
| Manual Input | Sloped Rectangle | \`sl-rect\` | Manual input step | \`manual-input\`, \`sloped-rectangle\` |
|
||||
| Manual Operation | Trapezoid Base Top | \`trap-t\` | Represents a manual task | \`inv-trapezoid\`, \`manual\`, \`trapezoid-top\` |
|
||||
| Multi-Document | Stacked Document | \`docs\` | Multiple documents | \`documents\`, \`st-doc\`, \`stacked-document\` |
|
||||
| Multi-Process | Stacked Rectangle | \`st-rect\` | Multiple processes | \`processes\`, \`procs\`, \`stacked-rectangle\` |
|
||||
| Odd | Odd | \`odd\` | Odd shape | |
|
||||
| Paper Tape | Flag | \`flag\` | Paper tape | \`paper-tape\` |
|
||||
| Prepare Conditional | Hexagon | \`hex\` | Preparation or condition step | \`hexagon\`, \`prepare\` |
|
||||
| Priority Action | Trapezoid Base Bottom | \`trap-b\` | Priority action | \`priority\`, \`trapezoid\`, \`trapezoid-bottom\` |
|
||||
| Process | Rectangle | \`rect\` | Standard process shape | \`proc\`, \`process\`, \`rectangle\` |
|
||||
| Start | Circle | \`circle\` | Starting point | \`circ\` |
|
||||
| Start | Small Circle | \`sm-circ\` | Small starting point | \`small-circle\`, \`start\` |
|
||||
| Stop | Double Circle | \`dbl-circ\` | Represents a stop point | \`double-circle\` |
|
||||
| Stop | Framed Circle | \`fr-circ\` | Stop point | \`framed-circle\`, \`stop\` |
|
||||
| Stored Data | Bow Tie Rectangle | \`bow-rect\` | Stored data | \`bow-tie-rectangle\`, \`stored-data\` |
|
||||
| Subprocess | Framed Rectangle | \`fr-rect\` | Subprocess | \`framed-rectangle\`, \`subproc\`, \`subprocess\`, \`subroutine\` |
|
||||
| Summary | Crossed Circle | \`cross-circ\` | Summary | \`crossed-circle\`, \`summary\` |
|
||||
| Tagged Document | Tagged Document | \`tag-doc\` | Tagged document | \`tag-doc\`, \`tagged-document\` |
|
||||
| Tagged Process | Tagged Rectangle | \`tag-rect\` | Tagged process | \`tag-proc\`, \`tagged-process\`, \`tagged-rectangle\` |
|
||||
| Terminal Point | Stadium | \`stadium\` | Terminal point | \`pill\`, \`terminal\` |
|
||||
| Text Block | Text Block | \`text\` | Text block | |
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
@@ -1065,10 +1065,6 @@ export interface ArchitectureDiagramConfig extends BaseDiagramConfig {
|
||||
export interface MindmapDiagramConfig extends BaseDiagramConfig {
|
||||
padding?: number;
|
||||
maxNodeWidth?: number;
|
||||
/**
|
||||
* Layout algorithm to use for positioning mindmap nodes
|
||||
*/
|
||||
layoutAlgorithm?: string;
|
||||
}
|
||||
/**
|
||||
* The object containing configurations specific for kanban diagrams
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// tests to check that comments are removed
|
||||
|
||||
import { cleanupComments } from './comments.js';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
@@ -8,12 +10,12 @@ describe('comments', () => {
|
||||
%% This is a comment
|
||||
%% This is another comment
|
||||
graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
%% This is a comment
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -27,9 +29,9 @@ graph TD
|
||||
%%{ init: {'theme': 'space before init'}}%%
|
||||
%%{init: {'theme': 'space after ending'}}%%
|
||||
graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
|
||||
B-->C
|
||||
B-->C
|
||||
%% This is a comment
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
@@ -37,9 +39,9 @@ graph TD
|
||||
%%{ init: {'theme': 'space before init'}}%%
|
||||
%%{init: {'theme': 'space after ending'}}%%
|
||||
graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
|
||||
B-->C
|
||||
B-->C
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -48,14 +50,14 @@ graph TD
|
||||
const text = `
|
||||
%% This is a comment
|
||||
graph TD
|
||||
A-->B
|
||||
%% This is a comment
|
||||
C-->D
|
||||
A-->B
|
||||
%% This is a comment
|
||||
C-->D
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
C-->D
|
||||
A-->B
|
||||
C-->D
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -68,11 +70,11 @@ graph TD
|
||||
|
||||
%% This is a comment
|
||||
graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
`;
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -80,12 +82,12 @@ graph TD
|
||||
it('should remove comments at end of text with no newline', () => {
|
||||
const text = `
|
||||
graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
%% This is a comment`;
|
||||
|
||||
expect(cleanupComments(text)).toMatchInlineSnapshot(`
|
||||
"graph TD
|
||||
A-->B
|
||||
A-->B
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
@@ -4,6 +4,5 @@
|
||||
* @returns cleaned text
|
||||
*/
|
||||
export const cleanupComments = (text: string): string => {
|
||||
const cleaned = text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
|
||||
return cleaned.trimStart();
|
||||
return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart();
|
||||
};
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { LayoutOptions, Position } from 'cytoscape';
|
||||
import type { Position } from 'cytoscape';
|
||||
import cytoscape from 'cytoscape';
|
||||
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
|
||||
import fcose from 'cytoscape-fcose';
|
||||
import { select } from 'd3';
|
||||
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
|
||||
@@ -40,7 +41,7 @@ registerIconPacks([
|
||||
icons: architectureIcons,
|
||||
},
|
||||
]);
|
||||
cytoscape.use(fcose as any);
|
||||
cytoscape.use(fcose);
|
||||
|
||||
function addServices(services: ArchitectureService[], cy: cytoscape.Core, db: ArchitectureDB) {
|
||||
services.forEach((service) => {
|
||||
@@ -428,7 +429,7 @@ function layoutArchitecture(
|
||||
},
|
||||
alignmentConstraint,
|
||||
relativePlacementConstraint,
|
||||
} as LayoutOptions);
|
||||
} as FcoseLayoutOptions);
|
||||
|
||||
// Once the diagram has been generated and the service's position cords are set, adjust the XY edges to have a 90deg bend
|
||||
layout.one('layoutstop', () => {
|
||||
|
@@ -238,15 +238,13 @@ export function edgeTypeStr2Type(typeStr: string): string {
|
||||
}
|
||||
|
||||
export function edgeStrToEdgeData(typeStr: string): string {
|
||||
switch (typeStr.replace(/^[\s-]+|[\s-]+$/g, '')) {
|
||||
case 'x':
|
||||
switch (typeStr.trim()) {
|
||||
case '--x':
|
||||
return 'arrow_cross';
|
||||
case 'o':
|
||||
case '--o':
|
||||
return 'arrow_circle';
|
||||
case '>':
|
||||
return 'arrow_point';
|
||||
default:
|
||||
return '';
|
||||
return 'arrow_point';
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-a
|
||||
const id = 'block';
|
||||
|
||||
const detector: DiagramDetector = (txt) => {
|
||||
return /^\s*block(-beta)?/.test(txt);
|
||||
return /^\s*block-beta/.test(txt);
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
|
@@ -36,10 +36,10 @@ CRLF \u000D\u000A
|
||||
|
||||
%%
|
||||
|
||||
"block-beta" { yy.getLogger().debug('Found block-beta'); return 'BLOCK_DIAGRAM_KEY'; }
|
||||
"block:" { yy.getLogger().debug('Found id-block'); return 'id-block'; }
|
||||
"block" { yy.getLogger().debug('Found block'); return 'BLOCK_DIAGRAM_KEY'; }
|
||||
|
||||
"block-beta" { return 'BLOCK_DIAGRAM_KEY'; }
|
||||
"block"\s+ { yy.getLogger().debug('Found space-block'); return 'block';}
|
||||
"block"\n+ { yy.getLogger().debug('Found nl-block'); return 'block';}
|
||||
"block:" { yy.getLogger().debug('Found space-block'); return 'id-block';}
|
||||
// \s*\%\%.* { yy.getLogger().debug('Found comment',yytext); }
|
||||
[\s]+ { yy.getLogger().debug('.', yytext); /* skip all whitespace */ }
|
||||
[\n]+ {yy.getLogger().debug('_', yytext); /* skip all whitespace */ }
|
||||
@@ -240,7 +240,7 @@ columnsStatement
|
||||
|
||||
blockStatement
|
||||
: id-block nodeStatement document end { yy.getLogger().debug('Rule: id-block statement : ', $2, $3); const id2 = yy.generateId(); $$ = { ...$2, type:'composite', children: $3 }; }
|
||||
| BLOCK_DIAGRAM_KEY document end { yy.getLogger().debug('Rule: blockStatement : ', $1, $2, $3); const id = yy.generateId(); $$ = { id, type:'composite', label:'', children: $2 }; }
|
||||
| block document end { yy.getLogger().debug('Rule: blockStatement : ', $1, $2, $3); const id = yy.generateId(); $$ = { id, type:'composite', label:'', children: $2 }; }
|
||||
;
|
||||
|
||||
node
|
||||
|
@@ -23,7 +23,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks[0].label).toBe('id');
|
||||
});
|
||||
it('a node with a square shape and a label', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id["A label"]
|
||||
`;
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks[0].type).toBe('square');
|
||||
});
|
||||
it('a diagram with multiple nodes', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id1
|
||||
id2
|
||||
`;
|
||||
@@ -51,7 +51,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks[1].type).toBe('na');
|
||||
});
|
||||
it('a diagram with multiple nodes', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id1
|
||||
id2
|
||||
id3
|
||||
@@ -72,7 +72,7 @@ describe('Block diagram', function () {
|
||||
});
|
||||
|
||||
it('a node with a square shape and a label', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id["A label"]
|
||||
id2`;
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks[1].type).toBe('na');
|
||||
});
|
||||
it('a diagram with multiple nodes with edges abc123', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id1["first"] --> id2["second"]
|
||||
`;
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('Block diagram', function () {
|
||||
expect(edges[0].arrowTypeEnd).toBe('arrow_point');
|
||||
});
|
||||
it('a diagram with multiple nodes with edges abc123', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
id1["first"] -- "a label" --> id2["second"]
|
||||
`;
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('Block diagram', function () {
|
||||
expect(edges[0].label).toBe('a label');
|
||||
});
|
||||
it('a diagram with column statements', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 2
|
||||
block1["Block 1"]
|
||||
`;
|
||||
@@ -127,7 +127,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks.length).toBe(1);
|
||||
});
|
||||
it('a diagram without column statements', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
block1["Block 1"]
|
||||
`;
|
||||
|
||||
@@ -137,7 +137,7 @@ describe('Block diagram', function () {
|
||||
expect(blocks.length).toBe(1);
|
||||
});
|
||||
it('a diagram with auto column statements', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns auto
|
||||
block1["Block 1"]
|
||||
`;
|
||||
@@ -149,7 +149,7 @@ describe('Block diagram', function () {
|
||||
});
|
||||
|
||||
it('blocks next to each other', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 2
|
||||
block1["Block 1"]
|
||||
block2["Block 2"]
|
||||
@@ -163,7 +163,7 @@ describe('Block diagram', function () {
|
||||
});
|
||||
|
||||
it('blocks on top of each other', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 1
|
||||
block1["Block 1"]
|
||||
block2["Block 2"]
|
||||
@@ -177,7 +177,7 @@ describe('Block diagram', function () {
|
||||
});
|
||||
|
||||
it('compound blocks 2', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
block
|
||||
aBlock["ABlock"]
|
||||
bBlock["BBlock"]
|
||||
@@ -205,7 +205,7 @@ describe('Block diagram', function () {
|
||||
expect(bBlock.type).toBe('square');
|
||||
});
|
||||
it('compound blocks of compound blocks', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
block
|
||||
aBlock["ABlock"]
|
||||
block
|
||||
@@ -240,7 +240,7 @@ describe('Block diagram', function () {
|
||||
expect(bBlock.type).toBe('square');
|
||||
});
|
||||
it('compound blocks with title', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
block:compoundBlock["Compound block"]
|
||||
columns 1
|
||||
block2["Block 2"]
|
||||
@@ -265,7 +265,7 @@ describe('Block diagram', function () {
|
||||
expect(block2.type).toBe('square');
|
||||
});
|
||||
it('blocks mixed with compound blocks', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 1
|
||||
block1["Block 1"]
|
||||
|
||||
@@ -292,7 +292,7 @@ describe('Block diagram', function () {
|
||||
});
|
||||
|
||||
it('Arrow blocks', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 3
|
||||
block1["Block 1"]
|
||||
blockArrow<[" "]>(right)
|
||||
@@ -316,7 +316,7 @@ describe('Block diagram', function () {
|
||||
expect(blockArrow.directions).toContain('right');
|
||||
});
|
||||
it('Arrow blocks with multiple points', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 1
|
||||
A
|
||||
blockArrow<[" "]>(up, down)
|
||||
@@ -339,7 +339,7 @@ describe('Block diagram', function () {
|
||||
expect(blockArrow.directions).not.toContain('right');
|
||||
});
|
||||
it('blocks with different widths', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 3
|
||||
one["One Slot"]
|
||||
two["Two slots"]:2
|
||||
@@ -354,7 +354,7 @@ describe('Block diagram', function () {
|
||||
expect(two.widthInColumns).toBe(2);
|
||||
});
|
||||
it('empty blocks', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 3
|
||||
space
|
||||
middle["In the middle"]
|
||||
@@ -373,7 +373,7 @@ describe('Block diagram', function () {
|
||||
expect(middle.label).toBe('In the middle');
|
||||
});
|
||||
it('classDef statements applied to a block', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
classDef black color:#ffffff, fill:#000000;
|
||||
|
||||
mc["Memcache"]
|
||||
@@ -391,7 +391,7 @@ describe('Block diagram', function () {
|
||||
expect(black.styles[0]).toEqual('color:#ffffff');
|
||||
});
|
||||
it('style statements applied to a block', () => {
|
||||
const str = `block
|
||||
const str = `block-beta
|
||||
columns 1
|
||||
B["A wide one in the middle"]
|
||||
style B fill:#f9F,stroke:#333,stroke-width:4px
|
||||
@@ -426,9 +426,9 @@ columns 1
|
||||
|
||||
describe('prototype properties', function () {
|
||||
function validateProperty(prop: string) {
|
||||
expect(() => block.parse(`block\n${prop}`)).not.toThrow();
|
||||
expect(() => block.parse(`block-beta\n${prop}`)).not.toThrow();
|
||||
expect(() =>
|
||||
block.parse(`block\nA; classDef ${prop} color:#ffffff,fill:#000000; class A ${prop}`)
|
||||
block.parse(`block-beta\nA; classDef ${prop} color:#ffffff,fill:#000000; class A ${prop}`)
|
||||
).not.toThrow();
|
||||
}
|
||||
|
||||
|
@@ -33,13 +33,13 @@ function setupDompurifyHooks() {
|
||||
const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
|
||||
|
||||
DOMPurify.addHook('beforeSanitizeAttributes', (node) => {
|
||||
if (node.tagName === 'A' && node.hasAttribute('target')) {
|
||||
if (node instanceof Element && node.tagName === 'A' && node.hasAttribute('target')) {
|
||||
node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target') ?? '');
|
||||
}
|
||||
});
|
||||
|
||||
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
|
||||
if (node instanceof Element && node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
|
||||
node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) ?? '');
|
||||
node.removeAttribute(TEMPORARY_ATTRIBUTE);
|
||||
if (node.getAttribute('target') === '_blank') {
|
||||
|
@@ -167,10 +167,7 @@ export const getTasks = function () {
|
||||
};
|
||||
|
||||
export const isInvalidDate = function (date, dateFormat, excludes, includes) {
|
||||
const formattedDate = date.format(dateFormat.trim());
|
||||
const dateOnly = date.format('YYYY-MM-DD');
|
||||
|
||||
if (includes.includes(formattedDate) || includes.includes(dateOnly)) {
|
||||
if (includes.includes(date.format(dateFormat.trim()))) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
@@ -183,7 +180,7 @@ export const isInvalidDate = function (date, dateFormat, excludes, includes) {
|
||||
if (excludes.includes(date.format('dddd').toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
return excludes.includes(formattedDate) || excludes.includes(dateOnly);
|
||||
return excludes.includes(date.format(dateFormat.trim()));
|
||||
};
|
||||
|
||||
export const setWeekday = function (txt) {
|
||||
|
@@ -581,11 +581,17 @@ export const draw = function (text, id, version, diagObj) {
|
||||
|
||||
rectangles
|
||||
.append('rect')
|
||||
.attr('id', (d) => 'exclude-' + d.start.format('YYYY-MM-DD'))
|
||||
.attr('x', (d) => timeScale(d.start.startOf('day')) + theSidePad)
|
||||
.attr('id', function (d) {
|
||||
return 'exclude-' + d.start.format('YYYY-MM-DD');
|
||||
})
|
||||
.attr('x', function (d) {
|
||||
return timeScale(d.start) + theSidePad;
|
||||
})
|
||||
.attr('y', conf.gridLineStartPadding)
|
||||
.attr('width', (d) => timeScale(d.end.endOf('day')) - timeScale(d.start.startOf('day')))
|
||||
|
||||
.attr('width', function (d) {
|
||||
const renderEnd = d.end.add(1, 'day');
|
||||
return timeScale(renderEnd) - timeScale(d.start);
|
||||
})
|
||||
.attr('height', h - theTopPad - conf.gridLineStartPadding)
|
||||
.attr('transform-origin', function (d, i) {
|
||||
return (
|
||||
@@ -609,20 +615,9 @@ export const draw = function (text, id, version, diagObj) {
|
||||
* @param h
|
||||
*/
|
||||
function makeGrid(theSidePad, theTopPad, w, h) {
|
||||
const dateFormat = diagObj.db.getDateFormat();
|
||||
const userAxisFormat = diagObj.db.getAxisFormat();
|
||||
let axisFormat;
|
||||
if (userAxisFormat) {
|
||||
axisFormat = userAxisFormat;
|
||||
} else if (dateFormat === 'D') {
|
||||
axisFormat = '%d';
|
||||
} else {
|
||||
axisFormat = conf.axisFormat ?? '%Y-%m-%d';
|
||||
}
|
||||
|
||||
let bottomXAxis = axisBottom(timeScale)
|
||||
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
|
||||
.tickFormat(timeFormat(axisFormat));
|
||||
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
|
||||
|
||||
const reTickInterval = /^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/;
|
||||
const resultTickInterval = reTickInterval.exec(
|
||||
@@ -674,7 +669,7 @@ export const draw = function (text, id, version, diagObj) {
|
||||
if (diagObj.db.topAxisEnabled() || conf.topAxis) {
|
||||
let topXAxis = axisTop(timeScale)
|
||||
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
|
||||
.tickFormat(timeFormat(axisFormat));
|
||||
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
|
||||
|
||||
if (resultTickInterval !== null) {
|
||||
const every = resultTickInterval[1];
|
||||
|
@@ -1,297 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { MindmapDB } from './mindmapDb.js';
|
||||
import type { MindmapLayoutNode, MindmapLayoutEdge } from './mindmapDb.js';
|
||||
import type { Edge } from '../../rendering-util/types.js';
|
||||
|
||||
// Mock the getConfig function
|
||||
vi.mock('../../diagram-api/diagramAPI.js', () => ({
|
||||
getConfig: vi.fn(() => ({
|
||||
mindmap: {
|
||||
layoutAlgorithm: 'cose-bilkent',
|
||||
padding: 10,
|
||||
maxNodeWidth: 200,
|
||||
useMaxWidth: true,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('MindmapDb getData function', () => {
|
||||
let db: MindmapDB;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new MindmapDB();
|
||||
// Clear the database before each test
|
||||
db.clear();
|
||||
});
|
||||
|
||||
describe('getData', () => {
|
||||
it('should return empty data when no mindmap is set', () => {
|
||||
const result = db.getData();
|
||||
|
||||
expect(result.nodes).toEqual([]);
|
||||
expect(result.edges).toEqual([]);
|
||||
expect(result.config).toBeDefined();
|
||||
expect(result.rootNode).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return structured data for simple mindmap', () => {
|
||||
// Create a simple mindmap structure
|
||||
db.addNode(0, 'root', 'Root Node', 0);
|
||||
db.addNode(1, 'child1', 'Child 1', 0);
|
||||
db.addNode(1, 'child2', 'Child 2', 0);
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
expect(result.nodes).toHaveLength(3);
|
||||
expect(result.edges).toHaveLength(2);
|
||||
expect(result.config).toBeDefined();
|
||||
expect(result.rootNode).toBeDefined();
|
||||
|
||||
// Check root node
|
||||
const rootNode = (result.nodes as MindmapLayoutNode[]).find((n) => n.id === '0');
|
||||
expect(rootNode).toBeDefined();
|
||||
expect(rootNode?.label).toBe('Root Node');
|
||||
expect(rootNode?.level).toBe(0);
|
||||
|
||||
// Check child nodes
|
||||
const child1 = (result.nodes as MindmapLayoutNode[]).find((n) => n.id === '1');
|
||||
expect(child1).toBeDefined();
|
||||
expect(child1?.label).toBe('Child 1');
|
||||
expect(child1?.level).toBe(1);
|
||||
|
||||
// Check edges
|
||||
expect(result.edges).toContainEqual(
|
||||
expect.objectContaining({
|
||||
start: '0',
|
||||
end: '1',
|
||||
depth: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return structured data for hierarchical mindmap', () => {
|
||||
// Create a hierarchical mindmap structure
|
||||
db.addNode(0, 'root', 'Root Node', 0);
|
||||
db.addNode(1, 'child1', 'Child 1', 0);
|
||||
db.addNode(2, 'grandchild1', 'Grandchild 1', 0);
|
||||
db.addNode(2, 'grandchild2', 'Grandchild 2', 0);
|
||||
db.addNode(1, 'child2', 'Child 2', 0);
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
expect(result.nodes).toHaveLength(5);
|
||||
expect(result.edges).toHaveLength(4);
|
||||
|
||||
// Check that all levels are represented
|
||||
const levels = result.nodes.map((n) => (n as MindmapLayoutNode).level);
|
||||
expect(levels).toContain(0); // root
|
||||
expect(levels).toContain(1); // children
|
||||
expect(levels).toContain(2); // grandchildren
|
||||
|
||||
// Check edge relationships
|
||||
const edgeRelations = result.edges.map(
|
||||
(e) => `${(e as MindmapLayoutEdge).start}->${(e as MindmapLayoutEdge).end}`
|
||||
);
|
||||
expect(edgeRelations).toContain('0->1'); // root to child1
|
||||
expect(edgeRelations).toContain('1->2'); // child1 to grandchild1
|
||||
expect(edgeRelations).toContain('1->3'); // child1 to grandchild2
|
||||
expect(edgeRelations).toContain('0->4'); // root to child2
|
||||
});
|
||||
|
||||
it('should preserve node properties in processed data', () => {
|
||||
// Add a node with specific properties
|
||||
db.addNode(0, 'root', 'Root Node', 2); // type 2 = rectangle
|
||||
|
||||
// Set additional properties
|
||||
const mindmap = db.getMindmap();
|
||||
if (mindmap) {
|
||||
mindmap.width = 150;
|
||||
mindmap.height = 75;
|
||||
mindmap.padding = 15;
|
||||
mindmap.section = 1;
|
||||
mindmap.class = 'custom-class';
|
||||
mindmap.icon = 'star';
|
||||
}
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
expect(result.nodes).toHaveLength(1);
|
||||
const node = result.nodes[0] as MindmapLayoutNode;
|
||||
|
||||
expect(node.type).toBe(2);
|
||||
expect(node.width).toBe(150);
|
||||
expect(node.height).toBe(75);
|
||||
expect(node.padding).toBe(15);
|
||||
expect(node.section).toBeUndefined(); // Root node has undefined section
|
||||
expect(node.cssClasses).toBe('mindmap-node section-root section--1 custom-class');
|
||||
expect(node.icon).toBe('star');
|
||||
});
|
||||
|
||||
it('should generate unique edge IDs', () => {
|
||||
db.addNode(0, 'root', 'Root Node', 0);
|
||||
db.addNode(1, 'child1', 'Child 1', 0);
|
||||
db.addNode(1, 'child2', 'Child 2', 0);
|
||||
db.addNode(1, 'child3', 'Child 3', 0);
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
const edgeIds = result.edges.map((e: Edge) => e.id);
|
||||
const uniqueIds = new Set(edgeIds);
|
||||
|
||||
expect(edgeIds).toHaveLength(3);
|
||||
expect(uniqueIds.size).toBe(3); // All IDs should be unique
|
||||
});
|
||||
|
||||
it('should handle nodes with missing optional properties', () => {
|
||||
db.addNode(0, 'root', 'Root Node', 0);
|
||||
|
||||
const result = db.getData();
|
||||
const node = result.nodes[0] as MindmapLayoutNode;
|
||||
|
||||
// Should handle undefined/missing properties gracefully
|
||||
expect(node.section).toBeUndefined(); // Root node has undefined section
|
||||
expect(node.cssClasses).toBe('mindmap-node section-root section--1'); // Root node gets special classes
|
||||
expect(node.icon).toBeUndefined();
|
||||
expect(node.x).toBeUndefined();
|
||||
expect(node.y).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should assign correct section classes based on sibling position', () => {
|
||||
// Create the example mindmap structure:
|
||||
// A
|
||||
// a0
|
||||
// aa0
|
||||
// a1
|
||||
// aaa
|
||||
// a2
|
||||
db.addNode(0, 'A', 'A', 0); // Root
|
||||
db.addNode(1, 'a0', 'a0', 0); // First child of root
|
||||
db.addNode(2, 'aa0', 'aa0', 0); // Child of a0
|
||||
db.addNode(1, 'a1', 'a1', 0); // Second child of root
|
||||
db.addNode(2, 'aaa', 'aaa', 0); // Child of a1
|
||||
db.addNode(1, 'a2', 'a2', 0); // Third child of root
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
// Find nodes by their labels
|
||||
const nodeA = result.nodes.find((n) => n.label === 'A') as MindmapLayoutNode;
|
||||
const nodeA0 = result.nodes.find((n) => n.label === 'a0') as MindmapLayoutNode;
|
||||
const nodeAa0 = result.nodes.find((n) => n.label === 'aa0') as MindmapLayoutNode;
|
||||
const nodeA1 = result.nodes.find((n) => n.label === 'a1') as MindmapLayoutNode;
|
||||
const nodeAaa = result.nodes.find((n) => n.label === 'aaa') as MindmapLayoutNode;
|
||||
const nodeA2 = result.nodes.find((n) => n.label === 'a2') as MindmapLayoutNode;
|
||||
|
||||
// Check section assignments
|
||||
expect(nodeA.section).toBeUndefined(); // Root has undefined section
|
||||
expect(nodeA0.section).toBe(0); // First child of root
|
||||
expect(nodeAa0.section).toBe(0); // Inherits from parent a0
|
||||
expect(nodeA1.section).toBe(1); // Second child of root
|
||||
expect(nodeAaa.section).toBe(1); // Inherits from parent a1
|
||||
expect(nodeA2.section).toBe(2); // Third child of root
|
||||
|
||||
// Check CSS classes
|
||||
expect(nodeA.cssClasses).toBe('mindmap-node section-root section--1');
|
||||
expect(nodeA0.cssClasses).toBe('mindmap-node section-0');
|
||||
expect(nodeAa0.cssClasses).toBe('mindmap-node section-0');
|
||||
expect(nodeA1.cssClasses).toBe('mindmap-node section-1');
|
||||
expect(nodeAaa.cssClasses).toBe('mindmap-node section-1');
|
||||
expect(nodeA2.cssClasses).toBe('mindmap-node section-2');
|
||||
});
|
||||
|
||||
it('should preserve custom classes while adding section classes', () => {
|
||||
db.addNode(0, 'root', 'Root Node', 0);
|
||||
db.addNode(1, 'child', 'Child Node', 0);
|
||||
|
||||
// Add custom classes to nodes
|
||||
const mindmap = db.getMindmap();
|
||||
if (mindmap) {
|
||||
mindmap.class = 'custom-root-class';
|
||||
if (mindmap.children?.[0]) {
|
||||
mindmap.children[0].class = 'custom-child-class';
|
||||
}
|
||||
}
|
||||
|
||||
const result = db.getData();
|
||||
const rootNode = result.nodes.find((n) => n.label === 'Root Node') as MindmapLayoutNode;
|
||||
const childNode = result.nodes.find((n) => n.label === 'Child Node') as MindmapLayoutNode;
|
||||
|
||||
// Should include both section classes and custom classes
|
||||
expect(rootNode.cssClasses).toBe('mindmap-node section-root section--1 custom-root-class');
|
||||
expect(childNode.cssClasses).toBe('mindmap-node section-0 custom-child-class');
|
||||
});
|
||||
|
||||
it('should not create any fake root nodes', () => {
|
||||
// Create a simple mindmap
|
||||
db.addNode(0, 'A', 'A', 0);
|
||||
db.addNode(1, 'a0', 'a0', 0);
|
||||
db.addNode(1, 'a1', 'a1', 0);
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
// Check that we only have the expected nodes
|
||||
expect(result.nodes).toHaveLength(3);
|
||||
expect(result.nodes.map((n) => n.label)).toEqual(['A', 'a0', 'a1']);
|
||||
|
||||
// Check that there's no node with label "mindmap" or any other fake root
|
||||
const mindmapNode = result.nodes.find((n) => n.label === 'mindmap');
|
||||
expect(mindmapNode).toBeUndefined();
|
||||
|
||||
// Verify the root node has the correct classes
|
||||
const rootNode = result.nodes.find((n) => n.label === 'A') as MindmapLayoutNode;
|
||||
expect(rootNode.cssClasses).toBe('mindmap-node section-root section--1');
|
||||
expect(rootNode.level).toBe(0);
|
||||
});
|
||||
|
||||
it('should assign correct section classes to edges', () => {
|
||||
// Create the example mindmap structure:
|
||||
// A
|
||||
// a0
|
||||
// aa0
|
||||
// a1
|
||||
// aaa
|
||||
// a2
|
||||
db.addNode(0, 'A', 'A', 0); // Root
|
||||
db.addNode(1, 'a0', 'a0', 0); // First child of root
|
||||
db.addNode(2, 'aa0', 'aa0', 0); // Child of a0
|
||||
db.addNode(1, 'a1', 'a1', 0); // Second child of root
|
||||
db.addNode(2, 'aaa', 'aaa', 0); // Child of a1
|
||||
db.addNode(1, 'a2', 'a2', 0); // Third child of root
|
||||
|
||||
const result = db.getData();
|
||||
|
||||
// Should have 5 edges: A->a0, a0->aa0, A->a1, a1->aaa, A->a2
|
||||
expect(result.edges).toHaveLength(5);
|
||||
|
||||
// Find edges by their start and end nodes
|
||||
const edgeA_a0 = result.edges.find(
|
||||
(e) => e.start === '0' && e.end === '1'
|
||||
) as MindmapLayoutEdge;
|
||||
const edgeA0_aa0 = result.edges.find(
|
||||
(e) => e.start === '1' && e.end === '2'
|
||||
) as MindmapLayoutEdge;
|
||||
const edgeA_a1 = result.edges.find(
|
||||
(e) => e.start === '0' && e.end === '3'
|
||||
) as MindmapLayoutEdge;
|
||||
const edgeA1_aaa = result.edges.find(
|
||||
(e) => e.start === '3' && e.end === '4'
|
||||
) as MindmapLayoutEdge;
|
||||
const edgeA_a2 = result.edges.find(
|
||||
(e) => e.start === '0' && e.end === '5'
|
||||
) as MindmapLayoutEdge;
|
||||
|
||||
// Check edge classes
|
||||
expect(edgeA_a0.classes).toBe('edge section-edge-0 edge-depth-1'); // A->a0: section-0, depth-1
|
||||
expect(edgeA0_aa0.classes).toBe('edge section-edge-0 edge-depth-2'); // a0->aa0: section-0, depth-2
|
||||
expect(edgeA_a1.classes).toBe('edge section-edge-1 edge-depth-1'); // A->a1: section-1, depth-1
|
||||
expect(edgeA1_aaa.classes).toBe('edge section-edge-1 edge-depth-2'); // a1->aaa: section-1, depth-2
|
||||
expect(edgeA_a2.classes).toBe('edge section-edge-2 edge-depth-1'); // A->a2: section-2, depth-1
|
||||
|
||||
// Check section assignments match the child nodes
|
||||
expect(edgeA_a0.section).toBe(0);
|
||||
expect(edgeA0_aa0.section).toBe(0);
|
||||
expect(edgeA_a1.section).toBe(1);
|
||||
expect(edgeA1_aaa.section).toBe(1);
|
||||
expect(edgeA_a2.section).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
@@ -4,21 +4,6 @@ import { sanitizeText } from '../../diagrams/common/common.js';
|
||||
import { log } from '../../logger.js';
|
||||
import type { MindmapNode } from './mindmapTypes.js';
|
||||
import defaultConfig from '../../defaultConfig.js';
|
||||
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
|
||||
|
||||
// Extend Node type for mindmap-specific properties
|
||||
export type MindmapLayoutNode = Node & {
|
||||
level: number;
|
||||
nodeId: string;
|
||||
type: number;
|
||||
section?: number;
|
||||
};
|
||||
|
||||
// Extend Edge type for mindmap-specific properties
|
||||
export type MindmapLayoutEdge = Edge & {
|
||||
depth: number;
|
||||
section?: number;
|
||||
};
|
||||
|
||||
const nodeType = {
|
||||
DEFAULT: 0,
|
||||
@@ -42,6 +27,7 @@ export class MindmapDB {
|
||||
this.nodeType = nodeType;
|
||||
this.clear();
|
||||
this.getType = this.getType.bind(this);
|
||||
this.getMindmap = this.getMindmap.bind(this);
|
||||
this.getElementById = this.getElementById.bind(this);
|
||||
this.getParent = this.getParent.bind(this);
|
||||
this.getMindmap = this.getMindmap.bind(this);
|
||||
@@ -170,215 +156,6 @@ export class MindmapDB {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign section numbers to nodes based on their position relative to root
|
||||
* @param node - The mindmap node to process
|
||||
* @param sectionNumber - The section number to assign (undefined for root)
|
||||
*/
|
||||
public assignSections(node: MindmapNode, sectionNumber?: number): void {
|
||||
// For root node, section should be undefined (not -1)
|
||||
if (node.level === 0) {
|
||||
node.section = undefined;
|
||||
} else {
|
||||
// For non-root nodes, assign the section number
|
||||
node.section = sectionNumber;
|
||||
}
|
||||
// For root node's children, assign section numbers based on their index
|
||||
// For other nodes, inherit parent's section number
|
||||
if (node.children) {
|
||||
for (const [index, child] of node.children.entries()) {
|
||||
const childSectionNumber = node.level === 0 ? index : sectionNumber;
|
||||
this.assignSections(child, childSectionNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert mindmap tree structure to flat array of nodes
|
||||
* @param node - The mindmap node to process
|
||||
* @param processedNodes - Array to collect processed nodes
|
||||
*/
|
||||
public flattenNodes(node: MindmapNode, processedNodes: MindmapLayoutNode[]): void {
|
||||
// Build CSS classes for the node
|
||||
const cssClasses = ['mindmap-node'];
|
||||
|
||||
// Add section-specific classes
|
||||
if (node.level === 0) {
|
||||
// Root node gets special classes
|
||||
cssClasses.push('section-root', 'section--1');
|
||||
} else if (node.section !== undefined) {
|
||||
// Child nodes get section class based on their section number
|
||||
cssClasses.push(`section-${node.section}`);
|
||||
}
|
||||
|
||||
// Add any custom classes from the node
|
||||
if (node.class) {
|
||||
cssClasses.push(node.class);
|
||||
}
|
||||
|
||||
const classes = cssClasses.join(' ');
|
||||
|
||||
// Map mindmap node type to valid shape name
|
||||
const getShapeFromType = (type: number) => {
|
||||
switch (type) {
|
||||
case nodeType.CIRCLE:
|
||||
return 'circle';
|
||||
case nodeType.RECT:
|
||||
return 'rect';
|
||||
case nodeType.ROUNDED_RECT:
|
||||
return 'rounded';
|
||||
case nodeType.CLOUD:
|
||||
return 'cloud';
|
||||
case nodeType.BANG:
|
||||
return 'bang';
|
||||
case nodeType.HEXAGON:
|
||||
return 'hexagon';
|
||||
case nodeType.DEFAULT:
|
||||
return 'defaultMindmapNode';
|
||||
case nodeType.NO_BORDER:
|
||||
default:
|
||||
return 'rect';
|
||||
}
|
||||
};
|
||||
|
||||
const processedNode: MindmapLayoutNode = {
|
||||
id: node.id.toString(),
|
||||
domId: 'node_' + node.id.toString(),
|
||||
label: node.descr,
|
||||
isGroup: false,
|
||||
shape: getShapeFromType(node.type),
|
||||
width: node.width,
|
||||
height: node.height ?? 0,
|
||||
padding: node.padding,
|
||||
cssClasses: classes,
|
||||
cssStyles: [],
|
||||
look: 'default',
|
||||
icon: node.icon,
|
||||
x: node.x,
|
||||
y: node.y,
|
||||
// Mindmap-specific properties
|
||||
level: node.level,
|
||||
nodeId: node.nodeId,
|
||||
type: node.type,
|
||||
section: node.section,
|
||||
};
|
||||
|
||||
processedNodes.push(processedNode);
|
||||
|
||||
// Recursively process children
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
this.flattenNodes(child, processedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate edges from parent-child relationships in mindmap tree
|
||||
* @param node - The mindmap node to process
|
||||
* @param edges - Array to collect edges
|
||||
*/
|
||||
public generateEdges(node: MindmapNode, edges: MindmapLayoutEdge[]): void {
|
||||
if (!node.children) {
|
||||
return;
|
||||
}
|
||||
for (const child of node.children) {
|
||||
// Build CSS classes for the edge
|
||||
let edgeClasses = 'edge';
|
||||
|
||||
// Add section-specific classes based on the child's section
|
||||
if (child.section !== undefined) {
|
||||
edgeClasses += ` section-edge-${child.section}`;
|
||||
}
|
||||
|
||||
// Add depth class based on the parent's level + 1 (depth of the edge)
|
||||
const edgeDepth = node.level + 1;
|
||||
edgeClasses += ` edge-depth-${edgeDepth}`;
|
||||
|
||||
const edge: MindmapLayoutEdge = {
|
||||
id: `edge_${node.id}_${child.id}`,
|
||||
start: node.id.toString(),
|
||||
end: child.id.toString(),
|
||||
type: 'normal',
|
||||
curve: 'basis',
|
||||
thickness: 'normal',
|
||||
look: 'default',
|
||||
classes: edgeClasses,
|
||||
// Store mindmap-specific data
|
||||
depth: node.level,
|
||||
section: child.section,
|
||||
};
|
||||
|
||||
edges.push(edge);
|
||||
|
||||
// Recursively process child edges
|
||||
this.generateEdges(child, edges);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get structured data for layout algorithms
|
||||
* Following the pattern established by ER diagrams
|
||||
* @returns Structured data containing nodes, edges, and config
|
||||
*/
|
||||
public getData(): LayoutData {
|
||||
const mindmapRoot = this.getMindmap();
|
||||
const config = getConfig();
|
||||
|
||||
if (!mindmapRoot) {
|
||||
return {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
config,
|
||||
};
|
||||
}
|
||||
log.debug('getData: mindmapRoot', mindmapRoot, config);
|
||||
|
||||
// Assign section numbers to all nodes based on their position relative to root
|
||||
this.assignSections(mindmapRoot);
|
||||
|
||||
// Convert tree structure to flat arrays
|
||||
const processedNodes: MindmapLayoutNode[] = [];
|
||||
const processedEdges: MindmapLayoutEdge[] = [];
|
||||
|
||||
this.flattenNodes(mindmapRoot, processedNodes);
|
||||
this.generateEdges(mindmapRoot, processedEdges);
|
||||
|
||||
log.debug(
|
||||
`getData: processed ${processedNodes.length} nodes and ${processedEdges.length} edges`
|
||||
);
|
||||
|
||||
// Create shapes map for ELK compatibility
|
||||
const shapes = new Map<string, any>();
|
||||
for (const node of processedNodes) {
|
||||
shapes.set(node.id, {
|
||||
shape: node.shape,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
padding: node.padding,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: processedNodes,
|
||||
edges: processedEdges,
|
||||
config,
|
||||
// Store the root node for mindmap-specific layout algorithms
|
||||
rootNode: mindmapRoot,
|
||||
// Properties required by dagre layout algorithm
|
||||
markers: [], // Mindmaps don't use markers
|
||||
direction: 'TB', // Top-to-bottom direction for mindmaps
|
||||
nodeSpacing: 50, // Default spacing between nodes
|
||||
rankSpacing: 50, // Default spacing between ranks
|
||||
// Add shapes for ELK compatibility
|
||||
shapes: Object.fromEntries(shapes),
|
||||
// Additional properties that layout algorithms might expect
|
||||
type: 'mindmap',
|
||||
diagramId: 'mindmap-' + Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
// Expose logger to grammar
|
||||
public getLogger() {
|
||||
return log;
|
||||
}
|
||||
|
@@ -1,84 +1,200 @@
|
||||
import cytoscape from 'cytoscape';
|
||||
// @ts-expect-error No types available
|
||||
import coseBilkent from 'cytoscape-cose-bilkent';
|
||||
import { select } from 'd3';
|
||||
import type { MermaidConfig } from '../../config.type.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import type { DrawDefinition } from '../../diagram-api/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
||||
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||
import type { LayoutData } from '../../rendering-util/types.js';
|
||||
import type { FilledMindMapNode } from './mindmapTypes.js';
|
||||
import type { D3Element } from '../../types.js';
|
||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||
import type { FilledMindMapNode, MindmapNode } from './mindmapTypes.js';
|
||||
import { drawNode, positionNode } from './svgDraw.js';
|
||||
import defaultConfig from '../../defaultConfig.js';
|
||||
import type { MindmapDB } from './mindmapDb.js';
|
||||
// Inject the layout algorithm into cytoscape
|
||||
cytoscape.use(coseBilkent);
|
||||
|
||||
/**
|
||||
* Update the layout data with actual node dimensions after drawing
|
||||
*/
|
||||
function _updateNodeDimensions(data4Layout: LayoutData, mindmapRoot: FilledMindMapNode) {
|
||||
const updateNode = (node: FilledMindMapNode) => {
|
||||
// Find the corresponding node in the layout data
|
||||
const layoutNode = data4Layout.nodes.find((n) => n.id === node.id.toString());
|
||||
if (layoutNode) {
|
||||
// Update with the actual dimensions calculated by drawNode
|
||||
layoutNode.width = node.width;
|
||||
layoutNode.height = node.height;
|
||||
log.debug('Updated node dimensions:', node.id, 'width:', node.width, 'height:', node.height);
|
||||
async function drawNodes(
|
||||
db: MindmapDB,
|
||||
svg: D3Element,
|
||||
mindmap: FilledMindMapNode,
|
||||
section: number,
|
||||
conf: MermaidConfig
|
||||
) {
|
||||
await drawNode(db, svg, mindmap, section, conf);
|
||||
if (mindmap.children) {
|
||||
await Promise.all(
|
||||
mindmap.children.map((child, index) =>
|
||||
drawNodes(db, svg, child, section < 0 ? index : section, conf)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'cytoscape' {
|
||||
interface EdgeSingular {
|
||||
_private: {
|
||||
bodyBounds: unknown;
|
||||
rscratch: {
|
||||
startX: number;
|
||||
startY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) {
|
||||
cy.edges().map((edge, id) => {
|
||||
const data = edge.data();
|
||||
if (edge[0]._private.bodyBounds) {
|
||||
const bounds = edge[0]._private.rscratch;
|
||||
log.trace('Edge: ', id, data);
|
||||
edgesEl
|
||||
.insert('path')
|
||||
.attr(
|
||||
'd',
|
||||
`M ${bounds.startX},${bounds.startY} L ${bounds.midX},${bounds.midY} L${bounds.endX},${bounds.endY} `
|
||||
)
|
||||
.attr('class', 'edge section-edge-' + data.section + ' edge-depth-' + data.depth);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively update children
|
||||
node.children?.forEach(updateNode);
|
||||
};
|
||||
function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) {
|
||||
cy.add({
|
||||
group: 'nodes',
|
||||
data: {
|
||||
id: mindmap.id.toString(),
|
||||
labelText: mindmap.descr,
|
||||
height: mindmap.height,
|
||||
width: mindmap.width,
|
||||
level: level,
|
||||
nodeId: mindmap.id,
|
||||
padding: mindmap.padding,
|
||||
type: mindmap.type,
|
||||
},
|
||||
position: {
|
||||
x: mindmap.x!,
|
||||
y: mindmap.y!,
|
||||
},
|
||||
});
|
||||
if (mindmap.children) {
|
||||
mindmap.children.forEach((child) => {
|
||||
addNodes(child, cy, conf, level + 1);
|
||||
cy.add({
|
||||
group: 'edges',
|
||||
data: {
|
||||
id: `${mindmap.id}_${child.id}`,
|
||||
source: mindmap.id,
|
||||
target: child.id,
|
||||
depth: level,
|
||||
section: child.section,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateNode(mindmapRoot);
|
||||
function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> {
|
||||
return new Promise((resolve) => {
|
||||
// Add temporary render element
|
||||
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'), // container to render in
|
||||
style: [
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'curve-style': 'bezier',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
// Remove element after layout
|
||||
renderEl.remove();
|
||||
addNodes(node, cy, conf, 0);
|
||||
|
||||
// Make cytoscape care about the dimensions of the nodes
|
||||
cy.nodes().forEach(function (n) {
|
||||
n.layoutDimensions = () => {
|
||||
const data = n.data();
|
||||
return { w: data.width, h: data.height };
|
||||
};
|
||||
});
|
||||
|
||||
cy.layout({
|
||||
name: 'cose-bilkent',
|
||||
// @ts-ignore Types for cose-bilkent are not correct?
|
||||
quality: 'proof',
|
||||
styleEnabled: false,
|
||||
animate: false,
|
||||
}).run();
|
||||
cy.ready((e) => {
|
||||
log.info('Ready', e);
|
||||
resolve(cy);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function positionNodes(db: MindmapDB, cy: cytoscape.Core) {
|
||||
cy.nodes().map((node, id) => {
|
||||
const data = node.data();
|
||||
data.x = node.position().x;
|
||||
data.y = node.position().y;
|
||||
positionNode(db, data);
|
||||
const el = db.getElementById(data.nodeId);
|
||||
log.info('id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data);
|
||||
el.attr(
|
||||
'transform',
|
||||
`translate(${node.position().x - data.width / 2}, ${node.position().y - data.height / 2})`
|
||||
);
|
||||
el.attr('attr', `apa-${id})`);
|
||||
});
|
||||
}
|
||||
|
||||
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
|
||||
log.debug('Rendering mindmap diagram\n' + text);
|
||||
const { securityLevel, mindmap: conf, layout } = getConfig();
|
||||
|
||||
// Draw the nodes first to get their dimensions, then update the layout data
|
||||
const db = diagObj.db as MindmapDB;
|
||||
|
||||
// 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 = db.getData();
|
||||
|
||||
// Create the root SVG - the element is the div containing the SVG element
|
||||
const svg = getDiagramElement(id, securityLevel);
|
||||
|
||||
data4Layout.type = diagObj.type;
|
||||
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout, {
|
||||
fallback: 'cose-bilkent',
|
||||
});
|
||||
// For mindmap diagrams, prioritize mindmap-specific layout algorithm configuration
|
||||
|
||||
data4Layout.diagramId = id;
|
||||
|
||||
// Ensure required properties are set for compatibility with different layout algorithms
|
||||
data4Layout.markers = ['point'];
|
||||
data4Layout.direction = 'TB';
|
||||
|
||||
const mm = db.getMindmap();
|
||||
if (!mm) {
|
||||
return;
|
||||
}
|
||||
data4Layout.nodes.forEach((node) => {
|
||||
node.from = 'mindmap';
|
||||
if (node.shape === 'rounded') {
|
||||
node.radius = 15;
|
||||
node.taper = 15;
|
||||
node.stroke = 'none';
|
||||
} else if (node.shape === 'rect') {
|
||||
node.height = 46;
|
||||
node.width = 92;
|
||||
}
|
||||
});
|
||||
// Use the unified rendering system
|
||||
await render(data4Layout, svg);
|
||||
|
||||
const conf = getConfig();
|
||||
conf.htmlLabels = false;
|
||||
|
||||
const svg = selectSvgElement(id);
|
||||
|
||||
// Draw the graph and start with drawing the nodes without proper position
|
||||
// this gives us the size of the nodes and we can set the positions later
|
||||
|
||||
const edgesElem = svg.append('g');
|
||||
edgesElem.attr('class', 'mindmap-edges');
|
||||
const nodesElem = svg.append('g');
|
||||
nodesElem.attr('class', 'mindmap-nodes');
|
||||
await drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf);
|
||||
|
||||
// Next step is to layout the mindmap, giving each node a position
|
||||
|
||||
const cy = await layoutMindmap(mm, conf);
|
||||
|
||||
// After this we can draw, first the edges and the then nodes with the correct position
|
||||
drawEdges(edgesElem, cy);
|
||||
positionNodes(db, cy);
|
||||
|
||||
// Setup the view box and size of the svg element
|
||||
setupViewPortForSVG(
|
||||
setupGraphViewbox(
|
||||
undefined,
|
||||
svg,
|
||||
conf?.padding ?? defaultConfig.mindmap.padding,
|
||||
'mindmapDiagram',
|
||||
conf?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
|
||||
conf.mindmap?.padding ?? defaultConfig.mindmap.padding,
|
||||
conf.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -64,12 +64,6 @@ const getStyles: DiagramStylesProvider = (options) =>
|
||||
.section-root text {
|
||||
fill: ${options.gitBranchLabel0};
|
||||
}
|
||||
.section-root span {
|
||||
color: ${options.gitBranchLabel0};
|
||||
}
|
||||
.section-2 span {
|
||||
color: ${options.gitBranchLabel0};
|
||||
}
|
||||
.icon-container {
|
||||
height:100%;
|
||||
display: flex;
|
||||
|
@@ -139,32 +139,6 @@ describe('pie', () => {
|
||||
}).rejects.toThrowError();
|
||||
});
|
||||
|
||||
it('should handle simple pie with zero slice value', async () => {
|
||||
await parser.parse(`pie title Default text position: Animal adoption
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"dogs" : 0
|
||||
"rats" : 40.12
|
||||
`);
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections.get('dogs')).toBe(0);
|
||||
expect(sections.get('rats')).toBe(40.12);
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative slice value', async () => {
|
||||
await expect(async () => {
|
||||
await parser.parse(`pie title Default text position: Animal adoption
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"dogs" : -60.67
|
||||
"rats" : 40.12
|
||||
`);
|
||||
}).rejects.toThrowError(
|
||||
'"dogs" has invalid value: -60.67. Negative values are not allowed in pie charts. All slice values must be >= 0.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unsafe properties', async () => {
|
||||
await expect(
|
||||
parser.parse(`pie title Unsafe props test
|
||||
|
@@ -34,11 +34,6 @@ const clear = (): void => {
|
||||
};
|
||||
|
||||
const addSection = ({ label, value }: D3Section): void => {
|
||||
if (value < 0) {
|
||||
throw new Error(
|
||||
`"${label}" has invalid value: ${value}. Negative values are not allowed in pie charts. All slice values must be >= 0.`
|
||||
);
|
||||
}
|
||||
if (!sections.has(label)) {
|
||||
sections.set(label, value);
|
||||
log.debug(`added new section: ${label}, with value: ${value}`);
|
||||
|
@@ -10,14 +10,20 @@ import { cleanAndMerge, parseFontSize } from '../../utils.js';
|
||||
import type { D3Section, PieDB, Sections } from './pieTypes.js';
|
||||
|
||||
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Section>[] => {
|
||||
const sum = [...sections.values()].reduce((acc, val) => acc + val, 0);
|
||||
|
||||
// Compute the position of each group on the pie:
|
||||
const pieData: D3Section[] = [...sections.entries()]
|
||||
.map(([label, value]) => ({ label, value }))
|
||||
.filter((d) => (d.value / sum) * 100 >= 1) // Remove values < 1%
|
||||
.sort((a, b) => b.value - a.value);
|
||||
|
||||
const pie: d3.Pie<unknown, D3Section> = d3pie<D3Section>().value((d) => d.value);
|
||||
.map((element: [string, number]): D3Section => {
|
||||
return {
|
||||
label: element[0],
|
||||
value: element[1],
|
||||
};
|
||||
})
|
||||
.sort((a: D3Section, b: D3Section): number => {
|
||||
return b.value - a.value;
|
||||
});
|
||||
const pie: d3.Pie<unknown, D3Section> = d3pie<D3Section>().value(
|
||||
(d3Section: D3Section): number => d3Section.value
|
||||
);
|
||||
return pie(pieData);
|
||||
};
|
||||
|
||||
@@ -83,21 +89,13 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
themeVariables.pie11,
|
||||
themeVariables.pie12,
|
||||
];
|
||||
let sum = 0;
|
||||
sections.forEach((section) => {
|
||||
sum += section;
|
||||
});
|
||||
|
||||
// Filter out arcs that would render as 0%
|
||||
const filteredArcs = arcs.filter((datum) => ((datum.data.value / sum) * 100).toFixed(0) !== '0');
|
||||
|
||||
// Set the color scale
|
||||
const color: d3.ScaleOrdinal<string, 12, never> = scaleOrdinal(myGeneratedColors);
|
||||
|
||||
// Build the pie chart: each part of the pie is a path that we build using the arc function.
|
||||
group
|
||||
.selectAll('mySlices')
|
||||
.data(filteredArcs)
|
||||
.data(arcs)
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arcGenerator)
|
||||
@@ -106,11 +104,15 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
})
|
||||
.attr('class', 'pieCircle');
|
||||
|
||||
let sum = 0;
|
||||
sections.forEach((section) => {
|
||||
sum += section;
|
||||
});
|
||||
// Now add the percentage.
|
||||
// Use the centroid method to get the best coordinates.
|
||||
group
|
||||
.selectAll('mySlices')
|
||||
.data(filteredArcs)
|
||||
.data(arcs)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text((datum: d3.PieArcDatum<D3Section>): string => {
|
||||
@@ -131,20 +133,15 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.attr('class', 'pieTitleText');
|
||||
|
||||
// Add the legends/annotations for each section
|
||||
const allSectionData: D3Section[] = [...sections.entries()].map(([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}));
|
||||
|
||||
const legend = group
|
||||
.selectAll('.legend')
|
||||
.data(allSectionData)
|
||||
.data(color.domain())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'legend')
|
||||
.attr('transform', (_datum, index: number): string => {
|
||||
const height = LEGEND_RECT_SIZE + LEGEND_SPACING;
|
||||
const offset = (height * allSectionData.length) / 2;
|
||||
const offset = (height * color.domain().length) / 2;
|
||||
const horizontal = 12 * LEGEND_RECT_SIZE;
|
||||
const vertical = index * height - offset;
|
||||
return 'translate(' + horizontal + ',' + vertical + ')';
|
||||
@@ -154,18 +151,20 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.append('rect')
|
||||
.attr('width', LEGEND_RECT_SIZE)
|
||||
.attr('height', LEGEND_RECT_SIZE)
|
||||
.style('fill', (d) => color(d.label))
|
||||
.style('stroke', (d) => color(d.label));
|
||||
.style('fill', color)
|
||||
.style('stroke', color);
|
||||
|
||||
legend
|
||||
.data(arcs)
|
||||
.append('text')
|
||||
.attr('x', LEGEND_RECT_SIZE + LEGEND_SPACING)
|
||||
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
|
||||
.text((d) => {
|
||||
.text((datum: d3.PieArcDatum<D3Section>): string => {
|
||||
const { label, value } = datum.data;
|
||||
if (db.getShowData()) {
|
||||
return `${d.label} [${d.value}]`;
|
||||
return `${label} [${value}]`;
|
||||
}
|
||||
return d.label;
|
||||
return label;
|
||||
});
|
||||
|
||||
const longestTextWidth = Math.max(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user