Merge branch 'develop' of https://github.com/mermaid-js/mermaid into mindmaps-and-elk-updates

This commit is contained in:
darshanr0107
2025-09-04 13:46:33 +05:30
139 changed files with 8861 additions and 4808 deletions

View File

@@ -1,5 +0,0 @@
---
'@mermaid-js/mermaid-zenuml': patch
---
Fixed a critical bug that the ZenUML diagram is not rendered.

View File

@@ -1,6 +0,0 @@
---
'@mermaid-js/layout-elk': patch
'mermaid': patch
---
feat: Exposing elk configuration forceNodeModelOrder and considerModelOrder to the mermaid configuration

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
chore: Remove the "-beta" suffix from the XYChart, Block, Sankey diagrams to reflect their stable status

View File

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

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Position the edge label in state diagram correctly relative to the edge

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
---
fix: Apply correct dateFormat in Gantt chart to show only day when specified

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: handle exclude dates properly in Gantt charts when using dateFormat: 'YYYY-MM-DD HH:mm:ss'

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: fixed connection gaps in flowchart for roundedRect, stadium and diamond shape

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

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Update casing of ID in requirement diagram

View File

@@ -1,5 +0,0 @@
---
'mermaid': minor
---
feat: Added support for per link curve styling in flowchart diagram using edge ids

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Make flowchart elk detector regex match less greedy

View File

@@ -1,8 +0,0 @@
---
'mermaid': patch
---
fix(block): overflowing blocks no longer affect later lines
This may change the layout of block diagrams that have overflowing lines
(i.e. block diagrams that use up more columns that the `columns` specifier).

View File

@@ -1,7 +0,0 @@
---
'mermaid': patch
---
fix: log warning for blocks exceeding column width
This update adds a validation check that logs a warning message when a block's width exceeds the defined column layout.

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Add escaped class literal name on namespace

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Allow equals sign in sequenceDiagram labels

View File

@@ -1,9 +0,0 @@
---
'mermaid': patch
---
Add validation for negative values in pie charts:
Prevents crashes during parsing by validating values post-parsing.
Provides clearer, user-friendly error messages for invalid negative inputs.

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
chore: migrate to class-based ArchitectureDB implementation

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: node border style for handdrawn shapes

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Update flowchart direction TD's behavior to be the same as TB

View File

@@ -1,5 +0,0 @@
---
'@mermaid-js/layout-elk': patch
---
Make elk not force node model order, but strongly consider it instead

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: correctly render non-directional lines for '---' in block diagrams

View File

@@ -1,9 +0,0 @@
---
'mermaid': patch
---
fix: fallback to raw text instead of rendering _Unsupported markdown_ or empty blocks
Instead of printing **Unsupported markdown: XXX**, or empty blocks when using a markdown feature
that Mermaid does not yet support when `htmlLabels: true`(default) or `htmlLabels: false`,
fallback to the raw markdown text.

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
chore: Update packet diagram to use new class-based database structure

View File

@@ -2,6 +2,7 @@
Ashish Jain
cpettitt
Dong Cai
fourcube
knsv
Knut Sveidqvist
Nikolay Rozhkov

View File

@@ -5,6 +5,7 @@ bmatrix
braintree
catmull
compositTitleSize
cose
curv
doublecircle
elem

View File

@@ -1,4 +1,5 @@
BRANDES
Buzan
circo
handDrawn
KOEPF

1
.github/lychee.toml vendored
View File

@@ -59,6 +59,7 @@ exclude = [
"https://huehive.co",
"https://foswiki.org",
"https://www.gnu.org",
"https://redmine.org",
"https://mermaid-preview.com"
]

View File

@@ -23,9 +23,6 @@ env:
jobs:
e2e-applitools:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- if: ${{ ! env.USE_APPLI }}
name: Warn if not using Applitools

View File

@@ -58,7 +58,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Commit and create pull request
uses: peter-evans/create-pull-request@1310d7dab503600742045e6fd4b84dda64352858
uses: peter-evans/create-pull-request@18e469570b1cf0dfc11d60ec121099f8ff3e617a
with:
add-paths: |
cypress/timings.json

View File

@@ -7,6 +7,5 @@ export default {
'prettier --write',
],
'.cspell/*.txt': ['tsx scripts/fixCSpell.ts'],
'**/*.md': ['pnpm cspell'],
'**/*.jison': ['pnpm -w run lint:jison'],
};

View File

@@ -14,7 +14,7 @@ interface CodeObject {
mermaid: CypressMermaidConfig;
}
const utf8ToB64 = (str: string): string => {
export const utf8ToB64 = (str: string): string => {
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
};
@@ -22,7 +22,7 @@ const batchId: string =
'mermaid-batch-' +
(Cypress.env('useAppli')
? Date.now().toString()
: Cypress.env('CYPRESS_COMMIT') || Date.now().toString());
: (Cypress.env('CYPRESS_COMMIT') ?? Date.now().toString()));
export const mermaidUrl = (
graphStr: string | string[],
@@ -61,9 +61,7 @@ export const imgSnapshotTest = (
sequence: {
...(_options.sequence ?? {}),
actorFontFamily: 'courier',
noteFontFamily: _options.sequence?.noteFontFamily
? _options.sequence.noteFontFamily
: 'courier',
noteFontFamily: _options.sequence?.noteFontFamily ?? 'courier',
messageFontFamily: 'courier',
},
};

View File

@@ -1,4 +1,4 @@
import { mermaidUrl } from '../../helpers/util.ts';
import { imgSnapshotTest, mermaidUrl, utf8ToB64 } from '../../helpers/util.ts';
describe('XSS', () => {
it('should handle xss in tags', () => {
const str =
@@ -141,4 +141,37 @@ describe('XSS', () => {
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize icon labels in architecture diagrams', () => {
const str = JSON.stringify({
code: `architecture-beta
group api(cloud)[API]
service db "<img src=x onerror=\\"xssAttack()\\">" [Database] in api`,
});
imgSnapshotTest(utf8ToB64(str), {}, true);
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize katex blocks', () => {
const str = JSON.stringify({
code: `sequenceDiagram
participant A as Alice<img src="x" onerror="xssAttack()">$$\\text{Alice}$$
A->>John: Hello John, how are you?`,
});
imgSnapshotTest(utf8ToB64(str), {}, true);
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize labels', () => {
const str = JSON.stringify({
code: `erDiagram
"<img src=x onerror=xssAttack()>" ||--|| ENTITY2 : "<img src=x onerror=xssAttack()>"
`,
});
imgSnapshotTest(utf8ToB64(str), {}, true);
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
});

View File

@@ -524,5 +524,18 @@ describe('Class diagram', () => {
`,
{}
);
it('should handle an empty class body with empty braces', () => {
imgSnapshotTest(
` classDiagram
class FooBase~T~ {}
class Bar {
+Zip
+Zap()
}
FooBase <|-- Ba
`,
{ flowchart: { defaultRenderer: 'elk' } }
);
});
});
});

View File

@@ -0,0 +1,79 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
describe('Mindmap Tidy Tree', () => {
it('1-tidy-tree: should render a simple mindmap without children', () => {
imgSnapshotTest(
` ---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
A
B
`
);
});
it('2-tidy-tree: should render a simple mindmap', () => {
imgSnapshotTest(
` ---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
`
);
});
it('3-tidy-tree: should render a mindmap with different shapes', () => {
imgSnapshotTest(
` ---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness&lt;br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
id)I am a cloud(
id))I am a bang((
Tools
`
);
});
it('4-tidy-tree: should render a mindmap with children', () => {
imgSnapshotTest(
` ---
config:
layout: tidy-tree
---
mindmap
((This is a mindmap))
child1
grandchild 1
grandchild 2
child2
grandchild 3
grandchild 4
child3
grandchild 5
grandchild 6
`
);
});
});

View File

@@ -0,0 +1,659 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
const looks = ['classic'];
const participantTypes = [
{ type: 'participant', display: 'participant' },
{ type: 'actor', display: 'actor' },
{ type: 'boundary', display: 'boundary' },
{ type: 'control', display: 'control' },
{ type: 'entity', display: 'entity' },
{ type: 'database', display: 'database' },
{ type: 'collections', display: 'collections' },
{ type: 'queue', display: 'queue' },
];
const restrictedTypes = ['boundary', 'control', 'entity', 'database', 'collections', 'queue'];
const interactionTypes = ['->>', '-->>', '->', '-->', '-x', '--x', '->>+', '-->>+'];
const notePositions = ['left of', 'right of', 'over'];
function getParticipantLine(name, type, alias) {
if (restrictedTypes.includes(type)) {
return ` participant ${name}@{ "type" : "${type}" }\n`;
} else if (alias) {
return ` participant ${name}@{ "type" : "${type}" } \n`;
} else {
return ` participant ${name}@{ "type" : "${type}" }\n`;
}
}
looks.forEach((look) => {
describe(`Sequence Diagram Tests - ${look} look`, () => {
it('should render all participant types', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.forEach((pt, index) => {
const name = `${pt.display}${index}`;
diagramCode += getParticipantLine(name, pt.type);
});
for (let i = 0; i < participantTypes.length - 1; i++) {
diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
}
imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } });
});
it('should render all interaction types', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
interactionTypes.forEach((interaction, index) => {
diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`;
});
imgSnapshotTest(diagramCode, { look });
});
it('should render participant creation and destruction', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.forEach((pt, index) => {
const name = `${pt.display}${index}`;
diagramCode += getParticipantLine('A', pt.type);
diagramCode += getParticipantLine('B', pt.type);
diagramCode += ` create participant ${name}@{ "type" : "${pt.type}" }\n`;
diagramCode += ` A ->> ${name}: Hello ${pt.display}\n`;
if (index % 2 === 0) {
diagramCode += ` destroy ${name}\n`;
}
});
imgSnapshotTest(diagramCode, { look });
});
it('should render notes in all positions', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
notePositions.forEach((position, index) => {
diagramCode += ` Note ${position} A: Note ${position} ${index}\n`;
});
diagramCode += ` A ->> B: Message with notes\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render parallel interactions', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 4).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` par Parallel actions\n`;
for (let i = 0; i < 3; i += 2) {
diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
if (i < participantTypes.length - 2) {
diagramCode += ` and\n`;
}
}
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render alternative flows', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += getParticipantLine('A', 'actor');
diagramCode += getParticipantLine('B', 'boundary');
diagramCode += ` alt Successful case\n`;
diagramCode += ` A ->> B: Request\n`;
diagramCode += ` B -->> A: Success\n`;
diagramCode += ` else Failure case\n`;
diagramCode += ` A ->> B: Request\n`;
diagramCode += ` B --x A: Failure\n`;
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render loops', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` loop For each participant\n`;
for (let i = 0; i < 3; i++) {
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Message ${i}\n`;
}
diagramCode += ` end\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render boxes around groups', () => {
let diagramCode = `sequenceDiagram\n`;
diagramCode += ` box Group 1\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
});
diagramCode += ` end\n`;
diagramCode += ` box rgb(200,220,255) Group 2\n`;
participantTypes.slice(3, 6).forEach((pt, index) => {
diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
});
diagramCode += ` end\n`;
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[3].display}0: Cross-group message\n`;
imgSnapshotTest(diagramCode, { look });
});
it('should render with different font settings', () => {
let diagramCode = `sequenceDiagram\n`;
participantTypes.slice(0, 3).forEach((pt, index) => {
diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
});
diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Regular message\n`;
diagramCode += ` Note right of ${participantTypes[1].display}1: Regular note\n`;
imgSnapshotTest(diagramCode, {
look,
sequence: {
actorFontFamily: 'courier',
actorFontSize: 14,
messageFontFamily: 'Arial',
messageFontSize: 12,
noteFontFamily: 'times',
noteFontSize: 16,
noteAlign: 'left',
},
});
});
});
});
// Additional tests for specific combinations
describe('Sequence Diagram Special Cases', () => {
it('should render complex sequence with all features', () => {
const diagramCode = `
sequenceDiagram
box rgb(200,220,255) Authentication
actor User
participant LoginUI@{ "type": "boundary" }
participant AuthService@{ "type": "control" }
participant UserDB@{ "type": "database" }
end
box rgb(200,255,220) Order Processing
participant Order@{ "type": "entity" }
participant OrderQueue@{ "type": "queue" }
participant AuditLogs@{ "type": "collections" }
end
User ->> LoginUI: Enter credentials
LoginUI ->> AuthService: Validate
AuthService ->> UserDB: Query user
UserDB -->> AuthService: User data
alt Valid credentials
AuthService -->> LoginUI: Success
LoginUI -->> User: Welcome
par Place order
User ->> Order: New order
Order ->> OrderQueue: Process
and
Order ->> AuditLogs: Record
end
loop Until confirmed
OrderQueue ->> Order: Update status
Order -->> User: Notification
end
else Invalid credentials
AuthService --x LoginUI: Failure
LoginUI --x User: Retry
end
`;
imgSnapshotTest(diagramCode, {});
});
it('should render with wrapped messages and notes', () => {
const diagramCode = `
sequenceDiagram
participant A
participant B
A ->> B: This is a very long message that should wrap properly in the diagram rendering
Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram
par Wrapped parallel
A ->> B: Parallel message 1<br>with explicit line break
and
B ->> A: Parallel message 2<br>with explicit line break
end
loop Wrapped loop
Note right of B: This is a long note<br>in a loop
A ->> B: Message in loop
end
`;
imgSnapshotTest(diagramCode, { sequence: { wrap: true } });
});
describe('Sequence Diagram Rendering with Different Participant Types', () => {
it('should render a sequence diagram with various participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
participant User@{ "type": "actor" }
participant AuthService@{ "type": "control" }
participant UI@{ "type": "boundary" }
participant OrderController@{ "type": "control" }
participant Product@{ "type": "entity" }
participant MongoDB@{ "type": "database" }
participant Products@{ "type": "collections" }
participant OrderQueue@{ "type": "queue" }
User ->> UI: Login request
UI ->> AuthService: Validate credentials
AuthService -->> UI: Authentication token
UI ->> OrderController: Place order
OrderController ->> Product: Check availability
Product -->> OrderController: Available
OrderController ->> MongoDB: Save order
MongoDB -->> OrderController: Order saved
OrderController ->> OrderQueue: Process payment
OrderQueue -->> User: Order confirmation
`
);
});
it('should render participant creation and destruction with different types', () => {
imgSnapshotTest(`
sequenceDiagram
participant Alice@{ "type" : "boundary" }
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl@{ "type" : "control" }
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bob
Bob->>Alice: I agree
`);
});
it('should handle complex interactions between different participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
box rgb(200,220,255) Authentication
participant User@{ "type": "actor" }
participant LoginUI@{ "type": "boundary" }
participant AuthService@{ "type": "control" }
participant UserDB@{ "type": "database" }
end
box rgb(200,255,220) Order Processing
participant Order@{ "type": "entity" }
participant OrderQueue@{ "type": "queue" }
participant AuditLogs@{ "type": "collections" }
end
User ->> LoginUI: Enter credentials
LoginUI ->> AuthService: Validate
AuthService ->> UserDB: Query user
UserDB -->> AuthService: User data
alt Valid credentials
AuthService -->> LoginUI: Success
LoginUI -->> User: Welcome
par Place order
User ->> Order: New order
Order ->> OrderQueue: Process
and
Order ->> AuditLogs: Record
end
loop Until confirmed
OrderQueue ->> Order: Update status
Order -->> User: Notification
end
else Invalid credentials
AuthService --x LoginUI: Failure
LoginUI --x User: Retry
end
`,
{ sequence: { useMaxWidth: false } }
);
});
it('should render parallel processes with different participant types', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Customer@{ "type": "actor" }
participant Frontend@{ "type": "participant" }
participant PaymentService@{ "type": "boundary" }
participant InventoryManager@{ "type": "control" }
participant Order@{ "type": "entity" }
participant OrdersDB@{ "type": "database" }
participant NotificationQueue@{ "type": "queue" }
Customer ->> Frontend: Place order
Frontend ->> Order: Create order
par Parallel Processing
Order ->> PaymentService: Process payment
and
Order ->> InventoryManager: Reserve items
end
PaymentService -->> Order: Payment confirmed
InventoryManager -->> Order: Items reserved
Order ->> OrdersDB: Save finalized order
OrdersDB -->> Order: Order saved
Order ->> NotificationQueue: Send confirmation
NotificationQueue -->> Customer: Order confirmation
`
);
});
});
it('should render different participant types with notes and loops', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Admin
participant Dashboard
participant AuthService@{ "type" : "boundary" }
participant UserManager@{ "type" : "control" }
participant UserProfile@{ "type" : "entity" }
participant UserDB@{ "type" : "database" }
participant Logs@{ "type" : "database" }
Admin ->> Dashboard: Open user management
loop Authentication check
Dashboard ->> AuthService: Verify admin rights
AuthService ->> Dashboard: Access granted
end
Dashboard ->> UserManager: List users
UserManager ->> UserDB: Query users
UserDB ->> UserManager: Return user data
Note right of UserDB: Encrypted data<br/>requires decryption
UserManager ->> UserProfile: Format profiles
UserProfile ->> UserManager: Formatted data
UserManager ->> Dashboard: Display users
Dashboard ->> Logs: Record access
Logs ->> Admin: Audit trail
`
);
});
it('should render different participant types with alternative flows', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Client
participant MobileApp
participant CloudService@{ "type" : "boundary" }
participant DataProcessor@{ "type" : "control" }
participant Transaction@{ "type" : "entity" }
participant TransactionsDB@{ "type" : "database" }
participant EventBus@{ "type" : "queue" }
Client ->> MobileApp: Initiate transaction
MobileApp ->> CloudService: Authenticate
alt Authentication successful
CloudService -->> MobileApp: Auth token
MobileApp ->> DataProcessor: Process data
DataProcessor ->> Transaction: Create transaction
Transaction ->> TransactionsDB: Save record
TransactionsDB -->> Transaction: Confirmation
Transaction ->> EventBus: Publish event
EventBus -->> Client: Notification
else Authentication failed
CloudService -->> MobileApp: Error
MobileApp -->> Client: Show error
end
`
);
});
it('should render different participant types with wrapping text', () => {
imgSnapshotTest(
`
sequenceDiagram
participant B@{ "type" : "boundary" }
participant C@{ "type" : "control" }
participant E@{ "type" : "entity" }
participant DB@{ "type" : "database" }
participant COL@{ "type" : "collections" }
participant Q@{ "type" : "queue" }
FE ->> B: Another long message<br/>with explicit<br/>line breaks
B -->> FE: Response message that is also quite long and needs to wrap
FE ->> C: Process data
C ->> E: Validate
E -->> C: Validation result
C ->> DB: Save
DB -->> C: Save result
C ->> COL: Log
COL -->> Q: Forward
Q -->> LongNameUser: Final response with confirmation of all actions taken
`,
{ sequence: { wrap: true } }
);
});
describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => {
it('should render long notes left of boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "boundary" }
actor Bob
Alice->>Bob: Hola
Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes left of control', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "control" }
actor Bob
Alice->>Bob: Hola
Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes right of entity', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "entity" }
actor Bob
Alice->>Bob: Hola
Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes right of database', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "database" }
actor Bob
Alice->>Bob: Hola
Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long notes over collections', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "collections" }
actor Bob
Alice->>Bob: Hola
Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long notes over queue', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "queue" }
actor Bob
Alice->>Bob: Hola
Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render notes over actor and boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
participant Charlie@{ "type" : "boundary" }
note over Alice: Some note
note over Charlie: Other note
`,
{}
);
});
it('should render long messages from database to collections', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob@{ "type" : "collections" }
Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render wrapped long messages from control to entity', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob@{ "type" : "entity" }
Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
Bob->>Alice: I'm short though
`,
{}
);
});
it('should render long messages from queue to boundary', () => {
imgSnapshotTest(
`
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob@{ "type" : "boundary" }
Alice->>Bob: I'm short
Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
`,
{}
);
});
it('should render wrapped long messages from actor to database', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "database" }
Alice->>Bob: I'm short
Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
`,
{}
);
});
});
describe('svg size', () => {
it('should render a sequence diagram when useMaxWidth is true (default)', () => {
renderGraph(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "boundary" }
participant John@{ "type" : "control" }
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: true } }
);
cy.get('svg').should((svg) => {
expect(svg).to.have.attr('width', '100%');
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05);
});
});
it('should render a sequence diagram when useMaxWidth is false', () => {
renderGraph(
`
sequenceDiagram
actor Alice
participant Bob@{ "type" : "boundary" }
participant John@{ "type" : "control" }
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
expect(width).to.be.within(820 * 0.95, 820 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
});
});

View File

@@ -893,6 +893,17 @@ describe('Sequence diagram', () => {
}
);
});
it('should handle bidirectional arrows with autonumber', () => {
imgSnapshotTest(`
sequenceDiagram
autonumber
participant A
participant B
A<<->>B: This is a bidirectional message
A->B: This is a normal message`);
});
it('should support actor links and properties when not mirrored EXPERIMENTAL: USE WITH CAUTION', () => {
//Be aware that the syntax for "properties" is likely to be changed.
imgSnapshotTest(

View File

@@ -101,14 +101,7 @@
C
D
</pre>
<pre class="mermaid">
mindmap
root((mindmap is a long thing))
A
B
C
D
</pre>
<pre class="mermaid">
---
config:
@@ -369,6 +362,10 @@
<hr />
<script type="module">
import mermaid from '/mermaid.esm.mjs';
import tidytree from '/mermaid-layout-tidy-tree.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.registerLayoutLoaders(tidytree);
mermaid.initialize({
theme: 'default',
logLevel: 3,

View File

@@ -41,10 +41,6 @@ graph TB
const { svg } = await mermaid.render('d22', value);
console.log(svg);
el.innerHTML = svg;
// mermaid.test1('first_slow', 1200).then((r) => console.info(r));
// mermaid.test1('second_fast', 200).then((r) => console.info(r));
// mermaid.test1('third_fast', 200).then((r) => console.info(r));
// mermaid.test1('forth_slow', 1200).then((r) => console.info(r));
</script>
</body>
</html>

View File

@@ -1,5 +1,6 @@
import externalExample from './mermaid-example-diagram.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
import tidyTree from './mermaid-layout-tidy-tree.esm.mjs';
import zenUml from './mermaid-zenuml.esm.mjs';
import mermaid from './mermaid.esm.mjs';
@@ -65,6 +66,7 @@ const contentLoaded = async function () {
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
mermaid.registerLayoutLoaders(layouts);
mermaid.registerLayoutLoaders(tidyTree);
mermaid.initialize(graphObj.mermaid);
/**
* CC-BY-4.0
@@ -182,7 +184,7 @@ const contentLoadedApi = async function () {
for (let i = 0; i < numCodes; i++) {
const { svg, bindFunctions } = await mermaid.render('newid' + i, graphObj.code[i], divs[i]);
div.innerHTML = svg;
bindFunctions(div);
bindFunctions?.(div);
}
} else {
const div = document.createElement('div');
@@ -194,7 +196,7 @@ const contentLoadedApi = async function () {
const { svg, bindFunctions } = await mermaid.render('newid', graphObj.code, div);
div.innerHTML = svg;
console.log(div.innerHTML);
bindFunctions(div);
bindFunctions?.(div);
}
}
};

View File

@@ -2,35 +2,35 @@
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 5815
"duration": 5841
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 2035
"duration": 2138
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3386
"duration": 3370
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 2089
"duration": 2052
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 11578
"duration": 12243
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 2119
"duration": 2065
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 27282
"duration": 31288
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 3377
"duration": 3421
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
@@ -38,95 +38,95 @@
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 18137
"duration": 18500
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 5455
"duration": 5793
},
{
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
"duration": 40850
"duration": 40966
},
{
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
"duration": 37964
"duration": 39176
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 23446
"duration": 23468
},
{
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
"duration": 37207
"duration": 38291
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 16531
"duration": 16949
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 9385
"duration": 9480
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 2697
"duration": 2753
},
{
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
"duration": 88648
"duration": 88028
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 15094
"duration": 15615
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 3548
"duration": 3706
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 44889
"duration": 43905
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 30487
"duration": 31217
},
{
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
"duration": 7375
"duration": 7531
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 24913
"duration": 25423
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 51927
"duration": 49664
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 31676
"duration": 32525
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 19897
"duration": 20915
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 53450
"duration": 53556
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 287035
"duration": 283038
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 58555
"duration": 59434
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 3179
"duration": 3101
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
@@ -134,87 +134,95 @@
},
{
"spec": "cypress/integration/rendering/kanban.spec.ts",
"duration": 7628
"duration": 7567
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 3764
"duration": 3817
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 2573
"duration": 2624
},
{
"spec": "cypress/integration/rendering/mindmap-tidy-tree.spec.js",
"duration": 4246
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 11269
"duration": 11967
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 148389
"duration": 151914
},
{
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
"duration": 113395
"duration": 116698
},
{
"spec": "cypress/integration/rendering/packet.spec.ts",
"duration": 4714
"duration": 4967
},
{
"spec": "cypress/integration/rendering/pie.spec.ts",
"duration": 6446
"duration": 6700
},
{
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
"duration": 9133
"duration": 8963
},
{
"spec": "cypress/integration/rendering/radar.spec.js",
"duration": 5544
"duration": 5540
},
{
"spec": "cypress/integration/rendering/requirement.spec.js",
"duration": 2709
"duration": 2782
},
{
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
"duration": 55647
"duration": 54797
},
{
"spec": "cypress/integration/rendering/sankey.spec.ts",
"duration": 6751
"duration": 6914
},
{
"spec": "cypress/integration/rendering/sequencediagram-v2.spec.js",
"duration": 20481
},
{
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
"duration": 36618
"duration": 38490
},
{
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
"duration": 29642
"duration": 30766
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 16037
"duration": 16705
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 30006
"duration": 30928
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 8451
"duration": 8424
},
{
"spec": "cypress/integration/rendering/treemap.spec.ts",
"duration": 11996
"duration": 12533
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 20627
"duration": 21197
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 3472
"duration": 3455
}
]
}

View File

@@ -6,7 +6,7 @@
# Frequently Asked Questions
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217)
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712)
2. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
3. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
4. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)

40
docs/config/layouts.md Normal file
View File

@@ -0,0 +1,40 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/layouts.md](../../packages/mermaid/src/docs/config/layouts.md).
# Layouts
This page lists the available layout algorithms supported in Mermaid diagrams.
## Supported Layouts
- **elk**: [ELK (Eclipse Layout Kernel)](https://www.eclipse.org/elk/)
- **tidy-tree**: Tidy tree layout for hierarchical diagrams [Tidy Tree Configuration](/config/tidy-tree)
- **cose-bilkent**: Cose Bilkent layout for force-directed graphs
- **dagre**: Dagre layout for layered graphs
## How to Use
You can specify the layout in your diagram's YAML config or initialization options. For example:
```mermaid-example
---
config:
layout: elk
---
graph TD;
A-->B;
B-->C;
```
```mermaid
---
config:
layout: elk
---
graph TD;
A-->B;
B-->C;
```

View File

@@ -19,6 +19,7 @@
- [addDirective](functions/addDirective.md)
- [getConfig](functions/getConfig.md)
- [getSiteConfig](functions/getSiteConfig.md)
- [getUserDefinedConfig](functions/getUserDefinedConfig.md)
- [reset](functions/reset.md)
- [sanitize](functions/sanitize.md)
- [saveConfigFromInitialize](functions/saveConfigFromInitialize.md)

View File

@@ -0,0 +1,19 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md](../../../../../packages/mermaid/src/docs/config/setup/config/functions/getUserDefinedConfig.md).
[**mermaid**](../../README.md)
---
# Function: getUserDefinedConfig()
> **getUserDefinedConfig**(): [`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)
Defined in: [packages/mermaid/src/config.ts:252](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L252)
## Returns
[`MermaidConfig`](../../mermaid/interfaces/MermaidConfig.md)

View File

@@ -10,10 +10,6 @@
# mermaid
## Classes
- [UnknownDiagramError](classes/UnknownDiagramError.md)
## Interfaces
- [DetailedError](interfaces/DetailedError.md)
@@ -27,6 +23,7 @@
- [RenderOptions](interfaces/RenderOptions.md)
- [RenderResult](interfaces/RenderResult.md)
- [RunOptions](interfaces/RunOptions.md)
- [UnknownDiagramError](interfaces/UnknownDiagramError.md)
## Type Aliases

View File

@@ -1,159 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/classes/UnknownDiagramError.md).
[**mermaid**](../../README.md)
---
# Class: UnknownDiagramError
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
## Extends
- `Error`
## Constructors
### new UnknownDiagramError()
> **new UnknownDiagramError**(`message`): [`UnknownDiagramError`](UnknownDiagramError.md)
Defined in: [packages/mermaid/src/errors.ts:2](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L2)
#### Parameters
##### message
`string`
#### Returns
[`UnknownDiagramError`](UnknownDiagramError.md)
#### Overrides
`Error.constructor`
## Properties
### cause?
> `optional` **cause**: `unknown`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
#### Inherited from
`Error.cause`
---
### message
> **message**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
#### Inherited from
`Error.message`
---
### name
> **name**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
`Error.name`
---
### stack?
> `optional` **stack**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
#### Inherited from
`Error.stack`
---
### prepareStackTrace()?
> `static` `optional` **prepareStackTrace**: (`err`, `stackTraces`) => `any`
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:143
Optional override for formatting stack traces
#### Parameters
##### err
`Error`
##### stackTraces
`CallSite`\[]
#### Returns
`any`
#### See
<https://v8.dev/docs/stack-trace-api#customizing-stack-traces>
#### Inherited from
`Error.prepareStackTrace`
---
### stackTraceLimit
> `static` **stackTraceLimit**: `number`
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:145
#### Inherited from
`Error.stackTraceLimit`
## Methods
### captureStackTrace()
> `static` **captureStackTrace**(`targetObject`, `constructorOpt`?): `void`
Defined in: node_modules/.pnpm/@types+node\@22.13.5/node_modules/@types/node/globals.d.ts:136
Create .stack property on a target object
#### Parameters
##### targetObject
`object`
##### constructorOpt?
`Function`
#### Returns
`void`
#### Inherited from
`Error.captureStackTrace`

View File

@@ -10,7 +10,7 @@
# Interface: LayoutData
Defined in: [packages/mermaid/src/rendering-util/types.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L170)
Defined in: [packages/mermaid/src/rendering-util/types.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L168)
## Indexable
@@ -22,7 +22,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:170](https://github.co
> **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/rendering-util/types.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L173)
Defined in: [packages/mermaid/src/rendering-util/types.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L171)
---
@@ -30,7 +30,7 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:173](https://github.co
> **edges**: `Edge`\[]
Defined in: [packages/mermaid/src/rendering-util/types.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L172)
Defined in: [packages/mermaid/src/rendering-util/types.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L170)
---
@@ -38,4 +38,4 @@ Defined in: [packages/mermaid/src/rendering-util/types.ts:172](https://github.co
> **nodes**: `Node`\[]
Defined in: [packages/mermaid/src/rendering-util/types.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L171)
Defined in: [packages/mermaid/src/rendering-util/types.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L169)

View File

@@ -32,7 +32,7 @@ page.
### detectType()
> **detectType**: (`text`, `config`?) => `string`
> **detectType**: (`text`, `config?`) => `string`
Defined in: [packages/mermaid/src/mermaid.ts:449](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L449)
@@ -105,7 +105,7 @@ An array of objects with the id of the diagram.
### ~~init()~~
> **init**: (`config`?, `nodes`?, `callback`?) => `Promise`<`void`>
> **init**: (`config?`, `nodes?`, `callback?`) => `Promise`<`void`>
Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L442)
@@ -117,7 +117,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:442](https://github.com/mermaid-js/
[`MermaidConfig`](MermaidConfig.md)
**Deprecated**, please set configuration in [initialize](Mermaid.md#initialize).
**Deprecated**, please set configuration in [initialize](#initialize).
##### nodes?
@@ -141,13 +141,13 @@ Called once for each rendered diagram's id.
#### Deprecated
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
Use [initialize](#initialize) and [run](#run) instead.
Renders the mermaid diagrams
#### Deprecated
Use [initialize](Mermaid.md#initialize) and [run](Mermaid.md#run) instead.
Use [initialize](#initialize) and [run](#run) instead.
---
@@ -176,7 +176,7 @@ Configuration object for mermaid.
### ~~mermaidAPI~~
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>; `render`: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
> **mermaidAPI**: `Readonly`<{ `defaultConfig`: [`MermaidConfig`](MermaidConfig.md); `getConfig`: () => [`MermaidConfig`](MermaidConfig.md); `getDiagramFromText`: (`text`, `metadata`) => `Promise`<`Diagram`>; `getSiteConfig`: () => [`MermaidConfig`](MermaidConfig.md); `globalReset`: () => `void`; `initialize`: (`userOptions`) => `void`; `parse`: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }; `render`: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>; `reset`: () => `void`; `setConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); `updateSiteConfig`: (`conf`) => [`MermaidConfig`](MermaidConfig.md); }>
Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L436)
@@ -184,73 +184,81 @@ Defined in: [packages/mermaid/src/mermaid.ts:436](https://github.com/mermaid-js/
#### Deprecated
Use [parse](Mermaid.md#parse) and [render](Mermaid.md#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
Use [parse](#parse) and [render](#render) instead. Please [open a discussion](https://github.com/mermaid-js/mermaid/discussions) if your use case does not fit the new API.
---
### parse()
> **parse**: (`text`, `parseOptions`) => `Promise`<`false` | [`ParseResult`](ParseResult.md)>(`text`, `parseOptions`?) => `Promise`<[`ParseResult`](ParseResult.md)>
> **parse**: {(`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>; (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>; }
Defined in: [packages/mermaid/src/mermaid.ts:437](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L437)
#### Call Signature
> (`text`, `parseOptions`): `Promise`<`false` | [`ParseResult`](ParseResult.md)>
Parse the text and validate the syntax.
#### Parameters
##### Parameters
##### text
###### text
`string`
The mermaid diagram definition.
##### parseOptions
###### parseOptions
[`ParseOptions`](ParseOptions.md) & `object`
Options for parsing.
#### Returns
##### Returns
`Promise`<`false` | [`ParseResult`](ParseResult.md)>
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
#### See
##### See
[ParseOptions](ParseOptions.md)
#### Throws
##### Throws
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
#### Call Signature
> (`text`, `parseOptions?`): `Promise`<[`ParseResult`](ParseResult.md)>
Parse the text and validate the syntax.
#### Parameters
##### Parameters
##### text
###### text
`string`
The mermaid diagram definition.
##### parseOptions?
###### parseOptions?
[`ParseOptions`](ParseOptions.md)
Options for parsing.
#### Returns
##### Returns
`Promise`<[`ParseResult`](ParseResult.md)>
An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
#### See
##### See
[ParseOptions](ParseOptions.md)
#### Throws
##### Throws
Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
@@ -332,7 +340,7 @@ Defined in: [packages/mermaid/src/mermaid.ts:444](https://github.com/mermaid-js/
### render()
> **render**: (`id`, `text`, `svgContainingElement`?) => `Promise`<[`RenderResult`](RenderResult.md)>
> **render**: (`id`, `text`, `svgContainingElement?`) => `Promise`<[`RenderResult`](RenderResult.md)>
Defined in: [packages/mermaid/src/mermaid.ts:438](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaid.ts#L438)

View File

@@ -10,7 +10,7 @@
# Interface: ParseOptions
Defined in: [packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76)
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mer
> `optional` **suppressErrors**: `boolean`
Defined in: [packages/mermaid/src/types.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L81)
Defined in: [packages/mermaid/src/types.ts:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L93)
If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
The `parseError` function will not be called.

View File

@@ -10,7 +10,7 @@
# Interface: ParseResult
Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mer
> **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92)
Defined in: [packages/mermaid/src/types.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L104)
The config passed as YAML frontmatter or directives
@@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives
> **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100)
The diagram type, e.g. 'flowchart', 'sequence', etc.

View File

@@ -10,7 +10,7 @@
# Interface: RenderResult
Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102)
Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/me
> `optional` **bindFunctions**: (`element`) => `void`
Defined in: [packages/mermaid/src/types.ts:120](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L120)
Defined in: [packages/mermaid/src/types.ts:132](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L132)
Bind function to be called after the svg has been inserted into the DOM.
This is necessary for adding event listeners to the elements in the svg.
@@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
> **diagramType**: `string`
Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110)
Defined in: [packages/mermaid/src/types.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L122)
The diagram type, e.g. 'flowchart', 'sequence', etc.
@@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
> **svg**: `string`
Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106)
Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118)
The svg code for the rendered graph.

View File

@@ -0,0 +1,65 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md](../../../../../packages/mermaid/src/docs/config/setup/mermaid/interfaces/UnknownDiagramError.md).
[**mermaid**](../../README.md)
---
# Interface: UnknownDiagramError
Defined in: [packages/mermaid/src/errors.ts:1](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/errors.ts#L1)
## Extends
- `Error`
## Properties
### cause?
> `optional` **cause**: `unknown`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es2022.error.d.ts:26
#### Inherited from
`Error.cause`
---
### message
> **message**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1077
#### Inherited from
`Error.message`
---
### name
> **name**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1076
#### Inherited from
`Error.name`
---
### stack?
> `optional` **stack**: `string`
Defined in: node_modules/.pnpm/typescript\@5.7.3/node_modules/typescript/lib/lib.es5.d.ts:1078
#### Inherited from
`Error.stack`

View File

@@ -10,6 +10,6 @@
# Type Alias: InternalHelpers
> **InternalHelpers**: _typeof_ `internalHelpers`
> **InternalHelpers** = _typeof_ `internalHelpers`
Defined in: [packages/mermaid/src/internals.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/internals.ts#L33)

View File

@@ -10,7 +10,7 @@
# Type Alias: ParseErrorFunction()
> **ParseErrorFunction**: (`err`, `hash`?) => `void`
> **ParseErrorFunction** = (`err`, `hash?`) => `void`
Defined in: [packages/mermaid/src/Diagram.ts:10](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/Diagram.ts#L10)

View File

@@ -10,6 +10,6 @@
# Type Alias: SVG
> **SVG**: `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
> **SVG** = `d3.Selection`<`SVGSVGElement`, `unknown`, `Element` | `null`, `unknown`>
Defined in: [packages/mermaid/src/diagram-api/types.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L126)

View File

@@ -10,6 +10,6 @@
# Type Alias: SVGGroup
> **SVGGroup**: `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
> **SVGGroup** = `d3.Selection`<`SVGGElement`, `unknown`, `Element` | `null`, `unknown`>
Defined in: [packages/mermaid/src/diagram-api/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/diagram-api/types.ts#L128)

89
docs/config/tidy-tree.md Normal file
View File

@@ -0,0 +1,89 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/tidy-tree.md](../../packages/mermaid/src/docs/config/tidy-tree.md).
# Tidy-tree Layout
The **tidy-tree** layout arranges nodes in a hierarchical, tree-like structure. It is especially useful for diagrams where parent-child relationships are important, such as mindmaps.
## Features
- Organizes nodes in a tidy, non-overlapping tree
- Ideal for mindmaps and hierarchical data
- Automatically adjusts spacing for readability
## Example Usage
```mermaid-example
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
```mermaid
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
```mermaid-example
---
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
```
```mermaid
---
config:
layout: tidy-tree
---
mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectiveness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
```
## Note
- Currently, tidy-tree is primarily supported for mindmap diagrams.

View File

@@ -73,7 +73,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram) ✅
- [Outline](https://docs.getoutline.com/s/guide/doc/diagrams-KQiKoT4wzK) ✅
- [Redmine](https://redmine.org)
- [Mermaid Macro](https://www.redmine.org/plugins/redmine_mermaid_macro)
- [Mermaid Macro](https://redmine.org/plugins/redmine_mermaid_macro)
- [Markdown for mermaid plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin)
- [redmine-mermaid](https://github.com/styz/redmine_mermaid)
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
@@ -117,7 +117,7 @@ Content Management Systems/Enterprise Content Management
- [Grav CMS](https://getgrav.org/)
- [Mermaid Diagrams Plugin](https://github.com/DanielFlaum/grav-plugin-mermaid-diagrams)
- [GitLab Markdown Adapter](https://github.com/Goutte/grav-plugin-gitlab-markdown-adapter)
- [Tiki](https://tiki.org)
- [Tiki Wiki CMS Groupware](https://tiki.org)
- [Tracker Entity Relationship Diagram](https://doc.tiki.org/Tracker-Entity-Relationship-Diagram)
- [VitePress](https://vitepress.vuejs.org/)
- [Plugin for Mermaid.js](https://emersonbottero.github.io/vitepress-plugin-mermaid/)

View File

@@ -6,6 +6,66 @@
# Blog
## [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
Mermaid just introduced a Visual Editor for Entity Relationship diagrams, letting anyone map database structures through a simple point-and-click interface instead of code. This no-code ER builder now sits alongside Mermaids editors for flowcharts, sequence, and class diagrams, enabling teams to craft and share polished data models for apps, AI, and business processes.
## [Mermaid supports Treemap Diagrams now!!!](https://docs.mermaidchart.com/blog/posts/mermaid-have-treemap-diagrams-now)
7/3/2025 • 4 mins
Mermaid has introduced Treemap diagrams, currently in beta, enhancing hierarchical data visualization. Treemap diagrams use nested rectangles to represent data relationships, focusing on size and proportions. They offer various applications, including budget visualization and market analysis. With simple syntax and customization options, users can effectively present complex data hierarchies.
## [AI Diagram Generators and Data Visualization: Best Practices](https://docs.mermaidchart.com/blog/posts/ai-diagram-generators-and-data-visualization-best-practices)
7/2/2025 • 6 mins
AI diagram generators transform complex data into clear, interactive visuals enabling faster analysis, better decisions, and stronger collaboration across teams. By combining automation with manual refinement, these tools empower anyone to communicate insights effectively, regardless of technical skill level.
## [How to Choose the Best AI Diagram Generator for Your Needs (2025)](https://docs.mermaidchart.com/blog/posts/how-to-choose-the-best-ai-diagram-generator-for-your-needs-2025)
6/26/2025 • 14 mins
AI diagram generators are transforming how developers visualize and communicate complex systems, reducing hours of manual work into minutes. With tools like Mermaid AI, users benefit from both code-based and visual editing, enabling seamless collaboration and precision. Whether youre diagramming workflows, software architecture, or data relationships, the right AI tool can significantly boost productivity and streamline communication.
## [5 Time-Saving Tips for Using Mermaids AI Diagram Generator Effectively](https://docs.mermaidchart.com/blog/posts/5-time-saving-tips-for-using-mermaids-ai-diagram-generator-effectively)
6/11/2025 • 10 mins
See how developers can save time and boost productivity using Mermaid Charts AI diagram generator. Learn five practical tips that help turn plain language into powerful, professional diagrams.
## [Enhancing Team Collaboration with AI-Powered Diagrams](https://docs.mermaidchart.com/blog/posts/enhancing-team-collaboration-with-ai-powered-diagrams)
5/27/2025 • 6 mins
Software teams move fast, but old-school diagramming tools cant keep up. Mermaid Chart replaces static slides and whiteboards with real-time, AI-generated visuals that evolve with your code and ideas. Just describe a process in plain English, and watch it come to life.
## [What is an AI Diagram Generator? Benefits and Use Cases](https://docs.mermaidchart.com/blog/posts/what-is-an-ai-diagram-generator-benefits-and-use-cases)
5/22/2025 • 6 mins
Discover how AI diagram generators like Mermaid Chart transform developer workflows. Instantly turn text into flowcharts, ERDs, and system diagrams, no manual drag-and-drop needed. Learn how it works, key benefits, and real-world use cases.
## [How to Use Mermaid Chart as an AI Diagram Generator for Developers](https://docs.mermaidchart.com/blog/posts/how-to-use-mermaid-chart-as-an-ai-diagram-generator)
5/21/2025 • 9 mins
Would an AI diagram generator make your life easier? We think it would!
## [Mermaid Chart VS Code Plugin: Create and Edit Mermaid.js Diagrams in Visual Studio Code](https://docs.mermaidchart.com/blog/posts/mermaid-chart-vs-code-plugin-create-and-edit-mermaid-js-diagrams-in-visual-studio-code)
3/21/2025 • 5 mins
The Mermaid Chart VS Code Plugin is a powerful developer diagramming tool that brings Mermaid.js diagramming directly into your Visual Studio Code environment. Whether youre visualizing software architecture, documenting API flows, fixing bad documentation, or managing flowcharts and sequence diagrams, this plugin integrates seamlessly into your workflow. Key Features of the Mermaid Chart VS Code \[…]
## [Mermaid Chart: The Evolution of Mermaid](https://docs.mermaidchart.com/blog/posts/mermaid-chart-the-evolution-of-mermaid)
1/30/2025 • 3 mins
Mermaid revolutionized diagramming with its simple, markdown-style syntax, empowering millions of developers worldwide. Now, Mermaid Chart takes it further with AI-powered visuals, a GUI for seamless editing, real-time collaboration, and advanced design tools. Experience the next generation of diagramming—faster, smarter, and built for modern teams. Try Mermaid Chart today!
## [GUI for editing Mermaid Class Diagrams](https://docs.mermaidchart.com/blog/posts/gui-for-editing-mermaid-class-diagrams)
1/17/2025 • 5 mins

View File

@@ -985,11 +985,23 @@ flowchart TD
- `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are:
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the width (`w`) accordingly to the height (`h`). If not defined, this will default to `off` Possible values are:
- `on`
- `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging.
If you want to resize an image, but keep the same aspect ratio, set `h`, and set `constraint: on` to constrain the aspect ratio. E.g.
```mermaid-example
flowchart TD
%% My image with a constrained aspect ratio
A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" }
```
```mermaid
flowchart TD
%% My image with a constrained aspect ratio
A@{ img: "https://mermaid.js.org/favicon.svg", label: "My example image label", pos: "t", h: 60, constraint: "on" }
```
## Links between nodes
@@ -1818,7 +1830,7 @@ config:
graph LR
```
#### Edge level curve style using Edge IDs (v\<MERMAID_RELEASE_VERSION>+)
#### Edge level curve style using Edge IDs (v11.10.0+)
You can assign IDs to [edges](#attaching-an-id-to-edges). After assigning an ID you can modify the line style by modifying the edge's `curve` property using the following syntax:

View File

@@ -314,3 +314,22 @@ You can also refer the [implementation in the live editor](https://github.com/me
cspell:locale en,en-gb
cspell:ignore Buzan
--->
## Layouts
Mermaid also supports a Tidy Tree layout for mindmaps.
```
---
config:
layout: tidy-tree
---
mindmap
root((mindmap is a long thing))
A
B
C
D
```
Instructions to add and register tidy-tree layout are present in [Tidy Tree Configuration](/config/tidy-tree)

View File

@@ -39,7 +39,7 @@ Drawing a pie chart is really simple in mermaid.
**Note:**
> Pie chart values must be **positive numbers greater than zero**.\
> Pie chart values must be **positive numbers greater than zero**.
> **Negative values are not allowed** and will result in an error.
\[pie] \[showData] (OPTIONAL)

View File

@@ -74,6 +74,126 @@ sequenceDiagram
Bob->>Alice: Hi Alice
```
### Boundary
If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "boundary" }
participant Bob
Alice->>Bob: Request from boundary
Bob->>Alice: Response to boundary
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "boundary" }
participant Bob
Alice->>Bob: Request from boundary
Bob->>Alice: Response to boundary
```
### Control
If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob
Alice->>Bob: Control request
Bob->>Alice: Control response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "control" }
participant Bob
Alice->>Bob: Control request
Bob->>Alice: Control response
```
### Entity
If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "entity" }
participant Bob
Alice->>Bob: Entity request
Bob->>Alice: Entity response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "entity" }
participant Bob
Alice->>Bob: Entity request
Bob->>Alice: Entity response
```
### Database
If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob
Alice->>Bob: DB query
Bob->>Alice: DB result
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob
Alice->>Bob: DB query
Bob->>Alice: DB result
```
### Collections
If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob
Alice->>Bob: Collections request
Bob->>Alice: Collections response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob
Alice->>Bob: Collections request
Bob->>Alice: Collections response
```
### Queue
If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below.
```mermaid-example
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob
Alice->>Bob: Queue message
Bob->>Alice: Queue response
```
```mermaid
sequenceDiagram
participant Alice@{ "type" : "queue" }
participant Bob
Alice->>Bob: Queue message
Bob->>Alice: Queue response
```
### Aliases
The actor can have a convenient identifier and a descriptive label.

View File

@@ -138,7 +138,7 @@ xychart
## Chart Theme Variables
Themes for xychart resides inside xychart attribute so to set the variables use this syntax:
Themes for xychart reside inside the `xychart` attribute, allowing customization through the following syntax:
```yaml
---
@@ -163,6 +163,52 @@ config:
| yAxisLineColor | Color of the y-axis line |
| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |
### Setting Colors for Lines and Bars
To set the color for lines and bars, use the `plotColorPalette` parameter. Colors in the palette will correspond sequentially to the elements in your chart (e.g., first bar/line will use the first color specified in the palette).
```mermaid-example
---
config:
themeVariables:
xyChart:
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
---
xychart
title "Different Colors in xyChart"
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
y-axis "valuesY" 0 --> 50
%% Black line
line [10,20,30,40]
%% Blue bar
bar [20,30,25,35]
%% Green bar
bar [15,25,20,30]
%% Red line
line [5,15,25,35]
```
```mermaid
---
config:
themeVariables:
xyChart:
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
---
xychart
title "Different Colors in xyChart"
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
y-axis "valuesY" 0 --> 50
%% Black line
line [10,20,30,40]
%% Blue bar
bar [20,30,25,35]
%% Green bar
bar [15,25,20,30]
%% Red line
line [5,15,25,35]
```
## Example on config and theme
```mermaid-example

View File

@@ -1,145 +0,0 @@
Please help me to implement the tidy-tree algorithm.
- I have added a placeholder with the correct signature in `packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/`.
Replace the cytoscape layout with one using tidy-tree.
This is the API for tidy-tree:
```
# non-layered-tidy-tree-layout
Draw non-layered tidy trees in linear time.
> This a JavaScript port from the project [cwi-swat/non-layered-tidy-trees](https://github.com/cwi-swat/non-layered-tidy-trees), which is written in Java. The algorithm used in that project is from the paper by _A.J. van der Ploeg_, [Drawing Non-layered Tidy Trees in Linear Time](http://oai.cwi.nl/oai/asset/21856/21856B.pdf). There is another JavaScript port from that project [d3-flextree](https://github.com/Klortho/d3-flextree), which depends on _d3-hierarchy_. This project is dependency free.
## Getting started
### Installation
```
npm install non-layered-tidy-tree-layout
```
Or
```
yarn add non-layered-tidy-tree-layout
````
There's also a built verison: `dist/non-layered-tidy-tree-layout.js` for use with browser `<script>` tag, or as a Javascript module.
### Usage
```js
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout'
// BoundingBox(gap, bottomPadding)
const bb = new BoundingBox(10, 20)
const layout = new Layout(bb)
const treeData = {
id: 0,
width: 40,
height: 40,
children: [
{
id: 1,
width: 40,
height: 40,
children: [{ id: 6, width: 400, height: 40 }]
},
{ id: 2, width: 40, height: 40 },
{ id: 3, width: 40, height: 40 },
{ id: 4, width: 40, height: 40 },
{ id: 5, width: 40, height: 80 }
]
}
const { result, boundingBox } = layout.layout(treeData)
// result:
// {
// id: 0,
// x: 300,
// y: 0,
// width: 40,
// height: 40,
// children: [
// {
// id: 1,
// x: 185,
// y: 60,
// width: 40,
// height: 40,
// children: [
// { id: 6, x: 5, y: 120, width: 400, height: 40 }
// ]
// },
// { id: 2, x: 242.5, y: 60, width: 40, height: 40 },
// { id: 3, x: 300, y: 60, width: 40, height: 40 },
// { id: 4, x: 357.5, y: 60, width: 40, height: 40 },
// { id: 5, x: 415, y: 60, width: 40, height: 80 }
// ]
// }
//
// boundingBox:
// {
// left: 5,
// right: 455,
// top: 0,
// bottom: 160
// }
````
The method `Layout.layout` modifies `treeData` inplace. It returns an object like `{ result: treeData, boundingBox: {left: num, right: num, top: num, bottom: num} }`. `result` is the same object `treeData` with calculated coordinates, `boundingBox` are the coordinates for the whole tree:
![](./screenshots/1.png)
The red dashed lines are the bounding boxes for each node. `Layout.layout()` produces coordinates to draw nodes, which are the grey boxes with black border.
The library also provides a class `Tree` and a method `layout`.
```js
/**
* Constructor for Tree.
* @param {number} width - width of bounding box
* @param {number} height - height of bounding box
* @param {number} y - veritcal coordinate of bounding box
* @param {array} children - a list of Tree instances
*/
new Tree(width, height, y, children);
/**
* Calculate x, y coordindates and assign them to tree.
* @param {Object} tree - a Tree object
*/
layout(tree);
```
In case your data structure are not the same as provided by the example above, you can refer to `src/helpers.js` to implement a `Layout` class that converts your data to a `Tree`, then call `layout` to calculate the coordinates for drawing.
## License
[MIT](./LICENSE)
## Changelog
### [2.0.1]
- Fixed bounding box calculation in `Layout.getSize` and `Layout.assignLayout` and `Layout.layout`
### [2.0.0]
- Added `Layout.layout`
- Removed `Layout.layoutTreeData`
### [1.0.0]
- Added `Layout`, `BoundingBox`, `layout`, `Tree`
```
```

View File

@@ -27,9 +27,6 @@
"devDependencies": {
"mermaid": "workspace:*"
},
"peerDependencies": {
"mermaid": "workspace:~"
},
"publishConfig": {
"access": "public"
}

View File

@@ -1,5 +1,16 @@
# @mermaid-js/layout-elk
## 0.1.9
### Patch Changes
- [#6857](https://github.com/mermaid-js/mermaid/pull/6857) [`b9ef683`](https://github.com/mermaid-js/mermaid/commit/b9ef683fb67b8959abc455d6cc5266c37ba435f6) Thanks [@knsv](https://github.com/knsv)! - feat: Exposing elk configuration forceNodeModelOrder and considerModelOrder to the mermaid configuration
- [#6849](https://github.com/mermaid-js/mermaid/pull/6849) [`2260948`](https://github.com/mermaid-js/mermaid/commit/2260948b7bda08f00616c2ce678bed1da69eb96c) Thanks [@anderium](https://github.com/anderium)! - Make elk not force node model order, but strongly consider it instead
- Updated dependencies [[`b9ef683`](https://github.com/mermaid-js/mermaid/commit/b9ef683fb67b8959abc455d6cc5266c37ba435f6), [`2c0931d`](https://github.com/mermaid-js/mermaid/commit/2c0931da46794b49d2523211e25f782900c34e94), [`33e08da`](https://github.com/mermaid-js/mermaid/commit/33e08daf175125295a06b1b80279437004a4e865), [`814b68b`](https://github.com/mermaid-js/mermaid/commit/814b68b4a94813f7c6b3d7fb4559532a7bab2652), [`fce7cab`](https://github.com/mermaid-js/mermaid/commit/fce7cabb71d68a20a66246fe23d066512126a412), [`fc07f0d`](https://github.com/mermaid-js/mermaid/commit/fc07f0d8abca49e4f887d7457b7b94fb07d1e3da), [`12e01bd`](https://github.com/mermaid-js/mermaid/commit/12e01bdb5cacf3569133979a5a4f1d8973e9aec1), [`01aaef3`](https://github.com/mermaid-js/mermaid/commit/01aaef39b4a1ec8bc5a0c6bfa3a20b712d67f4dc), [`daf8d8d`](https://github.com/mermaid-js/mermaid/commit/daf8d8d3befcd600618a629977b76463b38d0ad9), [`c36cd05`](https://github.com/mermaid-js/mermaid/commit/c36cd05c45ac3090181152b4dae41f8d7b569bd6), [`8bb29fc`](https://github.com/mermaid-js/mermaid/commit/8bb29fc879329ad109898e4025b4f4eba2ab0649), [`71b04f9`](https://github.com/mermaid-js/mermaid/commit/71b04f93b07f876df2b30656ef36036c1d0e4e4f), [`c99bce6`](https://github.com/mermaid-js/mermaid/commit/c99bce6bab4c7ce0b81b66d44f44853ce4aeb1c3), [`6cc1926`](https://github.com/mermaid-js/mermaid/commit/6cc192680a2531cab28f87a8061a53b786e010f3), [`9da6fb3`](https://github.com/mermaid-js/mermaid/commit/9da6fb39ae278401771943ac85d6d1b875f78cf1), [`e48b0ba`](https://github.com/mermaid-js/mermaid/commit/e48b0ba61dab7f95aa02da603b5b7d383b894932), [`4d62d59`](https://github.com/mermaid-js/mermaid/commit/4d62d5963238400270e9314c6e4d506f48147074), [`e9ce8cf`](https://github.com/mermaid-js/mermaid/commit/e9ce8cf4da9062d85098042044822100889bb0dd), [`9258b29`](https://github.com/mermaid-js/mermaid/commit/9258b2933bbe1ef41087345ffea3731673671c49), [`da90f67`](https://github.com/mermaid-js/mermaid/commit/da90f6760b6efb0da998bcb63b75eecc29e06c08), [`0133f1c`](https://github.com/mermaid-js/mermaid/commit/0133f1c0c5cff4fc4c8e0b99e9cf0b3d49dcbe71), [`895f9d4`](https://github.com/mermaid-js/mermaid/commit/895f9d43ff98ca05ebfba530789f677f31a011ff)]:
- mermaid@11.10.0
## 0.1.8
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.1.8",
"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,19 +0,0 @@
import type { InternalHelpers, LayoutData, RenderOptions, SVG } from 'mermaid';
export declare const render: (
data4Layout: LayoutData,
svg: SVG,
{
common,
getConfig,
insertCluster,
insertEdge,
insertEdgeLabel,
insertMarkers,
insertNode,
interpolateToCurve,
labelHelper,
log,
positionEdgeLabel,
}: InternalHelpers,
{ algorithm }: RenderOptions
) => Promise<void>;

View File

@@ -1,4 +1,7 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { executeTidyTreeLayout, validateLayoutData } from './layout.js';
import type { LayoutResult } from './types.js';
import type { LayoutData, MermaidConfig } from 'mermaid';
// Mock non-layered-tidy-tree-layout
vi.mock('non-layered-tidy-tree-layout', () => ({
@@ -42,10 +45,6 @@ vi.mock('non-layered-tidy-tree-layout', () => ({
})),
}));
import { executeTidyTreeLayout, validateLayoutData } from './layout.js';
import type { LayoutResult } from './types.js';
import type { LayoutData, MermaidConfig } from 'mermaid';
describe('Tidy-Tree Layout Algorithm', () => {
let mockConfig: MermaidConfig;
let mockLayoutData: LayoutData;
@@ -209,7 +208,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
describe('executeTidyTreeLayout function', () => {
it('should execute layout algorithm successfully', async () => {
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData, mockConfig);
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
expect(result).toBeDefined();
expect(result.nodes).toBeDefined();
@@ -219,7 +218,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
});
it('should return positioned nodes with coordinates', async () => {
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData, mockConfig);
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
expect(result.nodes.length).toBeGreaterThan(0);
result.nodes.forEach((node) => {
@@ -231,7 +230,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
});
it('should return positioned edges with coordinates', async () => {
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData, mockConfig);
const result: LayoutResult = await executeTidyTreeLayout(mockLayoutData);
expect(result.edges.length).toBeGreaterThan(0);
result.edges.forEach((edge) => {
@@ -251,7 +250,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
edges: [],
};
await expect(executeTidyTreeLayout(emptyData, mockConfig)).rejects.toThrow(
await expect(executeTidyTreeLayout(emptyData)).rejects.toThrow(
'No nodes found in layout data'
);
});
@@ -259,7 +258,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
it('should throw error for missing nodes', async () => {
const invalidData = { ...mockLayoutData, nodes: [] };
await expect(executeTidyTreeLayout(invalidData, mockConfig)).rejects.toThrow(
await expect(executeTidyTreeLayout(invalidData)).rejects.toThrow(
'No nodes found in layout data'
);
});
@@ -271,14 +270,14 @@ describe('Tidy-Tree Layout Algorithm', () => {
nodes: [mockLayoutData.nodes[0]],
};
const result = await executeTidyTreeLayout(singleNodeData, mockConfig);
const result = await executeTidyTreeLayout(singleNodeData);
expect(result).toBeDefined();
expect(result.nodes).toHaveLength(1);
expect(result.edges).toHaveLength(0);
});
it('should create bidirectional dual-tree layout with alternating left/right children', async () => {
const result = await executeTidyTreeLayout(mockLayoutData, mockConfig);
const result = await executeTidyTreeLayout(mockLayoutData);
expect(result).toBeDefined();
expect(result.nodes).toHaveLength(5);
@@ -383,7 +382,7 @@ describe('Tidy-Tree Layout Algorithm', () => {
],
};
const result = await executeTidyTreeLayout(testData, mockConfig);
const result = await executeTidyTreeLayout(testData);
expect(result).toBeDefined();
expect(result.nodes).toHaveLength(3);

View File

@@ -1,12 +1,13 @@
import type { LayoutData } from 'mermaid';
import type { Bounds, Point } from 'mermaid/src/types.js';
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
import type { MermaidConfig, LayoutData } from 'mermaid';
import type {
LayoutResult,
TidyTreeNode,
PositionedNode,
PositionedEdge,
Node,
Edge,
LayoutResult,
Node,
PositionedEdge,
PositionedNode,
TidyTreeNode,
} from './types.js';
/**
@@ -19,10 +20,7 @@ import type {
* @param config - Mermaid configuration object
* @returns Promise resolving to layout result with positioned nodes and edges
*/
export function executeTidyTreeLayout(
data: LayoutData,
_config: MermaidConfig
): Promise<LayoutResult> {
export function executeTidyTreeLayout(data: LayoutData): Promise<LayoutResult> {
let intersectionShift = 50;
return new Promise((resolve, reject) => {
@@ -46,35 +44,23 @@ export function executeTidyTreeLayout(
let leftResult = null;
let rightResult = null;
let leftBoundingBox = null;
let rightBoundingBox = null;
if (leftTree) {
const leftLayoutResult = layout.layout(leftTree);
leftResult = leftLayoutResult.result;
leftBoundingBox = leftLayoutResult.boundingBox;
}
if (rightTree) {
const rightLayoutResult = layout.layout(rightTree);
rightResult = rightLayoutResult.result;
rightBoundingBox = rightLayoutResult.boundingBox;
}
const positionedNodes = combineAndPositionTrees(
rootNode,
leftResult,
rightResult,
leftBoundingBox,
rightBoundingBox,
data
);
const positionedNodes = combineAndPositionTrees(rootNode, leftResult, rightResult);
const positionedEdges = calculateEdgePositions(
data.edges,
positionedNodes,
intersectionShift
);
resolve({
nodes: positionedNodes,
edges: positionedEdges,
@@ -203,10 +189,7 @@ function convertNodeToTidyTreeTransposed(
function combineAndPositionTrees(
rootNode: TidyTreeNode,
leftResult: TidyTreeNode | null,
rightResult: TidyTreeNode | null,
_leftBoundingBox: any,
_rightBoundingBox: any,
_data: LayoutData
rightResult: TidyTreeNode | null
): PositionedNode[] {
const positionedNodes: PositionedNode[] = [];
@@ -383,11 +366,7 @@ function positionRightTreeBidirectional(
* @param lineEnd - Ending point of the line
* @returns The intersection point
*/
function computeCircleEdgeIntersection(
circle: { x: number; y: number; width: number; height: number },
lineStart: { x: number; y: number },
lineEnd: { x: number; y: number }
): { x: number; y: number } {
function computeCircleEdgeIntersection(circle: Bounds, lineStart: Point, lineEnd: Point): Point {
const radius = Math.min(circle.width, circle.height) / 2;
const dx = lineEnd.x - lineStart.x;
@@ -407,53 +386,65 @@ function computeCircleEdgeIntersection(
};
}
/**
* Calculate intersection point of a line with a rectangle
* This is a simplified version that we'll use instead of importing from mermaid
*/
function intersection(
node: { x: number; y: number; width?: number; height?: number },
point1: { x: number; y: number },
point2: { x: number; y: number }
): { x: number; y: number } {
const nodeWidth = node.width ?? 100;
const nodeHeight = node.height ?? 50;
function intersection(node: PositionedNode, outsidePoint: Point, insidePoint: Point): Point {
const x = node.x;
const y = node.y;
const centerX = node.x;
const centerY = node.y;
if (!node.width || !node.height) {
return { x: outsidePoint.x, y: outsidePoint.y };
}
const dx = Math.abs(x - insidePoint.x);
const w = node?.width / 2;
let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
const h = node.height / 2;
const dx = point2.x - point1.x;
const dy = point2.y - point1.y;
const Q = Math.abs(outsidePoint.y - insidePoint.y);
const R = Math.abs(outsidePoint.x - insidePoint.x);
if (dx === 0 && dy === 0) {
return { x: centerX, y: centerY };
if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
// Intersection is top or bottom of rect.
const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
r = (R * q) / Q;
const res = {
x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q,
};
if (r === 0) {
res.x = outsidePoint.x;
res.y = outsidePoint.y;
}
if (R === 0) {
res.x = outsidePoint.x;
}
if (Q === 0) {
res.y = outsidePoint.y;
}
const halfWidth = nodeWidth / 2;
const halfHeight = nodeHeight / 2;
let intersectionX = centerX;
let intersectionY = centerY;
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 0) {
intersectionX = centerX + halfWidth;
intersectionY = centerY + (halfWidth * dy) / dx;
return res;
} else {
intersectionX = centerX - halfWidth;
intersectionY = centerY - (halfWidth * dy) / dx;
}
if (insidePoint.x < outsidePoint.x) {
r = outsidePoint.x - w - x;
} else {
if (dy > 0) {
intersectionY = centerY + halfHeight;
intersectionX = centerX + (halfHeight * dx) / dy;
} else {
intersectionY = centerY - halfHeight;
intersectionX = centerX - (halfHeight * dx) / dy;
r = x - w - outsidePoint.x;
}
const q = (Q * r) / R;
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
if (r === 0) {
_x = outsidePoint.x;
_y = outsidePoint.y;
}
if (R === 0) {
_x = outsidePoint.x;
}
if (Q === 0) {
_y = outsidePoint.y;
}
return { x: intersectionX, y: intersectionY };
return { x: _x, y: _y };
}
}
/**

View File

@@ -102,7 +102,7 @@ export const render = async (
}),
};
const layoutResult = await executeTidyTreeLayout(updatedLayoutData, data4Layout.config);
const layoutResult = await executeTidyTreeLayout(updatedLayoutData);
// Step 3: Position the nodes based on bidirectional layout results
log.debug('Positioning nodes based on bidirectional layout results');

View File

@@ -1,5 +1,14 @@
# @mermaid-js/mermaid-zenuml
## 0.2.2
### Patch Changes
- [#6798](https://github.com/mermaid-js/mermaid/pull/6798) [`3ffe961`](https://github.com/mermaid-js/mermaid/commit/3ffe9618aebc9ac96de6e3c826481f542f18c2a9) Thanks [@MrCoder](https://github.com/MrCoder)! - Fixed a critical bug that the ZenUML diagram is not rendered.
- Updated dependencies [[`b9ef683`](https://github.com/mermaid-js/mermaid/commit/b9ef683fb67b8959abc455d6cc5266c37ba435f6), [`2c0931d`](https://github.com/mermaid-js/mermaid/commit/2c0931da46794b49d2523211e25f782900c34e94), [`33e08da`](https://github.com/mermaid-js/mermaid/commit/33e08daf175125295a06b1b80279437004a4e865), [`814b68b`](https://github.com/mermaid-js/mermaid/commit/814b68b4a94813f7c6b3d7fb4559532a7bab2652), [`fce7cab`](https://github.com/mermaid-js/mermaid/commit/fce7cabb71d68a20a66246fe23d066512126a412), [`fc07f0d`](https://github.com/mermaid-js/mermaid/commit/fc07f0d8abca49e4f887d7457b7b94fb07d1e3da), [`12e01bd`](https://github.com/mermaid-js/mermaid/commit/12e01bdb5cacf3569133979a5a4f1d8973e9aec1), [`01aaef3`](https://github.com/mermaid-js/mermaid/commit/01aaef39b4a1ec8bc5a0c6bfa3a20b712d67f4dc), [`daf8d8d`](https://github.com/mermaid-js/mermaid/commit/daf8d8d3befcd600618a629977b76463b38d0ad9), [`c36cd05`](https://github.com/mermaid-js/mermaid/commit/c36cd05c45ac3090181152b4dae41f8d7b569bd6), [`8bb29fc`](https://github.com/mermaid-js/mermaid/commit/8bb29fc879329ad109898e4025b4f4eba2ab0649), [`71b04f9`](https://github.com/mermaid-js/mermaid/commit/71b04f93b07f876df2b30656ef36036c1d0e4e4f), [`c99bce6`](https://github.com/mermaid-js/mermaid/commit/c99bce6bab4c7ce0b81b66d44f44853ce4aeb1c3), [`6cc1926`](https://github.com/mermaid-js/mermaid/commit/6cc192680a2531cab28f87a8061a53b786e010f3), [`9da6fb3`](https://github.com/mermaid-js/mermaid/commit/9da6fb39ae278401771943ac85d6d1b875f78cf1), [`e48b0ba`](https://github.com/mermaid-js/mermaid/commit/e48b0ba61dab7f95aa02da603b5b7d383b894932), [`4d62d59`](https://github.com/mermaid-js/mermaid/commit/4d62d5963238400270e9314c6e4d506f48147074), [`e9ce8cf`](https://github.com/mermaid-js/mermaid/commit/e9ce8cf4da9062d85098042044822100889bb0dd), [`9258b29`](https://github.com/mermaid-js/mermaid/commit/9258b2933bbe1ef41087345ffea3731673671c49), [`da90f67`](https://github.com/mermaid-js/mermaid/commit/da90f6760b6efb0da998bcb63b75eecc29e06c08), [`0133f1c`](https://github.com/mermaid-js/mermaid/commit/0133f1c0c5cff4fc4c8e0b99e9cf0b3d49dcbe71), [`895f9d4`](https://github.com/mermaid-js/mermaid/commit/895f9d43ff98ca05ebfba530789f677f31a011ff)]:
- mermaid@11.10.0
## 0.2.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/mermaid-zenuml",
"version": "0.2.1",
"version": "0.2.2",
"description": "MermaidJS plugin for ZenUML integration",
"module": "dist/mermaid-zenuml.core.mjs",
"types": "dist/detector.d.ts",

View File

@@ -1,5 +1,72 @@
# mermaid
## 11.10.0
### Minor Changes
- [#6744](https://github.com/mermaid-js/mermaid/pull/6744) [`daf8d8d`](https://github.com/mermaid-js/mermaid/commit/daf8d8d3befcd600618a629977b76463b38d0ad9) Thanks [@SpecularAura](https://github.com/SpecularAura)! - feat: Added support for per link curve styling in flowchart diagram using edge ids
### Patch Changes
- [#6857](https://github.com/mermaid-js/mermaid/pull/6857) [`b9ef683`](https://github.com/mermaid-js/mermaid/commit/b9ef683fb67b8959abc455d6cc5266c37ba435f6) Thanks [@knsv](https://github.com/knsv)! - feat: Exposing elk configuration forceNodeModelOrder and considerModelOrder to the mermaid configuration
- [#6653](https://github.com/mermaid-js/mermaid/pull/6653) [`2c0931d`](https://github.com/mermaid-js/mermaid/commit/2c0931da46794b49d2523211e25f782900c34e94) Thanks [@darshanr0107](https://github.com/darshanr0107)! - chore: Remove the "-beta" suffix from the XYChart, Block, Sankey diagrams to reflect their stable status
- [#6683](https://github.com/mermaid-js/mermaid/pull/6683) [`33e08da`](https://github.com/mermaid-js/mermaid/commit/33e08daf175125295a06b1b80279437004a4e865) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Position the edge label in state diagram correctly relative to the edge
- [#6693](https://github.com/mermaid-js/mermaid/pull/6693) [`814b68b`](https://github.com/mermaid-js/mermaid/commit/814b68b4a94813f7c6b3d7fb4559532a7bab2652) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: Apply correct dateFormat in Gantt chart to show only day when specified
- [#6734](https://github.com/mermaid-js/mermaid/pull/6734) [`fce7cab`](https://github.com/mermaid-js/mermaid/commit/fce7cabb71d68a20a66246fe23d066512126a412) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: handle exclude dates properly in Gantt charts when using dateFormat: 'YYYY-MM-DD HH:mm:ss'
- [#6733](https://github.com/mermaid-js/mermaid/pull/6733) [`fc07f0d`](https://github.com/mermaid-js/mermaid/commit/fc07f0d8abca49e4f887d7457b7b94fb07d1e3da) Thanks [@omkarht](https://github.com/omkarht)! - fix: fixed connection gaps in flowchart for roundedRect, stadium and diamond shape
- [#6876](https://github.com/mermaid-js/mermaid/pull/6876) [`12e01bd`](https://github.com/mermaid-js/mermaid/commit/12e01bdb5cacf3569133979a5a4f1d8973e9aec1) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: sanitize icon labels and icon SVGs
Resolves CVE-2025-54880 reported by @fourcube
- [#6801](https://github.com/mermaid-js/mermaid/pull/6801) [`01aaef3`](https://github.com/mermaid-js/mermaid/commit/01aaef39b4a1ec8bc5a0c6bfa3a20b712d67f4dc) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: Update casing of ID in requirement diagram
- [#6796](https://github.com/mermaid-js/mermaid/pull/6796) [`c36cd05`](https://github.com/mermaid-js/mermaid/commit/c36cd05c45ac3090181152b4dae41f8d7b569bd6) Thanks [@HashanCP](https://github.com/HashanCP)! - fix: Make flowchart elk detector regex match less greedy
- [#6702](https://github.com/mermaid-js/mermaid/pull/6702) [`8bb29fc`](https://github.com/mermaid-js/mermaid/commit/8bb29fc879329ad109898e4025b4f4eba2ab0649) Thanks [@qraqras](https://github.com/qraqras)! - fix(block): overflowing blocks no longer affect later lines
This may change the layout of block diagrams that have overflowing lines
(i.e. block diagrams that use up more columns that the `columns` specifier).
- [#6717](https://github.com/mermaid-js/mermaid/pull/6717) [`71b04f9`](https://github.com/mermaid-js/mermaid/commit/71b04f93b07f876df2b30656ef36036c1d0e4e4f) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: log warning for blocks exceeding column width
This update adds a validation check that logs a warning message when a block's width exceeds the defined column layout.
- [#6820](https://github.com/mermaid-js/mermaid/pull/6820) [`c99bce6`](https://github.com/mermaid-js/mermaid/commit/c99bce6bab4c7ce0b81b66d44f44853ce4aeb1c3) Thanks [@kriss-u](https://github.com/kriss-u)! - fix: Add escaped class literal name on namespace
- [#6332](https://github.com/mermaid-js/mermaid/pull/6332) [`6cc1926`](https://github.com/mermaid-js/mermaid/commit/6cc192680a2531cab28f87a8061a53b786e010f3) Thanks [@ajuckel](https://github.com/ajuckel)! - fix: Allow equals sign in sequenceDiagram labels
- [#6651](https://github.com/mermaid-js/mermaid/pull/6651) [`9da6fb3`](https://github.com/mermaid-js/mermaid/commit/9da6fb39ae278401771943ac85d6d1b875f78cf1) Thanks [@darshanr0107](https://github.com/darshanr0107)! - Add validation for negative values in pie charts:
Prevents crashes during parsing by validating values post-parsing.
Provides clearer, user-friendly error messages for invalid negative inputs.
- [#6803](https://github.com/mermaid-js/mermaid/pull/6803) [`e48b0ba`](https://github.com/mermaid-js/mermaid/commit/e48b0ba61dab7f95aa02da603b5b7d383b894932) Thanks [@omkarht](https://github.com/omkarht)! - chore: migrate to class-based ArchitectureDB implementation
- [#6838](https://github.com/mermaid-js/mermaid/pull/6838) [`4d62d59`](https://github.com/mermaid-js/mermaid/commit/4d62d5963238400270e9314c6e4d506f48147074) Thanks [@saurabhg772244](https://github.com/saurabhg772244)! - fix: node border style for handdrawn shapes
- [#6739](https://github.com/mermaid-js/mermaid/pull/6739) [`e9ce8cf`](https://github.com/mermaid-js/mermaid/commit/e9ce8cf4da9062d85098042044822100889bb0dd) Thanks [@kriss-u](https://github.com/kriss-u)! - fix: Update flowchart direction TD's behavior to be the same as TB
- [#6833](https://github.com/mermaid-js/mermaid/pull/6833) [`9258b29`](https://github.com/mermaid-js/mermaid/commit/9258b2933bbe1ef41087345ffea3731673671c49) Thanks [@darshanr0107](https://github.com/darshanr0107)! - fix: correctly render non-directional lines for '---' in block diagrams
- [#6855](https://github.com/mermaid-js/mermaid/pull/6855) [`da90f67`](https://github.com/mermaid-js/mermaid/commit/da90f6760b6efb0da998bcb63b75eecc29e06c08) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: fallback to raw text instead of rendering _Unsupported markdown_ or empty blocks
Instead of printing **Unsupported markdown: XXX**, or empty blocks when using a markdown feature
that Mermaid does not yet support when `htmlLabels: true`(default) or `htmlLabels: false`,
fallback to the raw markdown text.
- [#6876](https://github.com/mermaid-js/mermaid/pull/6876) [`0133f1c`](https://github.com/mermaid-js/mermaid/commit/0133f1c0c5cff4fc4c8e0b99e9cf0b3d49dcbe71) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: sanitize KATEX blocks
Resolves CVE-2025-54881 reported by @fourcube
- [#6804](https://github.com/mermaid-js/mermaid/pull/6804) [`895f9d4`](https://github.com/mermaid-js/mermaid/commit/895f9d43ff98ca05ebfba530789f677f31a011ff) Thanks [@omkarht](https://github.com/omkarht)! - chore: Update packet diagram to use new class-based database structure
## 11.9.0
### Minor Changes
@@ -87,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 ...)
@@ -105,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.9.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",
@@ -68,7 +68,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "^7.0.4",
"@iconify/utils": "^2.1.33",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",

View File

@@ -78,3 +78,187 @@ describe('when working with site config', () => {
expect(config_4.altFontFamily).toBeUndefined();
});
});
describe('getUserDefinedConfig', () => {
beforeEach(() => {
configApi.reset();
});
it('should return empty object when no user config is defined', () => {
const userConfig = configApi.getUserDefinedConfig();
expect(userConfig).toEqual({});
});
it('should return config from initialize only', () => {
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial' };
configApi.saveConfigFromInitialize(initConfig);
const userConfig = configApi.getUserDefinedConfig();
expect(userConfig).toEqual(initConfig);
});
it('should return config from directives only', () => {
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
const directive2: MermaidConfig = { theme: 'forest' };
configApi.addDirective(directive1);
configApi.addDirective(directive2);
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
{
"fontFamily": "Arial",
"fontSize": 14,
"layout": "elk",
"theme": "forest",
}
`);
});
it('should combine initialize config and directives', () => {
const initConfig: MermaidConfig = { theme: 'dark', fontFamily: 'Arial', layout: 'dagre' };
const directive1: MermaidConfig = { layout: 'elk', fontSize: 14 };
const directive2: MermaidConfig = { theme: 'forest' };
configApi.saveConfigFromInitialize(initConfig);
configApi.addDirective(directive1);
configApi.addDirective(directive2);
const userConfig = configApi.getUserDefinedConfig();
expect(userConfig).toMatchInlineSnapshot(`
{
"fontFamily": "Arial",
"fontSize": 14,
"layout": "elk",
"theme": "forest",
}
`);
});
it('should handle nested config objects properly', () => {
const initConfig: MermaidConfig = {
flowchart: { nodeSpacing: 50, rankSpacing: 100 },
theme: 'default',
};
const directive: MermaidConfig = {
flowchart: { nodeSpacing: 75, curve: 'basis' },
mindmap: { padding: 20 },
};
configApi.saveConfigFromInitialize(initConfig);
configApi.addDirective(directive);
const userConfig = configApi.getUserDefinedConfig();
expect(userConfig).toMatchInlineSnapshot(`
{
"flowchart": {
"curve": "basis",
"nodeSpacing": 75,
"rankSpacing": 100,
},
"mindmap": {
"padding": 20,
},
"theme": "default",
}
`);
});
it('should handle complex nested overrides', () => {
const initConfig: MermaidConfig = {
flowchart: {
nodeSpacing: 50,
rankSpacing: 100,
curve: 'linear',
},
theme: 'default',
};
const directive1: MermaidConfig = {
flowchart: {
nodeSpacing: 75,
},
fontSize: 12,
};
const directive2: MermaidConfig = {
flowchart: {
curve: 'basis',
nodeSpacing: 100,
},
mindmap: {
padding: 15,
},
};
configApi.saveConfigFromInitialize(initConfig);
configApi.addDirective(directive1);
configApi.addDirective(directive2);
const userConfig = configApi.getUserDefinedConfig();
expect(userConfig).toMatchInlineSnapshot(`
{
"flowchart": {
"curve": "basis",
"nodeSpacing": 100,
"rankSpacing": 100,
},
"fontSize": 12,
"mindmap": {
"padding": 15,
},
"theme": "default",
}
`);
});
it('should return independent copies (not references)', () => {
const initConfig: MermaidConfig = { theme: 'dark', flowchart: { nodeSpacing: 50 } };
configApi.saveConfigFromInitialize(initConfig);
const userConfig1 = configApi.getUserDefinedConfig();
const userConfig2 = configApi.getUserDefinedConfig();
userConfig1.theme = 'neutral';
userConfig1.flowchart!.nodeSpacing = 999;
expect(userConfig2).toMatchInlineSnapshot(`
{
"flowchart": {
"nodeSpacing": 50,
},
"theme": "dark",
}
`);
});
it('should handle edge cases with undefined values', () => {
const initConfig: MermaidConfig = { theme: 'dark', layout: undefined };
const directive: MermaidConfig = { fontSize: 14, fontFamily: undefined };
configApi.saveConfigFromInitialize(initConfig);
configApi.addDirective(directive);
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
{
"fontSize": 14,
"layout": undefined,
"theme": "dark",
}
`);
});
it('should retain config from initialize after reset', () => {
const initConfig: MermaidConfig = { theme: 'dark' };
const directive: MermaidConfig = { layout: 'elk' };
configApi.saveConfigFromInitialize(initConfig);
configApi.addDirective(directive);
expect(configApi.getUserDefinedConfig()).toMatchInlineSnapshot(`
{
"layout": "elk",
"theme": "dark",
}
`);
configApi.reset();
});
});

View File

@@ -248,3 +248,17 @@ const checkConfig = (config: MermaidConfig) => {
issueWarning('LAZY_LOAD_DEPRECATED');
}
};
export const getUserDefinedConfig = (): MermaidConfig => {
let userConfig: MermaidConfig = {};
if (configFromInitialize) {
userConfig = assignWithDepth(userConfig, configFromInitialize);
}
for (const d of directives) {
userConfig = assignWithDepth(userConfig, d);
}
return userConfig;
};

View File

@@ -1,9 +1,9 @@
import { select } from 'd3';
import { log } from '../logger.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js';
import { decodeEntities } from '../utils.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';
/**
* @param dom
@@ -19,14 +19,14 @@ function applyStyle(dom, styleFn) {
* @param {any} node
* @returns {SVGForeignObjectElement} Node
*/
function addHtmlLabel(node) {
function addHtmlLabel(node, config) {
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div');
const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
const span = div.append('span');
span.html(label);
span.html(sanitizeText(label, config));
applyStyle(span, node.labelStyle);
span.attr('class', labelClass);
@@ -49,7 +49,8 @@ const createLabel = async (_vertexText, style, isTitle, isNode) => {
if (typeof vertexText === 'object') {
vertexText = vertexText[0];
}
if (evaluate(getConfig().flowchart.htmlLabels)) {
const config = getConfig();
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);
@@ -59,7 +60,7 @@ const createLabel = async (_vertexText, style, isTitle, isNode) => {
label,
labelStyle: style.replace('fill:', 'color:'),
};
let vertexNode = addHtmlLabel(node);
let vertexNode = addHtmlLabel(node, config);
// vertexNode.parentNode.removeChild(vertexNode);
return vertexNode;
} else {

View File

@@ -4,6 +4,5 @@
* @returns cleaned text
*/
export const cleanupComments = (text: string): string => {
const cleaned = text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
return cleaned.trimStart();
return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart();
};

View File

@@ -3,6 +3,7 @@ import { getConfig } from '../../diagram-api/diagramAPI.js';
import { createText } from '../../rendering-util/createText.js';
import { getIconSVG } from '../../rendering-util/icons.js';
import type { D3Element } from '../../types.js';
import { sanitizeText } from '../common/common.js';
import type { ArchitectureDB } from './architectureDb.js';
import { architectureIcons } from './architectureIcons.js';
import {
@@ -271,6 +272,7 @@ export const drawServices = async function (
elem: D3Element,
services: ArchitectureService[]
): Promise<number> {
const config = getConfig();
for (const service of services) {
const serviceElem = elem.append('g');
const iconSize = db.getConfigField('iconSize');
@@ -285,7 +287,7 @@ export const drawServices = async function (
width: iconSize * 1.5,
classes: 'architecture-service-label',
},
getConfig()
config
);
textElem
@@ -320,7 +322,7 @@ export const drawServices = async function (
.attr('class', 'node-icon-text')
.attr('style', `height: ${iconSize}px;`)
.append('div')
.html(service.iconText);
.html(sanitizeText(service.iconText, config));
const fontSize =
parseInt(
window

View File

@@ -1070,6 +1070,14 @@ describe('given a class diagram with members and methods ', function () {
parser.parse(str);
});
it('should handle an empty class body with {}', function () {
const str = 'classDiagram\nclass EmptyClass {}';
parser.parse(str);
const actual = parser.yy.getClass('EmptyClass');
expect(actual.label).toBe('EmptyClass');
expect(actual.members.length).toBe(0);
expect(actual.methods.length).toBe(0);
});
});
});

View File

@@ -293,6 +293,7 @@ classStatement
: classIdentifier
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
| classIdentifier STRUCT_START members STRUCT_STOP {yy.addMembers($1,$3);}
| classIdentifier STRUCT_START STRUCT_STOP {}
| classIdentifier STYLE_SEPARATOR alphaNumToken STRUCT_START members STRUCT_STOP {yy.setCssClass($1, $3);yy.addMembers($1,$5);}
;
@@ -301,8 +302,15 @@ classIdentifier
| CLASS className classLabel {$$=$2; yy.addClass($2);yy.setClassLabel($2, $3);}
;
emptyBody
:
| SPACE emptyBody
| NEWLINE emptyBody
;
annotationStatement
:ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
: ANNOTATION_START alphaNumToken ANNOTATION_END className { yy.addAnnotation($4,$2); }
;
members

View File

@@ -311,9 +311,8 @@ export const hasKatex = (text: string): boolean => (text.match(katexRegex)?.leng
* @returns Object containing \{width, height\}
*/
export const calculateMathMLDimensions = async (text: string, config: MermaidConfig) => {
text = await renderKatex(text, config);
const divElem = document.createElement('div');
divElem.innerHTML = text;
divElem.innerHTML = await renderKatexSanitized(text, config);
divElem.id = 'katex-temp';
divElem.style.visibility = 'hidden';
divElem.style.position = 'absolute';
@@ -325,14 +324,7 @@ export const calculateMathMLDimensions = async (text: string, config: MermaidCon
return dim;
};
/**
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatex = async (text: string, config: MermaidConfig): Promise<string> => {
const renderKatexUnsanitized = async (text: string, config: MermaidConfig): Promise<string> => {
if (!hasKatex(text)) {
return text;
}
@@ -373,6 +365,20 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
);
};
/**
* Attempts to render and return the KaTeX portion of a string with MathML
*
* @param text - The text to test
* @param config - Configuration for Mermaid
* @returns String containing MathML if KaTeX is supported, or an error message if it is not and stylesheets aren't present
*/
export const renderKatexSanitized = async (
text: string,
config: MermaidConfig
): Promise<string> => {
return sanitizeText(await renderKatexUnsanitized(text, config), config);
};
export default {
getRows,
sanitizeText,

View File

@@ -1,11 +1,12 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { v4 } from 'uuid';
import type { D3Element } from '../../types.js';
import { sanitizeText } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import type { MindmapNode } from './mindmapTypes.js';
import defaultConfig from '../../defaultConfig.js';
import type { LayoutData, Node, Edge } from '../../rendering-util/types.js';
import { getUserDefinedConfig } from '../../config.js';
// Extend Node type for mindmap-specific properties
export type MindmapLayoutNode = Node & {
@@ -223,7 +224,7 @@ export class MindmapDB {
const getShapeFromType = (type: number) => {
switch (type) {
case nodeType.CIRCLE:
return 'circle';
return 'mindmapCircle';
case nodeType.RECT:
return 'rect';
case nodeType.ROUNDED_RECT:
@@ -235,6 +236,7 @@ export class MindmapDB {
case nodeType.HEXAGON:
return 'hexagon';
case nodeType.DEFAULT:
return 'defaultMindmapNode';
case nodeType.NO_BORDER:
default:
return 'rect';
@@ -325,11 +327,19 @@ export class MindmapDB {
const mindmapRoot = this.getMindmap();
const config = getConfig();
const userDefinedConfig = getUserDefinedConfig();
const hasUserDefinedLayout = userDefinedConfig.layout !== undefined;
const finalConfig = config;
if (!hasUserDefinedLayout) {
finalConfig.layout = 'cose-bilkent';
}
if (!mindmapRoot) {
return {
nodes: [],
edges: [],
config,
config: finalConfig,
};
}
log.debug('getData: mindmapRoot', mindmapRoot, config);
@@ -362,11 +372,11 @@ export class MindmapDB {
return {
nodes: processedNodes,
edges: processedEdges,
config,
config: finalConfig,
// Store the root node for mindmap-specific layout algorithms
rootNode: mindmapRoot,
// Properties required by dagre layout algorithm
markers: [], // Mindmaps don't use markers
markers: ['point'], // Mindmaps don't use markers
direction: 'TB', // Top-to-bottom direction for mindmaps
nodeSpacing: 50, // Default spacing between nodes
rankSpacing: 50, // Default spacing between ranks
@@ -374,7 +384,7 @@ export class MindmapDB {
shapes: Object.fromEntries(shapes),
// Additional properties that layout algorithms might expect
type: 'mindmap',
diagramId: 'mindmap-' + Date.now(),
diagramId: 'mindmap-' + v4(),
};
}

View File

@@ -1,4 +1,3 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DrawDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
@@ -6,27 +5,9 @@ import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/rende
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import type { LayoutData } from '../../rendering-util/types.js';
import type { FilledMindMapNode } from './mindmapTypes.js';
import { drawNode } from './svgDraw.js';
import defaultConfig from '../../defaultConfig.js';
import type { MindmapDB } from './mindmapDb.js';
async function _drawNodes(
db: MindmapDB,
svg: any,
mindmap: FilledMindMapNode,
section: number,
conf: any
) {
await drawNode(db, svg, mindmap, section, conf);
if (mindmap.children) {
await Promise.all(
mindmap.children.map((child, index) =>
_drawNodes(db, svg, child, section < 0 ? index : section, conf)
)
);
}
}
/**
* Update the layout data with actual node dimensions after drawing
*/
@@ -42,9 +23,7 @@ function _updateNodeDimensions(data4Layout: LayoutData, mindmapRoot: FilledMindM
}
// Recursively update children
if (node.children) {
node.children.forEach(updateNode);
}
node.children?.forEach(updateNode);
};
updateNode(mindmapRoot);
@@ -52,7 +31,6 @@ function _updateNodeDimensions(data4Layout: LayoutData, mindmapRoot: FilledMindM
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering mindmap diagram\n' + text);
const { securityLevel, mindmap: conf, layout } = getConfig();
// Draw the nodes first to get their dimensions, then update the layout data
const db = diagObj.db as MindmapDB;
@@ -62,33 +40,44 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
const data4Layout = db.getData();
// Create the root SVG - the element is the div containing the SVG element
const svg = getDiagramElement(id, securityLevel);
const svg = getDiagramElement(id, data4Layout.config.securityLevel);
data4Layout.type = diagObj.type;
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout, {
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(data4Layout.config.layout, {
fallback: 'cose-bilkent',
});
// For mindmap diagrams, prioritize mindmap-specific layout algorithm configuration
data4Layout.diagramId = id;
// Ensure required properties are set for compatibility with different layout algorithms
data4Layout.markers = ['point'];
data4Layout.direction = 'TB';
const mm = db.getMindmap();
if (!mm) {
return;
}
data4Layout.nodes.forEach((node) => {
if (node.shape === 'rounded') {
node.radius = 15;
node.taper = 15;
node.stroke = 'none';
node.width = 0;
node.padding = 15;
} else if (node.shape === 'circle') {
node.padding = 10;
} else if (node.shape === 'rect') {
node.width = 0;
node.padding = 10;
}
});
// Use the unified rendering system
await render(data4Layout, svg);
// Setup the view box and size of the svg element
// Setup the view box and size of the svg element using config from data4Layout
setupViewPortForSVG(
svg,
conf?.padding ?? defaultConfig.mindmap.padding,
data4Layout.config.mindmap?.padding ?? defaultConfig.mindmap.padding,
'mindmapDiagram',
conf?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
data4Layout.config.mindmap?.useMaxWidth ?? defaultConfig.mindmap.useMaxWidth
);
};

View File

@@ -67,6 +67,9 @@ const getStyles: DiagramStylesProvider = (options) =>
.section-root span {
color: ${options.gitBranchLabel0};
}
.section-2 span {
color: ${options.gitBranchLabel0};
}
.icon-container {
height:100%;
display: flex;

View File

@@ -14,7 +14,7 @@
// Special states for recognizing aliases
// A special state for grabbing text up to the first comment/newline
%x ID ALIAS LINE
%x ID ALIAS LINE CONFIG CONFIG_DATA
%x acc_title
%x acc_descr
@@ -28,6 +28,11 @@
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM';
<ID>\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; }
<CONFIG>[^\}]+ { return 'CONFIG_CONTENT'; }
<CONFIG>\} { this.popState(); this.popState(); return 'CONFIG_END'; }
<ID>[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
<ID>[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
@@ -231,6 +236,8 @@ participant_statement
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
| 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;}
;
note_statement
@@ -301,6 +308,23 @@ signal
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
actor_with_config
: ACTOR config_object
{
$$ = {
type: 'addParticipant',
actor: $1,
config: $2
};
}
;
config_object
: CONFIG_START CONFIG_CONTENT CONFIG_END
{
$$ = $2.trim();
}
;
// actor
// : actor_participant
// | actor_actor

View File

@@ -1,4 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import * as yaml from 'js-yaml';
import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js';
@@ -13,6 +14,7 @@ import {
setDiagramTitle,
} from '../common/commonDb.js';
import type { Actor, AddMessageParams, Box, Message, Note } from './types.js';
import type { ParticipantMetaData } from '../../types.js';
interface SequenceState {
prevActor?: string;
@@ -75,6 +77,17 @@ const PLACEMENT = {
OVER: 2,
} as const;
export const PARTICIPANT_TYPE = {
ACTOR: 'actor',
BOUNDARY: 'boundary',
COLLECTIONS: 'collections',
CONTROL: 'control',
DATABASE: 'database',
ENTITY: 'entity',
PARTICIPANT: 'participant',
QUEUE: 'queue',
} as const;
export class SequenceDB implements DiagramDB {
private readonly state = new ImperativeState<SequenceState>(() => ({
prevActor: undefined,
@@ -119,9 +132,22 @@ export class SequenceDB implements DiagramDB {
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
type: string
type: string,
metadata?: any
) {
let assignedBox = this.state.records.currentBox;
let doc;
if (metadata !== undefined) {
let yamlData;
// detect if shapeData contains a newline character
if (!metadata.includes('\n')) {
yamlData = '{\n' + metadata + '\n}';
} else {
yamlData = metadata + '\n';
}
doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as ParticipantMetaData;
}
type = doc?.type ?? type;
const old = this.state.records.actors.get(id);
if (old) {
// If already set and trying to set to a new one throw error
@@ -518,7 +544,7 @@ export class SequenceDB implements DiagramDB {
});
break;
case 'addParticipant':
this.addActor(param.actor, param.actor, param.description, param.draw);
this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
break;
case 'createParticipant':
if (this.state.records.actors.has(param.actor)) {
@@ -527,7 +553,7 @@ export class SequenceDB implements DiagramDB {
);
}
this.state.records.lastCreated = param.actor;
this.addActor(param.actor, param.actor, param.description, param.draw);
this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
this.state.records.createdActors.set(param.actor, this.state.records.messages.length);
break;
case 'destroyParticipant':

View File

@@ -2058,4 +2058,272 @@ Bob->>Alice:Got it!
expect(messages[0].from).toBe('Alice');
expect(messages[0].to).toBe('Bob');
});
describe('when parsing extended participant syntax', () => {
it('should parse participants with different quote styles and whitespace', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice@{ "type" : "database" }
participant Bob@{ "type" : "database" }
participant Carl@{ type: "database" }
participant David@{ "type" : 'database' }
participant Eve@{ type: 'database' }
participant Favela@{ "type" : "database" }
Bob->>+Alice: Hi Alice
Alice->>+Bob: Hi Bob
`);
const actors = diagram.db.getActors();
expect(actors.get('Alice').type).toBe('database');
expect(actors.get('Alice').description).toBe('Alice');
expect(actors.get('Bob').type).toBe('database');
expect(actors.get('Bob').description).toBe('Bob');
expect(actors.get('Carl').type).toBe('database');
expect(actors.get('Carl').description).toBe('Carl');
expect(actors.get('David').type).toBe('database');
expect(actors.get('David').description).toBe('David');
expect(actors.get('Eve').type).toBe('database');
expect(actors.get('Eve').description).toBe('Eve');
expect(actors.get('Favela').type).toBe('database');
expect(actors.get('Favela').description).toBe('Favela');
// Verify messages were parsed correctly
const messages = diagram.db.getMessages();
expect(messages.length).toBe(4); // 2 messages + 2 activation messages
expect(messages[0].from).toBe('Bob');
expect(messages[0].to).toBe('Alice');
expect(messages[0].message).toBe('Hi Alice');
expect(messages[2].from).toBe('Alice'); // Second message (index 2 due to activation)
expect(messages[2].to).toBe('Bob');
expect(messages[2].message).toBe('Hi Bob');
});
it('should parse mixed participant types with extended syntax', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant lead
participant dsa@{ "type" : "queue" }
API->>+Database: getUserb
Database-->>-API: userb
dsa --> Database: hello
`);
// Verify actors were created
const actors = diagram.db.getActors();
expect(actors.get('lead').type).toBe('participant');
expect(actors.get('lead').description).toBe('lead');
// Participant with extended syntax
expect(actors.get('dsa').type).toBe('queue');
expect(actors.get('dsa').description).toBe('dsa');
// Implicitly created actors (from messages)
expect(actors.get('API').type).toBe('participant');
expect(actors.get('API').description).toBe('API');
expect(actors.get('Database').type).toBe('participant');
expect(actors.get('Database').description).toBe('Database');
// Verify messages were parsed correctly
const messages = diagram.db.getMessages();
expect(messages.length).toBe(5); // 3 messages + 2 activation messages
// First message with activation
expect(messages[0].from).toBe('API');
expect(messages[0].to).toBe('Database');
expect(messages[0].message).toBe('getUserb');
expect(messages[0].activate).toBe(true);
// Second message with deactivation
expect(messages[2].from).toBe('Database');
expect(messages[2].to).toBe('API');
expect(messages[2].message).toBe('userb');
// Third message
expect(messages[4].from).toBe('dsa');
expect(messages[4].to).toBe('Database');
expect(messages[4].message).toBe('hello');
});
it('should fail for malformed JSON in participant definition', async () => {
const invalidDiagram = `
sequenceDiagram
participant D@{ "type: "entity" }
participant E@{ "type": "dat
abase }
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it('should fail for missing colon separator', async () => {
const invalidDiagram = `
sequenceDiagram
participant C@{ "type" "control" }
C ->> C: action
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
it('should fail for missing closing brace', async () => {
const invalidDiagram = `
sequenceDiagram
participant E@{ "type": "entity"
E ->> E: process
`;
let error = false;
try {
await mermaidAPI.parse(invalidDiagram);
} catch (e) {
error = true;
}
expect(error).toBe(true);
});
});
describe('participant type parsing', () => {
it('should parse boundary participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant boundary@{ "type" : "boundary" }
boundary->boundary: test
`);
const actors = diagram.db.getActors();
expect(actors.get('boundary').type).toBe('boundary');
expect(actors.get('boundary').description).toBe('boundary');
});
it('should parse control participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant C@{ "type" : "control" }
C->C: test
`);
const actors = diagram.db.getActors();
expect(actors.get('C').type).toBe('control');
expect(actors.get('C').description).toBe('C');
});
it('should parse entity participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant E@{ "type" : "entity" }
E->E: test
`);
const actors = diagram.db.getActors();
expect(actors.get('E').type).toBe('entity');
expect(actors.get('E').description).toBe('E');
});
it('should parse database participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant D@{ "type" : "database" }
D->D: test
`);
const actors = diagram.db.getActors();
expect(actors.get('D').type).toBe('database');
expect(actors.get('D').description).toBe('D');
});
it('should parse collections participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant L@{ "type" : "collections" }
L->L: test
`);
const actors = diagram.db.getActors();
expect(actors.get('L').type).toBe('collections');
expect(actors.get('L').description).toBe('L');
});
it('should parse queue participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Q@{ "type" : "queue" }
Q->Q: test
`);
const actors = diagram.db.getActors();
expect(actors.get('Q').type).toBe('queue');
expect(actors.get('Q').description).toBe('Q');
});
});
describe('participant type parsing', () => {
it('should parse actor participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant A@{ "type" : "queue" }
A->A: test
`);
const actors = diagram.db.getActors();
expect(actors.get('A').type).toBe('queue');
expect(actors.get('A').description).toBe('A');
});
it('should parse participant participant', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant P@{ "type" : "database" }
P->P: test
`);
const actors = diagram.db.getActors();
expect(actors.get('P').type).toBe('database');
expect(actors.get('P').description).toBe('P');
});
it('should parse boundary using actor keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant Alice@{ "type" : "collections" }
participant Bob@{ "type" : "control" }
Alice->>Bob: Hello Bob, how are you?
`);
const actors = diagram.db.getActors();
expect(actors.get('Alice').type).toBe('collections');
expect(actors.get('Bob').type).toBe('control');
expect(actors.get('Bob').description).toBe('Bob');
});
it('should parse control using participant keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant C@{ "type" : "control" }
C->C: test
`);
const actors = diagram.db.getActors();
expect(actors.get('C').type).toBe('control');
expect(actors.get('C').description).toBe('C');
});
it('should parse entity using actor keyword', async () => {
const diagram = await Diagram.fromText(`
sequenceDiagram
participant E@{ "type" : "entity" }
E->E: test
`);
const actors = diagram.db.getActors();
expect(actors.get('E').type).toBe('entity');
expect(actors.get('E').description).toBe('E');
});
});
});

View File

@@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js';
import utils from '../../utils.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { Diagram } from '../../Diagram.js';
import { PARTICIPANT_TYPE } from './sequenceDb.js';
let conf = {};
@@ -476,7 +477,29 @@ const drawMessage = async function (diagram, msgModel, lineStartY: number, diagO
// add node number
if (sequenceVisible || conf.showSequenceNumbers) {
line.attr('marker-start', 'url(' + url + '#sequencenumber)');
const isBidirectional =
type === diagObj.db.LINETYPE.BIDIRECTIONAL_SOLID ||
type === diagObj.db.LINETYPE.BIDIRECTIONAL_DOTTED;
if (isBidirectional) {
const SEQUENCE_NUMBER_RADIUS = 6;
if (startx < stopx) {
line.attr('x1', startx + 2 * SEQUENCE_NUMBER_RADIUS);
} else {
line.attr('x1', startx + SEQUENCE_NUMBER_RADIUS);
}
}
diagram
.append('line')
.attr('x1', startx)
.attr('y1', lineStartY)
.attr('x2', startx)
.attr('y2', lineStartY)
.attr('stroke-width', 0)
.attr('marker-start', 'url(' + url + '#sequencenumber)');
diagram
.append('text')
.attr('x', startx)
@@ -724,11 +747,19 @@ function adjustCreatedDestroyedData(
msgModel.startx = msgModel.startx - adjustment;
}
}
const actorArray = [
PARTICIPANT_TYPE.ACTOR,
PARTICIPANT_TYPE.CONTROL,
PARTICIPANT_TYPE.ENTITY,
PARTICIPANT_TYPE.DATABASE,
];
// if it is a create message
if (createdActors.get(msg.to) == index) {
const actor = actors.get(msg.to);
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
const adjustment = actorArray.includes(actor.type)
? ACTOR_TYPE_WIDTH / 2 + 3
: actor.width / 2 + 3;
receiverAdjustment(actor, adjustment);
actor.starty = lineStartY - actor.height / 2;
bounds.bumpVerticalPos(actor.height / 2);
@@ -737,7 +768,7 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.from) == index) {
const actor = actors.get(msg.from);
if (conf.mirrorActors) {
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
senderAdjustment(actor, adjustment);
}
actor.stopy = lineStartY - actor.height / 2;
@@ -747,7 +778,9 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.to) == index) {
const actor = actors.get(msg.to);
if (conf.mirrorActors) {
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
const adjustment = actorArray.includes(actor.type)
? ACTOR_TYPE_WIDTH / 2 + 3
: actor.width / 2 + 3;
receiverAdjustment(actor, adjustment);
}
actor.stopy = lineStartY - actor.height / 2;
@@ -1065,10 +1098,11 @@ export const draw = async function (_text: string, id: string, _version: string,
for (const box of bounds.models.boxes) {
box.height = bounds.getVerticalPos() - box.y;
bounds.insert(box.x, box.y, box.x + box.width, box.height);
box.startx = box.x;
box.starty = box.y;
box.stopx = box.startx + box.width;
box.stopy = box.starty + box.height;
const boxPadding = conf.boxMargin * 2;
box.startx = box.x - boxPadding;
box.starty = box.y - boxPadding * 0.25;
box.stopx = box.startx + box.width + 2 * boxPadding;
box.stopy = box.starty + box.height + boxPadding * 0.75;
box.stroke = 'rgb(0,0,0, 0.5)';
svgDraw.drawBox(diagram, box, conf);
}
@@ -1333,6 +1367,9 @@ async function calculateActorMargins(
return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0));
}, 0);
const standardBoxPadding = conf.boxMargin * 8;
totalWidth += standardBoxPadding;
totalWidth -= 2 * conf.boxTextMargin;
if (box.wrap) {
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);

View File

@@ -13,6 +13,11 @@ const getStyles = (options) =>
stroke: ${options.actorLineColor};
}
.innerArc {
stroke-width: 1.5;
stroke-dasharray: none;
}
.messageLine0 {
stroke-width: 1.5;
stroke-dasharray: none;
@@ -115,6 +120,7 @@ const getStyles = (options) =>
fill: ${options.actorBkg};
stroke-width: 2px;
}
`;
export default getStyles;

View File

@@ -1,8 +1,12 @@
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import { sanitizeUrl } from '@braintree/sanitize-url';
import * as configApi from '../../config.js';
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
import common, {
calculateMathMLDimensions,
hasKatex,
renderKatexSanitized,
} from '../common/common.js';
import * as svgDrawCommon from '../common/svgDrawCommon.js';
export const ACTOR_TYPE_WIDTH = 18 * 2;
const TOP_ACTOR_CLASS = 'actor-top';
@@ -87,13 +91,13 @@ const popupMenuToggle = function (popId) {
export const drawKatex = async function (elem, textData, msgModel = null) {
let textElem = elem.append('foreignObject');
const lines = await renderKatex(textData.text, configApi.getConfig());
const linesSanitized = await renderKatexSanitized(textData.text, configApi.getConfig());
const divElem = textElem
.append('xhtml:div')
.attr('style', 'width: fit-content;')
.attr('xmlns', 'http://www.w3.org/1999/xhtml')
.html(lines);
.html(linesSanitized);
const dim = divElem.node().getBoundingClientRect();
textElem.attr('height', Math.round(dim.height)).attr('width', Math.round(dim.width));
@@ -411,6 +415,600 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
return height;
};
/**
* Draws an actor in the diagram with the attached line
*
* @param {any} elem - The diagram we'll draw to.
* @param {any} actor - The actor to draw.
* @param {any} conf - DrawText implementation discriminator object
* @param {boolean} isFooter - If the actor is the footer one
*/
const drawActorTypeCollections = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height;
const boxplusLineGroup = elem.append('g').lower();
var g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
var cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// DRAW STACKED RECTANGLES
const offset = 6;
const shadowRect = {
...rect,
x: rect.x + (isFooter ? -offset : -offset),
y: rect.y + (isFooter ? +offset : +offset),
class: 'actor',
};
const rectElem = drawRect(g, rect); // draw main rectangle on top
drawRect(g, shadowRect);
actor.rectData = rect;
if (actor.properties?.icon) {
const iconSrc = actor.properties.icon.trim();
if (iconSrc.charAt(0) === '@') {
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
} else {
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
}
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x - offset,
rect.y + offset,
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
let height = actor.height;
if (rectElem.node) {
const bounds = rectElem.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeQueue = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height;
const boxplusLineGroup = elem.append('g').lower();
let g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
let cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// Cylinder dimensions
const ry = rect.height / 2;
const rx = ry / (2.5 + rect.height / 50);
// Cylinder base group
const cylinderGroup = g.append('g');
const cylinderArc = g.append('g');
// Main cylinder body
cylinderGroup
.append('path')
.attr(
'd',
`M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 0,${rect.height}
h ${rect.width - 2 * rx}
a ${rx},${ry} 0 0 0 0,-${rect.height}
Z
`
)
.attr('class', cssclass);
cylinderArc
.append('path')
.attr(
'd',
`M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 0,${rect.height}`
)
.attr('stroke', '#666')
.attr('stroke-width', '1px')
.attr('class', cssclass);
cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`);
cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`);
actor.rectData = rect;
if (actor.properties?.icon) {
const iconSrc = actor.properties.icon.trim();
const iconX = rect.x + rect.width - 20;
const iconY = rect.y + 10;
if (iconSrc.charAt(0) === '@') {
svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1));
} else {
svgDrawCommon.drawImage(g, iconX, iconY, iconSrc);
}
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x,
rect.y,
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
let height = actor.height;
const lastPath = cylinderGroup.select('path:last-child');
if (lastPath.node()) {
const bounds = lastPath.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeControl = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 75;
const line = elem.append('g').lower();
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
const cx = actor.x + actor.width / 2;
const cy = actorY + 30;
const r = 18;
actElem
.append('defs')
.append('marker')
.attr('id', 'filled-head-control')
.attr('refX', 11)
.attr('refY', 5.8)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', '172.5')
.append('path')
.attr('d', 'M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z');
// Draw the base circle
actElem
.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', r)
.attr('fill', '#eaeaf7')
.attr('stroke', '#666')
.attr('stroke-width', 1.2);
// Draw looping arrow as arc path
actElem
.append('line')
.attr('marker-end', 'url(#filled-head-control)')
.attr('transform', `translate(${cx}, ${cy - r})`);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0);
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + r + (isFooter ? 5 : 10),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
return actor.height;
};
const drawActorTypeEntity = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 75;
const line = elem.append('g').lower();
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
const cx = actor.x + actor.width / 2;
const cy = actorY + (!isFooter ? 25 : 10);
const r = 18;
actElem
.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', r)
.attr('width', actor.width)
.attr('height', actor.height);
actElem
.append('line')
.attr('x1', cx - r)
.attr('x2', cx + r)
.attr('y1', cy + r)
.attr('y2', cy + r)
.attr('stroke', '#333')
.attr('stroke-width', 2);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + (conf?.sequence?.labelBoxHeight ?? 0);
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY + r - 5) / 2),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
if (!isFooter) {
actElem.attr('transform', `translate(${0}, ${r / 2})`);
} else {
actElem.attr('transform', `translate(${0}, ${r / 2})`);
}
return actor.height;
};
const drawActorTypeDatabase = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + actor.height + 2 * conf.boxTextMargin;
const boxplusLineGroup = elem.append('g').lower();
let g = boxplusLineGroup;
if (!isFooter) {
actorCnt++;
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
}
g.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
g = boxplusLineGroup.append('g');
actor.actorCnt = actorCnt;
if (actor.links != null) {
g.attr('id', 'root-' + actorCnt);
}
}
const rect = svgDrawCommon.getNoteRect();
let cssclass = 'actor';
if (actor.properties?.class) {
cssclass = actor.properties.class;
} else {
rect.fill = '#eaeaea';
}
if (isFooter) {
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssclass += ` ${TOP_ACTOR_CLASS}`;
}
rect.x = actor.x;
rect.y = actorY;
rect.width = actor.width;
rect.height = actor.height;
rect.class = cssclass;
rect.name = actor.name;
// Cylinder dimensions
rect.x = actor.x;
rect.y = actorY;
const w = rect.width / 4;
const h = rect.width / 4;
const rx = w / 2;
const ry = rx / (2.5 + w / 50);
// Cylinder base group
const cylinderGroup = g.append('g');
const d = `
M ${rect.x},${rect.y + ry}
a ${rx},${ry} 0 0 0 ${w},0
a ${rx},${ry} 0 0 0 -${w},0
l 0,${h - 2 * ry}
a ${rx},${ry} 0 0 0 ${w},0
l 0,-${h - 2 * ry}
`;
// Draw the main cylinder body
cylinderGroup
.append('path')
.attr('d', d)
.attr('fill', '#eaeaea')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('class', cssclass);
if (!isFooter) {
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${(rect.height + ry) / 4})`);
} else {
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`);
}
actor.rectData = rect;
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
g,
rect.x,
rect.y + (!isFooter ? (rect.height + ry) / 2 : (rect.height + h) / 4),
rect.width,
rect.height,
{ class: `actor ${ACTOR_BOX_CLASS}` },
conf
);
const lastPath = cylinderGroup.select('path:last-child');
if (lastPath.node()) {
const bounds = lastPath.node().getBBox();
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
}
return actor.height;
};
const drawActorTypeBoundary = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
const centerY = actorY + 80;
const radius = 30;
const line = elem.append('g').lower();
if (!isFooter) {
actorCnt++;
line
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', centerY)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line 200')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999')
.attr('name', actor.name);
actor.actorCnt = actorCnt;
}
const actElem = elem.append('g');
let cssClass = ACTOR_MAN_FIGURE_CLASS;
if (isFooter) {
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
} else {
cssClass += ` ${TOP_ACTOR_CLASS}`;
}
actElem.attr('class', cssClass);
actElem.attr('name', actor.name);
const rect = svgDrawCommon.getNoteRect();
rect.x = actor.x;
rect.y = actorY;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
actElem
.append('line')
.attr('id', 'actor-man-torso' + actorCnt)
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
.attr('y1', actorY + 10)
.attr('x2', actor.x + actor.width / 2 - 15)
.attr('y2', actorY + 10);
actElem
.append('line')
.attr('id', 'actor-man-arms' + actorCnt)
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
.attr('y1', actorY + 0) // starting Y
.attr('x2', actor.x + actor.width / 2 - radius * 2.5)
.attr('y2', actorY + 20); // ending Y (26px long, adjust as needed)
actElem
.append('circle')
.attr('cx', actor.x + actor.width / 2)
.attr('cy', actorY + 10)
.attr('r', radius);
const bounds = actElem.node().getBBox();
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
actor.description,
actElem,
rect.x,
rect.y + (!isFooter ? radius / 2 + 3 : radius / 2 - 4),
rect.width,
rect.height,
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
conf
);
if (!isFooter) {
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
} else {
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
}
return actor.height;
};
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
@@ -512,6 +1110,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) {
return await drawActorTypeActor(elem, actor, conf, isFooter);
case 'participant':
return await drawActorTypeParticipant(elem, actor, conf, isFooter);
case 'boundary':
return await drawActorTypeBoundary(elem, actor, conf, isFooter);
case 'control':
return await drawActorTypeControl(elem, actor, conf, isFooter);
case 'entity':
return await drawActorTypeEntity(elem, actor, conf, isFooter);
case 'database':
return await drawActorTypeDatabase(elem, actor, conf, isFooter);
case 'collections':
return await drawActorTypeCollections(elem, actor, conf, isFooter);
case 'queue':
return await drawActorTypeQueue(elem, actor, conf, isFooter);
}
};
@@ -965,7 +1575,7 @@ const _drawTextCandidateFunc = (function () {
.append('div')
.style('text-align', 'center')
.style('vertical-align', 'middle')
.html(await renderKatex(content, configApi.getConfig()));
.html(await renderKatexSanitized(content, configApi.getConfig()));
byTspan(content, s, x, y, width, height, textAttrs, conf);
_setTextAttrs(text, textAttrs);

View File

@@ -9,30 +9,17 @@ interface Feature {
interface EditorColumn {
title: string;
description: string;
redirectUrl: string;
redBarText?: string;
redirectUrl?: string;
buttonText?: string;
highlighted?: boolean;
proTrialUrl?: string;
proTrialButtonText?: string;
features: Feature[];
isButtonMargined?: boolean;
}
const editorColumns: EditorColumn[] = [
{
title: 'Playground',
description: 'Basic features, no login',
redirectUrl:
'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=editor_selection&utm_campaign=playground',
features: [
{ iconUrl: '/icons/public.svg', featureName: 'Diagram stored in URL' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' },
{ iconUrl: '/icons/whiteboard.svg', featureName: 'Whiteboard' },
],
},
{
title: 'Free',
description: 'Advanced features, free account',
redirectUrl:
'https://www.mermaidchart.com/app/sign-up?utm_source=mermaid_js&utm_medium=editor_selection&utm_campaign=mermaid_chart&redirect=%2Fapp%2Fdiagrams%2Fnew',
highlighted: true,
features: [
const mermaidChartFeatures: Feature[] = [
{ iconUrl: '/icons/folder.svg', featureName: 'Storage' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' },
{ iconUrl: '/icons/ai-diagram.svg', featureName: 'AI diagram generator' },
@@ -43,22 +30,115 @@ const editorColumns: EditorColumn[] = [
{ iconUrl: '/icons/version-history.svg', featureName: 'Version history' },
{ iconUrl: '/icons/comment.svg', featureName: 'Comments' },
{ iconUrl: '/icons/presentation.svg', featureName: 'Presentations' },
{ iconUrl: '/icons/plugins.svg', featureName: 'Advanced Plugins' },
],
{ iconUrl: '/icons/plugins.svg', featureName: 'Advanced plugins' },
];
const openSourceFeatures: Feature[] = [
{ iconUrl: '/icons/public.svg', featureName: 'Diagram stored in URL' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' },
{ iconUrl: '/icons/open-source.svg', featureName: 'Open source' },
{ 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 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',
features: [
{ iconUrl: '/icons/public.svg', featureName: 'Diagram stored in URL' },
{ iconUrl: '/icons/terminal.svg', featureName: 'Code editor' },
{ iconUrl: '/icons/open-source.svg', featureName: 'Open source' },
{ iconUrl: '/icons/version-history.svg', featureName: 'Version history' },
],
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) => {
@@ -84,44 +164,53 @@ onUnmounted(() => {
<template>
<div
v-if="isVisible"
class="fixed top-0 left-0 z-50 flex h-screen w-screen backdrop-blur-sm items-center justify-center"
class="fixed top-0 left-0 z-50 flex h-screen w-screen backdrop-blur-sm items-center justify-center bg-[#8585A4]/40 plausible-event-name=editorSelectionModalOpen"
@click.self="isVisible = false"
>
<div class="flex flex-row rounded-3xl shadow relative gap-5 pt-20 pb-10 px-10 bg-[#F1F8FA]">
<div
class="flex flex-col sm:flex-row rounded-3xl shadow relative gap-5 pt-10 sm:pt-20 pb-10 px-4 sm:px-10 bg-[#F1F8FA] overflow-y-auto max-h-full"
>
<div
v-for="column in editorColumns"
class="w-80 flex relative flex-col justify-start items-center bg-[#dceef1] p-8 text-gray-800 shadow"
:key="column.title"
class="sm:w-96 flex relative flex-col justify-start items-center p-6 sm:p-8 text-gray-800 shadow w-full"
:class="
column.highlighted ? 'bg-white rounded-b-3xl shadow-xl' : 'bg-[#DCEEF1] rounded-3xl'
column.highlighted ? 'bg-white rounded-b-3xl mt-10 sm:mt-0' : 'bg-[#DCEEF1] rounded-3xl'
"
>
<div
v-if="column.highlighted"
class="absolute -top-10 w-full rounded-t-3xl bg-[#E0095F] py-2 flex justify-center"
>
<p class="text-lg font-semibold text-white">Best for collaboration</p>
<p class="text-lg font-semibold text-white">{{ column.redBarText }}</p>
</div>
<header class="mb-6 w-full text-start space-y-1">
<p class="text-2xl font-medium capitalize text-[#1E1A2E]">
{{ column.title }}
</p>
<p class="text-sm text-gray-600">
{{ column.description }}
</p>
<p class="text-2xl font-medium text-[#1E1A2E]">{{ column.title }}</p>
<p class="text-sm text-gray-600">{{ column.description }}</p>
</header>
<a
v-if="column.redirectUrl"
:href="column.redirectUrl"
target="_blank"
class="mb-6 flex h-10 w-full items-center justify-center rounded-xl hover:bg-[#272040] hover:text-white hover:shadow-md"
:class="
column.highlighted
? 'bg-[#1e1a2e] text-[#BEDDE3] hover:text-[#5CA3B4]'
: 'bg-[#BEDDE3] hover:bg-[#5CA3B4] text-[#1E1A2E]'
"
class="flex h-10 w-full bg-[#BEDDE3] hover:bg-[#5CA3B4] text-[#1E1A2E] items-center justify-center rounded-xl hover:text-white hover:shadow-md"
:class="column.isButtonMargined ? 'mb-[88px]' : ' mb-6'"
>
Start free
{{ column.buttonText }}
</a>
<a
v-if="column.proTrialUrl"
:href="column.proTrialUrl"
target="_blank"
class="mb-6 flex h-10 w-full text-white items-center justify-center rounded-xl bg-[#E0095F] hover:bg-[#B0134A]"
>
{{ column.proTrialButtonText || 'Start Pro trial' }}
</a>
<div class="h-px w-full bg-[#bedde3] mb-6"></div>
<ul class="w-full space-y-2">
<li
v-for="{ iconUrl, featureName } in column.features"

View File

@@ -1,8 +1,8 @@
import type { MarkdownOptions } from 'vitepress';
import { defineConfig } from 'vitepress';
import packageJson from '../../../package.json' assert { type: 'json' };
import MermaidExample from './mermaid-markdown-all.js';
import { addCanonicalUrls } from './canonical-urls.js';
import MermaidExample from './mermaid-markdown-all.js';
const allMarkdownTransformers: MarkdownOptions = {
// the shiki theme to highlight code blocks
@@ -203,6 +203,7 @@ function sidebarConfig() {
{ text: 'Accessibility', link: '/config/accessibility' },
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' },
{ text: 'FAQ', link: '/config/faq' },
{ text: 'Layouts', link: '/config/layouts' },
],
},
];

View File

@@ -23,7 +23,7 @@ export default {
'home-hero-before': () => h(TopBar),
'doc-before': () => h(TopBar),
'layout-bottom': () => h(Tooltip),
'home-hero-after': () => h(EditorSelectionModal),
'layout-top': () => h(EditorSelectionModal),
});
},
enhanceApp({ app, router }: EnhanceAppContext) {

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

View File

@@ -1,6 +1,6 @@
# Frequently Asked Questions
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/556#issuecomment-363182217)
1. [How to add title to flowchart?](https://github.com/mermaid-js/mermaid/issues/1433#issuecomment-1991554712)
1. [How to specify custom CSS file?](https://github.com/mermaidjs/mermaid.cli/pull/24#issuecomment-373402785)
1. [How to fix tooltip misplacement issue?](https://github.com/mermaid-js/mermaid/issues/542#issuecomment-3343564621)
1. [How to specify gantt diagram xAxis format?](https://github.com/mermaid-js/mermaid/issues/269#issuecomment-373229136)

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