Compare commits

..

5 Commits

Author SHA1 Message Date
omkarht
2eb76c7c85 fix: simplify label creation for markdown in clusters and edges
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-12-17 12:58:02 +05:30
omkarht
705a7aea50 fix: streamline label handling for markdown in labelHelper function
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-12-15 19:12:27 +05:30
autofix-ci[bot]
86b1f8852c [autofix.ci] apply automated fixes 2025-12-15 13:04:21 +00:00
omkarht
8eacf42fe4 Merge branch 'develop' into fix/flowchart-labels-markdown-issue 2025-12-15 18:24:25 +05:30
omkarht
373ebc8fd6 feat: enhance label handling in flowchart with markdown support
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-12-15 18:22:31 +05:30
10 changed files with 55 additions and 113 deletions

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Ensure treemap labels render correctly in large nested diagrams

View File

@@ -468,65 +468,4 @@ classDef highlight fill:#f39c12,color:#000,stroke:#e67e22,stroke-width:2px;
);
});
*/
it('14: should render labels in complex treemap with many nested blocks', () => {
imgSnapshotTest(
`treemap-beta
"🔴 High Activity (Top 50%)":::redContainer
"packages/app (1115)": 15:::redleaf
"packages/app/src (844)": 12:::redleaf
"packages/app/src/lib (707)": 11:::redleaf
"packages/app/src/routes (353)": 7:::redleaf
"packages/app/tests (277)": 6:::redleaf
"packages/app/tests/e2e (245)": 6:::redleaf
"packages/app/tests/common (48)": 4:::redleaf
"packages/llm-prompts (29)": 3:::redleaf
"packages/emails (26)": 3:::redleaf
"packages/app/tests/api (23)": 3:::redleaf
"packages/emails/src (18)": 3:::redleaf
"packages/emails/src/emails (17)": 3:::redleaf
"packages/app/static (15)": 3:::redleaf
"packages/llm-prompts/diagram-chat (15)": 3:::redleaf
"packages/app/prisma (11)": 3:::redleaf
"packages/app/prisma/migrations (11)": 3:::redleaf
"packages/llm-prompts/shared (9)": 3:::redleaf
"packages/app/static/icons (8)": 3:::redleaf
"packages/emails/src/components (8)": 3:::redleaf
"packages/app-observability (8)": 3:::redleaf
"packages/mermaid-pre-render-server (8)": 3:::redleaf
"🟠 Medium Activity (35%)":::orangeContainer
"packages/app/tests/mobile (6)": 3:::orangeleaf
"packages/llm-prompts/diagram-chat/tests (6)": 3:::orangeleaf
"packages/eslint-plugin (6)": 3:::orangeleaf
"packages/eslint-plugin/src (5)": 3:::orangeleaf
"packages/eslint-plugin/src/rules (5)": 3:::orangeleaf
"packages/app/static/img (4)": 3:::orangeleaf
"packages/llm-prompts/tests (4)": 3:::orangeleaf
"packages/llm-prompts/common (3)": 3:::orangeleaf
"packages/mermaid-pre-render-server/tests (3)": 3:::orangeleaf
"packages/mermaid-pre-render-server/tests/e2e (3)": 3:::orangeleaf
"packages/app/tests/seed (2)": 3:::orangeleaf
"packages/llm-prompts/shared/patches (2)": 3:::orangeleaf
"packages/mermaid-pre-render-server/src (2)": 3:::orangeleaf
"packages/app/scripts (1)": 3:::orangeleaf
"🟡 Low Activity (15%)":::yellowContainer
"packages/llm-prompts/regenerate-diagram (1)": 3:::yellowleaf
"packages/llm-prompts/repair-diagram (1)": 3:::yellowleaf
"packages/app-buildship (1)": 3:::yellowleaf
"packages/app-buildship/src (1)": 3:::yellowleaf
"packages/app-buildship/src/cleanupAssets (1)": 3:::yellowleaf
"packages/icons (1)": 3:::yellowleaf
classDef root fill:#F5BA71
classDef redContainer fill:#F54927
classDef orangeContainer fill:#DE8118
classDef yellowContainer fill:#F5E514
classDef redleaf fill:#F54927,stroke:none,color:#FFFFFF
classDef orangeleaf fill:#DE8118,stroke:none,color:#FFFFFF
classDef yellowleaf fill:#F5E514,stroke:none,color:#333333
`,
{}
);
});
});

View File

@@ -10,7 +10,7 @@
# Interface: ParseOptions
Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
Defined in: [packages/mermaid/src/types.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L90)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mer
> `optional` **suppressErrors**: `boolean`
Defined in: [packages/mermaid/src/types.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L94)
Defined in: [packages/mermaid/src/types.ts:95](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L95)
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:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L97)
Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:97](https://github.com/mermaid-js/mer
> **config**: [`MermaidConfig`](MermaidConfig.md)
Defined in: [packages/mermaid/src/types.ts:105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L105)
Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106)
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:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L101)
Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102)
The diagram type, e.g. 'flowchart', 'sequence', etc.

View File

@@ -10,7 +10,7 @@
# Interface: RenderResult
Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L115)
Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:115](https://github.com/mermaid-js/me
> `optional` **bindFunctions**: (`element`) => `void`
Defined in: [packages/mermaid/src/types.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L133)
Defined in: [packages/mermaid/src/types.ts:134](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L134)
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:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L123)
Defined in: [packages/mermaid/src/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L124)
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:119](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L119)
Defined in: [packages/mermaid/src/types.ts:120](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L120)
The svg code for the rendered graph.

View File

@@ -85,6 +85,17 @@ export class FlowDB implements DiagramDB {
return common.sanitizeText(txt, this.config);
}
private sanitizeNodeLabelType(labelType?: string) {
switch (labelType) {
case 'markdown':
case 'string':
case 'text':
return labelType;
default:
return 'markdown';
}
}
/**
* Function to lookup domId from id in the graph definition.
*
@@ -208,6 +219,7 @@ export class FlowDB implements DiagramDB {
if (doc?.label) {
vertex.text = doc?.label;
vertex.labelType = this.sanitizeNodeLabelType(doc?.labelType);
}
if (doc?.icon) {
vertex.icon = doc?.icon;
@@ -267,7 +279,7 @@ export class FlowDB implements DiagramDB {
if (edge.text.startsWith('"') && edge.text.endsWith('"')) {
edge.text = edge.text.substring(1, edge.text.length - 1);
}
edge.labelType = linkTextObj.type;
edge.labelType = this.sanitizeNodeLabelType(linkTextObj.type);
}
if (type !== undefined) {
@@ -702,7 +714,7 @@ You have to call mermaid.initialize.`
title: title.trim(),
classes: [],
dir,
labelType: _title.type,
labelType: this.sanitizeNodeLabelType(_title?.type),
};
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
@@ -1012,6 +1024,7 @@ You have to call mermaid.initialize.`
const baseNode = {
id: vertex.id,
label: vertex.text,
labelType: vertex.labelType,
labelStyle: '',
parentId,
padding: config.flowchart?.padding || 8,
@@ -1088,6 +1101,7 @@ You have to call mermaid.initialize.`
id: subGraph.id,
label: subGraph.title,
labelStyle: '',
labelType: subGraph.labelType,
parentId: parentDB.get(subGraph.id),
padding: 8,
cssCompiledStyles: this.getCompiledStyles(subGraph.classes),
@@ -1119,6 +1133,7 @@ You have to call mermaid.initialize.`
end: rawEdge.end,
type: rawEdge.type ?? 'normal',
label: rawEdge.text,
labelType: rawEdge.labelType,
labelpos: 'c',
thickness: rawEdge.stroke,
minlen: rawEdge.length,

View File

@@ -29,7 +29,7 @@ export interface FlowVertex {
domId: string;
haveCallback?: boolean;
id: string;
labelType: 'text';
labelType: 'markdown' | 'string' | 'text';
link?: string;
linkTarget?: string;
props?: any;
@@ -62,7 +62,7 @@ export interface FlowEdge {
style?: string[];
length?: number;
text: string;
labelType: 'text';
labelType: 'markdown' | 'string' | 'text';
classes: string[];
id?: string;
animation?: 'fast' | 'slow';

View File

@@ -224,7 +224,6 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
.attr('dominant-baseline', 'middle')
.text((d) => (d.depth === 0 ? '' : d.data.name)) // Skip label for root section
.attr('font-weight', 'bold')
.attr('clip-path', (_d, i) => `url(#clip-section-${id}-${i})`) // Apply clip-path to prevent overflow
.attr('style', (d) => {
// Hide the label for the root section
if (d.depth === 0) {
@@ -310,17 +309,6 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
// Draw the leaf nodes
const leafNodes = treemapData.leaves();
const isComplexTreemap = leafNodes.length > 20;
const baseLabelFontSize = isComplexTreemap ? 16 : 38;
const baseValueFontSize = isComplexTreemap ? 14 : 28;
const minLabelFontSize = isComplexTreemap ? 4 : 8;
const minValueFontSize = isComplexTreemap ? 4 : 6;
const labelPadding = isComplexTreemap ? 2 : 4;
const minDisplayThreshold = isComplexTreemap ? 8 : 10;
const spacingBetweenLabelAndValue = isComplexTreemap ? 1 : 2;
const cell = g
.selectAll('.treemapLeafGroup')
.data(leafNodes)
@@ -371,7 +359,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
// .style('fill', (d) => colorScaleLabel(d.data.name))
.attr('style', (d) => {
const labelStyles =
`text-anchor: middle; dominant-baseline: middle; font-size: ${baseLabelFontSize}px;fill:` +
'text-anchor: middle; dominant-baseline: middle; font-size: 38px;fill:' +
colorScaleLabel(d.data.name) +
';';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
@@ -386,16 +374,21 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
const nodeHeight = d.y1 - d.y0;
const textNode = self.node()!;
const availableWidth = nodeWidth - 2 * labelPadding;
const availableHeight = nodeHeight - 2 * labelPadding;
const padding = 4;
const availableWidth = nodeWidth - 2 * padding;
const availableHeight = nodeHeight - 2 * padding;
if (availableWidth < minDisplayThreshold || availableHeight < minDisplayThreshold) {
if (availableWidth < 10 || availableHeight < 10) {
self.style('display', 'none');
return;
}
let currentLabelFontSize = parseInt(self.style('font-size'), 10);
const minLabelFontSize = 8;
const originalValueRelFontSize = 28; // Original font size of value, for max cap
const valueScaleFactor = 0.6; // Value font size as a factor of label font size
const minValueFontSize = 6;
const spacingBetweenLabelAndValue = 2;
// 1. Adjust label font size to fit width
while (
@@ -409,7 +402,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
// 2. Adjust both label and prospective value font size to fit combined height
let prospectiveValueFontSize = Math.max(
minValueFontSize,
Math.min(baseValueFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
);
let combinedHeight =
currentLabelFontSize + spacingBetweenLabelAndValue + prospectiveValueFontSize;
@@ -418,7 +411,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
currentLabelFontSize--;
prospectiveValueFontSize = Math.max(
minValueFontSize,
Math.min(baseValueFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
Math.min(originalValueRelFontSize, Math.round(currentLabelFontSize * valueScaleFactor))
);
if (
prospectiveValueFontSize < minValueFontSize &&
@@ -439,18 +432,13 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
self.style('font-size', `${currentLabelFontSize}px`);
// 3. Final visibility check for the label
if (isComplexTreemap) {
if (currentLabelFontSize < minLabelFontSize || availableHeight < minLabelFontSize) {
self.style('display', 'none');
}
} else {
if (
textNode.getComputedTextLength() > availableWidth ||
currentLabelFontSize < minLabelFontSize ||
availableHeight < currentLabelFontSize
) {
self.style('display', 'none');
}
if (
textNode.getComputedTextLength() > availableWidth ||
currentLabelFontSize < minLabelFontSize ||
availableHeight < currentLabelFontSize
) {
self.style('display', 'none');
// If label is hidden, value will be hidden by its own .each() loop
}
});
@@ -466,7 +454,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
})
.attr('style', (d) => {
const labelStyles =
`text-anchor: middle; dominant-baseline: hanging; font-size: ${baseValueFontSize}px;fill:` +
'text-anchor: middle; dominant-baseline: hanging; font-size: 28px;fill:' +
colorScaleLabel(d.data.name) +
';';
const styles = styles2String({ cssCompiledStyles: d.data.cssCompiledStyles } as Node);
@@ -493,11 +481,14 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
}
const finalLabelFontSize = parseFloat(labelElement.style('font-size'));
const originalValueFontSize = 28; // From initial style setting
const valueScaleFactor = 0.6;
const minValueFontSize = 6;
const spacingBetweenLabelAndValue = 2;
const actualValueFontSize = Math.max(
minValueFontSize,
Math.min(baseValueFontSize, Math.round(finalLabelFontSize * valueScaleFactor))
Math.min(originalValueFontSize, Math.round(finalLabelFontSize * valueScaleFactor))
);
valueTextElement.style('font-size', `${actualValueFontSize}px`);
@@ -509,7 +500,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
const nodeTotalHeight = d.y1 - d.y0;
const cellBottomPadding = 4;
const maxValueBottomY = nodeTotalHeight - cellBottomPadding;
const availableWidthForValue = nodeWidth - 2 * labelPadding;
const availableWidthForValue = nodeWidth - 2 * 4; // padding for value text
if (
valueTextElement.node()!.getComputedTextLength() > availableWidthForValue ||

View File

@@ -13,7 +13,7 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
_classes?: string
) => {
let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.htmlLabels);
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels);
if (!_classes) {
cssClasses = 'node default';
} else {
@@ -48,6 +48,7 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
style: node.labelStyle,
addSvgBackground: !!node.icon || !!node.img,
});
// Get the size of the label
let bbox = text.getBBox();
const halfPadding = (node?.padding ?? 0) / 2;

View File

@@ -1,6 +1,7 @@
export interface NodeMetaData {
shape?: string;
label?: string;
labelType?: string;
icon?: string;
form?: string;
pos?: 't' | 'b';