diff --git a/.changeset/chilly-hotels-mix.md b/.changeset/chilly-hotels-mix.md deleted file mode 100644 index 642ab6ecf..000000000 --- a/.changeset/chilly-hotels-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -fix: Jagged edge fix for icon shape diff --git a/.changeset/dry-students-act.md b/.changeset/dry-students-act.md deleted file mode 100644 index 43f439f2e..000000000 --- a/.changeset/dry-students-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -Add missing TypeScript dependencies diff --git a/.changeset/eleven-rocks-leave.md b/.changeset/eleven-rocks-leave.md new file mode 100644 index 000000000..a8fcbae1f --- /dev/null +++ b/.changeset/eleven-rocks-leave.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Kanban diagrams will not render when adding a number as ticket id or assigned for a task diff --git a/.changeset/fresh-bears-doubt.md b/.changeset/fresh-bears-doubt.md new file mode 100644 index 000000000..911ef74c1 --- /dev/null +++ b/.changeset/fresh-bears-doubt.md @@ -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. diff --git a/.changeset/heavy-cats-mate.md b/.changeset/heavy-cats-mate.md deleted file mode 100644 index c903cc47e..000000000 --- a/.changeset/heavy-cats-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -fix: Icon color fix for colored icons. diff --git a/.changeset/kind-drinks-invent.md b/.changeset/kind-drinks-invent.md deleted file mode 100644 index 244be2bf6..000000000 --- a/.changeset/kind-drinks-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': minor ---- - -Adding Kanban board, a new diagram type diff --git a/.changeset/thick-elephants-search.md b/.changeset/thick-elephants-search.md deleted file mode 100644 index 5e29c42d6..000000000 --- a/.changeset/thick-elephants-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -fix: error `mermaid.parse` on an invalid shape, so that it matches the errors thrown by `mermaid.render` diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 1de071283..a69804655 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -10,6 +10,10 @@ href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css" rel="stylesheet" /> +
++--- +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 ++
+--- +config: + look: handDrawn + theme: default +--- +flowchart LR + n22@{ shape: h-cyl } + n00 --> n11 + n00 --> n22 + n11 --> n22 ++
+flowchart LR +nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' } + ++
+flowchart LR +nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' } +style A fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR +nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' } +A:::AClass +classDef AClass fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR + nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' } + ++
+flowchart LR +nA[Default] --> A@{ icon: 'fa:bell', form: 'square' } + ++
+flowchart LR +nA[Style] --> A@{ icon: 'fa:bell', form: 'square' } +style A fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR +nA[Class] --> A@{ icon: 'fa:bell', form: 'square' } +A:::AClass +classDef AClass fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR + nA[Class] --> A@{ icon: 'logos:aws', form: 'square' } + ++
+flowchart LR +nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' } + ++
+flowchart LR +nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' } +style A fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR +nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' } +A:::AClass +classDef AClass fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR + nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' } + A:::AClass + classDef AClass fill:#f9f,stroke:#333,stroke-width:4px ++
+flowchart LR + nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' } + style A fill:#f9f,stroke:#333,stroke-width:4px +
kanban id2[In progress] docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }-
+--- config: kanban: @@ -118,6 +226,30 @@ kanban @@ -41,7 +45,7 @@ onMounted(() => { > {{ taglines[index].label }} diff --git a/packages/mermaid/src/docs/.vitepress/theme/index.ts b/packages/mermaid/src/docs/.vitepress/theme/index.ts index 05f15ea6a..3ec200937 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/index.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/index.ts @@ -9,8 +9,6 @@ import Contributors from '../components/Contributors.vue'; import HomePage from '../components/HomePage.vue'; // @ts-ignore Type not available import TopBar from '../components/TopBar.vue'; -// @ts-ignore Type not available -import ProductHuntBadge from '../components/ProductHuntBadge.vue'; import { getRedirect } from './redirect.js'; // @ts-ignore Type not available import 'uno.css'; @@ -25,7 +23,6 @@ export default { 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. 'home-hero-before': () => h(TopBar), - 'home-hero-info-before': () => h(ProductHuntBadge), 'home-features-after': () => h(HomePage), 'doc-before': () => h(TopBar), }); diff --git a/packages/mermaid/src/docs/ecosystem/mermaid-chart.md b/packages/mermaid/src/docs/ecosystem/mermaid-chart.md index 83695dab7..77a7020b7 100644 --- a/packages/mermaid/src/docs/ecosystem/mermaid-chart.md +++ b/packages/mermaid/src/docs/ecosystem/mermaid-chart.md @@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
- + ## About diff --git a/packages/mermaid/src/docs/news/blog.md b/packages/mermaid/src/docs/news/blog.md index 4f67758f3..d15f79cdc 100644 --- a/packages/mermaid/src/docs/news/blog.md +++ b/packages/mermaid/src/docs/news/blog.md @@ -1,5 +1,17 @@ # 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/) 24 September 2024 · 5 mins diff --git a/packages/mermaid/src/rendering-util/rendering-elements/clusters.js b/packages/mermaid/src/rendering-util/rendering-elements/clusters.js index 3bd9c9dc7..1dd87d438 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/clusters.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/clusters.js @@ -471,6 +471,13 @@ const shapes = { 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) => { const shape = node.shape || 'rect'; const cluster = await shapes[shape](elem, node); diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts b/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts index e2eea5e19..5af6cd17a 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts @@ -1,6 +1,6 @@ import { log } from '../../logger.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 { D3Selection } from '../../types.js'; import type { graphlib } from 'dagre-d3-es'; @@ -10,7 +10,11 @@ type NodeElement = D3Selection| Awaited >; const nodeElems = new Map (); -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 el; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes.ts index 4f6459d85..dbfc93677 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes.ts @@ -449,14 +449,6 @@ export const shapesDefs = [ aliases: ['lined-document'], handler: linedWaveEdgedRect, }, - { - semanticName: 'Class Box', - name: 'Class Box', - shortName: 'classBox', - description: 'Class Box', - aliases: ['class-box'], - handler: classBox, - }, ] as const satisfies ShapeDefinition[]; const generateShapeMap = () => { @@ -477,8 +469,13 @@ const generateShapeMap = () => { icon, iconRounded, imageSquare, - kanbanItem, anchor, + + // Kanban diagram + kanbanItem, + + // class diagram + classBox, } as const; const entries = [ diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts index 313e5c7af..e8e16853a 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts @@ -26,16 +26,18 @@ export async function iconCircle ( const topLabel = node.pos === 't'; - const { nodeBorder } = themeVariables; + const { nodeBorder, mainBkg } = themeVariables; const { stylesMap } = compileStyles(node); // @ts-expect-error -- Passing a D3.Selection seems to work for some reason const rc = rough.svg(shapeSvg); - const options = userNodeOverrides(node, { stroke: 'transparent' }); + const options = userNodeOverrides(node, {}); if (node.look !== 'handDrawn') { options.roughness = 0; options.fillStyle = 'solid'; } + const fill = stylesMap.get('fill'); + options.stroke = fill ?? mainBkg; const iconElem = shapeSvg.append('g'); if (node.icon) { diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts index ab778de71..40c427ef5 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts @@ -30,7 +30,7 @@ export async function iconRounded ( const height = iconSize + halfPadding * 2; const width = iconSize + halfPadding * 2; - const { nodeBorder } = themeVariables; + const { nodeBorder, mainBkg } = themeVariables; const { stylesMap } = compileStyles(node); const x = -width / 2; @@ -40,12 +40,14 @@ export async function iconRounded ( // @ts-expect-error -- Passing a D3.Selection seems to work for some reason const rc = rough.svg(shapeSvg); - const options = userNodeOverrides(node, { stroke: 'transparent' }); + const options = userNodeOverrides(node, {}); if (node.look !== 'handDrawn') { options.roughness = 0; options.fillStyle = 'solid'; } + const fill = stylesMap.get('fill'); + options.stroke = fill ?? mainBkg; const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 5), options); @@ -58,7 +60,7 @@ export async function iconRounded ( 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); if (node.icon) { diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts index 8cbccb74d..e3536b89e 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts @@ -3,6 +3,7 @@ import { log } from '../../../logger.js'; import { getIconSVG } from '../../icons.js'; import type { Node, ShapeRenderOptions } from '../../types.js'; import intersect from '../intersect/index.js'; +import { createRoundedRectPathD } from './roundedRectPath.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import { labelHelper, updateNodeBounds } from './util.js'; import type { D3Selection } from '../../../types.js'; @@ -29,7 +30,7 @@ export async function iconSquare ( const height = iconSize + halfPadding * 2; const width = iconSize + halfPadding * 2; - const { nodeBorder } = themeVariables; + const { nodeBorder, mainBkg } = themeVariables; const { stylesMap } = compileStyles(node); const x = -width / 2; @@ -39,14 +40,16 @@ export async function iconSquare ( // @ts-expect-error -- Passing a D3.Selection seems to work for some reason const rc = rough.svg(shapeSvg); - const options = userNodeOverrides(node, { stroke: 'transparent' }); + const options = userNodeOverrides(node, {}); if (node.look !== 'handDrawn') { options.roughness = 0; 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 outerHeight = height + bbox.height + labelPadding; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/kanbanItem.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/kanbanItem.ts index 61dc3f85d..fba867a71 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/kanbanItem.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/kanbanItem.ts @@ -1,28 +1,33 @@ import { labelHelper, insertLabel, updateNodeBounds, getNodeClasses } from './util.js'; import intersect from '../intersect/index.js'; -import type { SVG } from '../../../diagram-api/types.js'; import type { Node, KanbanNode, ShapeRenderOptions } from '../../types.js'; import { createRoundedRectPathD } from './roundedRectPath.js'; import { userNodeOverrides, styles2String } from './handDrawnShapeStyles.js'; import rough from 'roughjs'; +import type { D3Selection } from '../../../types.js'; -const colorFromPriority = (priority: KanbanNode['priority']) => { +const colorFromPriority = (priority: NonNullable ) => { switch (priority) { case 'Very High': return 'red'; case 'High': return 'orange'; + case 'Medium': + return null; // no stroke case 'Low': return 'blue'; case 'Very Low': return 'lightblue'; } }; -export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRenderOptions) => { - const unknownNode = node as unknown; - const kanbanNode = unknownNode as KanbanNode; +export async function kanbanItem ( + parent: D3Selection , + // Omit the 'shape' prop since otherwise, it causes a TypeScript circular dependency error + kanbanNode: Omit | Omit , + { config }: ShapeRenderOptions +) { const { labelStyles, nodeStyles } = styles2String(kanbanNode); - kanbanNode.labelStyle = labelStyles; + kanbanNode.labelStyle = labelStyles || ''; const labelPaddingX = 10; const orgWidth = kanbanNode.width; @@ -38,10 +43,10 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende let ticketUrl = ''; 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); link = shapeSvg - .insert('svg:a', ':first-child') + .insert ('svg:a', ':first-child') .attr('class', 'kanban-ticket-link') .attr('xlink:href', ticketUrl) .attr('target', '_blank'); @@ -49,21 +54,29 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende const options = { useHtmlLabels: kanbanNode.useHtmlLabels, - labelStyle: kanbanNode.labelStyle, + labelStyle: kanbanNode.labelStyle || '', width: kanbanNode.width, - icon: kanbanNode.icon, img: kanbanNode.img, - padding: kanbanNode.padding, + padding: kanbanNode.padding || 8, centerLabel: false, }; - const { label: labelEl, bbox: bbox2 } = await insertLabel( - link ? link : shapeSvg, - kanbanNode.ticket || '', - options - ); + let labelEl, bbox2; + if (link) { + ({ label: labelEl, bbox: bbox2 } = await insertLabel( + 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( shapeSvg, - kanbanNode.assigned || '', + ('assigned' in kanbanNode && kanbanNode.assigned) || '', options ); kanbanNode.width = orgWidth; @@ -107,21 +120,23 @@ export const kanbanItem = async (parent: SVG, node: Node, { config }: ShapeRende : rc.rectangle(x, y, totalWidth, totalHeight, options); 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 { rect = shapeSvg.insert('rect', ':first-child'); rect .attr('class', 'basic label-container __APA__') .attr('style', nodeStyles) - .attr('rx', rx) - .attr('ry', ry) + .attr('rx', rx ?? 5) + .attr('ry', ry ?? 5) .attr('x', x) .attr('y', y) .attr('width', totalWidth) .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 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('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; -}; +} diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts index dfad92949..4a7f66a87 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts @@ -13,7 +13,7 @@ export async function note ( ) { const { labelStyles, nodeStyles } = styles2String(node); node.labelStyle = labelStyles; - const useHtmlLabels = node.useHtmlLabels || getConfig().htmlLabels; + const useHtmlLabels = node.useHtmlLabels || getConfig().flowchart?.htmlLabels !== false; if (!useHtmlLabels) { node.centerLabel = true; } diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts index f8a2fb52b..29f2c267f 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/tiltedCylinder.ts @@ -125,7 +125,7 @@ export async function tiltedCylinder ( ) { let x = rx * rx * (1 - (y * y) / (ry * ry)); if (x != 0) { - x = Math.sqrt(x); + x = Math.sqrt(Math.abs(x)); } x = rx - x; if (point.x - (node.x ?? 0) > 0) { diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts index af8f75de5..52471ecc0 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts @@ -10,8 +10,7 @@ import type { D3Selection, Point } from '../../../types.js'; export const labelHelper = async ( parent: D3Selection , node: Node, - _classes?: string, - _shapeSvg?: D3Selection + _classes?: string ) => { let cssClasses; const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.htmlLabels); @@ -22,12 +21,10 @@ export const labelHelper = async ( } // Add outer g element - const shapeSvg = _shapeSvg - ? _shapeSvg - : parent - .insert('g') - .attr('class', cssClasses) - .attr('id', node.domId || node.id); + const shapeSvg = parent + .insert('g') + .attr('class', cssClasses) + .attr('id', node.domId || node.id); // Create the label and insert it after the rect const labelEl = shapeSvg @@ -119,7 +116,7 @@ export const labelHelper = async ( labelEl.insert('rect', ':first-child'); return { shapeSvg, bbox, halfPadding, label: labelEl }; }; -export const insertLabel = async ( +export const insertLabel = async ( parent: D3Selection , label: string, options: { @@ -136,7 +133,10 @@ export const insertLabel = async ( const useHtmlLabels = options.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels); // 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()), { useHtmlLabels, diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts index e49218f71..86cfd50b3 100644 --- a/packages/mermaid/src/rendering-util/types.ts +++ b/packages/mermaid/src/rendering-util/types.ts @@ -1,5 +1,6 @@ export type MarkdownWordType = 'normal' | 'strong' | 'em'; import type { MermaidConfig } from '../config.type.js'; +import type { ClusterShapeID } from './rendering-elements/clusters.js'; import type { ShapeID } from './rendering-elements/shapes.js'; export interface MarkdownWord { content: string; @@ -9,8 +10,7 @@ export type MarkdownLine = MarkdownWord[]; /** Returns `true` if the line fits a constraint (e.g. it's under 𝑛 chars) */ export type CheckFitFunction = (text: MarkdownLine) => boolean; -// Common properties for any node in the system -export interface Node { +interface BaseNode { id: string; label?: string; description?: string[]; @@ -38,7 +38,6 @@ export interface Node { linkTarget?: string; tooltip?: string; padding?: number; //REMOVE?, use from LayoutData.config - Keep, this could be shape specific - shape?: ShapeID; isGroup: boolean; width?: number; height?: number; @@ -75,6 +74,22 @@ export interface Node { 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 export interface Edge { id: string; @@ -118,9 +133,9 @@ export interface RectOptions { } // 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 -} +}; // Specific interfaces for layout and render data export interface LayoutData { @@ -154,11 +169,11 @@ export interface ShapeRenderOptions { dir?: Node['dir']; } -export interface KanbanNode extends Node { +export type KanbanNode = Node & { // Kanban specif data priority?: 'Very High' | 'High' | 'Medium' | 'Low' | 'Very Low'; ticket?: string; assigned?: string; icon?: string; level: number; -} +};