Merge branch 'mermaid-js:develop' into 5952/architecture-extreme-height

This commit is contained in:
Nicolas Newman 2024-11-16 09:55:48 -06:00 committed by GitHub
commit 082de76eef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 354 additions and 133 deletions

View File

@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Jagged edge fix for icon shape

View File

@ -1,5 +0,0 @@
---
'mermaid': patch
---
Add missing TypeScript dependencies

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Kanban diagrams will not render when adding a number as ticket id or assigned for a task

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Intersection calculations for tilted cylinder/DAS when using handdrawn look. Some random seeds could cause the calculations to break.

View File

@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Icon color fix for colored icons.

View File

@ -1,5 +0,0 @@
---
'mermaid': minor
---
Adding Kanban board, a new diagram type

View File

@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: error `mermaid.parse` on an invalid shape, so that it matches the errors thrown by `mermaid.render`

View File

@ -10,6 +10,10 @@
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet" rel="stylesheet"
/> />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link <link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet" rel="stylesheet"
@ -84,12 +88,116 @@
</head> </head>
<body> <body>
<pre id="diagram4" class="mermaid">
---
config:
look: handDrawn
theme: default
---
flowchart LR
n00@{ shape: triangle, label: 'This is a label for triangle shape' }
n11@{ shape: sloped-rectangle, label: 'This is a label for sloped-rectangle shape' }
n22@{ shape: horizontal-cylinder, label: 'This is a label for horizontal-cylinder shape' }
n33@{ shape: flipped-triangle, label: 'This is a label for flipped-triangle shape' }
n44@{ shape: hourglass, label: 'This is a label for hourglass shape' }
n00 --> n11
n00 --> n22
n00 --> n33
n00 --> n44
n11 --> n22
n11 --> n33
n11 --> n44
n22 --> n33
n22 --> n44
n33 --> n44
</pre>
<pre id="diagram4" class="mermaid">
---
config:
look: handDrawn
theme: default
---
flowchart LR
n22@{ shape: h-cyl }
n00 --> n11
n00 --> n22
n11 --> n22
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2"> <pre id="diagram4" class="mermaid2">
kanban kanban
id2[In progress] id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' } docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre> </pre>
<pre id="diagram4" class="mermaid"> <pre id="diagram4" class="mermaid2">
--- ---
config: config:
kanban: kanban:
@ -118,6 +226,30 @@ kanban
<script type="module"> <script type="module">
import mermaid from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs'; import layouts from './mermaid-layout-elk.esm.mjs';
const staticBellIconPack = {
prefix: 'fa6-regular',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () => staticBellIconPack,
},
]);
mermaid.registerLayoutLoaders(layouts); mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) { mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err); console.error('Mermaid error: ', err);
@ -135,7 +267,7 @@ kanban
// layout: 'dagre', // layout: 'dagre',
// layout: 'elk', // layout: 'elk',
// layout: 'fixed', // layout: 'fixed',
htmlLabels: false, // htmlLabels: false,
flowchart: { titleTopMargin: 10 }, flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat', // fontFamily: 'Caveat',
// fontFamily: 'Kalam', // fontFamily: 'Kalam',

View File

@ -20,7 +20,7 @@
#### Defined in #### Defined in
[packages/mermaid/src/rendering-util/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L128) [packages/mermaid/src/rendering-util/types.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L144)
--- ---
@ -30,7 +30,7 @@
#### Defined in #### Defined in
[packages/mermaid/src/rendering-util/types.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L127) [packages/mermaid/src/rendering-util/types.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L143)
--- ---
@ -40,4 +40,4 @@
#### Defined in #### Defined in
[packages/mermaid/src/rendering-util/types.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L126) [packages/mermaid/src/rendering-util/types.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L142)

View File

@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in #### Defined in
[packages/mermaid/src/types.ts:95](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L95) [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
--- ---
@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in #### Defined in
[packages/mermaid/src/types.ts:85](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L85) [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
--- ---
@ -63,4 +63,4 @@ The svg code for the rendered graph.
#### Defined in #### Defined in
[packages/mermaid/src/types.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L81) [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)

View File

@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br /> <br />
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About ## About

View File

@ -6,6 +6,18 @@
# Blog # Blog
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
October 31, 2024 · 2 mins
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
October 24, 2024 · 4 mins
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/) ## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins 24 September 2024 · 5 mins

View File

@ -319,7 +319,6 @@ Below is a comprehensive list of the newly introduced shapes and their correspon
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** | | **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- | | --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` | | Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
| Class Box | Class Box | `classBox` | Class Box | `class-box` |
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` | | Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` | | Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` | | Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |

View File

@ -1,5 +1,33 @@
# mermaid # mermaid
## 11.4.0
### Minor Changes
- [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type
- [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes:
- Updates the class diagram to the new unified way of rendering.
- Includes a new "classBox" shape to be used in diagrams
- Other updates such as:
- the option to hide the empty members box in class diagrams,
- support for handDrawn look,
- the introduction of the classDef statement into class diagrams,
- support for styling the default class,
- support lollipop interfaces.
- Includes fixes / additions for #5562 #3139 and #4037
### Patch Changes
- [#5937](https://github.com/mermaid-js/mermaid/pull/5937) [`17b7831`](https://github.com/mermaid-js/mermaid/commit/17b783135f9b2b7748b620dbf81d0f56ab4755f1) Thanks [@saurabhg772244](https://github.com/saurabhg772244)! - fix: Jagged edge fix for icon shape
- [#5933](https://github.com/mermaid-js/mermaid/pull/5933) [`72d60d2`](https://github.com/mermaid-js/mermaid/commit/72d60d2633584eb59bccdb6cf30b9522db645db2) Thanks [@remcohaszing](https://github.com/remcohaszing)! - Add missing TypeScript dependencies
- [#5937](https://github.com/mermaid-js/mermaid/pull/5937) [`17b7831`](https://github.com/mermaid-js/mermaid/commit/17b783135f9b2b7748b620dbf81d0f56ab4755f1) Thanks [@saurabhg772244](https://github.com/saurabhg772244)! - fix: Icon color fix for colored icons.
- [#6002](https://github.com/mermaid-js/mermaid/pull/6002) [`5fabd41`](https://github.com/mermaid-js/mermaid/commit/5fabd414fbee01e43bf6c900907ffc1511ca7440) Thanks [@aloisklink](https://github.com/aloisklink)! - fix: error `mermaid.parse` on an invalid shape, so that it matches the errors thrown by `mermaid.render`
## 11.3.0 ## 11.3.0
### Minor Changes ### Minor Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "mermaid", "name": "mermaid",
"version": "11.3.0", "version": "11.4.0",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.", "description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module", "type": "module",
"module": "./dist/mermaid.core.mjs", "module": "./dist/mermaid.core.mjs",

View File

@ -172,7 +172,6 @@ This Markdown should be kept.
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** | "| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- | | --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` | | Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
| Class Box | Class Box | \`classBox\` | Class Box | \`class-box\` |
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` | | Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` | | Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` | | Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |

View File

@ -893,7 +893,7 @@ const addNodeFromVertex = (
node.cssCompiledStyles = getCompiledStyles(vertex.classes); node.cssCompiledStyles = getCompiledStyles(vertex.classes);
node.cssClasses = vertex.classes.join(' '); node.cssClasses = vertex.classes.join(' ');
} else { } else {
nodes.push({ const baseNode = {
id: vertex.id, id: vertex.id,
label: vertex.text, label: vertex.text,
labelStyle: '', labelStyle: '',
@ -902,10 +902,8 @@ const addNodeFromVertex = (
cssStyles: vertex.styles, cssStyles: vertex.styles,
cssCompiledStyles: getCompiledStyles(['default', 'node', ...vertex.classes]), cssCompiledStyles: getCompiledStyles(['default', 'node', ...vertex.classes]),
cssClasses: 'default ' + vertex.classes.join(' '), cssClasses: 'default ' + vertex.classes.join(' '),
shape: getTypeFromVertex(vertex),
dir: vertex.dir, dir: vertex.dir,
domId: vertex.domId, domId: vertex.domId,
isGroup,
look, look,
link: vertex.link, link: vertex.link,
linkTarget: vertex.linkTarget, linkTarget: vertex.linkTarget,
@ -916,7 +914,20 @@ const addNodeFromVertex = (
assetWidth: vertex.assetWidth, assetWidth: vertex.assetWidth,
assetHeight: vertex.assetHeight, assetHeight: vertex.assetHeight,
constraint: vertex.constraint, constraint: vertex.constraint,
}); };
if (isGroup) {
nodes.push({
...baseNode,
isGroup: true,
shape: 'rect',
});
} else {
nodes.push({
...baseNode,
isGroup: false,
shape: getTypeFromVertex(vertex),
});
}
} }
}; };

View File

@ -84,6 +84,7 @@ const getData = function () {
shape: 'kanbanItem', shape: 'kanbanItem',
level: item.level, level: item.level,
rx: 5, rx: 5,
ry: 5,
cssStyles: ['text-align: left'], cssStyles: ['text-align: left'],
} satisfies KanbanNode; } satisfies KanbanNode;
_nodes.push(childNode); _nodes.push(childNode);
@ -129,20 +130,21 @@ const addNode = (level: number, id: string, descr: string, type: number, shapeDa
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`); throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
} }
if (doc?.shape) { // if shape is defined in the yaml data, use it if it is a valid shape kanbanItem
if (doc?.shape && doc.shape === 'kanbanItem') {
node.shape = doc?.shape; node.shape = doc?.shape;
} }
if (doc?.label) { if (doc?.label) {
node.label = doc?.label; node.label = doc?.label;
} }
if (doc?.icon) { if (doc?.icon) {
node.icon = doc?.icon; node.icon = doc?.icon.toString();
} }
if (doc?.assigned) { if (doc?.assigned) {
node.assigned = doc?.assigned; node.assigned = doc?.assigned.toString();
} }
if (doc?.ticket) { if (doc?.ticket) {
node.ticket = doc?.ticket; node.ticket = doc?.ticket.toString();
} }
if (doc?.priority) { if (doc?.priority) {

View File

@ -7,6 +7,7 @@ import type { KanbanDB } from './kanbanTypes.js';
import defaultConfig from '../../defaultConfig.js'; import defaultConfig from '../../defaultConfig.js';
import { insertCluster } from '../../rendering-util/rendering-elements/clusters.js'; import { insertCluster } from '../../rendering-util/rendering-elements/clusters.js';
import { insertNode, positionNode } from '../../rendering-util/rendering-elements/nodes.js'; import { insertNode, positionNode } from '../../rendering-util/rendering-elements/nodes.js';
import type { ClusterNode } from '../../rendering-util/types.js';
export const draw: DrawDefinition = async (text, id, _version, diagObj) => { export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering kanban diagram\n' + text); log.debug('Rendering kanban diagram\n' + text);
@ -26,7 +27,10 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
sectionsElem.attr('class', 'sections'); sectionsElem.attr('class', 'sections');
const nodesElem = svg.append('g'); const nodesElem = svg.append('g');
nodesElem.attr('class', 'items'); nodesElem.attr('class', 'items');
const sections = data4Layout.nodes.filter((node) => node.isGroup); const sections = data4Layout.nodes.filter(
// TODO: TypeScript 5.5 will infer this predicate automatically
(node): node is typeof node & ClusterNode => node.isGroup
);
let cnt = 0; let cnt = 0;
// TODO set padding // TODO set padding
const padding = 10; const padding = 10;
@ -60,10 +64,15 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
let y = top; let y = top;
const sectionItems = data4Layout.nodes.filter((node) => node.parentId === section.id); const sectionItems = data4Layout.nodes.filter((node) => node.parentId === section.id);
for (const item of sectionItems) { for (const item of sectionItems) {
if (item.isGroup) {
// Kanban diagrams should not have groups within groups
// this should never happen
throw new Error('Groups within groups are not allowed in Kanban diagrams');
}
item.x = section.x; item.x = section.x;
item.width = WIDTH - 1.5 * padding; item.width = WIDTH - 1.5 * padding;
const nodeEl = await insertNode(nodesElem, item, { config: conf }); const nodeEl = await insertNode(nodesElem, item, { config: conf });
const bbox = nodeEl.node().getBBox(); const bbox = nodeEl.node()!.getBBox();
item.y = y + bbox.height / 2; item.y = y + bbox.height / 2;
await positionNode(item); await positionNode(item);
y = item.y + bbox.height / 2 + padding / 2; y = item.y + bbox.height / 2 + padding / 2;

View File

@ -1,14 +0,0 @@
<template>
<div class="-mt-6 mb-8">
<a
href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard"
target="_blank"
><img
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light"
alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt"
style="width: 250px; height: 54px"
width="250"
height="54"
/></a>
</div>
</template>

View File

@ -7,17 +7,21 @@ interface Taglines {
} }
const taglines: Taglines[] = [ const taglines: Taglines[] = [
{
label: 'Explore the Mermaid Whiteboard from the creators of Mermaid',
url: 'https://www.mermaidchart.com/whiteboard?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=whiteboard',
},
{ {
label: 'Use the Visual Editor in Mermaid Chart to design and build diagrams', label: 'Use the Visual Editor in Mermaid Chart to design and build diagrams',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=visual_editor', url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=visual_editor',
}, },
{ {
label: 'Diagram live with teammates in Mermaid Chart', label: 'Diagram live with teammates in Mermaid Chart',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=teams', url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=teams',
}, },
{ {
label: 'Skip the rough draft with Mermaid AI in Mermaid Chart', label: 'Replace ChatGPT Pro, Mermaid.live, and LucidChart with Mermaid Pro',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=mermaid_ai', url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=AIbundle',
}, },
]; ];
@ -25,7 +29,7 @@ let index = ref(Math.floor(Math.random() * taglines.length));
onMounted(() => { onMounted(() => {
setInterval(() => { setInterval(() => {
index.value = (index.value + 1) % taglines.length; index.value = (index.value + 1) % taglines.length;
}, 60_000); }, 40_000);
}); });
</script> </script>
@ -41,7 +45,7 @@ onMounted(() => {
> >
<span class="font-semibold">{{ taglines[index].label }}</span> <span class="font-semibold">{{ taglines[index].label }}</span>
<button class="rounded bg-[#111113] p-1 px-2 text-sm font-semibold tracking-wide"> <button class="rounded bg-[#111113] p-1 px-2 text-sm font-semibold tracking-wide">
Try it now Try now
</button> </button>
</a> </a>
</transition> </transition>

View File

@ -9,8 +9,6 @@ import Contributors from '../components/Contributors.vue';
import HomePage from '../components/HomePage.vue'; import HomePage from '../components/HomePage.vue';
// @ts-ignore Type not available // @ts-ignore Type not available
import TopBar from '../components/TopBar.vue'; import TopBar from '../components/TopBar.vue';
// @ts-ignore Type not available
import ProductHuntBadge from '../components/ProductHuntBadge.vue';
import { getRedirect } from './redirect.js'; import { getRedirect } from './redirect.js';
// @ts-ignore Type not available // @ts-ignore Type not available
import 'uno.css'; import 'uno.css';
@ -25,7 +23,6 @@ export default {
return h(Theme.Layout, null, { return h(Theme.Layout, null, {
// Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar. // Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar.
'home-hero-before': () => h(TopBar), 'home-hero-before': () => h(TopBar),
'home-hero-info-before': () => h(ProductHuntBadge),
'home-features-after': () => h(HomePage), 'home-features-after': () => h(HomePage),
'doc-before': () => h(TopBar), 'doc-before': () => h(TopBar),
}); });

View File

@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br /> <br />
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About ## About

View File

@ -1,5 +1,17 @@
# Blog # Blog
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
October 31, 2024 · 2 mins
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
October 24, 2024 · 4 mins
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/) ## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins 24 September 2024 · 5 mins

View File

@ -471,6 +471,13 @@ const shapes = {
let clusterElems = new Map(); let clusterElems = new Map();
/**
* @typedef {keyof typeof shapes} ClusterShapeID
*/
/**
* @param {import('../types.js').ClusterNode} node - Shape defaults to 'rect'
*/
export const insertCluster = async (elem, node) => { export const insertCluster = async (elem, node) => {
const shape = node.shape || 'rect'; const shape = node.shape || 'rect';
const cluster = await shapes[shape](elem, node); const cluster = await shapes[shape](elem, node);

View File

@ -1,6 +1,6 @@
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import { shapes } from './shapes.js'; import { shapes } from './shapes.js';
import type { Node, ShapeRenderOptions } from '../types.js'; import type { Node, NonClusterNode, ShapeRenderOptions } from '../types.js';
import type { SVGGroup } from '../../mermaid.js'; import type { SVGGroup } from '../../mermaid.js';
import type { D3Selection } from '../../types.js'; import type { D3Selection } from '../../types.js';
import type { graphlib } from 'dagre-d3-es'; import type { graphlib } from 'dagre-d3-es';
@ -10,7 +10,11 @@ type NodeElement = D3Selection<SVGAElement> | Awaited<ReturnType<ShapeHandler>>;
const nodeElems = new Map<string, NodeElement>(); const nodeElems = new Map<string, NodeElement>();
export async function insertNode(elem: SVGGroup, node: Node, renderOptions: ShapeRenderOptions) { export async function insertNode(
elem: SVGGroup,
node: NonClusterNode,
renderOptions: ShapeRenderOptions
) {
let newEl: NodeElement | undefined; let newEl: NodeElement | undefined;
let el; let el;

View File

@ -449,14 +449,6 @@ export const shapesDefs = [
aliases: ['lined-document'], aliases: ['lined-document'],
handler: linedWaveEdgedRect, handler: linedWaveEdgedRect,
}, },
{
semanticName: 'Class Box',
name: 'Class Box',
shortName: 'classBox',
description: 'Class Box',
aliases: ['class-box'],
handler: classBox,
},
] as const satisfies ShapeDefinition[]; ] as const satisfies ShapeDefinition[];
const generateShapeMap = () => { const generateShapeMap = () => {
@ -477,8 +469,13 @@ const generateShapeMap = () => {
icon, icon,
iconRounded, iconRounded,
imageSquare, imageSquare,
kanbanItem,
anchor, anchor,
// Kanban diagram
kanbanItem,
// class diagram
classBox,
} as const; } as const;
const entries = [ const entries = [

View File

@ -26,16 +26,18 @@ export async function iconCircle<T extends SVGGraphicsElement>(
const topLabel = node.pos === 't'; const topLabel = node.pos === 't';
const { nodeBorder } = themeVariables; const { nodeBorder, mainBkg } = themeVariables;
const { stylesMap } = compileStyles(node); const { stylesMap } = compileStyles(node);
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
options.roughness = 0; options.roughness = 0;
options.fillStyle = 'solid'; options.fillStyle = 'solid';
} }
const fill = stylesMap.get('fill');
options.stroke = fill ?? mainBkg;
const iconElem = shapeSvg.append('g'); const iconElem = shapeSvg.append('g');
if (node.icon) { if (node.icon) {

View File

@ -30,7 +30,7 @@ export async function iconRounded<T extends SVGGraphicsElement>(
const height = iconSize + halfPadding * 2; const height = iconSize + halfPadding * 2;
const width = iconSize + halfPadding * 2; const width = iconSize + halfPadding * 2;
const { nodeBorder } = themeVariables; const { nodeBorder, mainBkg } = themeVariables;
const { stylesMap } = compileStyles(node); const { stylesMap } = compileStyles(node);
const x = -width / 2; const x = -width / 2;
@ -40,12 +40,14 @@ export async function iconRounded<T extends SVGGraphicsElement>(
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
options.roughness = 0; options.roughness = 0;
options.fillStyle = 'solid'; options.fillStyle = 'solid';
} }
const fill = stylesMap.get('fill');
options.stroke = fill ?? mainBkg;
const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 5), options); const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 5), options);
@ -58,7 +60,7 @@ export async function iconRounded<T extends SVGGraphicsElement>(
stroke: 'none', stroke: 'none',
}); });
const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); const iconShape = shapeSvg.insert(() => iconNode, ':first-child').attr('class', 'icon-shape2');
const outerShape = shapeSvg.insert(() => outerNode); const outerShape = shapeSvg.insert(() => outerNode);
if (node.icon) { if (node.icon) {

View File

@ -3,6 +3,7 @@ import { log } from '../../../logger.js';
import { getIconSVG } from '../../icons.js'; import { getIconSVG } from '../../icons.js';
import type { Node, ShapeRenderOptions } from '../../types.js'; import type { Node, ShapeRenderOptions } from '../../types.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import { createRoundedRectPathD } from './roundedRectPath.js';
import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { labelHelper, updateNodeBounds } from './util.js'; import { labelHelper, updateNodeBounds } from './util.js';
import type { D3Selection } from '../../../types.js'; import type { D3Selection } from '../../../types.js';
@ -29,7 +30,7 @@ export async function iconSquare<T extends SVGGraphicsElement>(
const height = iconSize + halfPadding * 2; const height = iconSize + halfPadding * 2;
const width = iconSize + halfPadding * 2; const width = iconSize + halfPadding * 2;
const { nodeBorder } = themeVariables; const { nodeBorder, mainBkg } = themeVariables;
const { stylesMap } = compileStyles(node); const { stylesMap } = compileStyles(node);
const x = -width / 2; const x = -width / 2;
@ -39,14 +40,16 @@ export async function iconSquare<T extends SVGGraphicsElement>(
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason // @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg); const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, { stroke: 'transparent' }); const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') { if (node.look !== 'handDrawn') {
options.roughness = 0; options.roughness = 0;
options.fillStyle = 'solid'; options.fillStyle = 'solid';
} }
const fill = stylesMap.get('fill');
options.stroke = fill ?? mainBkg;
const iconNode = rc.rectangle(x, y, width, height, options); const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 0.1), options);
const outerWidth = Math.max(width, bbox.width); const outerWidth = Math.max(width, bbox.width);
const outerHeight = height + bbox.height + labelPadding; const outerHeight = height + bbox.height + labelPadding;

View File

@ -1,28 +1,33 @@
import { labelHelper, insertLabel, updateNodeBounds, getNodeClasses } from './util.js'; import { labelHelper, insertLabel, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js'; import intersect from '../intersect/index.js';
import type { SVG } from '../../../diagram-api/types.js';
import type { Node, KanbanNode, ShapeRenderOptions } from '../../types.js'; import type { Node, KanbanNode, ShapeRenderOptions } from '../../types.js';
import { createRoundedRectPathD } from './roundedRectPath.js'; import { createRoundedRectPathD } from './roundedRectPath.js';
import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js'; import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js';
import rough from 'roughjs'; import rough from 'roughjs';
import type { D3Selection } from '../../../types.js';
const colorFromPriority = (priority: KanbanNode['priority']) => { const colorFromPriority = (priority: NonNullable<KanbanNode['priority']>) => {
switch (priority) { switch (priority) {
case 'Very High': case 'Very High':
return 'red'; return 'red';
case 'High': case 'High':
return 'orange'; return 'orange';
case 'Medium':
return null; // no stroke
case 'Low': case 'Low':
return 'blue'; return 'blue';
case 'Very Low': case 'Very Low':
return 'lightblue'; return 'lightblue';
} }
}; };
export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRenderOptions) => { export async function kanbanItem<T extends SVGGraphicsElement>(
const unknownNode = node as unknown; parent: D3Selection<T>,
const kanbanNode = unknownNode as KanbanNode; // Omit the 'shape' prop since otherwise, it causes a TypeScript circular dependency error
kanbanNode: Omit<Node, 'shape'> | Omit<KanbanNode, 'level' | 'shape'>,
{ config }: ShapeRenderOptions
) {
const { labelStyles, nodeStyles } = styles2String(kanbanNode); const { labelStyles, nodeStyles } = styles2String(kanbanNode);
kanbanNode.labelStyle = labelStyles; kanbanNode.labelStyle = labelStyles || '';
const labelPaddingX = 10; const labelPaddingX = 10;
const orgWidth = kanbanNode.width; const orgWidth = kanbanNode.width;
@ -38,10 +43,10 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende
let ticketUrl = ''; let ticketUrl = '';
let link; let link;
if (kanbanNode.ticket && config?.kanban?.ticketBaseUrl) { if ('ticket' in kanbanNode && kanbanNode.ticket && config?.kanban?.ticketBaseUrl) {
ticketUrl = config?.kanban?.ticketBaseUrl.replace('#TICKET#', kanbanNode.ticket); ticketUrl = config?.kanban?.ticketBaseUrl.replace('#TICKET#', kanbanNode.ticket);
link = shapeSvg link = shapeSvg
.insert('svg:a', ':first-child') .insert<SVGAElement>('svg:a', ':first-child')
.attr('class', 'kanban-ticket-link') .attr('class', 'kanban-ticket-link')
.attr('xlink:href', ticketUrl) .attr('xlink:href', ticketUrl)
.attr('target', '_blank'); .attr('target', '_blank');
@ -49,21 +54,29 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende
const options = { const options = {
useHtmlLabels: kanbanNode.useHtmlLabels, useHtmlLabels: kanbanNode.useHtmlLabels,
labelStyle: kanbanNode.labelStyle, labelStyle: kanbanNode.labelStyle || '',
width: kanbanNode.width, width: kanbanNode.width,
icon: kanbanNode.icon,
img: kanbanNode.img, img: kanbanNode.img,
padding: kanbanNode.padding, padding: kanbanNode.padding || 8,
centerLabel: false, centerLabel: false,
}; };
const { label: labelEl, bbox: bbox2 } = await insertLabel( let labelEl, bbox2;
link ? link : shapeSvg, if (link) {
kanbanNode.ticket || '', ({ label: labelEl, bbox: bbox2 } = await insertLabel(
options link,
); ('ticket' in kanbanNode && kanbanNode.ticket) || '',
options
));
} else {
({ label: labelEl, bbox: bbox2 } = await insertLabel(
shapeSvg,
('ticket' in kanbanNode && kanbanNode.ticket) || '',
options
));
}
const { label: labelElAssigned, bbox: bboxAssigned } = await insertLabel( const { label: labelElAssigned, bbox: bboxAssigned } = await insertLabel(
shapeSvg, shapeSvg,
kanbanNode.assigned || '', ('assigned' in kanbanNode && kanbanNode.assigned) || '',
options options
); );
kanbanNode.width = orgWidth; kanbanNode.width = orgWidth;
@ -107,21 +120,23 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende
: rc.rectangle(x, y, totalWidth, totalHeight, options); : rc.rectangle(x, y, totalWidth, totalHeight, options);
rect = shapeSvg.insert(() => roughNode, ':first-child'); rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', cssStyles); rect.attr('class', 'basic label-container').attr('style', cssStyles ? cssStyles : null);
} else { } else {
rect = shapeSvg.insert('rect', ':first-child'); rect = shapeSvg.insert('rect', ':first-child');
rect rect
.attr('class', 'basic label-container __APA__') .attr('class', 'basic label-container __APA__')
.attr('style', nodeStyles) .attr('style', nodeStyles)
.attr('rx', rx) .attr('rx', rx ?? 5)
.attr('ry', ry) .attr('ry', ry ?? 5)
.attr('x', x) .attr('x', x)
.attr('y', y) .attr('y', y)
.attr('width', totalWidth) .attr('width', totalWidth)
.attr('height', totalHeight); .attr('height', totalHeight);
if (kanbanNode.priority) {
const line = shapeSvg.append('line', ':first-child'); const priority = 'priority' in kanbanNode && kanbanNode.priority;
if (priority) {
const line = shapeSvg.append('line');
const lineX = x + 2; const lineX = x + 2;
const y1 = y + Math.floor((rx ?? 0) / 2); const y1 = y + Math.floor((rx ?? 0) / 2);
@ -133,7 +148,7 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende
.attr('y2', y2) .attr('y2', y2)
.attr('stroke-width', '4') .attr('stroke-width', '4')
.attr('stroke', colorFromPriority(kanbanNode.priority)); .attr('stroke', colorFromPriority(priority));
} }
} }
@ -145,4 +160,4 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende
}; };
return shapeSvg; return shapeSvg;
}; }

View File

@ -13,7 +13,7 @@ export async function note<T extends SVGGraphicsElement>(
) { ) {
const { labelStyles, nodeStyles } = styles2String(node); const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles; node.labelStyle = labelStyles;
const useHtmlLabels = node.useHtmlLabels || getConfig().htmlLabels; const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart?.htmlLabels !== false;
if (!useHtmlLabels) { if (!useHtmlLabels) {
node.centerLabel = true; node.centerLabel = true;
} }

View File

@ -125,7 +125,7 @@ export async function tiltedCylinder<T extends SVGGraphicsElement>(
) { ) {
let x = rx * rx * (1 - (y * y) / (ry * ry)); let x = rx * rx * (1 - (y * y) / (ry * ry));
if (x != 0) { if (x != 0) {
x = Math.sqrt(x); x = Math.sqrt(Math.abs(x));
} }
x = rx - x; x = rx - x;
if (point.x - (node.x ?? 0) > 0) { if (point.x - (node.x ?? 0) > 0) {

View File

@ -10,8 +10,7 @@ import type { D3Selection, Point } from '../../../types.js';
export const labelHelper = async <T extends SVGGraphicsElement>( export const labelHelper = async <T extends SVGGraphicsElement>(
parent: D3Selection<T>, parent: D3Selection<T>,
node: Node, node: Node,
_classes?: string, _classes?: string
_shapeSvg?: D3Selection<T>
) => { ) => {
let cssClasses; let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.htmlLabels); const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.htmlLabels);
@ -22,12 +21,10 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
} }
// Add outer g element // Add outer g element
const shapeSvg = _shapeSvg const shapeSvg = parent
? _shapeSvg .insert('g')
: parent .attr('class', cssClasses)
.insert('g') .attr('id', node.domId || node.id);
.attr('class', cssClasses)
.attr('id', node.domId || node.id);
// Create the label and insert it after the rect // Create the label and insert it after the rect
const labelEl = shapeSvg const labelEl = shapeSvg
@ -119,7 +116,7 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
labelEl.insert('rect', ':first-child'); labelEl.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label: labelEl }; return { shapeSvg, bbox, halfPadding, label: labelEl };
}; };
export const insertLabel = async ( export const insertLabel = async <T extends SVGGraphicsElement>(
parent: D3Selection<T>, parent: D3Selection<T>,
label: string, label: string,
options: { options: {
@ -136,7 +133,10 @@ export const insertLabel = async (
const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels); const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels);
// Create the label and insert it after the rect // Create the label and insert it after the rect
const labelEl = parent.insert('g').attr('class', 'label').attr('style', options.labelStyle); const labelEl = parent
.insert('g')
.attr('class', 'label')
.attr('style', options.labelStyle || '');
const text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), { const text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
useHtmlLabels, useHtmlLabels,

View File

@ -1,5 +1,6 @@
export type MarkdownWordType = 'normal' | 'strong' | 'em'; export type MarkdownWordType = 'normal' | 'strong' | 'em';
import type { MermaidConfig } from '../config.type.js'; import type { MermaidConfig } from '../config.type.js';
import type { ClusterShapeID } from './rendering-elements/clusters.js';
import type { ShapeID } from './rendering-elements/shapes.js'; import type { ShapeID } from './rendering-elements/shapes.js';
export interface MarkdownWord { export interface MarkdownWord {
content: string; content: string;
@ -9,8 +10,7 @@ export type MarkdownLine = MarkdownWord[];
/** Returns `true` if the line fits a constraint (e.g. it's under 𝑛 chars) */ /** Returns `true` if the line fits a constraint (e.g. it's under 𝑛 chars) */
export type CheckFitFunction = (text: MarkdownLine) => boolean; export type CheckFitFunction = (text: MarkdownLine) => boolean;
// Common properties for any node in the system interface BaseNode {
export interface Node {
id: string; id: string;
label?: string; label?: string;
description?: string[]; description?: string[];
@ -38,7 +38,6 @@ export interface Node {
linkTarget?: string; linkTarget?: string;
tooltip?: string; tooltip?: string;
padding?: number; //REMOVE?, use from LayoutData.config - Keep, this could be shape specific padding?: number; //REMOVE?, use from LayoutData.config - Keep, this could be shape specific
shape?: ShapeID;
isGroup: boolean; isGroup: boolean;
width?: number; width?: number;
height?: number; height?: number;
@ -75,6 +74,22 @@ export interface Node {
constraint?: 'on' | 'off'; constraint?: 'on' | 'off';
} }
/**
* Group/cluster nodes, e.g. nodes that contain other nodes.
*/
export interface ClusterNode extends BaseNode {
shape?: ClusterShapeID;
isGroup: true;
}
export interface NonClusterNode extends BaseNode {
shape?: ShapeID;
isGroup: false;
}
// Common properties for any node in the system
export type Node = ClusterNode | NonClusterNode;
// Common properties for any edge in the system // Common properties for any edge in the system
export interface Edge { export interface Edge {
id: string; id: string;
@ -118,9 +133,9 @@ export interface RectOptions {
} }
// Extending the Node interface for specific types if needed // Extending the Node interface for specific types if needed
export interface ClassDiagramNode extends Node { export type ClassDiagramNode = Node & {
memberData: any; // Specific property for class diagram nodes memberData: any; // Specific property for class diagram nodes
} };
// Specific interfaces for layout and render data // Specific interfaces for layout and render data
export interface LayoutData { export interface LayoutData {
@ -154,11 +169,11 @@ export interface ShapeRenderOptions {
dir?: Node['dir']; dir?: Node['dir'];
} }
export interface KanbanNode extends Node { export type KanbanNode = Node & {
// Kanban specif data // Kanban specif data
priority?: 'Very High' | 'High' | 'Medium' | 'Low' | 'Very Low'; priority?: 'Very High' | 'High' | 'Medium' | 'Low' | 'Very Low';
ticket?: string; ticket?: string;
assigned?: string; assigned?: string;
icon?: string; icon?: string;
level: number; level: number;
} };