Compare commits

..

8 Commits

Author SHA1 Message Date
darshanr0107
361c0076c8 Merge branch 'develop' into 6587-class-diagram-stereotype-styling 2025-09-08 11:45:20 +05:30
darshanr0107
ad35a930c6 fix: preserve annotations and adjust styling for clarity
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-09-03 14:13:40 +05:30
darshanr0107
a495eb03e2 Revert "fix: address failing argos tests"
This reverts commit 8f9ee39af7.
2025-09-03 13:50:12 +05:30
darshanr0107
8f9ee39af7 fix: address failing argos tests
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-09-01 20:04:05 +05:30
darshanr0107
eb81bdd617 Merge branch 'develop' into 6587-class-diagram-stereotype-styling 2025-07-16 17:29:22 +05:30
darshanr0107
faa03bb4b2 chore: resolve PR comments
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-07-16 15:15:13 +05:30
darshanr0107
fbf1085b57 added changeset 2025-07-10 19:16:34 +05:30
darshanr0107
e2b7f7fc84 add support for stereotype annotations for class diagrams 2025-07-10 19:03:31 +05:30
107 changed files with 7250 additions and 6755 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Render newlines as spaces in class diagrams

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Handle arrows correctly when auto number is enabled

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
chore: Fix mindmap rendering in docs and apply tidytree layout

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Ensure edge label color is applied when using classDef with edge IDs

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.

View File

@@ -0,0 +1,7 @@
---
'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

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Resolve gantt chart crash due to invalid array length

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Added support for styling class diagram elements based on stereotype annotations

View File

@@ -1,5 +0,0 @@
---
'mermaid': minor
---
feat: Add IDs in architecture diagrams

View File

@@ -1,5 +0,0 @@
---
'mermaid': minor
---
feat: Deprecate flowchart.htmlLabels in favor of root-level htmlLabels

View File

@@ -8,7 +8,6 @@ compositTitleSize
cose
curv
doublecircle
elem
elems
gantt
gitgraph

View File

@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
@@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -62,4 +62,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10

View File

@@ -53,7 +53,7 @@ jobs:
args: -X POST "$APPLITOOLS_SERVER_URL/api/externals/github/push?apiKey=$APPLITOOLS_API_KEY&CommitSha=$GITHUB_SHA&BranchName=${APPLITOOLS_BRANCH}$&ParentBranchName=$APPLITOOLS_PARENT_BRANCH"
- name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress
with:
start: pnpm run dev

View File

@@ -27,12 +27,12 @@ jobs:
with:
node-version-file: '.node-version'
- name: Install dependencies
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress
with:
install: false
@@ -58,7 +58,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Commit and create pull request
uses: peter-evans/create-pull-request@915d841dae6a4f191bb78faf61a257411d7be4d2
uses: peter-evans/create-pull-request@18e469570b1cf0dfc11d60ec121099f8ff3e617a
with:
add-paths: |
cypress/timings.json

View File

@@ -45,7 +45,7 @@ jobs:
node-version-file: '.node-version'
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
@@ -59,7 +59,7 @@ jobs:
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with:
# just perform install
runTests: false
@@ -95,13 +95,13 @@ jobs:
# These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
with:
runTests: false
@@ -117,7 +117,7 @@ jobs:
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
id: cypress
with:
install: false

View File

@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Restore lychee cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}

View File

@@ -36,7 +36,7 @@ jobs:
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10
uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
with:
version: pnpm changeset:version
publish: pnpm changeset:publish

View File

@@ -20,18 +20,18 @@ jobs:
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
sarif_file: results.sarif

View File

@@ -19,7 +19,7 @@ jobs:
message: 'chore: update browsers list'
push: false
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
with:
branch: update-browserslist
title: Update Browserslist

View File

@@ -98,12 +98,12 @@ describe('Configuration', () => {
it('should handle arrowMarkerAbsolute set to true', () => {
renderGraph(
`flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
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]
`,
{
arrowMarkerAbsolute: true,
}
@@ -113,7 +113,8 @@ describe('Configuration', () => {
cy.get('path')
.first()
.should('have.attr', 'marker-end')
.and('include', 'url(http://localhost');
.should('exist')
.and('include', 'url(http\\:\\/\\/localhost');
});
});
it('should not taint the initial configuration when using multiple directives', () => {

View File

@@ -1028,4 +1028,88 @@ class C13["With Città foreign language"]
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using interface annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using abstract annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;abstract&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using enumeration annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;enumeration&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
});

View File

@@ -109,7 +109,7 @@ describe('Flowchart ELK', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
verifyNumber(maxWidthValue, 380, 15);
verifyNumber(maxWidthValue, 380);
});
});
it('8-elk: should render a flowchart when useMaxWidth is false', () => {
@@ -128,7 +128,7 @@ describe('Flowchart ELK', () => {
const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±5%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
verifyNumber(width, 380, 15);
verifyNumber(width, 380);
expect(svg).to.not.have.attr('style');
});
});

View File

@@ -1186,17 +1186,4 @@ end
imgSnapshotTest(graph, { htmlLabels: false });
});
});
it('V2 - 17: should apply class def colour to edge label', () => {
imgSnapshotTest(
` graph LR
id1(Start) link@-- "Label" -->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
class id2 myClass
classDef myClass fill:#bbf,stroke:#f66,stroke-width:2px,color:white,stroke-dasharray: 5 5
class link myClass
`
);
});
});

View File

@@ -803,34 +803,4 @@ describe('Gantt diagram', () => {
{}
);
});
it('should handle numeric timestamps with dateFormat x', () => {
imgSnapshotTest(
`
gantt
title Process time profile (ms)
dateFormat x
axisFormat %L
tickInterval 250millisecond
section Pipeline
Parse JSON p1: 000, 120
`,
{}
);
});
it('should handle numeric timestamps with dateFormat X', () => {
imgSnapshotTest(
`
gantt
title Process time profile (ms)
dateFormat X
axisFormat %L
tickInterval 250millisecond
section Pipeline
Parse JSON p1: 000, 120
`,
{}
);
});
});

View File

@@ -32,8 +32,26 @@
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&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=Recursive:wght@300..1000&display=swap"
rel="stylesheet"
/>
<style>
.recursive-mermaid {
font-family: 'Recursive', sans-serif;
font-optical-sizing: auto;
font-weight: 500;
font-style: normal;
font-variation-settings:
'slnt' 0,
'CASL' 0,
'CRSV' 0.5,
'MONO' 0;
}
body {
/* background: rgb(221, 208, 208); */
/* background: #333; */
@@ -45,7 +63,9 @@
h1 {
color: grey;
}
.mermaid {
border: 1px solid red;
}
.mermaid2 {
display: none;
}
@@ -83,6 +103,11 @@
width: 100%;
}
.class2 {
fill: red;
fill-opacity: 1;
}
/* tspan {
font-size: 6px !important;
} */
@@ -105,194 +130,76 @@
</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: elk
layout: tidy-tree
---
flowchart-elk TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
one --> two
three --> two
two --> c2
</pre
mindmap
root((mindmap is a long thing))
A
B
C
D
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
layout: tidy-tree
---
flowchart TB
process_C
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end
</pre
mindmap
root((mindmap))
A
B
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
layout: tidy-tree
---
flowchart TB
subgraph container_Beta
process_C
end
subgraph container_Alpha
subgraph process_B
pppB
end
subgraph process_A
pppA
end
process_B-->|via_AWSBatch|container_Beta
process_A-->|messages|container_Beta
end
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">
---
config:
layout: elk
---
flowchart TB
subgraph container_Beta
process_C
end
</pre>
process_B-->|via_AWSBatch|container_Beta
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
classDiagram
note "I love this diagram!\nDo you love it?"
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
Class12 <|.. Class08
Class11 ..>Class12
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class01 : -int privateChimp
Class01 : +int publicGorilla
Class01 : #int protectedMarmoset
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
note for Class10 "Cool class\nI said it's very cool class!"
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
requirementDiagram
requirement test_req {
id: 1
text: the test text.
risk: high
verifymethod: test
}
element test_entity {
type: simulation
}
test_entity - satisfies -> test_req
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
compute1
subgraph project
router
nat
subgraph subnet1
compute1
end
end
%% router --> subnet1
subnet1 --> nat
%% nat --> internet
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart-elk TB
internet
nat
router
lb1
lb2
compute1
compute2
subgraph project
router
nat
subgraph subnet1
compute1
lb1
end
subgraph subnet2
compute2
lb2
end
end
internet --> router
router --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
</pre
>
<pre id="diagram4" class="mermaid">
---
config:
@@ -320,27 +227,106 @@ treemap
"Leaf 2.2": 25
"Leaf 2.3": 12
</pre>
<pre id="diagram5" class="mermaid">
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
I["fa:fa-code Text"] -- Mermaid js --> D["Use<br/>the<br/>editor!"]
I --> D & D
D@{ shape: question}
I@{ shape: question}
classDef class1 fill:red,color:blue,stroke:#FFD600;
</pre>
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
treemap:
valueFormat: '$0,0'
---
treemap
"Budget"
"Operations"
"Salaries": 7000
"Equipment": 2000
"Supplies": 1000
"Marketing"
"Advertising": 4000
"Events": 1000
</pre
>
<pre id="diagram4" class="mermaid">
treemap
title Accessible Treemap Title
"Category A"
"Item A1": 10
"Item A2": 20
"Category B"
"Item B1": 15
"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&lt;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
@@ -348,7 +334,7 @@ treemap
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On effectiveness&lt;br/>and features
On Automatic creation
Uses
Creative techniques
@@ -357,112 +343,105 @@ treemap
Tools
Pen and paper
Mermaid
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: linear
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness&lt;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
A[A] --> B[B]
A[A] --- B([C])
A@{ shape: diamond}
%%B@{ shape: diamond}
root{mindmap} --- Origins --- Europe
Origins --> Asia
root --- Background --- Rich
Background --- Poor
subgraph apa
Background
Poor
end
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
flowchart:
curve: linear
---
flowchart LR
A[A] -- Mermaid js --> B[B]
A[A] -- Mermaid js --- B[B]
A@{ shape: diamond}
B@{ shape: diamond}
root{mindmap} --- Origins --- Europe
Origins --> Asia
root --- Background --- Rich
Background --- Poor
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
flowchart:
curve: rounded
---
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
I --> D & D
D@{ shape: question}
I@{ shape: question}
<pre id="diagram4" class="mermaid2">
flowchart
D(("for D"))
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
flowchart:
curve: rounded
elk:
nodePlacementStrategy: NETWORK_SIMPLEX
---
<pre id="diagram4" class="mermaid2">
flowchart LR
D["Use the editor"] -- Mermaid js --> I["fa:fa-code Text"]
D --> I & I
a["a"]
D@{ shape: trap-b}
I@{ shape: lean-l}
A e1@==> B
e1@{ animate: true}
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
<pre id="diagram4" class="mermaid2">
flowchart LR
%% subgraph s1["Untitled subgraph"]
C["Evaluate"]
%% end
B --> C
A e1@--> B
classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
class e1 animate
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
flowchart:
//curve: linear
---
<h2>infinite</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
%% A ==> B
%% A2 --> B2
A{A} --> B((Bo boo)) & B & B & B
A e1@--> B
classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
</pre>
<h2>Mermaid - edge-animation-slow</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
e1@{ animation: fast}
</pre>
<h2>Mermaid - edge-animation-fast</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
classDef animate stroke-dasharray: 1000,stroke-dashoffset: 1000,animation: dash 10s linear;
class e1 edge-animation-fast
</pre>
</pre>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
theme: default
look: classic
---
flowchart LR
subgraph s1["APA"]
D{"Use the editor"}
end
subgraph S2["S2"]
s1
I>"fa:fa-code Text"]
E["E"]
end
D -- Mermaid js --> I
D --> I & E
E --> I
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
info </pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -487,7 +466,7 @@ config:
end
end
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -500,7 +479,45 @@ config:
D-->I
D-->I
</pre>
<pre id="diagram4" class="mermaid">
<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
@@ -539,7 +556,7 @@ flowchart LR
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -555,7 +572,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -564,7 +581,7 @@ flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -576,7 +593,7 @@ flowchart LR
end
</pre
>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@@ -594,7 +611,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
@@ -613,81 +630,81 @@ kanban
task3[💻 Develop login feature]@{ ticket: 103 }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
@@ -751,22 +768,18 @@ kanban
alert('It worked');
}
await mermaid.initialize({
// theme: 'base',
// theme: 'forest',
// theme: 'default',
// theme: 'forest',
// handDrawnSeed: 12,
// look: 'handDrawn',
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
// layout: 'dagre',
layout: 'elk',
// layout: 'elk',
// layout: 'fixed',
// htmlLabels: false,
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
// fontFamily: 'courier',
fontFamily: 'arial',
fontFamily: "'Recursive', sans-serif",
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "es2020",
"lib": ["es2020", "dom"],
"types": ["cypress", "node", "@argos-ci/cypress/support"],
"types": ["cypress", "node", "@argos-ci/cypress/dist/support.d.ts"],
"allowImportingTsExtensions": true,
"noEmit": true
},

View File

@@ -11,7 +11,7 @@
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link

View File

@@ -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

View File

@@ -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).

View File

@@ -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
@@ -236,27 +237,22 @@ A --> C[End]
Some common flowchart configurations are:
- _htmlLabels_: true/false
- _curve_: linear/curve
- _diagramPadding_: number
- _useMaxWidth_: number
**Deprecated configurations:**
- ~~_htmlLabels_~~: Use global `htmlLabels` instead
For a complete list of flowchart configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
The following code snippet changes flowchart config:
```
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
```
`%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%`
**Note:** `flowchart.htmlLabels` has been deprecated. Use the global `htmlLabels` configuration instead.
Here we are overriding only the flowchart config, and not the general config, setting `htmlLabels` to `true` and `curve` to `linear`.
```mermaid-example
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]
@@ -267,7 +263,7 @@ A --> C[End]
```
```mermaid
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]

View File

@@ -18,7 +18,6 @@
- [addDirective](functions/addDirective.md)
- [getConfig](functions/getConfig.md)
- [getEffectiveHtmlLabels](functions/getEffectiveHtmlLabels.md)
- [getSiteConfig](functions/getSiteConfig.md)
- [getUserDefinedConfig](functions/getUserDefinedConfig.md)
- [reset](functions/reset.md)

View File

@@ -1,29 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getEffectiveHtmlLabels.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getEffectiveHtmlLabels.md).
[**mermaid**](../../README.md)
---
# Function: getEffectiveHtmlLabels()
> **getEffectiveHtmlLabels**(`config`): `boolean`
Defined in: [packages/mermaid/src/config.ts:272](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L272)
Helper function to handle deprecated flowchart.htmlLabels
## Parameters
### config
[`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
The configuration object
## Returns
`boolean`

View File

@@ -12,7 +12,7 @@
> **getUserDefinedConfig**(): [`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
Defined in: [packages/mermaid/src/config.ts:254](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L254)
Defined in: [packages/mermaid/src/config.ts:252](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L252)
## Returns

View File

@@ -10,7 +10,7 @@
# Interface: ExternalDiagramDefinition
Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L96)
Defined in: [packages/mermaid/src/diagram-api/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L94)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/me
> **detector**: `DiagramDetector`
Defined in: [packages/mermaid/src/diagram-api/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L98)
Defined in: [packages/mermaid/src/diagram-api/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L96)
---
@@ -26,7 +26,7 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:98](https://github.com/me
> **id**: `string`
Defined in: [packages/mermaid/src/diagram-api/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L97)
Defined in: [packages/mermaid/src/diagram-api/types.ts:95](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L95)
---
@@ -34,4 +34,4 @@ Defined in: [packages/mermaid/src/diagram-api/types.ts:97](https://github.com/me
> **loader**: `DiagramLoader`
Defined in: [packages/mermaid/src/diagram-api/types.ts:99](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L99)
Defined in: [packages/mermaid/src/diagram-api/types.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L97)

View File

@@ -10,7 +10,7 @@
# Interface: LayoutLoaderDefinition
Defined in: [packages/mermaid/src/rendering-util/render.ts:24](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L24)
Defined in: [packages/mermaid/src/rendering-util/render.ts:21](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L21)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/rendering-util/render.ts:24](https://github.co
> `optional` **algorithm**: `string`
Defined in: [packages/mermaid/src/rendering-util/render.ts:27](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L27)
Defined in: [packages/mermaid/src/rendering-util/render.ts:24](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L24)
---
@@ -26,7 +26,7 @@ Defined in: [packages/mermaid/src/rendering-util/render.ts:27](https://github.co
> **loader**: `LayoutLoader`
Defined in: [packages/mermaid/src/rendering-util/render.ts:26](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L26)
Defined in: [packages/mermaid/src/rendering-util/render.ts:23](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L23)
---
@@ -34,4 +34,4 @@ Defined in: [packages/mermaid/src/rendering-util/render.ts:26](https://github.co
> **name**: `string`
Defined in: [packages/mermaid/src/rendering-util/render.ts:25](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L25)
Defined in: [packages/mermaid/src/rendering-util/render.ts:22](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L22)

View File

@@ -10,7 +10,7 @@
# Interface: RenderOptions
Defined in: [packages/mermaid/src/rendering-util/render.ts:10](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L10)
Defined in: [packages/mermaid/src/rendering-util/render.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L7)
## Properties
@@ -18,4 +18,4 @@ Defined in: [packages/mermaid/src/rendering-util/render.ts:10](https://github.co
> `optional` **algorithm**: `string`
Defined in: [packages/mermaid/src/rendering-util/render.ts:11](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L11)
Defined in: [packages/mermaid/src/rendering-util/render.ts:8](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L8)

View File

@@ -12,4 +12,4 @@
> **SVG** = `d3.Selection`<`SVGSVGElement`, `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)
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)

View File

@@ -12,4 +12,4 @@
> **SVGGroup** = `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
Defined in: [packages/mermaid/src/diagram-api/types.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L130)
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)

View File

@@ -372,7 +372,7 @@ The list of configuration objects are described [in the mermaidAPI documentation
```html
<script type="module">
import mermaid from './mermaid.esm.mjs';
let config = { startOnLoad: true, htmlLabels: true, flowchart: { useMaxWidth: false } };
let config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } };
mermaid.initialize(config);
</script>
```

View File

@@ -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)

View File

@@ -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)

View File

@@ -6,18 +6,6 @@
# Blog
## [The Essential Guide to Mermaid Chart Plugin for VS Code \[08/2025\]](https://docs.mermaidchart.com/blog/posts/the-essential-guide-to-mermaid-chart-plugin-for-vs-code-08-2025)
9/9/2025 • 5 mins
Creating diagrams in VS Code has never been easier—the Mermaid VS Code plugin transforms text-based syntax into clear, professional visuals right inside your editor. With live previews, smart editing, and AI-powered diagram generation, it removes the friction from visualization so you can focus on coding.
## [How to Create Perfect Flowcharts Using AI in 2025 Step-by-Step Guide](https://docs.mermaidchart.com/blog/posts/how-to-create-perfect-flowcharts-using-ai-in-2025-step-by-step-guide)
7/22/2025 • 8 mins
In 2025, AI tools make creating flowcharts faster and easier than ever. With Mermaids AI flowchart generator, you can turn plain text into clear, accurate diagrams in seconds. This guide walks through how it works and how to get the most out of it for technical and business workflows alike.
## [Mermaid introduces the Visual Editor for Entity Relationship diagrams](https://docs.mermaidchart.com/blog/posts/mermaid-introduces-the-visual-editor-for-entity-relationship-diagrams)
7/15/2025 • 7 mins

View File

@@ -194,7 +194,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add other custom icons, by [registering an icon pack](../config/icons.md).
Users can use any of the 200,000+ icons available in iconify.design, or [add custom icons](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -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

View File

@@ -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

View File

@@ -38,5 +38,3 @@ Each user journey is split into sections, these describe the part of the task
the user is trying to complete.
Tasks syntax is `Task name: <score>: <comma separated list of actors>`
Score is a number between 1 and 5, inclusive.

View File

@@ -63,36 +63,36 @@
]
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.55.2",
"@argos-ci/cypress": "^6.1.1",
"@applitools/eyes-cypress": "^3.44.9",
"@argos-ci/cypress": "^5.0.2",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.29.7",
"@changesets/cli": "^2.27.12",
"@cspell/eslint-plugin": "^8.19.4",
"@cypress/code-coverage": "^3.14.6",
"@cypress/code-coverage": "^3.12.49",
"@eslint/js": "^9.26.0",
"@rollup/plugin-typescript": "^12.1.4",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@rollup/plugin-typescript": "^12.1.2",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/js-yaml": "^4.0.9",
"@types/jsdom": "^21.1.7",
"@types/lodash": "^4.17.20",
"@types/lodash": "^4.17.15",
"@types/mdast": "^4.0.4",
"@types/node": "^22.18.6",
"@types/node": "^22.13.5",
"@types/rollup-plugin-visualizer": "^5.0.3",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/spy": "^3.2.4",
"@vitest/ui": "^3.2.4",
"@vitest/coverage-v8": "^3.0.6",
"@vitest/spy": "^3.0.6",
"@vitest/ui": "^3.0.6",
"ajv": "^8.17.1",
"chokidar": "3.6.0",
"concurrently": "^9.2.1",
"concurrently": "^9.1.2",
"cors": "^2.8.5",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
"cspell": "^9.2.1",
"cypress": "^14.5.4",
"cspell": "^9.1.3",
"cypress": "^14.5.1",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.23",
"esbuild": "^0.25.10",
"cypress-split": "^1.24.14",
"esbuild": "^0.25.0",
"eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-cypress": "^4.3.0",
@@ -106,30 +106,30 @@
"eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^59.0.1",
"express": "^5.1.0",
"globals": "^16.4.0",
"globby": "^14.1.0",
"globals": "^16.0.0",
"globby": "^14.0.2",
"husky": "^9.1.7",
"jest": "^30.1.3",
"jest": "^30.0.4",
"jison": "^0.4.18",
"js-yaml": "^4.1.0",
"jsdom": "^26.1.0",
"langium-cli": "3.3.0",
"lint-staged": "^16.1.6",
"lint-staged": "^16.1.2",
"markdown-table": "^3.0.4",
"nyc": "^17.1.0",
"path-browserify": "^1.0.1",
"prettier": "^3.6.2",
"prettier-plugin-jsdoc": "^1.3.3",
"prettier": "^3.5.2",
"prettier-plugin-jsdoc": "^1.3.2",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^6.0.3",
"start-server-and-test": "^2.1.2",
"start-server-and-test": "^2.0.10",
"tslib": "^2.8.1",
"tsx": "^4.20.5",
"tsx": "^4.7.3",
"typescript": "~5.7.3",
"typescript-eslint": "^8.38.0",
"vite": "^7.0.7",
"vite": "^7.0.3",
"vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.2.4"
"vitest": "^3.0.6"
},
"nyc": {
"report-dir": "coverage/cypress"

View File

@@ -3,7 +3,6 @@
"version": "1.0.0",
"description": "Mermaid examples package",
"author": "Sidharth Vinod",
"license": "MIT",
"type": "module",
"module": "./dist/mermaid-examples.core.mjs",
"types": "./dist/mermaid.d.ts",

View File

@@ -37,12 +37,12 @@
]
},
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@braintree/sanitize-url": "^7.0.4",
"d3": "^7.9.0",
"khroma": "^2.1.0"
},
"devDependencies": {
"concurrently": "^9.2.1",
"concurrently": "^9.1.2",
"mermaid": "workspace:*",
"rimraf": "^6.0.1"
},

View File

@@ -1,16 +1,5 @@
# @mermaid-js/layout-elk
## 0.2.0
### Minor Changes
- [#6802](https://github.com/mermaid-js/mermaid/pull/6802) [`c8e5027`](https://github.com/mermaid-js/mermaid/commit/c8e50276e877c4de7593a09ec458c99353e65af8) Thanks [@darshanr0107](https://github.com/darshanr0107)! - feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
### Patch Changes
- Updated dependencies [[`33bc4a0`](https://github.com/mermaid-js/mermaid/commit/33bc4a0b4e2ca6d937bb0a8c4e2081b1362b2800), [`e0b45c2`](https://github.com/mermaid-js/mermaid/commit/e0b45c2d2b41c2a9038bf87646fa3ccd7560eb20), [`012530e`](https://github.com/mermaid-js/mermaid/commit/012530e98e9b8b80962ab270b6bb3b6d9f6ada05), [`c8e5027`](https://github.com/mermaid-js/mermaid/commit/c8e50276e877c4de7593a09ec458c99353e65af8)]:
- mermaid@11.11.0
## 0.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.2.0",
"version": "0.1.9",
"description": "ELK layout engine for mermaid",
"module": "dist/mermaid-layout-elk.core.mjs",
"types": "dist/layouts.d.ts",

View File

@@ -1,67 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
intersection,
ensureTrulyOutside,
makeInsidePoint,
tryNodeIntersect,
replaceEndpoint,
type RectLike,
type P,
} from '../geometry.js';
const approx = (a: number, b: number, eps = 1e-6) => Math.abs(a - b) < eps;
describe('geometry helpers', () => {
it('intersection: vertical approach hits bottom border', () => {
const rect: RectLike = { x: 0, y: 0, width: 100, height: 50 };
const h = rect.height / 2; // 25
const outside: P = { x: 0, y: 100 };
const inside: P = { x: 0, y: 0 };
const res = intersection(rect, outside, inside);
expect(approx(res.x, 0)).toBe(true);
expect(approx(res.y, h)).toBe(true);
});
it('ensureTrulyOutside nudges near-boundary point outward', () => {
const rect: RectLike = { x: 0, y: 0, width: 100, height: 50 };
// near bottom boundary (y ~ h)
const near: P = { x: 0, y: rect.height / 2 - 0.2 };
const out = ensureTrulyOutside(rect, near, 10);
expect(out.y).toBeGreaterThan(rect.height / 2);
});
it('makeInsidePoint keeps x for vertical and y from center', () => {
const rect: RectLike = { x: 10, y: 5, width: 100, height: 50 };
const outside: P = { x: 10, y: 40 };
const center: P = { x: 99, y: -123 }; // center y should be used
const inside = makeInsidePoint(rect, outside, center);
expect(inside.x).toBe(outside.x);
expect(inside.y).toBe(center.y);
});
it('tryNodeIntersect returns null for wrong-side intersections', () => {
const rect: RectLike = { x: 0, y: 0, width: 100, height: 50 };
const outside: P = { x: -50, y: 0 };
const node = { intersect: () => ({ x: 10, y: 0 }) } as any; // right side of center
const res = tryNodeIntersect(node, rect, outside);
expect(res).toBeNull();
});
it('replaceEndpoint dedup removes end/start appropriately', () => {
const pts: P[] = [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
];
// remove duplicate end
replaceEndpoint(pts, 'end', { x: 1, y: 1 });
expect(pts.length).toBe(1);
const pts2: P[] = [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
];
// remove duplicate start
replaceEndpoint(pts2, 'start', { x: 0, y: 0 });
expect(pts2.length).toBe(1);
});
});

View File

@@ -1,209 +0,0 @@
/* Geometry utilities extracted from render.ts for reuse and testing */
export interface P {
x: number;
y: number;
}
export interface RectLike {
x: number; // center x
y: number; // center y
width: number;
height: number;
padding?: number;
}
export interface NodeLike {
intersect?: (p: P) => P | null;
}
export const EPS = 1;
export const PUSH_OUT = 10;
export const onBorder = (bounds: RectLike, p: P, tol = 0.5): boolean => {
const halfW = bounds.width / 2;
const halfH = bounds.height / 2;
const left = bounds.x - halfW;
const right = bounds.x + halfW;
const top = bounds.y - halfH;
const bottom = bounds.y + halfH;
const onLeft = Math.abs(p.x - left) <= tol && p.y >= top - tol && p.y <= bottom + tol;
const onRight = Math.abs(p.x - right) <= tol && p.y >= top - tol && p.y <= bottom + tol;
const onTop = Math.abs(p.y - top) <= tol && p.x >= left - tol && p.x <= right + tol;
const onBottom = Math.abs(p.y - bottom) <= tol && p.x >= left - tol && p.x <= right + tol;
return onLeft || onRight || onTop || onBottom;
};
/**
* Compute intersection between a rectangle (center x/y, width/height) and the line
* segment from insidePoint -\> outsidePoint. Returns the point on the rectangle border.
*
* This version avoids snapping to outsidePoint when certain variables evaluate to 0
* (previously caused vertical top/bottom cases to miss the border). It only enforces
* axis-constant behavior for purely vertical/horizontal approaches.
*/
export const intersection = (node: RectLike, outsidePoint: P, insidePoint: P): P => {
const x = node.x;
const y = node.y;
const dx = Math.abs(x - insidePoint.x);
const w = node.width / 2;
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
const h = node.height / 2;
const Q = Math.abs(outsidePoint.y - insidePoint.y);
const R = Math.abs(outsidePoint.x - insidePoint.x);
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
// Intersection is top or bottom of rect.
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,
};
// Keep axis-constant special-cases only
if (R === 0) {
res.x = outsidePoint.x;
}
if (Q === 0) {
res.y = outsidePoint.y;
}
return res;
} else {
// Intersection on sides of rect
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;
// Only handle axis-constant cases
if (R === 0) {
_x = outsidePoint.x;
}
if (Q === 0) {
_y = outsidePoint.y;
}
return { x: _x, y: _y };
}
};
export const outsideNode = (node: RectLike, point: P): boolean => {
const x = node.x;
const y = node.y;
const dx = Math.abs(point.x - x);
const dy = Math.abs(point.y - y);
const w = node.width / 2;
const h = node.height / 2;
return dx >= w || dy >= h;
};
export const ensureTrulyOutside = (bounds: RectLike, p: P, push = PUSH_OUT): P => {
const dx = Math.abs(p.x - bounds.x);
const dy = Math.abs(p.y - bounds.y);
const w = bounds.width / 2;
const h = bounds.height / 2;
if (Math.abs(dx - w) < EPS || Math.abs(dy - h) < EPS) {
const dirX = p.x - bounds.x;
const dirY = p.y - bounds.y;
const len = Math.sqrt(dirX * dirX + dirY * dirY);
if (len > 0) {
return {
x: bounds.x + (dirX / len) * (len + push),
y: bounds.y + (dirY / len) * (len + push),
};
}
}
return p;
};
export const makeInsidePoint = (bounds: RectLike, outside: P, center: P): P => {
const isVertical = Math.abs(outside.x - bounds.x) < EPS;
const isHorizontal = Math.abs(outside.y - bounds.y) < EPS;
return {
x: isVertical
? outside.x
: outside.x < bounds.x
? bounds.x - bounds.width / 4
: bounds.x + bounds.width / 4,
y: isHorizontal ? outside.y : center.y,
};
};
export const tryNodeIntersect = (node: NodeLike, bounds: RectLike, outside: P): P | null => {
if (!node?.intersect) {
return null;
}
const res = node.intersect(outside);
if (!res) {
return null;
}
const wrongSide =
(outside.x < bounds.x && res.x > bounds.x) || (outside.x > bounds.x && res.x < bounds.x);
if (wrongSide) {
return null;
}
const dist = Math.hypot(outside.x - res.x, outside.y - res.y);
if (dist <= EPS) {
return null;
}
return res;
};
export const fallbackIntersection = (bounds: RectLike, outside: P, center: P): P => {
const inside = makeInsidePoint(bounds, outside, center);
return intersection(bounds, outside, inside);
};
export const computeNodeIntersection = (
node: NodeLike,
bounds: RectLike,
outside: P,
center: P
): P => {
const outside2 = ensureTrulyOutside(bounds, outside);
return tryNodeIntersect(node, bounds, outside2) ?? fallbackIntersection(bounds, outside2, center);
};
export const replaceEndpoint = (
points: P[],
which: 'start' | 'end',
value: P | null | undefined,
tol = 0.1
) => {
if (!value || points.length === 0) {
return;
}
if (which === 'start') {
if (
points.length > 0 &&
Math.abs(points[0].x - value.x) < tol &&
Math.abs(points[0].y - value.y) < tol
) {
// duplicate start remove it
points.shift();
} else {
points[0] = value;
}
} else {
const last = points.length - 1;
if (
points.length > 0 &&
Math.abs(points[last].x - value.x) < tol &&
Math.abs(points[last].y - value.y) < tol
) {
// duplicate end remove it
points.pop();
} else {
points[last] = value;
}
}
};

View File

@@ -1,26 +1,11 @@
import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from 'mermaid';
// @ts-ignore TODO: Investigate D3 issue
import { curveLinear } from 'd3';
import ELK from 'elkjs/lib/elk.bundled.js';
import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from 'mermaid';
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
import {
type P,
type RectLike,
outsideNode,
computeNodeIntersection,
replaceEndpoint,
onBorder,
} from './geometry.js';
type Node = LayoutData['nodes'][number];
// Minimal structural type to avoid depending on d3 Selection typings
interface D3Selection<T extends Element> {
node(): T | null;
attr(name: string, value: string): D3Selection<T>;
}
// 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;
@@ -31,9 +16,18 @@ interface LabelData {
interface NodeWithVertex extends Omit<Node, 'domId'> {
children?: LayoutData['nodes'];
labelData?: LabelData;
domId?: D3Selection<SVGAElement | SVGGElement>;
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,
@@ -67,26 +61,39 @@ export const render = async (
// Add the element to the DOM
if (!node.isGroup) {
const child = node as NodeWithVertex;
// 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,
};
graph.children.push(child);
nodeDb[node.id] = node;
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 {
// A subgraph
const child: NodeWithVertex & { children: NodeWithVertex[] } = {
...node,
domId: undefined,
children: [],
};
// Let elk render with the copy
graph.children.push(child);
// Save the original containing the intersection function
nodeDb[node.id] = child;
await addVertices(nodeEl, nodeArr, child, node.id);
@@ -161,7 +168,7 @@ export const render = async (
height: node.height,
};
if (node.isGroup) {
log.debug('Id abc88 subgraph = ', node.id, node.x, node.y, node.labelData);
log.debug('id abc88 subgraph = ', node.id, node.x, node.y, node.labelData);
const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');
// TODO use faster way of cloning
const clusterNode = JSON.parse(JSON.stringify(node));
@@ -170,10 +177,10 @@ export const render = async (
clusterNode.width = Math.max(clusterNode.width, node.labelData.width);
await insertCluster(subgraphEl, clusterNode);
log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);
log.debug('id (UIO)= ', node.id, node.width, node.shape, node.labels);
} else {
log.info(
'Id NODE = ',
'id NODE = ',
node.id,
node.x,
node.y,
@@ -215,19 +222,25 @@ export const render = async (
});
});
subgraphs.forEach(function (subgraph: { id: string | number }) {
const data: any = { id: subgraph.id };
if (parentLookupDb.parentById[subgraph.id] !== undefined) {
data.parent = parentLookupDb.parentById[subgraph.id];
}
});
return parentLookupDb;
};
const getEdgeStartEndPoint = (edge: any) => {
// edge.start and edge.end are IDs (string/number) in our layout data
const sourceId: string | number = edge.start;
const targetId: string | number = edge.end;
const source: any = edge.start;
const target: any = edge.end;
const source = sourceId;
const target = targetId;
// Save the original source and target
const sourceId = source;
const targetId = target;
const startNode = nodeDb[sourceId];
const endNode = nodeDb[targetId];
const startNode = nodeDb[edge.start.id];
const endNode = nodeDb[edge.end.id];
if (!startNode || !endNode) {
return { source, target };
@@ -250,112 +263,6 @@ export const render = async (
/**
* Add edges to graph based on parsed graph definition
*/
// Edge helper maps and utilities (de-duplicated)
const ARROW_MAP: Record<string, [string, string]> = {
arrow_open: ['arrow_open', 'arrow_open'],
arrow_cross: ['arrow_open', 'arrow_cross'],
double_arrow_cross: ['arrow_cross', 'arrow_cross'],
arrow_point: ['arrow_open', 'arrow_point'],
double_arrow_point: ['arrow_point', 'arrow_point'],
arrow_circle: ['arrow_open', 'arrow_circle'],
double_arrow_circle: ['arrow_circle', 'arrow_circle'],
};
const computeStroke = (
stroke: string | undefined,
defaultStyle?: string,
defaultLabelStyle?: string
) => {
// Defaults correspond to 'normal'
let thickness = 'normal';
let pattern = 'solid';
let style = '';
let labelStyle = '';
if (stroke === 'dotted') {
pattern = 'dotted';
style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
} else if (stroke === 'thick') {
thickness = 'thick';
style = 'stroke-width: 3.5px;fill:none;';
} else {
// normal
style = defaultStyle ?? 'fill:none;';
if (defaultLabelStyle !== undefined) {
labelStyle = defaultLabelStyle;
}
}
return { thickness, pattern, style, labelStyle };
};
const getCurve = (edgeInterpolate: any, edgesDefaultInterpolate: any, confCurve: any) => {
if (edgeInterpolate !== undefined) {
return interpolateToCurve(edgeInterpolate, curveLinear);
}
if (edgesDefaultInterpolate !== undefined) {
return interpolateToCurve(edgesDefaultInterpolate, curveLinear);
}
// @ts-ignore TODO: fix this
return interpolateToCurve(confCurve, curveLinear);
};
const buildEdgeData = (
edge: any,
defaults: {
defaultStyle?: string;
defaultLabelStyle?: string;
defaultInterpolate?: any;
confCurve: any;
},
common: any
) => {
const edgeData: any = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;
// maintain legacy behavior
edge.text = edge.label;
// Arrowhead fill vs none
edgeData.arrowhead = edge.type === 'arrow_open' ? 'none' : 'normal';
// Arrow types
const arrowMap = ARROW_MAP[edge.type] ?? ARROW_MAP.arrow_open;
edgeData.arrowTypeStart = arrowMap[0];
edgeData.arrowTypeEnd = arrowMap[1];
// Optional edge label positioning flags
edgeData.startLabelRight = edge.startLabelRight;
edgeData.endLabelLeft = edge.endLabelLeft;
// Stroke
const strokeRes = computeStroke(edge.stroke, defaults.defaultStyle, defaults.defaultLabelStyle);
edgeData.thickness = strokeRes.thickness;
edgeData.pattern = strokeRes.pattern;
edgeData.style = (edgeData.style || '') + (strokeRes.style || '');
edgeData.labelStyle = (edgeData.labelStyle || '') + (strokeRes.labelStyle || '');
// Curve
// @ts-ignore - defaults.confCurve is present at runtime but missing in type
edgeData.curve = getCurve(edge.interpolate, defaults.defaultInterpolate, defaults.confCurve);
// Arrowhead style + labelpos when we have label text
const hasText = (edge?.text ?? '') !== '';
if (hasText) {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
} else if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
edgeData.labelType = edge.labelType;
edgeData.label = (edge?.text ?? '').replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style ?? 'stroke: #333; stroke-width: 1.5px;fill:none;';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
return edgeData;
};
const addEdges = async function (
dataForLayout: { edges: any; direction?: string },
graph: {
@@ -377,6 +284,7 @@ export const render = async (
const edges = dataForLayout.edges;
const labelsEl = svg.insert('g').attr('class', 'edgeLabels');
const linkIdCnt: any = {};
const dir = dataForLayout.direction || 'DOWN';
let defaultStyle: string | undefined;
let defaultLabelStyle: string | undefined;
@@ -406,24 +314,105 @@ export const render = async (
linkIdCnt[linkIdBase]++;
log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]);
}
const linkId = linkIdBase; // + '_' + linkIdCnt[linkIdBase];
const linkId = linkIdBase + '_' + linkIdCnt[linkIdBase];
edge.id = linkId;
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
const linkNameStart = 'LS_' + edge.start;
const linkNameEnd = 'LE_' + edge.end;
const edgeData: any = { style: '', labelStyle: '' };
edgeData.minlen = edge.length || 1;
edge.text = edge.label;
// Set link type for rendering
if (edge.type === 'arrow_open') {
edgeData.arrowhead = 'none';
} else {
edgeData.arrowhead = 'normal';
}
// Check of arrow types, placed here in order not to break old rendering
edgeData.arrowTypeStart = 'arrow_open';
edgeData.arrowTypeEnd = 'arrow_open';
/* eslint-disable no-fallthrough */
switch (edge.type) {
case 'double_arrow_cross':
edgeData.arrowTypeStart = 'arrow_cross';
case 'arrow_cross':
edgeData.arrowTypeEnd = 'arrow_cross';
break;
case 'double_arrow_point':
edgeData.arrowTypeStart = 'arrow_point';
case 'arrow_point':
edgeData.arrowTypeEnd = 'arrow_point';
break;
case 'double_arrow_circle':
edgeData.arrowTypeStart = 'arrow_circle';
case 'arrow_circle':
edgeData.arrowTypeEnd = 'arrow_circle';
break;
}
let style = '';
let labelStyle = '';
edgeData.startLabelRight = edge.startLabelRight;
edgeData.endLabelLeft = edge.endLabelLeft;
switch (edge.stroke) {
case 'normal':
style = 'fill:none;';
if (defaultStyle !== undefined) {
style = defaultStyle;
}
if (defaultLabelStyle !== undefined) {
labelStyle = defaultLabelStyle;
}
edgeData.thickness = 'normal';
edgeData.pattern = 'solid';
break;
case 'dotted':
edgeData.thickness = 'normal';
edgeData.pattern = 'dotted';
edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
break;
case 'thick':
edgeData.thickness = 'thick';
edgeData.pattern = 'solid';
edgeData.style = 'stroke-width: 3.5px;fill:none;';
break;
}
edgeData.style = edgeData.style += style;
edgeData.labelStyle = edgeData.labelStyle += labelStyle;
const conf = getConfig();
const edgeData = buildEdgeData(
edge,
{
defaultStyle,
defaultLabelStyle,
defaultInterpolate: edges.defaultInterpolate,
// @ts-ignore - conf.curve exists at runtime but is missing from typing
confCurve: conf.curve,
},
common
);
if (edge.interpolate !== undefined) {
edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear);
} else if (edges.defaultInterpolate !== undefined) {
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear);
} else {
// @ts-ignore TODO: fix this
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
}
if (edge.text === undefined) {
if (edge.style !== undefined) {
edgeData.arrowheadStyle = 'fill: #333';
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
edgeData.labelpos = 'c';
}
edgeData.labelType = edge.labelType;
edgeData.label = (edge?.text || '').replace(common.lineBreakRegex, '\n');
if (edge.style === undefined) {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;';
}
edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:');
edgeData.id = linkId;
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
@@ -432,11 +421,13 @@ export const render = async (
// calculate start and end points of the edge, note that the source and target
// can be modified for shapes that have ports
const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge);
// @ts-ignore TODO: fix this
const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
log.debug('abc78 source and target', source, target);
// Add the edge to the graph
graph.edges.push({
// @ts-ignore TODO: fix this
id: 'e' + edge.start + edge.end,
...edge,
sources: [source],
targets: [target],
@@ -470,7 +461,6 @@ export const render = async (
case 'RL':
return 'LEFT';
case 'TB':
case 'TD': // TD is an alias for TB in Mermaid
return 'DOWN';
case 'BT':
return 'UP';
@@ -494,203 +484,6 @@ export const render = async (
}
}
// Node bounds helpers (global)
const getEffectiveGroupWidth = (node: any): number => {
const labelW = node?.labels?.[0]?.width ?? 0;
const padding = node?.padding ?? 0;
return Math.max(node.width ?? 0, labelW + padding);
};
const boundsFor = (node: any): RectLike => {
const width = node?.isGroup ? getEffectiveGroupWidth(node) : node.width;
return {
x: node.offset.posX + node.width / 2,
y: node.offset.posY + node.height / 2,
width,
height: node.height,
padding: node.padding,
};
};
// Helper utilities for endpoint handling around cutter2
type Side = 'start' | 'end';
const approxEq = (a: number, b: number, eps = 1e-6) => Math.abs(a - b) < eps;
const isCenterApprox = (pt: P, node: { x: number; y: number }) =>
approxEq(pt.x, node.x) && approxEq(pt.y, node.y);
const getCandidateBorderPoint = (
points: P[],
node: any,
side: Side
): { candidate: P; centerApprox: boolean } => {
if (!points?.length) {
return { candidate: { x: node.x, y: node.y } as P, centerApprox: true };
}
if (side === 'start') {
const first = points[0];
const centerApprox = isCenterApprox(first, node);
const candidate = centerApprox && points.length > 1 ? points[1] : first;
return { candidate, centerApprox };
} else {
const last = points[points.length - 1];
const centerApprox = isCenterApprox(last, node);
const candidate = centerApprox && points.length > 1 ? points[points.length - 2] : last;
return { candidate, centerApprox };
}
};
const dropAutoCenterPoint = (points: P[], side: Side, doDrop: boolean) => {
if (!doDrop) {
return;
}
if (side === 'start') {
if (points.length > 0) {
points.shift();
}
} else {
if (points.length > 0) {
points.pop();
}
}
};
const applyStartIntersectionIfNeeded = (points: P[], startNode: any, startBounds: RectLike) => {
let firstOutsideStartIndex = -1;
for (const [i, p] of points.entries()) {
if (outsideNode(startBounds, p)) {
firstOutsideStartIndex = i;
break;
}
}
if (firstOutsideStartIndex !== -1) {
const outsidePointForStart = points[firstOutsideStartIndex];
const startCenter = points[0];
const startIntersection = computeNodeIntersection(
startNode,
startBounds,
outsidePointForStart,
startCenter
);
replaceEndpoint(points, 'start', startIntersection);
log.debug('UIO cutter2: start-only intersection applied', { startIntersection });
}
};
const applyEndIntersectionIfNeeded = (points: P[], endNode: any, endBounds: RectLike) => {
let outsideIndexForEnd = -1;
for (let i = points.length - 1; i >= 0; i--) {
if (outsideNode(endBounds, points[i])) {
outsideIndexForEnd = i;
break;
}
}
if (outsideIndexForEnd !== -1) {
const outsidePointForEnd = points[outsideIndexForEnd];
const endCenter = points[points.length - 1];
const endIntersection = computeNodeIntersection(
endNode,
endBounds,
outsidePointForEnd,
endCenter
);
replaceEndpoint(points, 'end', endIntersection);
log.debug('UIO cutter2: end-only intersection applied', { endIntersection });
}
};
const cutter2 = (startNode: any, endNode: any, _points: any[]) => {
const startBounds = boundsFor(startNode);
const endBounds = boundsFor(endNode);
if (_points.length === 0) {
return [];
}
// Copy the original points array
const points: P[] = [..._points] as P[];
// The first point is the center of sNode, the last point is the center of eNode
const startCenter = points[0];
const endCenter = points[points.length - 1];
// Minimal, structured logging for diagnostics
log.debug('PPP cutter2: bounds', { startBounds, endBounds });
log.debug('PPP cutter2: original points', _points);
let firstOutsideStartIndex = -1;
// Single iteration through the array
for (const [i, point] of points.entries()) {
if (firstOutsideStartIndex === -1 && outsideNode(startBounds, point)) {
firstOutsideStartIndex = i;
}
if (outsideNode(endBounds, point)) {
// keep scanning; we'll also scan from the end for the last outside point
}
}
// Calculate intersection with start node if we found a point outside it
if (firstOutsideStartIndex !== -1) {
const outsidePointForStart = points[firstOutsideStartIndex];
const startIntersection = computeNodeIntersection(
startNode,
startBounds,
outsidePointForStart,
startCenter
);
log.debug('UIO cutter2: start intersection', startIntersection);
replaceEndpoint(points, 'start', startIntersection);
}
// Calculate intersection with end node
let outsidePointForEnd = null;
let outsideIndexForEnd = -1;
for (let i = points.length - 1; i >= 0; i--) {
if (outsideNode(endBounds, points[i])) {
outsidePointForEnd = points[i];
outsideIndexForEnd = i;
break;
}
}
if (!outsidePointForEnd && points.length > 1) {
outsidePointForEnd = points[points.length - 2];
outsideIndexForEnd = points.length - 2;
}
if (outsidePointForEnd) {
const endIntersection = computeNodeIntersection(
endNode,
endBounds,
outsidePointForEnd,
endCenter
);
log.debug('UIO cutter2: end intersection', { endIntersection, outsideIndexForEnd });
replaceEndpoint(points, 'end', endIntersection);
}
// Final cleanup: Check if the last point is too close to the previous point
if (points.length > 1) {
const lastPoint = points[points.length - 1];
const secondLastPoint = points[points.length - 2];
const distance = Math.sqrt(
(lastPoint.x - secondLastPoint.x) ** 2 + (lastPoint.y - secondLastPoint.y) ** 2
);
if (distance < 2) {
log.debug('UIO cutter2: trimming tail point (too close)', {
distance,
lastPoint,
secondLastPoint,
});
points.pop();
}
}
log.debug('UIO cutter2: final points', points);
return points;
};
// @ts-ignore - ELK is not typed
const elk = new ELK();
const element = svg.select('g');
@@ -702,19 +495,17 @@ export const render = async (
id: 'root',
layoutOptions: {
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
'elk.layered.crossingMinimization.forceNodeModelOrder':
data4Layout.config.elk?.forceNodeModelOrder,
'elk.layered.considerModelOrder.strategy': data4Layout.config.elk?.considerModelOrder,
'elk.algorithm': algorithm,
'nodePlacement.strategy': data4Layout.config.elk?.nodePlacementStrategy,
'elk.layered.mergeEdges': data4Layout.config.elk?.mergeEdges,
'elk.direction': 'DOWN',
'spacing.baseValue': 40,
'elk.layered.crossingMinimization.forceNodeModelOrder':
data4Layout.config.elk?.forceNodeModelOrder,
'elk.layered.considerModelOrder.strategy': data4Layout.config.elk?.considerModelOrder,
'spacing.baseValue': 35,
'elk.layered.unnecessaryBendpoints': true,
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk?.cycleBreakingStrategy,
// 'elk.layered.cycleBreaking.strategy': 'GREEDY_MODEL_ORDER',
// 'elk.layered.cycleBreaking.strategy': 'MODEL_ORDER',
// 'spacing.nodeNode': 20,
// 'spacing.nodeNodeBetweenLayers': 25,
// 'spacing.edgeNode': 20,
@@ -722,28 +513,22 @@ export const render = async (
// 'spacing.edgeEdge': 10,
// 'spacing.edgeEdgeBetweenLayers': 20,
// 'spacing.nodeSelfLoop': 20,
// Tweaking options
// 'nodePlacement.favorStraightEdges': true,
// 'elk.layered.nodePlacement.favorStraightEdges': true,
// 'nodePlacement.feedbackEdges': true,
'elk.layered.wrapping.multiEdge.improveCuts': true,
'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
// 'elk.layered.wrapping.multiEdge.improveCuts': true,
// 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
// 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
// 'elk.layered.wrapping.strategy': 'SINGLE_EDGE',
'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
'elk.layered.mergeHierarchyEdges': true,
// 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
// 'elk.layered.mergeHierarchyEdges': true,
// 'elk.layered.feedbackEdges': true,
// 'elk.layered.crossingMinimization.semiInteractive': true,
// 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
// 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
// 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
// 'elk.insideSelfLoops.activate': true,
// 'elk.separateConnectedComponents': true,
// 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',
// 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES
// 'elk.layered.considerModelOrder.strategy': 'EDGES', // NODES_AND_EDGES
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
},
children: [],
@@ -753,7 +538,7 @@ export const render = async (
log.info('Drawing flowchart using v4 renderer', elk);
// Set the direction of the graph based on the parsed information
const dir = data4Layout.direction ?? 'DOWN';
const dir = data4Layout.direction || 'DOWN';
elkGraph.layoutOptions['elk.direction'] = dir2ElkDirection(dir);
// Create the lookup db for the subgraphs and their children to used when creating
@@ -784,16 +569,15 @@ export const render = async (
// Subgraph
if (parentLookupDb.childrenById[node.id] !== undefined) {
// Set label and adjust node width separately (avoid side effects in labels array)
node.labels = [
{
text: node.label,
width: node?.labelData?.width ?? 50,
height: node?.labelData?.height ?? 50,
width: node?.labelData?.width || 50,
height: node?.labelData?.height || 50,
},
(node.width = node.width + 2 * node.padding),
log.debug('UIO node label', node?.labelData?.width, node.padding),
];
node.width = node.width + 2 * node.padding;
log.debug('UIO node label', node?.labelData?.width, node.padding);
node.layoutOptions = {
'spacing.baseValue': 30,
'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]',
@@ -857,7 +641,7 @@ export const render = async (
try {
g = await elk.layout(elkGraph);
log.debug('APA01 after - success');
log.info('APA01 layout result:', JSON.stringify(g, null, 2));
log.debug('APA01 layout result:', JSON.stringify(g, null, 2));
} catch (error) {
log.error('APA01 ELK layout error:', error);
throw error;
@@ -918,10 +702,10 @@ export const render = async (
// sw = Math.max(bbox.width, startNode.width, startNode.labels[0].width);
sw = Math.max(startNode.width, startNode.labels[0].width + startNode.padding);
// sw = startNode.width;
log.info(
log.debug(
'UIO width',
startNode.id,
startNode.width,
startNode.with,
'bbox.width=',
bbox.width,
'lw=',
@@ -941,7 +725,7 @@ export const render = async (
log.debug(
'UIO width',
startNode.id,
startNode.width,
startNode.with,
bbox.width,
'EW = ',
ew,
@@ -949,109 +733,38 @@ export const render = async (
startNode.innerHTML
);
}
startNode.x = startNode.offset.posX + startNode.width / 2;
startNode.y = startNode.offset.posY + startNode.height / 2;
endNode.x = endNode.offset.posX + endNode.width / 2;
endNode.y = endNode.offset.posY + endNode.height / 2;
// Only add center points for non-subgraph nodes or when the edge path doesn't already end near the target
const shouldAddStartCenter = startNode.shape !== 'rect33';
const shouldAddEndCenter = endNode.shape !== 'rect33';
if (shouldAddStartCenter) {
edge.points.unshift({
x: startNode.x,
y: startNode.y,
});
}
if (shouldAddEndCenter) {
edge.points.push({
x: endNode.x,
y: endNode.y,
});
}
// Debug and sanitize points around cutter2
const prevPoints = Array.isArray(edge.points) ? [...edge.points] : [];
const endBounds = boundsFor(endNode);
log.debug(
'PPP cutter2: Points before cutter2:',
JSON.stringify(edge.points),
'endBounds:',
endBounds,
onBorder(endBounds, edge.points[edge.points.length - 1])
);
// Block for reducing variable scope and guardrails for the cutter function
{
const startBounds = boundsFor(startNode);
const endBounds = boundsFor(endNode);
const startIsGroup = !!startNode?.isGroup;
const endIsGroup = !!endNode?.isGroup;
const { candidate: startCandidate, centerApprox: startCenterApprox } =
getCandidateBorderPoint(prevPoints as P[], startNode, 'start');
const { candidate: endCandidate, centerApprox: endCenterApprox } =
getCandidateBorderPoint(prevPoints as P[], endNode, 'end');
const skipStart = startIsGroup && onBorder(startBounds, startCandidate);
const skipEnd = endIsGroup && onBorder(endBounds, endCandidate);
dropAutoCenterPoint(prevPoints as P[], 'start', skipStart && startCenterApprox);
dropAutoCenterPoint(prevPoints as P[], 'end', skipEnd && endCenterApprox);
if (skipStart || skipEnd) {
if (!skipStart) {
applyStartIntersectionIfNeeded(prevPoints as P[], startNode, startBounds);
}
if (!skipEnd) {
applyEndIntersectionIfNeeded(prevPoints as P[], endNode, endBounds);
}
log.debug('PPP cutter2: skipping cutter2 due to on-border group endpoint(s)', {
skipStart,
skipEnd,
startCenterApprox,
endCenterApprox,
startCandidate,
endCandidate,
});
edge.points = prevPoints;
} else {
edge.points = cutter2(startNode, endNode, prevPoints);
}
}
log.debug('PPP cutter2: Points after cutter2:', JSON.stringify(edge.points));
const hasNaN = (pts: { x: number; y: number }[]) =>
pts?.some((p) => !Number.isFinite(p?.x) || !Number.isFinite(p?.y));
if (!Array.isArray(edge.points) || edge.points.length < 2 || hasNaN(edge.points)) {
log.warn(
'POI cutter2: Invalid points from cutter2, falling back to prevPoints',
edge.points
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]
);
// Fallback to previous points and strip any invalid ones just in case
const cleaned = prevPoints.filter((p) => Number.isFinite(p?.x) && Number.isFinite(p?.y));
edge.points = cleaned.length >= 2 ? cleaned : prevPoints;
}
log.debug('UIO cutter2: Points after cutter2 (sanitized):', edge.points);
// Remove consecutive duplicate points to avoid zero-length segments in path builders
const deduped = edge.points.filter(
(p: { x: number; y: number }, i: number, arr: { x: number; y: number }[]) => {
if (i === 0) {
return true;
}
const prev = arr[i - 1];
return Math.abs(p.x - prev.x) > 1e-6 || Math.abs(p.y - prev.y) > 1e-6;
if (distance(intersection, edge.points[0]) > epsilon) {
edge.points.unshift(intersection);
}
);
if (deduped.length !== edge.points.length) {
log.debug('UIO cutter2: removed consecutive duplicate points', {
before: edge.points,
after: deduped,
});
}
edge.points = deduped;
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);
}
}
const paths = insertEdge(
edgesEl,
edge,
@@ -1059,10 +772,8 @@ export const render = async (
data4Layout.type,
startNode,
endNode,
data4Layout.diagramId,
true
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;

View File

@@ -1,12 +0,0 @@
# @mermaid-js/layout-tidy-tree
## 0.2.0
### Minor Changes
- [#6802](https://github.com/mermaid-js/mermaid/pull/6802) [`c8e5027`](https://github.com/mermaid-js/mermaid/commit/c8e50276e877c4de7593a09ec458c99353e65af8) Thanks [@darshanr0107](https://github.com/darshanr0107)! - feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
### Patch Changes
- Updated dependencies [[`33bc4a0`](https://github.com/mermaid-js/mermaid/commit/33bc4a0b4e2ca6d937bb0a8c4e2081b1362b2800), [`e0b45c2`](https://github.com/mermaid-js/mermaid/commit/e0b45c2d2b41c2a9038bf87646fa3ccd7560eb20), [`012530e`](https://github.com/mermaid-js/mermaid/commit/012530e98e9b8b80962ab270b6bb3b6d9f6ada05), [`c8e5027`](https://github.com/mermaid-js/mermaid/commit/c8e50276e877c4de7593a09ec458c99353e65af8)]:
- mermaid@11.11.0

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/layout-tidy-tree",
"version": "0.2.0",
"version": "0.1.0",
"description": "Tidy-tree layout engine for mermaid",
"module": "dist/mermaid-layout-tidy-tree.core.mjs",
"types": "dist/layouts.d.ts",

View File

@@ -33,7 +33,7 @@
],
"license": "MIT",
"dependencies": {
"@zenuml/core": "^3.41.4"
"@zenuml/core": "^3.35.2"
},
"devDependencies": {
"mermaid": "workspace:^"

View File

@@ -1,19 +1,5 @@
# mermaid
## 11.11.0
### Minor Changes
- [#6704](https://github.com/mermaid-js/mermaid/pull/6704) [`012530e`](https://github.com/mermaid-js/mermaid/commit/012530e98e9b8b80962ab270b6bb3b6d9f6ada05) Thanks [@omkarht](https://github.com/omkarht)! - feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.
- [#6802](https://github.com/mermaid-js/mermaid/pull/6802) [`c8e5027`](https://github.com/mermaid-js/mermaid/commit/c8e50276e877c4de7593a09ec458c99353e65af8) Thanks [@darshanr0107](https://github.com/darshanr0107)! - feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
### Patch Changes
- [#6905](https://github.com/mermaid-js/mermaid/pull/6905) [`33bc4a0`](https://github.com/mermaid-js/mermaid/commit/33bc4a0b4e2ca6d937bb0a8c4e2081b1362b2800) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Render newlines as spaces in class diagrams
- [#6886](https://github.com/mermaid-js/mermaid/pull/6886) [`e0b45c2`](https://github.com/mermaid-js/mermaid/commit/e0b45c2d2b41c2a9038bf87646fa3ccd7560eb20) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Handle arrows correctly when auto number is enabled
## 11.10.0
### Minor Changes
@@ -168,6 +154,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 ...)
@@ -186,12 +173,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

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.11.0",
"version": "11.10.0",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -67,29 +67,29 @@
]
},
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.2",
"@braintree/sanitize-url": "^7.0.4",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
"cytoscape": "^3.33.1",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.18",
"dayjs": "^1.11.13",
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"marked": "^16.3.0",
"marked": "^16.0.0",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
"ts-dedent": "^2.2.0",
"uuid": "^11.1.0"
},
"devDependencies": {
"@adobe/jsonschema2md": "^8.0.5",
"@adobe/jsonschema2md": "^8.0.2",
"@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.9",
"@types/cytoscape-fcose": "^2.2.4",
@@ -105,30 +105,30 @@
"@types/stylis": "^4.2.7",
"@types/uuid": "^10.0.0",
"ajv": "^8.17.1",
"canvas": "^3.2.0",
"canvas": "^3.1.0",
"chokidar": "3.6.0",
"concurrently": "^9.2.1",
"concurrently": "^9.1.2",
"csstree-validator": "^4.0.1",
"globby": "^14.1.0",
"globby": "^14.0.2",
"jison": "^0.4.18",
"js-base64": "^3.7.8",
"js-base64": "^3.7.7",
"jsdom": "^26.1.0",
"json-schema-to-typescript": "^15.0.4",
"micromatch": "^4.0.8",
"path-browserify": "^1.0.1",
"prettier": "^3.6.2",
"prettier": "^3.5.2",
"remark": "^15.0.1",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"rimraf": "^6.0.1",
"start-server-and-test": "^2.1.2",
"type-fest": "^4.41.0",
"typedoc": "^0.28.13",
"typedoc-plugin-markdown": "^4.8.1",
"start-server-and-test": "^2.0.10",
"type-fest": "^4.35.0",
"typedoc": "^0.28.9",
"typedoc-plugin-markdown": "^4.8.0",
"typescript": "~5.7.3",
"unist-util-flatmap": "^1.0.0",
"unist-util-visit": "^5.0.0",
"vitepress": "^1.6.4",
"vitepress": "^1.0.2",
"vitepress-plugin-search": "1.0.4-alpha.22"
},
"files": [

View File

@@ -227,8 +227,6 @@ export const reset = (config = siteConfig): void => {
const ConfigWarning = {
LAZY_LOAD_DEPRECATED:
'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
FLOWCHART_HTML_LABELS_DEPRECATED:
'flowchart.htmlLabels is deprecated. Please use global htmlLabels instead.',
} as const;
type ConfigWarningStrings = keyof typeof ConfigWarning;
@@ -264,15 +262,3 @@ export const getUserDefinedConfig = (): MermaidConfig => {
return userConfig;
};
/**
* Helper function to handle deprecated flowchart.htmlLabels
* @param config - The configuration object
*/
export const getEffectiveHtmlLabels = (config: MermaidConfig): boolean => {
if (config.flowchart?.htmlLabels !== undefined) {
issueWarning('FLOWCHART_HTML_LABELS_DEPRECATED');
}
return config.htmlLabels ?? config.flowchart?.htmlLabels ?? true;
};

View File

@@ -248,12 +248,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
*/
diagramPadding?: number;
/**
* @deprecated
* **DEPRECATED: Use global `htmlLabels` instead.**
*
* Flag for setting whether or not a html tag should be used for rendering labels on nodes and edges.
* This property is deprecated.
* Please use the global `htmlLabels` configuration instead.
* Flag for setting whether or not a html tag should be used for rendering labels on the edges.
*
*/
htmlLabels?: boolean;

View File

@@ -4,8 +4,7 @@ import createLabel from './createLabel.js';
import { createText } from '../rendering-util/createText.js';
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../config.js';
import { evaluate } from '../diagrams/common/common.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
const rect = async (parent, node) => {
@@ -21,7 +20,7 @@ const rect = async (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const useHtmlLabels = getEffectiveHtmlLabels(siteConfig);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
@@ -39,7 +38,7 @@ const rect = async (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@@ -151,7 +150,7 @@ const roundedWithTitle = async (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@@ -191,7 +190,7 @@ const roundedWithTitle = async (parent, node) => {
node.y -
node.height / 2 -
node.padding / 3 +
(getEffectiveHtmlLabels(siteConfig) ? 5 : 3) +
(evaluate(siteConfig.flowchart.htmlLabels) ? 5 : 3) +
subGraphTitleTopMargin
})`
);

View File

@@ -1,7 +1,6 @@
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../config.js';
import { sanitizeText } from '../diagrams/common/common.js';
import { evaluate, sanitizeText } from '../diagrams/common/common.js';
import { log } from '../logger.js';
import { replaceIconSubstring } from '../rendering-util/createText.js';
import { decodeEntities } from '../utils.js';
@@ -51,7 +50,7 @@ const createLabel = async (_vertexText, style, isTitle, isNode) => {
vertexText = vertexText[0];
}
const config = getConfig();
if (getEffectiveHtmlLabels(config)) {
if (evaluate(config.flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.debug('vertexText' + vertexText);

View File

@@ -3,9 +3,8 @@ import createLabel from './createLabel.js';
import { createText } from '../rendering-util/createText.js';
import { line, curveBasis, select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../config.js';
import utils from '../utils.js';
import { getUrl } from '../diagrams/common/common.js';
import { evaluate, getUrl } from '../diagrams/common/common.js';
import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
import { addEdgeMarkers } from './edgeMarker.js';
@@ -20,7 +19,7 @@ export const clear = () => {
export const insertEdgeLabel = async (elem, edge) => {
const config = getConfig();
const useHtmlLabels = getEffectiveHtmlLabels(config);
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
// Create the actual text element
const labelElement =
edge.labelType === 'markdown'
@@ -134,7 +133,7 @@ export const insertEdgeLabel = async (elem, edge) => {
* @param {any} value
*/
function setTerminalWidth(fo, value) {
if (getEffectiveHtmlLabels(getConfig()) && fo) {
if (getConfig().flowchart.htmlLabels && fo) {
fo.style.width = value.length * 9 + 'px';
fo.style.height = '12px';
}

View File

@@ -1,7 +1,6 @@
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../config.js';
import { evaluate } from '../diagrams/common/common.js';
import { log } from '../logger.js';
import { getArrowPoints } from './blockArrowHelper.js';
import createLabel from './createLabel.js';
@@ -589,7 +588,7 @@ const rectWithTitle = async (parent, node) => {
const text = label.node().appendChild(await createLabel(title, node.labelStyle, true, true));
let bbox = { width: 0, height: 0 };
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@@ -610,7 +609,7 @@ const rectWithTitle = async (parent, node) => {
)
);
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = descr.children[0];
const dv = select(descr);
bbox = div.getBoundingClientRect();
@@ -918,7 +917,7 @@ const class_box = async (parent, node) => {
.node()
.appendChild(await createLabel(interfaceLabelText, node.labelStyle, true, true));
let interfaceBBox = interfaceLabel.getBBox();
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = interfaceLabel.children[0];
const dv = select(interfaceLabel);
interfaceBBox = div.getBoundingClientRect();
@@ -933,7 +932,7 @@ const class_box = async (parent, node) => {
let classTitleString = node.classData.label;
if (node.classData.type !== undefined && node.classData.type !== '') {
if (getEffectiveHtmlLabels(getConfig())) {
if (getConfig().flowchart.htmlLabels) {
classTitleString += '&lt;' + node.classData.type + '&gt;';
} else {
classTitleString += '<' + node.classData.type + '>';
@@ -944,7 +943,7 @@ const class_box = async (parent, node) => {
.appendChild(await createLabel(classTitleString, node.labelStyle, true, true));
select(classTitleLabel).attr('class', 'classTitle');
let classTitleBBox = classTitleLabel.getBBox();
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = classTitleLabel.children[0];
const dv = select(classTitleLabel);
classTitleBBox = div.getBoundingClientRect();
@@ -959,7 +958,7 @@ const class_box = async (parent, node) => {
node.classData.members.forEach(async (member) => {
const parsedInfo = member.getDisplayDetails();
let parsedText = parsedInfo.displayText;
if (getEffectiveHtmlLabels(getConfig())) {
if (getConfig().flowchart.htmlLabels) {
parsedText = parsedText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
const lbl = labelContainer
@@ -973,7 +972,7 @@ const class_box = async (parent, node) => {
)
);
let bbox = lbl.getBBox();
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = lbl.children[0];
const dv = select(lbl);
bbox = div.getBoundingClientRect();
@@ -993,7 +992,7 @@ const class_box = async (parent, node) => {
node.classData.methods.forEach(async (member) => {
const parsedInfo = member.getDisplayDetails();
let displayText = parsedInfo.displayText;
if (getEffectiveHtmlLabels(getConfig())) {
if (getConfig().flowchart.htmlLabels) {
displayText = displayText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
const lbl = labelContainer
@@ -1007,7 +1006,7 @@ const class_box = async (parent, node) => {
)
);
let bbox = lbl.getBBox();
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
const div = lbl.children[0];
const dv = select(lbl);
bbox = div.getBoundingClientRect();

View File

@@ -1,11 +1,10 @@
import { updateNodeBounds, labelHelper } from './util.js';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import intersect from '../intersect/index.js';
const note = async (parent, node) => {
const useHtmlLabels = getEffectiveHtmlLabels(getConfig()) || node.useHtmlLabels;
const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart.htmlLabels;
if (!useHtmlLabels) {
node.centerLabel = true;
}

View File

@@ -1,15 +1,14 @@
import createLabel from '../createLabel.js';
import { createText } from '../../rendering-util/createText.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import { select } from 'd3';
import { sanitizeText } from '../../diagrams/common/common.js';
import { evaluate, sanitizeText } from '../../diagrams/common/common.js';
import { decodeEntities } from '../../utils.js';
export const labelHelper = async (parent, node, _classes, isNode) => {
const config = getConfig();
let classes;
const useHtmlLabels = node.useHtmlLabels || getEffectiveHtmlLabels(config);
const useHtmlLabels = node.useHtmlLabels || evaluate(config.flowchart.htmlLabels);
if (!_classes) {
classes = 'node default';
} else {
@@ -61,7 +60,7 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
let bbox = text.getBBox();
const halfPadding = node.padding / 2;
if (getEffectiveHtmlLabels(config)) {
if (evaluate(config.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);

View File

@@ -3,7 +3,6 @@ import type * as d3 from 'd3';
import type { SetOptional, SetRequired } from 'type-fest';
import type { Diagram } from '../Diagram.js';
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
import type { DiagramOrientation } from '../diagrams/git/gitGraphTypes.js';
export interface DiagramMetadata {
title?: string;
@@ -36,8 +35,7 @@ export interface DiagramDB {
getAccTitle?: () => string;
setAccDescription?: (description: string) => void;
getAccDescription?: () => string;
getDirection?: () => string | undefined;
setDirection?: (dir: DiagramOrientation) => void;
setDisplayMode?: (title: string) => void;
bindFunctions?: (element: Element) => void;
}

View File

@@ -1,48 +0,0 @@
import { describe } from 'vitest';
import { draw } from './architectureRenderer.js';
import { Diagram } from '../../Diagram.js';
import { addDetector } from '../../diagram-api/detectType.js';
import architectureDetector from './architectureDetector.js';
import { ensureNodeFromSelector, jsdomIt } from '../../tests/util.js';
const { id, detector, loader } = architectureDetector;
addDetector(id, detector, loader); // Add architecture schemas to Mermaid
describe('architecture diagram SVGs', () => {
jsdomIt('should add ids', async () => {
const svgNode = await drawDiagram(`
architecture-beta
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
`);
const nodesForGroup = svgNode.querySelectorAll(`#group-api`);
expect(nodesForGroup.length).toBe(1);
const serviceIds = [...svgNode.querySelectorAll(`[id^=service-]`)].map(({ id }) => id).sort();
expect(serviceIds).toStrictEqual([
'service-db',
'service-disk1',
'service-disk2',
'service-server',
]);
const edgeIds = [...svgNode.querySelectorAll(`.edge[id^=L_]`)].map(({ id }) => id).sort();
expect(edgeIds).toStrictEqual(['L_db_server_0', 'L_disk1_server_0', 'L_disk2_db_0']);
});
});
async function drawDiagram(diagramText: string): Promise<Element> {
const diagram = await Diagram.fromText(diagramText, {});
await draw('NOT_USED', 'svg', '1.0.0', diagram);
return ensureNodeFromSelector('#svg');
}

View File

@@ -20,7 +20,6 @@ import {
type ArchitectureJunction,
type ArchitectureService,
} from './architectureTypes.js';
import { getEdgeId } from '../../utils.js';
export const drawEdges = async function (
edgesEl: D3Element,
@@ -92,8 +91,7 @@ export const drawEdges = async function (
g.insert('path')
.attr('d', `M ${startX},${startY} L ${midX},${midY} L${endX},${endY} `)
.attr('class', 'edge')
.attr('id', getEdgeId(source, target, { prefix: 'L' }));
.attr('class', 'edge');
if (sourceArrow) {
const xShift = isArchitectureDirectionX(sourceDir)
@@ -208,9 +206,8 @@ export const drawGroups = async function (
if (data.type === 'group') {
const { h, w, x1, y1 } = node.boundingBox();
const groupsNode = groupsEl.append('rect');
groupsNode
.attr('id', `group-${data.id}`)
groupsEl
.append('rect')
.attr('x', x1 + halfIconSize)
.attr('y', y1 + halfIconSize)
.attr('width', w)
@@ -265,7 +262,6 @@ export const drawGroups = async function (
')'
);
}
db.setElementForId(data.id, groupsNode);
}
})
);
@@ -346,9 +342,9 @@ export const drawServices = async function (
);
}
serviceElem.attr('id', `service-${service.id}`).attr('class', 'architecture-service');
serviceElem.attr('class', 'architecture-service');
const { width, height } = serviceElem.node().getBBox();
const { width, height } = serviceElem._groups[0][0].getBBox();
service.width = width;
service.height = height;
db.setElementForId(service.id, serviceElem);

View File

@@ -3,7 +3,6 @@ import { select, curveLinear } from 'd3';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import { render } from '../../dagre-wrapper/index.js';
import utils, { getEdgeId } from '../../utils.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
@@ -261,7 +260,7 @@ export const addRelations = function (relations: ClassRelation[], g: graphlib.Gr
edgeData.labelpos = 'c';
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
if (getEffectiveHtmlLabels(getConfig())) {
if (getConfig().flowchart?.htmlLabels ?? getConfig().htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else {

View File

@@ -36,15 +36,57 @@ export async function textHelper<T extends SVGGraphicsElement>(
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
if (node.annotations.length > 0) {
const annotation = node.annotations[0];
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
const annotation = node.annotations[0].toLowerCase();
let isSupported = false;
switch (annotation) {
case 'interface':
case 'abstract':
case 'enumeration':
isSupported = true;
break;
}
if (!isSupported) {
await addText(
annotationGroup,
{ text: `«${node.annotations[0]}»` } as unknown as ClassMember,
0,
[]
);
annotationGroup.style('opacity', '1');
}
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroupHeight = annotationGroupBBox.height;
}
labelGroup = shapeSvg.insert('g').attr('class', 'label-group text');
await addText(labelGroup, node, 0, ['font-weight: bolder']);
// Determine styling based on annotations
let labelStyles = [''];
let labelClass = '';
if (node.annotations && node.annotations.length > 0) {
const annotation = node.annotations[0].toLowerCase();
switch (annotation) {
case 'abstract':
labelClass = 'abstract';
labelStyles = [];
break;
case 'enumeration':
labelClass = 'enumeration';
labelStyles = [];
break;
case 'interface':
labelClass = 'interface';
labelStyles = [];
break;
default:
labelClass = '';
labelStyles = [];
break;
}
}
// Apply the CSS class to the label group
labelGroup.attr('class', `label-group text classTitle ${labelClass}`);
await addText(labelGroup, node, 0, labelStyles);
const labelGroupBBox = labelGroup.node()!.getBBox();
labelGroupHeight = labelGroupBBox.height;
@@ -71,7 +113,7 @@ export async function textHelper<T extends SVGGraphicsElement>(
// Center annotation
if (annotationGroup !== null) {
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2})`);
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2}, 0)`);
}
// Adjust label

View File

@@ -31,7 +31,7 @@ const getStyles = (options) =>
}
.classTitle {
font-weight: bolder;
font-weight: normal;
}
.node rect,
.node circle,
@@ -148,6 +148,20 @@ g.classGroup line {
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
.classTitle.abstract {
font-style: italic;
font-weight: normal;
}
.classTitle.enumeration {
text-decoration: underline;
font-weight: normal;
}
.classTitle.interface {
font-weight: bold;
}
.edgeTerminals {
font-size: 11px;

View File

@@ -1,5 +1,4 @@
import DOMPurify from 'dompurify';
import { getEffectiveHtmlLabels } from '../../config.js';
import type { MermaidConfig } from '../../config.type.js';
// Remove and ignore br:s
@@ -65,7 +64,7 @@ export const removeScript = (txt: string): string => {
};
const sanitizeMore = (text: string, config: MermaidConfig) => {
if (getEffectiveHtmlLabels(config)) {
if (config.flowchart?.htmlLabels !== false) {
const level = config.securityLevel;
if (level === 'antiscript' || level === 'strict') {
text = removeScript(text);

View File

@@ -268,9 +268,7 @@ const fixTaskDates = function (startTime, endTime, dateFormat, excludes, include
const getStartDate = function (prevTime, dateFormat, str) {
str = str.trim();
if ((dateFormat.trim() === 'x' || dateFormat.trim() === 'X') && /^\d+$/.test(str)) {
return new Date(Number(str));
}
// Test for after
const afterRePattern = /^after\s+(?<ids>[\d\w- ]+)/;
const afterStatement = afterRePattern.exec(str);

View File

@@ -37,7 +37,6 @@ export class MindmapDB {
private nodes: MindmapNode[] = [];
private count = 0;
private elements: Record<number, D3Element> = {};
private baseLevel?: number;
public readonly nodeType: typeof nodeType;
constructor() {
@@ -55,7 +54,6 @@ export class MindmapDB {
this.nodes = [];
this.count = 0;
this.elements = {};
this.baseLevel = undefined;
}
public getParent(level: number): MindmapNode | null {
@@ -74,17 +72,6 @@ export class MindmapDB {
public addNode(level: number, id: string, descr: string, type: number): void {
log.info('addNode', level, id, descr, type);
let isRoot = false;
if (this.nodes.length === 0) {
this.baseLevel = level;
level = 0;
isRoot = true;
} else if (this.baseLevel !== undefined) {
level = level - this.baseLevel;
isRoot = false;
}
const conf = getConfig();
let padding = conf.mindmap?.padding ?? defaultConfig.mindmap.padding;
@@ -105,7 +92,6 @@ export class MindmapDB {
children: [],
width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth,
padding,
isRoot,
};
const parent = this.getParent(level);
@@ -113,7 +99,7 @@ export class MindmapDB {
parent.children.push(node);
this.nodes.push(node);
} else {
if (isRoot) {
if (this.nodes.length === 0) {
this.nodes.push(node);
} else {
throw new Error(
@@ -218,7 +204,8 @@ export class MindmapDB {
// Build CSS classes for the node
const cssClasses = ['mindmap-node'];
if (node.isRoot === true) {
// Add section-specific classes
if (node.level === 0) {
// Root node gets special classes
cssClasses.push('section-root', 'section--1');
} else if (node.section !== undefined) {

View File

@@ -15,7 +15,6 @@ export interface MindmapNode {
icon?: string;
x?: number;
y?: number;
isRoot?: boolean;
}
export type FilledMindMapNode = RequiredDeep<MindmapNode>;

View File

@@ -321,7 +321,7 @@ export class RequirementDB implements DiagramDB {
id: `${relation.src}-${relation.dst}-${counter}`,
start: this.requirements.get(relation.src)?.name ?? this.elements.get(relation.src)?.name,
end: this.requirements.get(relation.dst)?.name ?? this.elements.get(relation.dst)?.name,
label: `«${relation.type}»`,
label: `&lt;&lt;${relation.type}&gt;&gt;`,
classes: 'relationshipLine',
style: ['fill:none', isContains ? '' : 'stroke-dasharray: 10,7'],
labelpos: 'c',

View File

@@ -40,15 +40,6 @@ const getStyles = (options) => `
.relationshipLabel {
fill: ${options.relationLabelColor};
}
.edgeLabel {
background-color: ${options.edgeLabelBackground};
}
.edgeLabel .label rect {
fill: ${options.edgeLabelBackground};
}
.edgeLabel .label text {
fill: ${options.relationLabelColor};
}
.divider {
stroke: ${options.nodeBorder};
stroke-width: 1;

View File

@@ -40,26 +40,105 @@ const openSourceFeatures: Feature[] = [
{ iconUrl: '/icons/version-history.svg', featureName: 'Version history' },
];
const editorColumns: EditorColumn[] = [
{
title: 'Mermaid Pro',
description: 'Unlock AI and real-time collaboration',
highlighted: true,
redBarText: 'Recommended',
proTrialButtonText: 'Start free trial',
proTrialUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=2_editor_selection&utm_campaign=start_pro&redirect=%2Fapp%2Fuser%2Fbilling%2Fcheckout%3FisFromMermaid%3Dtrue',
features: mermaidChartFeatures,
},
{
title: 'Open Source',
description: 'Code only, no login',
buttonText: 'Start free',
redirectUrl: 'https://mermaid.live/edit',
features: openSourceFeatures,
},
const playgroundFeatures: Feature[] = [
{ iconUrl: '/icons/public.svg', featureName: 'Diagram stored in URL' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' },
{ iconUrl: '/icons/whiteboard.svg', featureName: 'Whiteboard' },
];
const editorColumnVariants: EditorColumn[][] = [
[
{
title: 'Playground',
description: 'Basic features, no login',
redirectUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=3_editor_selection_A&utm_campaign=start_playground',
buttonText: 'Start free',
features: playgroundFeatures,
},
{
title: 'Free or Pro',
description: 'Advanced features, Free or Pro account',
proTrialUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=3_editor_selection_A&utm_campaign=start_free',
proTrialButtonText: 'Start free',
highlighted: true,
redBarText: 'Best for collaboration',
features: mermaidChartFeatures,
},
{
title: 'Open Source',
description: 'Code only, no login',
redirectUrl: 'https://mermaid.live/edit',
buttonText: 'Start free',
features: openSourceFeatures,
},
],
[
{
title: 'Mermaid Pro',
description: 'Unlock AI and real-time collaboration',
redirectUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=editor_selection_B&utm_campaign=start_free',
buttonText: 'Start Free',
highlighted: true,
redBarText: 'Recommended',
proTrialButtonText: 'Start Pro trial',
proTrialUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=editor_selection_B&utm_campaign=start_trial&redirect=%2Fapp%2Fuser%2Fbilling%2Fcheckout%3FisFromMermaid%3Dtrue',
features: mermaidChartFeatures,
},
{
title: 'Open Source',
description: 'Code only, no login',
buttonText: 'Start free',
redirectUrl: 'https://mermaid.live/edit',
isButtonMargined: true,
features: openSourceFeatures,
},
],
[
{
title: 'Mermaid Pro',
description: 'Unlock AI and real-time collaboration',
highlighted: true,
redBarText: 'Recommended',
proTrialButtonText: 'Start free trial',
proTrialUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=editor_selection_C&utm_campaign=start_trial&redirect=%2Fapp%2Fuser%2Fbilling%2Fcheckout%3FisFromMermaid%3Dtrue',
features: mermaidChartFeatures,
},
{
title: 'Open Source',
description: 'Code only, no login',
buttonText: 'Start free',
redirectUrl: 'https://mermaid.live/edit',
features: openSourceFeatures,
},
],
[
{
title: 'Mermaid Pro',
description: 'Unlock AI and real-time collaboration',
highlighted: true,
redBarText: 'Recommended',
proTrialButtonText: 'Start free',
proTrialUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=editor_selection_D&utm_campaign=start_free',
features: mermaidChartFeatures,
},
{
title: 'Open Source',
description: 'Code only, no login',
redirectUrl: 'https://mermaid.live/edit',
buttonText: 'Start free',
features: openSourceFeatures,
},
],
];
const editorColumns = editorColumnVariants[Math.floor(Math.random() * editorColumnVariants.length)];
const isVisible = ref(false);
const handleMouseDown = (e: MouseEvent) => {

View File

@@ -1,13 +1,7 @@
import mermaid, { type MermaidConfig } from 'mermaid';
import zenuml from '../../../../../mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
import tidyTreeLayout from '../../../../../mermaid-layout-tidy-tree/dist/mermaid-layout-tidy-tree.core.mjs';
import layouts from '../../../../../mermaid-layout-elk/dist/mermaid-layout-elk.core.mjs';
const init = Promise.all([
mermaid.registerExternalDiagrams([zenuml]),
mermaid.registerLayoutLoaders(layouts),
mermaid.registerLayoutLoaders(tidyTreeLayout),
]);
const init = mermaid.registerExternalDiagrams([zenuml]);
mermaid.registerIconPacks([
{
name: 'logos',

View File

@@ -17,6 +17,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
@@ -185,27 +186,22 @@ A --> C[End]
Some common flowchart configurations are:
- _htmlLabels_: true/false
- _curve_: linear/curve
- _diagramPadding_: number
- _useMaxWidth_: number
**Deprecated configurations:**
- ~~_htmlLabels_~~: Use global `htmlLabels` instead
For a complete list of flowchart configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
The following code snippet changes flowchart config:
```
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
```
`%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%`
**Note:** `flowchart.htmlLabels` has been deprecated. Use the global `htmlLabels` configuration instead.
Here we are overriding only the flowchart config, and not the general config, setting `htmlLabels` to `true` and `curve` to `linear`.
```mermaid-example
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
graph TD
A(Forest) --> B[/Another/]
A --> C[End]

View File

@@ -368,7 +368,7 @@ The list of configuration objects are described [in the mermaidAPI documentation
```html
<script type="module">
import mermaid from './mermaid.esm.mjs';
let config = { startOnLoad: true, htmlLabels: true, flowchart: { useMaxWidth: false } };
let config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } };
mermaid.initialize(config);
</script>
```

View File

@@ -23,6 +23,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)

View File

@@ -33,11 +33,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)

View File

@@ -1,17 +1,5 @@
# Blog
## [The Essential Guide to Mermaid Chart Plugin for VS Code [08/2025]](https://docs.mermaidchart.com/blog/posts/the-essential-guide-to-mermaid-chart-plugin-for-vs-code-08-2025)
9/9/2025 • 5 mins
Creating diagrams in VS Code has never been easier—the Mermaid VS Code plugin transforms text-based syntax into clear, professional visuals right inside your editor. With live previews, smart editing, and AI-powered diagram generation, it removes the friction from visualization so you can focus on coding.
## [How to Create Perfect Flowcharts Using AI in 2025 Step-by-Step Guide](https://docs.mermaidchart.com/blog/posts/how-to-create-perfect-flowcharts-using-ai-in-2025-step-by-step-guide)
7/22/2025 • 8 mins
In 2025, AI tools make creating flowcharts faster and easier than ever. With Mermaids AI flowchart generator, you can turn plain text into clear, accurate diagrams in seconds. This guide walks through how it works and how to get the most out of it for technical and business workflows alike.
## [Mermaid introduces the Visual Editor for Entity Relationship diagrams](https://docs.mermaidchart.com/blog/posts/mermaid-introduces-the-visual-editor-for-entity-relationship-diagrams)
7/15/2025 • 7 mins

View File

@@ -17,25 +17,25 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"@vueuse/core": "^13.9.0",
"@vueuse/core": "^13.1.0",
"font-awesome": "^4.7.0",
"jiti": "^2.4.2",
"mermaid": "workspace:^",
"vue": "^3.5.21"
"vue": "^3.4.38"
},
"devDependencies": {
"@iconify-json/carbon": "^1.2.13",
"@unocss/reset": "^66.5.1",
"@iconify-json/carbon": "^1.1.37",
"@unocss/reset": "^66.0.0",
"@vite-pwa/vitepress": "^1.0.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue": "^6.0.0",
"fast-glob": "^3.3.3",
"https-localhost": "^4.7.1",
"pathe": "^2.0.3",
"unocss": "^66.5.1",
"unplugin-vue-components": "^28.4.1",
"vite": "^7.0.7",
"vite-plugin-pwa": "^1.0.3",
"vitepress": "1.6.4",
"unocss": "^66.4.2",
"unplugin-vue-components": "^28.4.0",
"vite": "^6.1.1",
"vite-plugin-pwa": "^1.0.0",
"vitepress": "1.6.3",
"workbox-window": "^7.3.0"
}
}

View File

@@ -156,7 +156,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add other custom icons, by [registering an icon pack](../config/icons.md).
Users can use any of the 200,000+ icons available in iconify.design, or [add custom icons](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -83,6 +83,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)
@@ -96,6 +97,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
@@ -105,6 +107,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
@@ -113,15 +116,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

View File

@@ -20,5 +20,3 @@ Each user journey is split into sections, these describe the part of the task
the user is trying to complete.
Tasks syntax is `Task name: <score>: <comma separated list of actors>`
Score is a number between 1 and 5, inclusive.

View File

@@ -13,10 +13,6 @@ const virtualModuleId = 'virtual:mermaid-config';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
export default defineConfig({
build: {
// Vite v7 changes the default target and drops old browser support
target: 'modules',
},
optimizeDeps: {
// vitepress is aliased with replacement `join(DIST_CLIENT_PATH, '/index')`
// This needs to be excluded from optimization

View File

@@ -2,7 +2,6 @@ import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
import assignWithDepth from './assignWithDepth.js';
import type { MermaidConfig } from './config.type.js';
import { getEffectiveHtmlLabels } from './config.js';
import mermaid from './mermaid.js';
import mermaidAPI, {
appendDivSvgG,
@@ -359,11 +358,10 @@ describe('mermaidAPI', () => {
});
describe('no htmlLabels in the configuration', () => {
const mocked_config_no_htmlLabels: MermaidConfig = {
const mocked_config_no_htmlLabels = {
themeCSS: 'default',
fontFamily: 'serif',
altFontFamily: 'sans-serif',
htmlLabels: false, // Explicitly set to false
};
describe('creates styles for shape elements "rect", "polygon", "ellipse", and "circle"', () => {
@@ -1150,63 +1148,4 @@ flowchart TD
}
);
});
describe('flowchart.htmlLabels deprecation behavior', () => {
beforeEach(() => {
mermaidAPI.globalReset();
});
it('should use root-level htmlLabels when only root-level is set', () => {
const config: MermaidConfig = { htmlLabels: true };
expect(config.htmlLabels).toBe(true);
const config2: MermaidConfig = { htmlLabels: false };
expect(config2.htmlLabels).toBe(false);
});
it('should check config.htmlLabels value directly when set', () => {
const config1: MermaidConfig = { htmlLabels: true };
expect(config1.htmlLabels).toBe(true);
const config2: MermaidConfig = { htmlLabels: false };
expect(config2.htmlLabels).toBe(false);
const config3: MermaidConfig = { htmlLabels: undefined };
expect(config3.htmlLabels).toBeUndefined();
});
it('should use getEffectiveHtmlLabels only when fallback logic is needed', () => {
// Only call getEffectiveHtmlLabels when we need the fallback behavior
const configWithDeprecated: MermaidConfig = { flowchart: { htmlLabels: true } };
expect(getEffectiveHtmlLabels(configWithDeprecated)).toBe(true);
const configWithBoth: MermaidConfig = {
htmlLabels: false,
flowchart: { htmlLabels: true },
};
expect(getEffectiveHtmlLabels(configWithBoth)).toBe(false); // Root takes precedence
const configEmpty: MermaidConfig = {};
expect(getEffectiveHtmlLabels(configEmpty)).toBe(true); // Default to true
});
it('should verify the precedence logic: config.htmlLabels ?? config.flowchart?.htmlLabels ?? true', () => {
// Test the exact precedence chain
const config1: MermaidConfig = { htmlLabels: true };
const result1 = config1.htmlLabels ?? config1.flowchart?.htmlLabels ?? true;
expect(result1).toBe(true);
const config2: MermaidConfig = { htmlLabels: false };
const result2 = config2.htmlLabels ?? config2.flowchart?.htmlLabels ?? true;
expect(result2).toBe(false);
const config3: MermaidConfig = { flowchart: { htmlLabels: true } };
const result3 = config3.htmlLabels ?? config3.flowchart?.htmlLabels ?? true;
expect(result3).toBe(true);
const config4: MermaidConfig = {};
const result4 = config4.htmlLabels ?? config4.flowchart?.htmlLabels ?? true;
expect(result4).toBe(true);
});
});
});

View File

@@ -11,7 +11,6 @@ import packageJson from '../package.json' assert { type: 'json' };
import { addSVGa11yTitleDescription, setA11yDiagramInfo } from './accessibility.js';
import assignWithDepth from './assignWithDepth.js';
import * as configApi from './config.js';
import { getEffectiveHtmlLabels } from './config.js';
import type { MermaidConfig } from './config.type.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
@@ -129,7 +128,7 @@ export const createCssStyles = (
// classDefs defined in the diagram text
if (classDefs instanceof Map) {
const htmlLabels = getEffectiveHtmlLabels(config);
const htmlLabels = config.htmlLabels ?? config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config?
const cssHtmlElements = ['> *', 'span']; // TODO make a constant
const cssShapeElements = ['rect', 'polygon', 'ellipse', 'circle', 'path']; // TODO make a constant

View File

@@ -4,9 +4,6 @@ import { internalHelpers } from '../internals.js';
import { log } from '../logger.js';
import type { LayoutData } from './types.js';
// console.log('MUST be removed, this only for keeping dev server working');
// import tmp from './layout-algorithms/dagre/index.js';
export interface RenderOptions {
algorithm?: string;
}

View File

@@ -1,6 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import { evaluate } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import { getSubGraphTitleMargins } from '../../utils/subGraphTitleMargins.js';
import { select } from 'd3';
@@ -26,7 +25,7 @@ const rect = async (parent, node) => {
.attr('id', node.id)
.attr('data-look', node.look);
const useHtmlLabels = getEffectiveHtmlLabels(siteConfig);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label ');
@@ -40,7 +39,7 @@ const rect = async (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@@ -189,7 +188,7 @@ const roundedWithTitle = async (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@@ -265,7 +264,7 @@ const roundedWithTitle = async (parent, node) => {
label.attr(
'transform',
`translate(${node.x - bbox.width / 2}, ${y + 1 - (getEffectiveHtmlLabels(siteConfig) ? 0 : 3)})`
`translate(${node.x - bbox.width / 2}, ${y + 1 - (evaluate(siteConfig.flowchart.htmlLabels) ? 0 : 3)})`
);
const rectBox = rect.node().getBBox();
@@ -296,7 +295,7 @@ const kanbanSection = async (parent, node) => {
.attr('id', node.id)
.attr('data-look', node.look);
const useHtmlLabels = getEffectiveHtmlLabels(siteConfig);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label ');
@@ -311,7 +310,7 @@ const kanbanSection = async (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (getEffectiveHtmlLabels(siteConfig)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();

View File

@@ -1,7 +1,7 @@
import { select } from 'd3';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import common, {
evaluate,
hasKatex,
renderKatexSanitized,
sanitizeText,
@@ -64,7 +64,7 @@ const createLabel = async (_vertexText, style, isTitle, isNode) => {
vertexText = vertexText[0];
}
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
log.info('vertexText' + vertexText);

View File

@@ -1,13 +1,9 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../config.js';
import { evaluate, getUrl } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import { createText } from '../createText.js';
import utils from '../../utils.js';
import {
getLineFunctionsWithOffset,
markerOffsets,
markerOffsets2,
} from '../../utils/lineWithOffset.js';
import { getLineFunctionsWithOffset } from '../../utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '../../utils/subGraphTitleMargins.js';
import {
@@ -29,10 +25,10 @@ import {
import rough from 'roughjs';
import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts';
import { isLabelStyle, styles2String } from './shapes/handDrawnShapeStyles.js';
import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
export const edgeLabels = new Map();
export const terminalLabels = new Map();
const edgeLabels = new Map();
const terminalLabels = new Map();
export const clear = () => {
edgeLabels.clear();
@@ -45,12 +41,10 @@ export const getLabelStyles = (styleArray) => {
};
export const insertEdgeLabel = async (elem, edge) => {
let useHtmlLabels = getEffectiveHtmlLabels(getConfig());
let useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
const { labelStyles } = styles2String(edge);
edge.labelStyle = labelStyles;
const labelElement = await createText(elem, edge.label, {
style: edge.labelStyle,
style: getLabelStyles(edge.labelStyle),
useHtmlLabels,
addSvgBackground: true,
isNode: false,
@@ -61,7 +55,7 @@ export const insertEdgeLabel = async (elem, edge) => {
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
// Create inner g, label, this will be positioned now for centering the text
const label = edgeLabel.insert('g').attr('class', 'label').attr('data-id', edge.id);
const label = edgeLabel.insert('g').attr('class', 'label');
label.node().appendChild(labelElement);
// Center the label
@@ -161,7 +155,7 @@ export const insertEdgeLabel = async (elem, edge) => {
* @param {any} value
*/
function setTerminalWidth(fo, value) {
if (getEffectiveHtmlLabels(getConfig()) && fo) {
if (getConfig().flowchart.htmlLabels && fo) {
fo.style.width = value.length * 9 + 'px';
fo.style.height = '12px';
}
@@ -444,33 +438,7 @@ const fixCorners = function (lineData) {
}
return newLineData;
};
const generateDashArray = (len, oValueS, oValueE) => {
const middleLength = len - oValueS - oValueE;
const dashLength = 2; // Length of each dash
const gapLength = 2; // Length of each gap
const dashGapPairLength = dashLength + gapLength;
// Calculate number of complete dash-gap pairs that can fit
const numberOfPairs = Math.floor(middleLength / dashGapPairLength);
// Generate the middle pattern array
const middlePattern = Array(numberOfPairs).fill(`${dashLength} ${gapLength}`).join(' ');
// Combine all parts
const dashArray = `0 ${oValueS} ${middlePattern} ${oValueE}`;
return dashArray;
};
export const insertEdge = function (
elem,
edge,
clusterDb,
diagramType,
startNode,
endNode,
id,
skipIntersect = false
) {
export const insertEdge = function (elem, edge, clusterDb, diagramType, startNode, endNode, id) {
const { handDrawnSeed } = getConfig();
let points = edge.points;
let pointsHasChanged = false;
@@ -484,12 +452,11 @@ export const insertEdge = function (
edgeClassStyles.push(edge.cssCompiledStyles[key]);
}
log.debug('UIO intersect check', edge.points, head.x, tail.x);
if (head.intersect && tail.intersect && !skipIntersect) {
if (head.intersect && tail.intersect) {
points = points.slice(1, edge.points.length - 1);
points.unshift(tail.intersect(points[0]));
log.debug(
'Last point UIO',
'Last point APA12',
edge.start,
'-->',
edge.end,
@@ -499,7 +466,6 @@ export const insertEdge = function (
);
points.push(head.intersect(points[points.length - 1]));
}
const pointsStr = btoa(JSON.stringify(points));
if (edge.toCluster) {
log.info('to cluster abc88', clusterDb.get(edge.toCluster));
points = cutPathAtIntersect(edge.points, clusterDb.get(edge.toCluster).node);
@@ -563,10 +529,6 @@ export const insertEdge = function (
curve = curveBasis;
}
// if (edge.curve) {
// curve = edge.curve;
// }
const { x, y } = getLineFunctionsWithOffset(edge);
const lineFunction = line().x(x).y(y).curve(curve);
@@ -598,14 +560,10 @@ export const insertEdge = function (
strokeClasses += ' edge-pattern-solid';
}
let svgPath;
let linePath =
edge.curve === 'rounded'
? generateRoundedPath(applyMarkerOffsetsToPoints(lineData, edge), 5)
: lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
let linePath = lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : edge.style ? [edge.style] : [];
let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:'));
let animatedEdge = false;
if (edge.look === 'handDrawn') {
const rc = rough.svg(elem);
Object.assign([], lineData);
@@ -636,10 +594,7 @@ export const insertEdge = function (
animationClass = ' edge-animation-' + edge.animation;
}
const pathStyle =
(stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles) +
';' +
(edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
const pathStyle = stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles;
svgPath = elem
.append('path')
.attr('d', linePath)
@@ -649,39 +604,11 @@ export const insertEdge = function (
' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '')
)
.attr('style', pathStyle);
//eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
strokeColor = pathStyle.match(/stroke:([^;]+)/)?.[1];
// Possible fix to remove eslint-disable-next-line
//strokeColor = /stroke:([^;]+)/.exec(pathStyle)?.[1];
animatedEdge =
edge.animate === true || !!edge.animation || stylesFromClasses.includes('animation');
const pathNode = svgPath.node();
const len = typeof pathNode.getTotalLength === 'function' ? pathNode.getTotalLength() : 0;
const oValueS = markerOffsets2[edge.arrowTypeStart] || 0;
const oValueE = markerOffsets2[edge.arrowTypeEnd] || 0;
if (edge.look === 'neo' && !animatedEdge) {
const dashArray =
edge.pattern === 'dotted' || edge.pattern === 'dashed'
? generateDashArray(len, oValueS, oValueE)
: `0 ${oValueS} ${len - oValueS - oValueE} ${oValueE}`;
// No offset needed because we already start with a zero-length dash that effectively sets us up for a gap at the start.
const mOffset = `stroke-dasharray: ${dashArray}; stroke-dashoffset: 0;`;
svgPath.attr('style', mOffset + svgPath.attr('style'));
}
}
// MC Special
svgPath.attr('data-edge', true);
svgPath.attr('data-et', 'edge');
svgPath.attr('data-id', edge.id);
svgPath.attr('data-points', pointsStr);
// DEBUG code, adds a red circle at each edge coordinate
// DEBUG code, DO NOT REMOVE
// adds a red circle at each edge coordinate
// cornerPoints.forEach((point) => {
// elem
// .append('circle')
@@ -691,27 +618,19 @@ export const insertEdge = function (
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
if (edge.showPoints) {
lineData.forEach((point) => {
elem
.append('circle')
.style('stroke', 'red')
.style('fill', 'red')
.attr('r', 1)
.attr('cx', point.x)
.attr('cy', point.y);
});
}
// lineData.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
let url = '';
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url =
window.location.protocol +
'//' +
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
url = getUrl(true);
}
log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd);
@@ -730,134 +649,3 @@ export const insertEdge = function (
paths.originalPath = edge.points;
return paths;
};
/**
* Generates SVG path data with rounded corners from an array of points.
* @param {Array} points - Array of points in the format [{x: Number, y: Number}, ...]
* @param {Number} radius - The radius of the rounded corners
* @returns {String} - SVG path data string
*/
function generateRoundedPath(points, radius) {
if (points.length < 2) {
return '';
}
let path = '';
const size = points.length;
const epsilon = 1e-5;
for (let i = 0; i < size; i++) {
const currPoint = points[i];
const prevPoint = points[i - 1];
const nextPoint = points[i + 1];
if (i === 0) {
// Move to the first point
path += `M${currPoint.x},${currPoint.y}`;
} else if (i === size - 1) {
// Last point, draw a straight line to the final point
path += `L${currPoint.x},${currPoint.y}`;
} else {
// Calculate vectors for incoming and outgoing segments
const dx1 = currPoint.x - prevPoint.x;
const dy1 = currPoint.y - prevPoint.y;
const dx2 = nextPoint.x - currPoint.x;
const dy2 = nextPoint.y - currPoint.y;
const len1 = Math.hypot(dx1, dy1);
const len2 = Math.hypot(dx2, dy2);
// Prevent division by zero
if (len1 < epsilon || len2 < epsilon) {
path += `L${currPoint.x},${currPoint.y}`;
continue;
}
// Normalize the vectors
const nx1 = dx1 / len1;
const ny1 = dy1 / len1;
const nx2 = dx2 / len2;
const ny2 = dy2 / len2;
// Calculate the angle between the vectors
const dot = nx1 * nx2 + ny1 * ny2;
// Clamp the dot product to avoid numerical issues with acos
const clampedDot = Math.max(-1, Math.min(1, dot));
const angle = Math.acos(clampedDot);
// Skip rounding if the angle is too small or too close to 180 degrees
if (angle < epsilon || Math.abs(Math.PI - angle) < epsilon) {
path += `L${currPoint.x},${currPoint.y}`;
continue;
}
// Calculate the distance to offset the control point
const cutLen = Math.min(radius / Math.sin(angle / 2), len1 / 2, len2 / 2);
// Calculate the start and end points of the curve
const startX = currPoint.x - nx1 * cutLen;
const startY = currPoint.y - ny1 * cutLen;
const endX = currPoint.x + nx2 * cutLen;
const endY = currPoint.y + ny2 * cutLen;
// Draw the line to the start of the curve
path += `L${startX},${startY}`;
// Draw the quadratic Bezier curve
path += `Q${currPoint.x},${currPoint.y} ${endX},${endY}`;
}
}
return path;
}
// Helper function to calculate delta and angle between two points
function calculateDeltaAndAngle(point1, point2) {
if (!point1 || !point2) {
return { angle: 0, deltaX: 0, deltaY: 0 };
}
const deltaX = point2.x - point1.x;
const deltaY = point2.y - point1.y;
const angle = Math.atan2(deltaY, deltaX);
return { angle, deltaX, deltaY };
}
// Function to adjust the first and last points of the points array
function applyMarkerOffsetsToPoints(points, edge) {
// Copy the points array to avoid mutating the original data
const newPoints = points.map((point) => ({ ...point }));
// Handle the first point (start of the edge)
if (points.length >= 2 && markerOffsets[edge.arrowTypeStart]) {
const offsetValue = markerOffsets[edge.arrowTypeStart];
const point1 = points[0];
const point2 = points[1];
const { angle } = calculateDeltaAndAngle(point1, point2);
const offsetX = offsetValue * Math.cos(angle);
const offsetY = offsetValue * Math.sin(angle);
newPoints[0].x = point1.x + offsetX;
newPoints[0].y = point1.y + offsetY;
}
// Handle the last point (end of the edge)
const n = points.length;
if (n >= 2 && markerOffsets[edge.arrowTypeEnd]) {
const offsetValue = markerOffsets[edge.arrowTypeEnd];
const point1 = points[n - 1];
const point2 = points[n - 2];
const { angle } = calculateDeltaAndAngle(point2, point1);
const offsetX = offsetValue * Math.cos(angle);
const offsetY = offsetValue * Math.sin(angle);
newPoints[n - 1].x = point1.x - offsetX;
newPoints[n - 1].y = point1.y - offsetY;
}
return newPoints;
}

View File

@@ -20,11 +20,7 @@ export const compileStyles = (node: Node) => {
// the array is the styles of node from the classes it is using
// node.cssStyles is an array of styles directly set on the node
// concat the arrays and remove duplicates such that the values from node.cssStyles are used if there are duplicates
const stylesMap = styles2Map([
...(node.cssCompiledStyles || []),
...(node.cssStyles || []),
...(node.labelStyle || []),
]);
const stylesMap = styles2Map([...(node.cssCompiledStyles || []), ...(node.cssStyles || [])]);
return { stylesMap, stylesArray: [...stylesMap] };
};

View File

@@ -5,7 +5,6 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js';
import { getConfig } from '../../../config.js';
import { getEffectiveHtmlLabels } from '../../../config.js';
export async function note<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
@@ -14,7 +13,7 @@ export async function note<T extends SVGGraphicsElement>(
) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const useHtmlLabels = getEffectiveHtmlLabels(getConfig()) || node.useHtmlLabels;
const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart?.htmlLabels !== false;
if (!useHtmlLabels) {
node.centerLabel = true;
}

View File

@@ -1,7 +1,6 @@
import { createText } from '../../createText.js';
import type { Node } from '../../types.js';
import { getConfig } from '../../../diagram-api/diagramAPI.js';
import { getEffectiveHtmlLabels } from '../../../config.js';
import { select } from 'd3';
import defaultConfig from '../../../defaultConfig.js';
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
@@ -131,7 +130,7 @@ export const insertLabel = async <T extends SVGGraphicsElement>(
addSvgBackground?: boolean | undefined;
}
) => {
const useHtmlLabels = getEffectiveHtmlLabels(getConfig()) || options.useHtmlLabels;
const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels);
// Create the label and insert it after the rect
const labelEl = parent
@@ -149,7 +148,7 @@ export const insertLabel = async <T extends SVGGraphicsElement>(
let bbox = text.getBBox();
const halfPadding = options.padding / 2;
if (getEffectiveHtmlLabels(getConfig())) {
if (evaluate(getConfig()?.flowchart?.htmlLabels)) {
const div = text.children[0];
const dv = select(text);

Some files were not shown because too many files have changed in this diff Show More