mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-12-10 06:24:16 +01:00
Compare commits
39 Commits
fix-update
...
architectu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a57b98c004 | ||
|
|
4bb5753635 | ||
|
|
22e1b17ac3 | ||
|
|
18f461267d | ||
|
|
4430eddb24 | ||
|
|
0a23304d1a | ||
|
|
7effdc147b | ||
|
|
6e67515f41 | ||
|
|
d5c4eff251 | ||
|
|
5324fd8dfd | ||
|
|
37e3a6951b | ||
|
|
0de0b063e4 | ||
|
|
619515e5a9 | ||
|
|
59c8b07509 | ||
|
|
9e72bbf62d | ||
|
|
2a2c46f1e2 | ||
|
|
f25df353d4 | ||
|
|
398345a8bc | ||
|
|
7fd2d94ef7 | ||
|
|
bcc1472b9d | ||
|
|
ddc1cfe6c8 | ||
|
|
18e9c1174d | ||
|
|
789018abf6 | ||
|
|
e87f77a865 | ||
|
|
a9579083bf | ||
|
|
6fd78d0856 | ||
|
|
531f5e9380 | ||
|
|
ad4c227477 | ||
|
|
a44e3e992c | ||
|
|
ca5b370ffb | ||
|
|
450754221e | ||
|
|
7a4f5b62c9 | ||
|
|
cf08ba0ef8 | ||
|
|
4829dfa8c5 | ||
|
|
e7811886c3 | ||
|
|
32eda8565c | ||
|
|
9c85521e16 | ||
|
|
8a565bce92 | ||
|
|
baf510b935 |
@@ -38,6 +38,11 @@ export const packageOptions = {
|
||||
packageName: 'mermaid-layout-tidy-tree',
|
||||
file: 'index.ts',
|
||||
},
|
||||
'mermaid-layout-fcose': {
|
||||
name: 'mermaid-layout-fcose',
|
||||
packageName: 'mermaid-layout-fcose',
|
||||
file: 'index.ts',
|
||||
},
|
||||
examples: {
|
||||
name: 'mermaid-examples',
|
||||
packageName: 'examples',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Render newlines as spaces in class diagrams
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
fix: Handle arrows correctly when auto number is enabled
|
||||
5
.changeset/deep-pumas-run.md
Normal file
5
.changeset/deep-pumas-run.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': patch
|
||||
---
|
||||
|
||||
chore: Fix mindmap rendering in docs and apply tidytree layout
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
---
|
||||
|
||||
Add IDs in architecture diagrams
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
---
|
||||
|
||||
feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
'@mermaid-js/layout-tidy-tree': minor
|
||||
'@mermaid-js/layout-elk': minor
|
||||
---
|
||||
|
||||
feat: Update mindmap rendering to support multiple layouts, improved edge intersections, and new shapes
|
||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@@ -26,8 +26,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ['javascript']
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
language: ['javascript', 'actions']
|
||||
# CodeQL supports [ 'actions', 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||
uses: github/codeql-action/init@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||
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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||
uses: github/codeql-action/autobuild@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||
|
||||
# ℹ️ 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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||
uses: github/codeql-action/analyze@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||
|
||||
2
.github/workflows/e2e-applitools.yml
vendored
2
.github/workflows/e2e-applitools.yml
vendored
@@ -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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
id: cypress
|
||||
with:
|
||||
start: pnpm run dev
|
||||
|
||||
4
.github/workflows/e2e-timings.yml
vendored
4
.github/workflows/e2e-timings.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
- name: Install dependencies
|
||||
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
with:
|
||||
runTests: false
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
id: cypress
|
||||
with:
|
||||
install: false
|
||||
|
||||
10
.github/workflows/e2e.yml
vendored
10
.github/workflows/e2e.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
- name: Cache snapshots
|
||||
id: cache-snapshot
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
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@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: ./cypress/snapshots
|
||||
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
|
||||
|
||||
- name: Install dependencies
|
||||
uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
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@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12
|
||||
uses: cypress-io/github-action@108b8684ae52e735ff7891524cbffbcd4be5b19f # v6.7.16
|
||||
id: cypress
|
||||
with:
|
||||
install: false
|
||||
|
||||
2
.github/workflows/link-checker.yml
vendored
2
.github/workflows/link-checker.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
|
||||
uses: changesets/action@06245a4e0a36c064a573d4150030f5ec548e4fcc # v1.4.10
|
||||
with:
|
||||
version: pnpm changeset:version
|
||||
publish: pnpm changeset:publish
|
||||
|
||||
6
.github/workflows/scorecard.yml
vendored
6
.github/workflows/scorecard.yml
vendored
@@ -20,18 +20,18 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||
uses: github/codeql-action/upload-sarif@5378192d256ef1302a6980fffe5ca04426d43091 # v3.28.21
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
2
.github/workflows/update-browserlist.yml
vendored
2
.github/workflows/update-browserlist.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
message: 'chore: update browsers list'
|
||||
push: false
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@67ccf781d68cd99b580ae25a5c18a1cc84ffff1f # v7.0.6
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
branch: update-browserslist
|
||||
title: Update Browserslist
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"types": ["cypress", "node", "@argos-ci/cypress/dist/support.d.ts"],
|
||||
"types": ["cypress", "node", "@argos-ci/cypress/support"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
|
||||
@@ -29,7 +29,8 @@ In GitHub, you first [**fork a mermaid repository**](https://github.com/mermaid-
|
||||
|
||||
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
|
||||
|
||||
> **💡 Tip** > [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
|
||||
> **💡 Tip**
|
||||
> [Here is a GitHub document that gives an overview of the process](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
|
||||
|
||||
```bash
|
||||
git clone git@github.com/your-fork/mermaid
|
||||
|
||||
@@ -33,7 +33,8 @@ 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:
|
||||
|
||||
@@ -47,7 +48,8 @@ Where to start:
|
||||
|
||||
## A Question Or a Suggestion?
|
||||
|
||||
> **💡 Tip** > **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
|
||||
> **💡 Tip**
|
||||
> **Have a look at** [**how to open an issue**](./questions-and-suggestions.md).
|
||||
|
||||
If you have faced a vulnerability [report it to us](./security.md).
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ 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
|
||||
|
||||
@@ -29,7 +29,6 @@ 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)
|
||||
|
||||
@@ -35,13 +35,11 @@ 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)
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
|
||||
# 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 Mermaid’s 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
|
||||
|
||||
@@ -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 custom icons](../config/icons.md).
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@ 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)
|
||||
@@ -153,7 +152,6 @@ 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
|
||||
@@ -163,7 +161,6 @@ 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
|
||||
@@ -172,18 +169,15 @@ 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
|
||||
|
||||
@@ -360,7 +360,8 @@ 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
|
||||
|
||||
|
||||
54
package.json
54
package.json
@@ -63,36 +63,36 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-cypress": "^3.44.9",
|
||||
"@argos-ci/cypress": "^5.0.2",
|
||||
"@applitools/eyes-cypress": "^3.55.2",
|
||||
"@argos-ci/cypress": "^6.1.1",
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.27.12",
|
||||
"@changesets/cli": "^2.29.7",
|
||||
"@cspell/eslint-plugin": "^8.19.4",
|
||||
"@cypress/code-coverage": "^3.12.49",
|
||||
"@cypress/code-coverage": "^3.14.6",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^5.0.0",
|
||||
"@rollup/plugin-typescript": "^12.1.4",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/lodash": "^4.17.15",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/node": "^22.13.5",
|
||||
"@types/node": "^22.18.6",
|
||||
"@types/rollup-plugin-visualizer": "^5.0.3",
|
||||
"@vitest/coverage-v8": "^3.0.6",
|
||||
"@vitest/spy": "^3.0.6",
|
||||
"@vitest/ui": "^3.0.6",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/spy": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"ajv": "^8.17.1",
|
||||
"chokidar": "3.6.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"cors": "^2.8.5",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cspell": "^9.1.3",
|
||||
"cypress": "^14.5.1",
|
||||
"cspell": "^9.2.1",
|
||||
"cypress": "^14.5.4",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"cypress-split": "^1.24.14",
|
||||
"esbuild": "^0.25.0",
|
||||
"cypress-split": "^1.24.23",
|
||||
"esbuild": "^0.25.10",
|
||||
"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.0.0",
|
||||
"globby": "^14.0.2",
|
||||
"globals": "^16.4.0",
|
||||
"globby": "^14.1.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^30.0.4",
|
||||
"jest": "^30.1.3",
|
||||
"jison": "^0.4.18",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"langium-cli": "3.3.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"lint-staged": "^16.1.6",
|
||||
"markdown-table": "^3.0.4",
|
||||
"nyc": "^17.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^3.5.2",
|
||||
"prettier-plugin-jsdoc": "^1.3.2",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-jsdoc": "^1.3.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"start-server-and-test": "^2.0.10",
|
||||
"start-server-and-test": "^2.1.2",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.7.3",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "~5.7.3",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
"vite": "^7.0.3",
|
||||
"vite": "^7.0.7",
|
||||
"vite-plugin-istanbul": "^7.0.0",
|
||||
"vitest": "^3.0.6"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"nyc": {
|
||||
"report-dir": "coverage/cypress"
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^7.0.4",
|
||||
"@braintree/sanitize-url": "^7.1.1",
|
||||
"d3": "^7.9.0",
|
||||
"khroma": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"mermaid": "workspace:*",
|
||||
"rimraf": "^6.0.1"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-elk",
|
||||
"version": "0.1.9",
|
||||
"version": "0.2.0",
|
||||
"description": "ELK layout engine for mermaid",
|
||||
"module": "dist/mermaid-layout-elk.core.mjs",
|
||||
"types": "dist/layouts.d.ts",
|
||||
|
||||
48
packages/mermaid-layout-fcose/package.json
Normal file
48
packages/mermaid-layout-fcose/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-fcose",
|
||||
"version": "0.1.0",
|
||||
"description": "FCoSE layout engine for architecture diagrams",
|
||||
"module": "dist/mermaid-layout-fcose.core.mjs",
|
||||
"types": "dist/layouts.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-layout-fcose.core.mjs",
|
||||
"types": "./dist/layouts.d.ts"
|
||||
},
|
||||
"./": "./"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"fcose",
|
||||
"mermaid",
|
||||
"layout",
|
||||
"architecture"
|
||||
],
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
},
|
||||
"contributors": [
|
||||
"Knut Sveidqvist"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cytoscape": "^3.27.0",
|
||||
"cytoscape-fcose": "^2.2.0",
|
||||
"d3": "^7.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3",
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "^11.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
22
packages/mermaid-layout-fcose/src/index.ts
Normal file
22
packages/mermaid-layout-fcose/src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* FCoSE Layout Algorithm for Architecture Diagrams
|
||||
*
|
||||
* This module provides a layout algorithm implementation using the
|
||||
* cytoscape-fcose algorithm for positioning nodes and edges in architecture
|
||||
* diagrams with spatial constraints and group alignments.
|
||||
*
|
||||
* The algorithm is optimized for architecture diagrams and supports:
|
||||
* - Spatial maps for relative positioning
|
||||
* - Group alignments for organizing related services
|
||||
* - XY edges with 90-degree bends
|
||||
* - Complex edge routing
|
||||
*
|
||||
* The algorithm follows the unified rendering pattern and can be used
|
||||
* by architecture diagrams that provide compatible LayoutData with
|
||||
* architecture-specific data structures.
|
||||
*/
|
||||
|
||||
export { default } from './layouts.js';
|
||||
export * from './types.js';
|
||||
export * from './layout.js';
|
||||
export { render } from './render.js';
|
||||
637
packages/mermaid-layout-fcose/src/layout.ts
Normal file
637
packages/mermaid-layout-fcose/src/layout.ts
Normal file
@@ -0,0 +1,637 @@
|
||||
import type { LayoutData } from 'mermaid';
|
||||
import type { LayoutOptions, Position } from 'cytoscape';
|
||||
import cytoscape from 'cytoscape';
|
||||
import fcose from 'cytoscape-fcose';
|
||||
import { select } from 'd3';
|
||||
import type {
|
||||
ArchitectureAlignment,
|
||||
ArchitectureDataStructures,
|
||||
ArchitectureGroupAlignments,
|
||||
ArchitectureSpatialMap,
|
||||
LayoutResult,
|
||||
PositionedNode,
|
||||
PositionedEdge,
|
||||
} from './types.js';
|
||||
import type {
|
||||
FcoseAlignmentConstraint,
|
||||
FcoseRelativePlacementConstraint,
|
||||
} from './cytoscape-fcose.d.js';
|
||||
|
||||
cytoscape.use(fcose as any);
|
||||
|
||||
/**
|
||||
* Architecture direction types
|
||||
*/
|
||||
export type ArchitectureDirection = 'L' | 'R' | 'T' | 'B';
|
||||
|
||||
const ArchitectureDirectionName = {
|
||||
L: 'left',
|
||||
R: 'right',
|
||||
T: 'top',
|
||||
B: 'bottom',
|
||||
} as const;
|
||||
|
||||
function isArchitectureDirectionY(x: ArchitectureDirection): boolean {
|
||||
return x === 'T' || x === 'B';
|
||||
}
|
||||
|
||||
function isArchitectureDirectionXY(a: ArchitectureDirection, b: ArchitectureDirection): boolean {
|
||||
const aX = a === 'L' || a === 'R';
|
||||
const bY = b === 'T' || b === 'B';
|
||||
const aY = a === 'T' || a === 'B';
|
||||
const bX = b === 'L' || b === 'R';
|
||||
return (aX && bY) || (aY && bX);
|
||||
}
|
||||
|
||||
function getOppositeArchitectureDirection(x: ArchitectureDirection): ArchitectureDirection {
|
||||
if (x === 'L' || x === 'R') {
|
||||
return x === 'L' ? 'R' : 'L';
|
||||
} else {
|
||||
return x === 'T' ? 'B' : 'T';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the fcose layout algorithm on architecture diagram data
|
||||
*
|
||||
* This function takes layout data and uses cytoscape-fcose to calculate
|
||||
* optimal node positions for architecture diagrams with spatial constraints.
|
||||
*
|
||||
* @param data - The layout data containing nodes, edges, and configuration
|
||||
* @returns Promise resolving to layout result with positioned nodes and edges
|
||||
*/
|
||||
export function executeFcoseLayout(data: LayoutData): Promise<LayoutResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (!data.nodes || !Array.isArray(data.nodes) || data.nodes.length === 0) {
|
||||
throw new Error('No nodes found in layout data');
|
||||
}
|
||||
|
||||
if (!data.edges || !Array.isArray(data.edges)) {
|
||||
data.edges = [];
|
||||
}
|
||||
|
||||
// Extract architecture-specific data structures if available
|
||||
const dataStructures = data.dataStructures as ArchitectureDataStructures | undefined;
|
||||
|
||||
const spatialMaps = dataStructures?.spatialMaps ?? [];
|
||||
const groupAlignments = dataStructures?.groupAlignments ?? {};
|
||||
|
||||
// Get icon size from config (default to 50)
|
||||
// Try to get from architecture config, or use a reasonable default
|
||||
const iconSize = data.config?.architecture?.iconSize || data.config?.iconSize || 50;
|
||||
|
||||
// Create a hidden container for cytoscape
|
||||
const renderEl = select('body')
|
||||
.append('div')
|
||||
.attr('id', 'cy-fcose')
|
||||
.attr('style', 'display:none');
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy-fcose'),
|
||||
style: [
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'curve-style': 'straight',
|
||||
label: 'data(label)',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge.segments',
|
||||
style: {
|
||||
'curve-style': 'segments',
|
||||
'segment-weights': '0',
|
||||
'segment-distances': [0.5],
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
// @ts-ignore Incorrect library types
|
||||
'compound-sizing-wrt-labels': 'include',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[label]',
|
||||
style: {
|
||||
'text-valign': 'bottom',
|
||||
'text-halign': 'center',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.node-service',
|
||||
style: {
|
||||
label: 'data(label)',
|
||||
width: 'data(width)',
|
||||
height: 'data(height)',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.node-junction',
|
||||
style: {
|
||||
width: 'data(width)',
|
||||
height: 'data(height)',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.node-group',
|
||||
style: {
|
||||
// @ts-ignore Incorrect library types
|
||||
padding: `${iconSize * 0.5}px`,
|
||||
},
|
||||
},
|
||||
],
|
||||
layout: {
|
||||
name: 'grid',
|
||||
boundingBox: {
|
||||
x1: 0,
|
||||
x2: 100,
|
||||
y1: 0,
|
||||
y2: 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Add nodes to cytoscape
|
||||
// First add groups, then services/junctions (to ensure parents exist before children)
|
||||
const nodeMap = new Map<string, any>();
|
||||
const groups = data.nodes.filter((n) => n.isGroup);
|
||||
const services = data.nodes.filter((n) => !n.isGroup);
|
||||
|
||||
// Add groups first
|
||||
groups.forEach((node) => {
|
||||
const cyNode = cy.add({
|
||||
group: 'nodes',
|
||||
data: {
|
||||
id: node.id,
|
||||
label: node.label || '',
|
||||
parent: node.parentId,
|
||||
type: 'group',
|
||||
},
|
||||
classes: 'node-group',
|
||||
});
|
||||
nodeMap.set(node.id, { node: cyNode, originalNode: node });
|
||||
});
|
||||
|
||||
// Then add services and junctions
|
||||
services.forEach((node) => {
|
||||
const nodeType = (node as any).type === 'junction' ? 'junction' : 'service';
|
||||
const cyNode = cy.add({
|
||||
group: 'nodes',
|
||||
data: {
|
||||
id: node.id,
|
||||
label: node.label || '',
|
||||
parent: node.parentId,
|
||||
width: node.width || iconSize,
|
||||
height: node.height || iconSize,
|
||||
type: nodeType,
|
||||
},
|
||||
classes: nodeType === 'junction' ? 'node-junction' : 'node-service',
|
||||
});
|
||||
nodeMap.set(node.id, { node: cyNode, originalNode: node });
|
||||
});
|
||||
|
||||
// Add edges to cytoscape
|
||||
const edgeMap = new Map<string, any>();
|
||||
data.edges.forEach((edge) => {
|
||||
const edgeData: any = {
|
||||
id: edge.id,
|
||||
source: edge.start || edge.source,
|
||||
target: edge.end || edge.target,
|
||||
label: edge.label || '',
|
||||
};
|
||||
|
||||
// Preserve architecture-specific edge data
|
||||
if ((edge as any).lhsDir) {
|
||||
edgeData.sourceDir = (edge as any).lhsDir;
|
||||
edgeData.targetDir = (edge as any).rhsDir;
|
||||
}
|
||||
|
||||
const edgeType =
|
||||
(edge as any).lhsDir && (edge as any).rhsDir
|
||||
? isArchitectureDirectionXY((edge as any).lhsDir, (edge as any).rhsDir)
|
||||
? 'segments'
|
||||
: 'straight'
|
||||
: 'straight';
|
||||
|
||||
const cyEdge = cy.add({
|
||||
group: 'edges',
|
||||
data: edgeData,
|
||||
classes: edgeType,
|
||||
});
|
||||
edgeMap.set(edge.id, { edge: cyEdge, originalEdge: edge });
|
||||
});
|
||||
|
||||
// Make cytoscape care about the dimensions of the nodes
|
||||
cy.nodes().forEach(function (n) {
|
||||
n.layoutDimensions = () => {
|
||||
const nodeData = n.data();
|
||||
return { w: nodeData.width || iconSize, h: nodeData.height || iconSize };
|
||||
};
|
||||
});
|
||||
|
||||
// Get alignment constraints
|
||||
const alignmentConstraint = getAlignments(data.nodes, spatialMaps, groupAlignments);
|
||||
|
||||
// Get relative placement constraints
|
||||
const relativePlacementConstraint = getRelativeConstraints(spatialMaps, data.nodes, iconSize);
|
||||
|
||||
// Run fcose layout
|
||||
const layout = cy.layout({
|
||||
name: 'fcose',
|
||||
quality: 'proof',
|
||||
styleEnabled: false,
|
||||
animate: false,
|
||||
nodeDimensionsIncludeLabels: false,
|
||||
idealEdgeLength(edge: any) {
|
||||
const [nodeA, nodeB] = edge.connectedNodes();
|
||||
const parentA = nodeA.data('parent');
|
||||
const parentB = nodeB.data('parent');
|
||||
const elasticity = parentA === parentB ? 1.5 * iconSize : 0.5 * iconSize;
|
||||
return elasticity;
|
||||
},
|
||||
edgeElasticity(edge: any) {
|
||||
const [nodeA, nodeB] = edge.connectedNodes();
|
||||
const parentA = nodeA.data('parent');
|
||||
const parentB = nodeB.data('parent');
|
||||
const elasticity = parentA === parentB ? 0.45 : 0.001;
|
||||
return elasticity;
|
||||
},
|
||||
alignmentConstraint,
|
||||
relativePlacementConstraint,
|
||||
} as LayoutOptions);
|
||||
|
||||
// Handle XY edges (edges with bends)
|
||||
layout.one('layoutstop', () => {
|
||||
function getSegmentWeights(
|
||||
source: Position,
|
||||
target: Position,
|
||||
pointX: number,
|
||||
pointY: number
|
||||
) {
|
||||
const { x: sX, y: sY } = source;
|
||||
const { x: tX, y: tY } = target;
|
||||
|
||||
let D =
|
||||
(pointY - sY + ((sX - pointX) * (sY - tY)) / (sX - tX)) /
|
||||
Math.sqrt(1 + Math.pow((sY - tY) / (sX - tX), 2));
|
||||
let W = Math.sqrt(Math.pow(pointY - sY, 2) + Math.pow(pointX - sX, 2) - Math.pow(D, 2));
|
||||
|
||||
const distAB = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2));
|
||||
W = W / distAB;
|
||||
|
||||
let delta1 = (tX - sX) * (pointY - sY) - (tY - sY) * (pointX - sX);
|
||||
delta1 = delta1 >= 0 ? 1 : -1;
|
||||
|
||||
let delta2 = (tX - sX) * (pointX - sX) + (tY - sY) * (pointY - sY);
|
||||
delta2 = delta2 >= 0 ? 1 : -1;
|
||||
|
||||
D = Math.abs(D) * delta1;
|
||||
W = W * delta2;
|
||||
|
||||
return { distances: D, weights: W };
|
||||
}
|
||||
|
||||
cy.startBatch();
|
||||
cy.edges().forEach((cyEdge) => {
|
||||
// Check if edge has data method and data exists
|
||||
if (
|
||||
cyEdge &&
|
||||
typeof cyEdge.data === 'function' &&
|
||||
typeof cyEdge.source === 'function' &&
|
||||
typeof cyEdge.target === 'function'
|
||||
) {
|
||||
try {
|
||||
const edgeData = cyEdge.data();
|
||||
if (edgeData?.sourceDir && edgeData?.targetDir) {
|
||||
const sourceNode = cyEdge.source();
|
||||
const targetNode = cyEdge.target();
|
||||
if (
|
||||
sourceNode &&
|
||||
targetNode &&
|
||||
typeof sourceNode.position === 'function' &&
|
||||
typeof targetNode.position === 'function'
|
||||
) {
|
||||
const { x: sX, y: sY } = sourceNode.position();
|
||||
const { x: tX, y: tY } = targetNode.position();
|
||||
if (
|
||||
sX !== tX &&
|
||||
sY !== tY &&
|
||||
!isNaN(sX) &&
|
||||
!isNaN(sY) &&
|
||||
!isNaN(tX) &&
|
||||
!isNaN(tY)
|
||||
) {
|
||||
const sourceDir = edgeData.sourceDir as ArchitectureDirection;
|
||||
if (
|
||||
typeof cyEdge.sourceEndpoint === 'function' &&
|
||||
typeof cyEdge.targetEndpoint === 'function'
|
||||
) {
|
||||
const sEP = cyEdge.sourceEndpoint();
|
||||
const tEP = cyEdge.targetEndpoint();
|
||||
if (
|
||||
sEP &&
|
||||
tEP &&
|
||||
typeof sEP.x === 'number' &&
|
||||
typeof sEP.y === 'number' &&
|
||||
typeof tEP.x === 'number' &&
|
||||
typeof tEP.y === 'number'
|
||||
) {
|
||||
const [pointX, pointY] = isArchitectureDirectionY(sourceDir)
|
||||
? [sEP.x, tEP.y]
|
||||
: [tEP.x, sEP.y];
|
||||
const { weights, distances } = getSegmentWeights(sEP, tEP, pointX, pointY);
|
||||
if (typeof cyEdge.style === 'function') {
|
||||
cyEdge.style('segment-distances', distances);
|
||||
cyEdge.style('segment-weights', weights);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// skip edges that can't be processed
|
||||
void error;
|
||||
}
|
||||
}
|
||||
});
|
||||
cy.endBatch();
|
||||
layout.run();
|
||||
});
|
||||
|
||||
layout.run();
|
||||
|
||||
cy.ready(() => {
|
||||
// Extract positioned nodes
|
||||
const positionedNodes: PositionedNode[] = [];
|
||||
cy.nodes().forEach((cyNode) => {
|
||||
const nodeData = nodeMap.get(cyNode.id());
|
||||
if (!nodeData) {
|
||||
return;
|
||||
}
|
||||
const nodeType = typeof cyNode.data === 'function' ? cyNode.data('type') : undefined;
|
||||
const isGroup = nodeType === 'group';
|
||||
|
||||
let pos: { x: number; y: number } | null = null;
|
||||
|
||||
if (isGroup && typeof cyNode.boundingBox === 'function') {
|
||||
const bbox = cyNode.boundingBox();
|
||||
if (
|
||||
bbox &&
|
||||
typeof bbox.x1 === 'number' &&
|
||||
typeof bbox.y1 === 'number' &&
|
||||
!isNaN(bbox.x1) &&
|
||||
!isNaN(bbox.y1)
|
||||
) {
|
||||
pos = { x: bbox.x1, y: bbox.y1 };
|
||||
if (
|
||||
typeof bbox.w === 'number' &&
|
||||
typeof bbox.h === 'number' &&
|
||||
!isNaN(bbox.w) &&
|
||||
!isNaN(bbox.h)
|
||||
) {
|
||||
positionedNodes.push({
|
||||
id: cyNode.id(),
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: bbox.w,
|
||||
height: bbox.h,
|
||||
originalNode: nodeData.originalNode,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pos && typeof cyNode.position === 'function') {
|
||||
const nodePos = cyNode.position();
|
||||
if (
|
||||
nodePos &&
|
||||
typeof nodePos.x === 'number' &&
|
||||
typeof nodePos.y === 'number' &&
|
||||
!isNaN(nodePos.x) &&
|
||||
!isNaN(nodePos.y)
|
||||
) {
|
||||
pos = { x: nodePos.x, y: nodePos.y };
|
||||
}
|
||||
}
|
||||
|
||||
if (pos) {
|
||||
positionedNodes.push({
|
||||
id: cyNode.id(),
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
width: typeof cyNode.data === 'function' ? cyNode.data('width') : undefined,
|
||||
height: typeof cyNode.data === 'function' ? cyNode.data('height') : undefined,
|
||||
originalNode: nodeData.originalNode,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Extract positioned edges
|
||||
const positionedEdges: PositionedEdge[] = [];
|
||||
cy.edges().forEach((cyEdge) => {
|
||||
if (
|
||||
cyEdge &&
|
||||
typeof cyEdge.id === 'function' &&
|
||||
typeof cyEdge.source === 'function' &&
|
||||
typeof cyEdge.target === 'function'
|
||||
) {
|
||||
const edgeData = edgeMap.get(cyEdge.id());
|
||||
if (edgeData) {
|
||||
const sourceNode = cyEdge.source();
|
||||
const targetNode = cyEdge.target();
|
||||
if (
|
||||
sourceNode &&
|
||||
targetNode &&
|
||||
typeof sourceNode.position === 'function' &&
|
||||
typeof targetNode.position === 'function'
|
||||
) {
|
||||
const sourcePos = sourceNode.position();
|
||||
const targetPos = targetNode.position();
|
||||
if (
|
||||
sourcePos &&
|
||||
targetPos &&
|
||||
typeof sourcePos.x === 'number' &&
|
||||
typeof sourcePos.y === 'number' &&
|
||||
typeof targetPos.x === 'number' &&
|
||||
typeof targetPos.y === 'number'
|
||||
) {
|
||||
positionedEdges.push({
|
||||
id: cyEdge.id(),
|
||||
source: sourceNode.id(),
|
||||
target: targetNode.id(),
|
||||
points: [
|
||||
{ x: sourcePos.x, y: sourcePos.y },
|
||||
{ x: targetPos.x, y: targetPos.y },
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up
|
||||
renderEl.remove();
|
||||
|
||||
resolve({
|
||||
nodes: positionedNodes,
|
||||
edges: positionedEdges,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alignment constraints for fcose
|
||||
*/
|
||||
function getAlignments(
|
||||
nodes: LayoutData['nodes'],
|
||||
spatialMaps: ArchitectureSpatialMap[],
|
||||
groupAlignments: ArchitectureGroupAlignments
|
||||
): FcoseAlignmentConstraint {
|
||||
const flattenAlignments = (
|
||||
alignmentObj: Record<number, Record<string, string[]>>,
|
||||
alignmentDir: ArchitectureAlignment
|
||||
): Record<string, string[]> => {
|
||||
return Object.entries(alignmentObj).reduce(
|
||||
(prev, [dir, alignments]) => {
|
||||
let cnt = 0;
|
||||
const arr = Object.entries(alignments);
|
||||
if (arr.length === 1) {
|
||||
prev[dir] = arr[0][1];
|
||||
return prev;
|
||||
}
|
||||
for (let i = 0; i < arr.length - 1; i++) {
|
||||
for (let j = i + 1; j < arr.length; j++) {
|
||||
const [aGroupId, aNodeIds] = arr[i];
|
||||
const [bGroupId, bNodeIds] = arr[j];
|
||||
const alignment = groupAlignments[aGroupId]?.[bGroupId];
|
||||
|
||||
if (alignment === alignmentDir) {
|
||||
prev[dir] ??= [];
|
||||
prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds];
|
||||
} else if (aGroupId === 'default' || bGroupId === 'default') {
|
||||
prev[dir] ??= [];
|
||||
prev[dir] = [...prev[dir], ...aNodeIds, ...bNodeIds];
|
||||
} else {
|
||||
const keyA = `${dir}-${cnt++}`;
|
||||
prev[keyA] = aNodeIds;
|
||||
const keyB = `${dir}-${cnt++}`;
|
||||
prev[keyB] = bNodeIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
},
|
||||
{} as Record<string, string[]>
|
||||
);
|
||||
};
|
||||
|
||||
// Create a node lookup by group
|
||||
const nodeGroupMap = new Map<string, string>();
|
||||
nodes.forEach((node) => {
|
||||
nodeGroupMap.set(node.id, node.parentId || 'default');
|
||||
});
|
||||
|
||||
const alignments = spatialMaps.map((spatialMap) => {
|
||||
const horizontalAlignments: Record<number, Record<string, string[]>> = {};
|
||||
const verticalAlignments: Record<number, Record<string, string[]>> = {};
|
||||
|
||||
Object.entries(spatialMap).forEach(([id, [x, y]]) => {
|
||||
const nodeGroup = nodeGroupMap.get(id) ?? 'default';
|
||||
|
||||
horizontalAlignments[y] ??= {};
|
||||
horizontalAlignments[y][nodeGroup] ??= [];
|
||||
horizontalAlignments[y][nodeGroup].push(id);
|
||||
|
||||
verticalAlignments[x] ??= {};
|
||||
verticalAlignments[x][nodeGroup] ??= [];
|
||||
verticalAlignments[x][nodeGroup].push(id);
|
||||
});
|
||||
|
||||
return {
|
||||
horiz: Object.values(flattenAlignments(horizontalAlignments, 'horizontal')).filter(
|
||||
(arr) => arr.length > 1
|
||||
),
|
||||
vert: Object.values(flattenAlignments(verticalAlignments, 'vertical')).filter(
|
||||
(arr) => arr.length > 1
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const [horizontal, vertical] = alignments.reduce(
|
||||
([prevHoriz, prevVert], { horiz, vert }) => {
|
||||
return [
|
||||
[...prevHoriz, ...horiz],
|
||||
[...prevVert, ...vert],
|
||||
];
|
||||
},
|
||||
[[] as string[][], [] as string[][]]
|
||||
);
|
||||
|
||||
return {
|
||||
horizontal,
|
||||
vertical,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative placement constraints for fcose
|
||||
*/
|
||||
function getRelativeConstraints(
|
||||
spatialMaps: ArchitectureSpatialMap[],
|
||||
nodes: LayoutData['nodes'],
|
||||
iconSize: number
|
||||
): FcoseRelativePlacementConstraint[] {
|
||||
const relativeConstraints: FcoseRelativePlacementConstraint[] = [];
|
||||
const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
|
||||
const strToPos = (pos: string) => pos.split(',').map((p) => parseInt(p));
|
||||
|
||||
spatialMaps.forEach((spatialMap) => {
|
||||
const invSpatialMap = Object.fromEntries(
|
||||
Object.entries(spatialMap).map(([id, pos]) => [posToStr(pos), id])
|
||||
);
|
||||
|
||||
const queue = [posToStr([0, 0])];
|
||||
const visited: Record<string, number> = {};
|
||||
const directions: Record<ArchitectureDirection, number[]> = {
|
||||
L: [-1, 0],
|
||||
R: [1, 0],
|
||||
T: [0, 1],
|
||||
B: [0, -1],
|
||||
};
|
||||
|
||||
while (queue.length > 0) {
|
||||
const curr = queue.shift();
|
||||
if (curr) {
|
||||
visited[curr] = 1;
|
||||
const currId = invSpatialMap[curr];
|
||||
if (currId) {
|
||||
const currPos = strToPos(curr);
|
||||
Object.entries(directions).forEach(([dir, shift]) => {
|
||||
const newPos = posToStr([currPos[0] + shift[0], currPos[1] + shift[1]]);
|
||||
const newId = invSpatialMap[newPos];
|
||||
if (newId && !visited[newPos]) {
|
||||
queue.push(newPos);
|
||||
relativeConstraints.push({
|
||||
[ArchitectureDirectionName[dir as ArchitectureDirection]]: newId,
|
||||
[ArchitectureDirectionName[
|
||||
getOppositeArchitectureDirection(dir as ArchitectureDirection)
|
||||
]]: currId,
|
||||
gap: 1.5 * iconSize,
|
||||
} as any);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return relativeConstraints;
|
||||
}
|
||||
13
packages/mermaid-layout-fcose/src/layouts.ts
Normal file
13
packages/mermaid-layout-fcose/src/layouts.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { LayoutLoaderDefinition } from 'mermaid';
|
||||
|
||||
const loader = async () => await import(`./render.js`);
|
||||
|
||||
const fcoseLayout: LayoutLoaderDefinition[] = [
|
||||
{
|
||||
name: 'architecture-fcose',
|
||||
loader,
|
||||
algorithm: 'architecture-fcose',
|
||||
},
|
||||
];
|
||||
|
||||
export default fcoseLayout;
|
||||
201
packages/mermaid-layout-fcose/src/render.ts
Normal file
201
packages/mermaid-layout-fcose/src/render.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import type { InternalHelpers, LayoutData, RenderOptions, SVG } from 'mermaid';
|
||||
import { executeFcoseLayout } from './layout.js';
|
||||
|
||||
interface NodeWithPosition {
|
||||
id: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
domId?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render function for fcose layout algorithm
|
||||
|
||||
* The fcose layout is optimized for architecture diagrams with spatial constraints
|
||||
* and group alignments.
|
||||
*/
|
||||
export const render = async (
|
||||
data4Layout: LayoutData,
|
||||
svg: SVG,
|
||||
{
|
||||
insertCluster,
|
||||
insertEdge,
|
||||
insertEdgeLabel,
|
||||
insertMarkers,
|
||||
insertNode,
|
||||
log,
|
||||
positionEdgeLabel,
|
||||
}: InternalHelpers,
|
||||
{ algorithm: _algorithm }: RenderOptions
|
||||
) => {
|
||||
const nodeDb: Record<string, NodeWithPosition> = {};
|
||||
const clusterDb: Record<string, any> = {};
|
||||
|
||||
const element = svg.select('g');
|
||||
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||
|
||||
const subGraphsEl = element.insert('g').attr('class', 'subgraphs');
|
||||
const edgePaths = element.insert('g').attr('class', 'edgePaths');
|
||||
const edgeLabels = element.insert('g').attr('class', 'edgeLabels');
|
||||
const nodes = element.insert('g').attr('class', 'nodes');
|
||||
|
||||
await Promise.all(
|
||||
data4Layout.nodes.map(async (node) => {
|
||||
if (node.isGroup) {
|
||||
const clusterNode: NodeWithPosition = {
|
||||
...node,
|
||||
id: node.id,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
};
|
||||
clusterDb[node.id] = clusterNode;
|
||||
nodeDb[node.id] = clusterNode;
|
||||
|
||||
const clusterResult = await insertCluster(subGraphsEl, node);
|
||||
if (clusterResult?.cluster) {
|
||||
clusterNode.domId = clusterResult.cluster;
|
||||
}
|
||||
} else {
|
||||
const nodeWithPosition: NodeWithPosition = {
|
||||
...node,
|
||||
id: node.id,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
};
|
||||
nodeDb[node.id] = nodeWithPosition;
|
||||
|
||||
const nodeEl = await insertNode(nodes, node, {
|
||||
config: data4Layout.config,
|
||||
dir: data4Layout.direction ?? 'TB',
|
||||
});
|
||||
|
||||
const boundingBox = nodeEl.node()!.getBBox();
|
||||
nodeWithPosition.width = boundingBox.width;
|
||||
nodeWithPosition.height = boundingBox.height;
|
||||
nodeWithPosition.domId = nodeEl;
|
||||
|
||||
log.debug(`Node ${node.id} dimensions: ${boundingBox.width}x${boundingBox.height}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const updatedLayoutData = {
|
||||
...data4Layout,
|
||||
nodes: data4Layout.nodes.map((node) => {
|
||||
const nodeWithDimensions = nodeDb[node.id];
|
||||
return {
|
||||
...node,
|
||||
width: nodeWithDimensions.width ?? node.width ?? 100,
|
||||
height: nodeWithDimensions.height ?? node.height ?? 50,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const layoutResult = await executeFcoseLayout(updatedLayoutData);
|
||||
|
||||
log.debug('Positioning nodes based on fcose layout results');
|
||||
|
||||
layoutResult.nodes.forEach((positionedNode) => {
|
||||
const node = nodeDb[positionedNode.id];
|
||||
if (
|
||||
node &&
|
||||
positionedNode.x !== undefined &&
|
||||
positionedNode.y !== undefined &&
|
||||
!isNaN(positionedNode.x) &&
|
||||
!isNaN(positionedNode.y)
|
||||
) {
|
||||
node.x = positionedNode.x;
|
||||
node.y = positionedNode.y;
|
||||
|
||||
if (node.domId) {
|
||||
if (
|
||||
node.isGroup &&
|
||||
positionedNode.width !== undefined &&
|
||||
positionedNode.height !== undefined
|
||||
) {
|
||||
const rect = node.domId.select('rect');
|
||||
if (!rect.empty()) {
|
||||
rect.attr('width', positionedNode.width);
|
||||
rect.attr('height', positionedNode.height);
|
||||
}
|
||||
}
|
||||
node.domId.attr('transform', `translate(${positionedNode.x}, ${positionedNode.y})`);
|
||||
log.debug(
|
||||
`Positioned node ${node.id} at (${positionedNode.x}, ${positionedNode.y})${node.isGroup ? ` with size ${positionedNode.width}x${positionedNode.height}` : ''}`
|
||||
);
|
||||
} else if (node.isGroup) {
|
||||
const clusterElement = subGraphsEl.select(`#${node.id}`);
|
||||
if (clusterElement && !clusterElement.empty()) {
|
||||
if (positionedNode.width !== undefined && positionedNode.height !== undefined) {
|
||||
const rect = clusterElement.select('rect');
|
||||
if (!rect.empty()) {
|
||||
rect.attr('width', positionedNode.width);
|
||||
rect.attr('height', positionedNode.height);
|
||||
}
|
||||
}
|
||||
clusterElement.attr('transform', `translate(${positionedNode.x}, ${positionedNode.y})`);
|
||||
log.debug(
|
||||
`Positioned group ${node.id} at (${positionedNode.x}, ${positionedNode.y})${positionedNode.width !== undefined ? ` with size ${positionedNode.width}x${positionedNode.height}` : ''}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (node && (isNaN(positionedNode.x) || isNaN(positionedNode.y))) {
|
||||
log.warn(
|
||||
`Node ${node.id} has invalid position: x=${positionedNode.x}, y=${positionedNode.y}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
data4Layout.edges.map(async (edge) => {
|
||||
await insertEdgeLabel(edgeLabels, edge);
|
||||
|
||||
const startNode = nodeDb[edge.start ?? ''];
|
||||
const endNode = nodeDb[edge.end ?? ''];
|
||||
|
||||
if (startNode && endNode) {
|
||||
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
||||
|
||||
if (positionedEdge) {
|
||||
const edgeWithPath = {
|
||||
...edge,
|
||||
points: positionedEdge.points,
|
||||
};
|
||||
const paths = insertEdge(
|
||||
edgePaths,
|
||||
edgeWithPath,
|
||||
clusterDb,
|
||||
data4Layout.type,
|
||||
startNode,
|
||||
endNode,
|
||||
data4Layout.diagramId
|
||||
);
|
||||
|
||||
positionEdgeLabel(edgeWithPath, paths);
|
||||
} else {
|
||||
const edgeWithPath = {
|
||||
...edge,
|
||||
points: [
|
||||
{ x: startNode.x ?? 0, y: startNode.y ?? 0 },
|
||||
{ x: endNode.x ?? 0, y: endNode.y ?? 0 },
|
||||
],
|
||||
};
|
||||
|
||||
const paths = insertEdge(
|
||||
edgePaths,
|
||||
edgeWithPath,
|
||||
clusterDb,
|
||||
data4Layout.type,
|
||||
startNode,
|
||||
endNode,
|
||||
data4Layout.diagramId
|
||||
);
|
||||
positionEdgeLabel(edgeWithPath, paths);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
53
packages/mermaid-layout-fcose/src/types.ts
Normal file
53
packages/mermaid-layout-fcose/src/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { LayoutData } from 'mermaid';
|
||||
|
||||
export type Node = LayoutData['nodes'][number];
|
||||
export type Edge = LayoutData['edges'][number];
|
||||
|
||||
/**
|
||||
* Positioned node after layout calculation
|
||||
*/
|
||||
export interface PositionedNode {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
originalNode?: Node;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positioned edge after layout calculation
|
||||
*/
|
||||
export interface PositionedEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
points: { x: number; y: number }[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of layout algorithm execution
|
||||
*/
|
||||
export interface LayoutResult {
|
||||
nodes: PositionedNode[];
|
||||
edges: PositionedEdge[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Architecture-specific data structures for fcose layout
|
||||
*/
|
||||
export type ArchitectureSpatialMap = Record<string, number[]>;
|
||||
|
||||
export type ArchitectureAlignment = 'vertical' | 'horizontal' | 'bend';
|
||||
|
||||
export type ArchitectureGroupAlignments = Record<
|
||||
string,
|
||||
Record<string, Exclude<ArchitectureAlignment, 'bend'>>
|
||||
>;
|
||||
|
||||
export interface ArchitectureDataStructures {
|
||||
spatialMaps: ArchitectureSpatialMap[];
|
||||
groupAlignments: ArchitectureGroupAlignments;
|
||||
}
|
||||
11
packages/mermaid-layout-fcose/tsconfig.json
Normal file
11
packages/mermaid-layout-fcose/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/**/*.d.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
||||
|
||||
12
packages/mermaid-layout-tidy-tree/CHANGELOG.md
Normal file
12
packages/mermaid-layout-tidy-tree/CHANGELOG.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# @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
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-tidy-tree",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "Tidy-tree layout engine for mermaid",
|
||||
"module": "dist/mermaid-layout-tidy-tree.core.mjs",
|
||||
"types": "dist/layouts.d.ts",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zenuml/core": "^3.35.2"
|
||||
"@zenuml/core": "^3.41.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mermaid": "workspace:^"
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# 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
|
||||
@@ -154,7 +168,6 @@
|
||||
### 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 ...)
|
||||
@@ -173,14 +186,12 @@
|
||||
### 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "11.10.0",
|
||||
"version": "11.11.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.0.4",
|
||||
"@iconify/utils": "^3.0.1",
|
||||
"@braintree/sanitize-url": "^7.1.1",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@mermaid-js/parser": "workspace:^",
|
||||
"@types/d3": "^7.4.3",
|
||||
"cytoscape": "^3.29.3",
|
||||
"cytoscape": "^3.33.1",
|
||||
"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.13",
|
||||
"dayjs": "^1.11.18",
|
||||
"dompurify": "^3.2.5",
|
||||
"katex": "^0.16.22",
|
||||
"khroma": "^2.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^16.0.0",
|
||||
"marked": "^16.3.0",
|
||||
"roughjs": "^4.6.6",
|
||||
"stylis": "^4.3.6",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/jsonschema2md": "^8.0.2",
|
||||
"@adobe/jsonschema2md": "^8.0.5",
|
||||
"@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.1.0",
|
||||
"canvas": "^3.2.0",
|
||||
"chokidar": "3.6.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"csstree-validator": "^4.0.1",
|
||||
"globby": "^14.0.2",
|
||||
"globby": "^14.1.0",
|
||||
"jison": "^0.4.18",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-base64": "^3.7.8",
|
||||
"jsdom": "^26.1.0",
|
||||
"json-schema-to-typescript": "^15.0.4",
|
||||
"micromatch": "^4.0.8",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^3.5.2",
|
||||
"prettier": "^3.6.2",
|
||||
"remark": "^15.0.1",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"start-server-and-test": "^2.0.10",
|
||||
"type-fest": "^4.35.0",
|
||||
"typedoc": "^0.28.9",
|
||||
"typedoc-plugin-markdown": "^4.8.0",
|
||||
"start-server-and-test": "^2.1.2",
|
||||
"type-fest": "^4.41.0",
|
||||
"typedoc": "^0.28.13",
|
||||
"typedoc-plugin-markdown": "^4.8.1",
|
||||
"typescript": "~5.7.3",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vitepress": "^1.0.2",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-search": "1.0.4-alpha.22"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -3,7 +3,9 @@ import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
import type { D3Element } from '../../types.js';
|
||||
import { cleanAndMerge } from '../../utils.js';
|
||||
import { cleanAndMerge, getEdgeId } from '../../utils.js';
|
||||
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
|
||||
|
||||
import {
|
||||
clear as commonClear,
|
||||
getAccDescription,
|
||||
@@ -351,15 +353,147 @@ export class ArchitectureDB implements DiagramDB {
|
||||
public getDiagramTitle = getDiagramTitle;
|
||||
public getAccDescription = getAccDescription;
|
||||
public setAccDescription = setAccDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
|
||||
* @param field - the config field to access
|
||||
* @returns
|
||||
*/
|
||||
// export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
|
||||
// field: T
|
||||
// ): Required<ArchitectureDiagramConfig>[T] {
|
||||
// return db.getConfig()[field];
|
||||
// }
|
||||
/**
|
||||
* Converts architecture diagram data to LayoutData format for unified rendering
|
||||
*/
|
||||
public getData(): LayoutData {
|
||||
const config = commonGetConfig();
|
||||
const nodes: Node[] = [];
|
||||
const edges: Edge[] = [];
|
||||
|
||||
const groups = this.getGroups();
|
||||
for (const group of groups) {
|
||||
const padding = this.getConfigField('padding');
|
||||
const fontSize = this.getConfigField('fontSize');
|
||||
|
||||
const groupWidth = 200;
|
||||
let groupHeight = 150;
|
||||
|
||||
if (group.title || group.icon) {
|
||||
groupHeight += fontSize + padding;
|
||||
}
|
||||
|
||||
nodes.push({
|
||||
id: group.id,
|
||||
label: group.title,
|
||||
parentId: group.in,
|
||||
isGroup: true,
|
||||
shape: 'rect',
|
||||
icon: group.icon ? `mermaid-architecture:${group.icon}` : undefined,
|
||||
width: groupWidth,
|
||||
height: groupHeight,
|
||||
padding: padding,
|
||||
cssClasses: 'architecture-group',
|
||||
cssCompiledStyles: [
|
||||
'stroke: #cccccc',
|
||||
'stroke-width: 2px',
|
||||
'stroke-dasharray: 8,8',
|
||||
'fill: transparent',
|
||||
],
|
||||
labelStyle: '',
|
||||
look: config.look || 'classic',
|
||||
rx: 5,
|
||||
ry: 5,
|
||||
});
|
||||
}
|
||||
|
||||
const services = this.getServices();
|
||||
for (const service of services) {
|
||||
const iconSize = this.getConfigField('iconSize');
|
||||
let nodeWidth = iconSize;
|
||||
let nodeHeight = iconSize;
|
||||
|
||||
if (service.title) {
|
||||
nodeHeight += iconSize * 0.3;
|
||||
nodeWidth = Math.max(nodeWidth, iconSize * 1.5);
|
||||
}
|
||||
|
||||
nodes.push({
|
||||
id: service.id,
|
||||
label: service.title,
|
||||
parentId: service.in,
|
||||
isGroup: false,
|
||||
shape: service.icon || (service as any).iconText ? 'icon' : 'squareRect',
|
||||
icon: service.icon ? `mermaid-architecture:${service.icon}` : 'mermaid-architecture:blank',
|
||||
width: service.width || nodeWidth,
|
||||
height: service.height || nodeHeight,
|
||||
cssClasses: 'architecture-service',
|
||||
look: config.look,
|
||||
padding: this.getConfigField('padding') / 4,
|
||||
description: (service as any).iconText ? [(service as any).iconText] : undefined,
|
||||
assetWidth: iconSize,
|
||||
assetHeight: iconSize,
|
||||
});
|
||||
}
|
||||
|
||||
const junctions = this.getJunctions();
|
||||
for (const junction of junctions) {
|
||||
nodes.push({
|
||||
id: junction.id,
|
||||
parentId: junction.in,
|
||||
isGroup: false,
|
||||
shape: 'squareRect',
|
||||
width: 2,
|
||||
height: 2,
|
||||
cssClasses: 'architecture-junction',
|
||||
look: config.look,
|
||||
type: 'junction' as any,
|
||||
padding: 0,
|
||||
});
|
||||
}
|
||||
|
||||
const architectureEdges = this.getEdges();
|
||||
let edgeCounter = 0;
|
||||
for (const edge of architectureEdges) {
|
||||
const edgeData = {
|
||||
id: getEdgeId(edge.lhsId, edge.rhsId, { counter: edgeCounter, prefix: 'L' }),
|
||||
start: edge.lhsId,
|
||||
end: edge.rhsId,
|
||||
source: edge.lhsId,
|
||||
target: edge.rhsId,
|
||||
label: edge.title || '',
|
||||
labelpos: 'c',
|
||||
type: 'normal',
|
||||
minlen: 2,
|
||||
weight: 1,
|
||||
classes: 'edge-thickness-normal edge-pattern-solid architecture-edge',
|
||||
look: config.look || 'classic',
|
||||
curve: 'linear',
|
||||
arrowTypeStart: edge.lhsInto ? 'point' : 'none',
|
||||
arrowTypeEnd: edge.rhsInto ? 'point' : 'none',
|
||||
arrowheadStyle: 'fill: #333',
|
||||
thickness: 'normal',
|
||||
pattern: 'solid',
|
||||
style: ['stroke: #333333', 'stroke-width: 3px', 'fill: none'],
|
||||
cssCompiledStyles: [],
|
||||
labelStyle: [],
|
||||
lhsDir: edge.lhsDir,
|
||||
rhsDir: edge.rhsDir,
|
||||
lhsInto: edge.lhsInto,
|
||||
rhsInto: edge.rhsInto,
|
||||
lhsGroup: edge.lhsGroup,
|
||||
rhsGroup: edge.rhsGroup,
|
||||
} as Edge & {
|
||||
lhsDir: any;
|
||||
rhsDir: any;
|
||||
lhsInto?: boolean;
|
||||
rhsInto?: boolean;
|
||||
lhsGroup?: boolean;
|
||||
rhsGroup?: boolean;
|
||||
};
|
||||
|
||||
edges.push(edgeData);
|
||||
edgeCounter++;
|
||||
}
|
||||
|
||||
const result = {
|
||||
nodes,
|
||||
edges,
|
||||
config,
|
||||
dataStructures: this.getDataStructures(),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import { parser } from './architectureParser.js';
|
||||
import { ArchitectureDB } from './architectureDb.js';
|
||||
import styles from './architectureStyles.js';
|
||||
import { renderer } from './architectureRenderer.js';
|
||||
import { renderer } from './architectureRenderer-unified.js';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
||||
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||
import type { LayoutData } from '../../rendering-util/types.js';
|
||||
import utils from '../../utils.js';
|
||||
|
||||
import { registerIconPacks } from '../../rendering-util/icons.js';
|
||||
import { architectureIcons } from './architectureIcons.js';
|
||||
|
||||
export const getClasses = function (
|
||||
_text: string,
|
||||
_diagramObj: any
|
||||
): Map<string, DiagramStyleClassDef> {
|
||||
return new Map();
|
||||
};
|
||||
|
||||
export const draw = async function (_text: string, id: string, _version: string, diag: any) {
|
||||
registerIconPacks([
|
||||
{
|
||||
name: architectureIcons.prefix,
|
||||
icons: architectureIcons,
|
||||
},
|
||||
]);
|
||||
const { securityLevel, architecture: conf, layout } = getConfig();
|
||||
|
||||
const data4Layout = diag.db.getData() as LayoutData;
|
||||
|
||||
const svg = getDiagramElement(id, securityLevel);
|
||||
|
||||
data4Layout.type = diag.type;
|
||||
const layoutToUse = layout || 'architecture-fcose';
|
||||
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layoutToUse, { fallback: 'dagre' });
|
||||
|
||||
data4Layout.nodeSpacing = 100;
|
||||
data4Layout.rankSpacing = 100;
|
||||
data4Layout.markers = ['point'];
|
||||
data4Layout.diagramId = id;
|
||||
|
||||
log.debug('Architecture layout data:', data4Layout);
|
||||
await render(data4Layout, svg);
|
||||
|
||||
const padding = conf?.padding ?? 8;
|
||||
utils.insertTitle(svg, 'architectureTitleText', 0, diag.db.getDiagramTitle());
|
||||
|
||||
setupViewPortForSVG(svg, padding, 'architecture', conf?.useMaxWidth ?? true);
|
||||
};
|
||||
|
||||
export const renderer = { draw };
|
||||
@@ -2,6 +2,7 @@ import type { DiagramDBBase } from '../../diagram-api/types.js';
|
||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||
import type { D3Element } from '../../types.js';
|
||||
import type cytoscape from 'cytoscape';
|
||||
import type { LayoutData } from '../../rendering-util/types.js';
|
||||
|
||||
/*=======================================*\
|
||||
| Architecture Diagram Types |
|
||||
@@ -256,7 +257,8 @@ export interface ArchitectureDB extends DiagramDBBase<ArchitectureDiagramConfig>
|
||||
getEdges: () => ArchitectureEdge[];
|
||||
setElementForId: (id: string, element: D3Element) => void;
|
||||
getElementById: (id: string) => D3Element;
|
||||
getDataStructures: () => ArchitectureDataStructures;
|
||||
getData: () => LayoutData;
|
||||
getDirection: () => string;
|
||||
}
|
||||
|
||||
export type ArchitectureAdjacencyList = Record<string, ArchitectureDirectionPairMap>;
|
||||
|
||||
@@ -40,105 +40,26 @@ const openSourceFeatures: Feature[] = [
|
||||
{ iconUrl: '/icons/version-history.svg', featureName: 'Version history' },
|
||||
];
|
||||
|
||||
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 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 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) => {
|
||||
|
||||
@@ -17,7 +17,6 @@ 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
|
||||
|
||||
@@ -23,7 +23,6 @@ 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)
|
||||
|
||||
@@ -33,13 +33,11 @@ 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)
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# 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 Mermaid’s 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
|
||||
|
||||
@@ -17,25 +17,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jiti": "^2.4.2",
|
||||
"mermaid": "workspace:^",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.1.37",
|
||||
"@unocss/reset": "^66.0.0",
|
||||
"@iconify-json/carbon": "^1.2.13",
|
||||
"@unocss/reset": "^66.5.1",
|
||||
"@vite-pwa/vitepress": "^1.0.0",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"fast-glob": "^3.3.3",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^2.0.3",
|
||||
"unocss": "^66.4.2",
|
||||
"unplugin-vue-components": "^28.4.0",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-pwa": "^1.0.0",
|
||||
"vitepress": "1.6.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",
|
||||
"workbox-window": "^7.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 custom icons](../config/icons.md).
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ 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)
|
||||
@@ -97,7 +96,6 @@ 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
|
||||
@@ -107,7 +105,6 @@ 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
|
||||
@@ -116,18 +113,15 @@ 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
|
||||
|
||||
@@ -9,6 +9,7 @@ import intersectRect from '../rendering-elements/intersect/intersect-rect.js';
|
||||
import createLabel from './createLabel.js';
|
||||
import { createRoundedRectPathD } from './shapes/roundedRectPath.ts';
|
||||
import { styles2String, userNodeOverrides } from './shapes/handDrawnShapeStyles.js';
|
||||
import { getIconSVG } from '../icons.js';
|
||||
|
||||
const rect = async (parent, node) => {
|
||||
log.info('Creating subgraph rect for ', node.id, node);
|
||||
@@ -30,6 +31,21 @@ const rect = async (parent, node) => {
|
||||
// Create the label and insert it after the rect
|
||||
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label ');
|
||||
|
||||
let iconWidth = 0;
|
||||
if (node.icon) {
|
||||
const iconSize = node.padding;
|
||||
const iconEl = labelEl.append('g').attr('class', 'cluster-label-icon');
|
||||
iconEl.html(
|
||||
`<g>${await getIconSVG(node.icon, {
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
fallbackPrefix: '',
|
||||
})}</g>`
|
||||
);
|
||||
const iconBBox = iconEl.node().getBBox();
|
||||
iconWidth = iconBBox.width;
|
||||
}
|
||||
|
||||
const text = await createText(labelEl, node.label, {
|
||||
style: node.labelStyle,
|
||||
useHtmlLabels,
|
||||
@@ -47,8 +63,9 @@ const rect = async (parent, node) => {
|
||||
dv.attr('height', bbox.height);
|
||||
}
|
||||
|
||||
const width = node.width <= bbox.width + node.padding ? bbox.width + node.padding : node.width;
|
||||
if (node.width <= bbox.width + node.padding) {
|
||||
const labelWidth = iconWidth > 0 ? iconWidth + bbox.width : bbox.width;
|
||||
const width = node.width <= labelWidth + node.padding ? labelWidth + node.padding : node.width;
|
||||
if (node.width <= labelWidth + node.padding) {
|
||||
node.diff = (width - node.width) / 2 - node.padding;
|
||||
} else {
|
||||
node.diff = -node.padding;
|
||||
@@ -93,11 +110,20 @@ const rect = async (parent, node) => {
|
||||
.attr('height', height);
|
||||
}
|
||||
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
|
||||
labelEl.attr(
|
||||
'transform',
|
||||
// This puts the label on top of the box instead of inside it
|
||||
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
|
||||
);
|
||||
|
||||
if (node.icon) {
|
||||
const textX = iconWidth > 0 ? iconWidth + node.padding / 4 : node.padding / 4;
|
||||
const textY = node.padding / 4;
|
||||
select(text).attr('transform', `translate(${textX}, ${textY})`);
|
||||
|
||||
const labelX = x + node.padding / 4;
|
||||
const labelY = y + node.padding / 4;
|
||||
labelEl.attr('transform', `translate(${labelX}, ${labelY})`);
|
||||
} else {
|
||||
const labelX = node.x - labelWidth / 2;
|
||||
const labelY = node.y - node.height / 2 + subGraphTitleTopMargin;
|
||||
labelEl.attr('transform', `translate(${labelX}, ${labelY})`);
|
||||
}
|
||||
|
||||
if (labelStyles) {
|
||||
const span = labelEl.select('span');
|
||||
|
||||
@@ -44,18 +44,15 @@ ValueConverter -->> Package: Return AST
|
||||
```
|
||||
|
||||
- When to override `TokenBuilder`?
|
||||
|
||||
- To override keyword rules.
|
||||
- To override terminal rules that need a custom function.
|
||||
- To manually reorder the list of rules.
|
||||
|
||||
- When to override `Lexer`?
|
||||
|
||||
- To modify input before tokenizing.
|
||||
- To insert/modify tokens that cannot or have not been parsed.
|
||||
|
||||
- When to override `LangiumParser`?
|
||||
|
||||
- To insert or modify attributes that can't be parsed.
|
||||
|
||||
- When to override `ValueConverter`?
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# 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
|
||||
@@ -154,7 +168,6 @@
|
||||
### 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 ...)
|
||||
@@ -173,14 +186,12 @@
|
||||
### 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mermaid-js/tiny",
|
||||
"version": "11.10.0",
|
||||
"version": "11.11.0",
|
||||
"description": "Tiny version of mermaid",
|
||||
"type": "commonjs",
|
||||
"main": "./dist/mermaid.tiny.js",
|
||||
|
||||
10061
pnpm-lock.yaml
generated
10061
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user