fix: node label positioning in mindmap nodes

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
This commit is contained in:
darshanr0107
2025-10-01 17:00:56 +05:30
parent 9a4dc563ed
commit 81a43e3d7c
8 changed files with 42 additions and 40 deletions

View File

@@ -8,6 +8,8 @@ import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
import rough from 'roughjs';
const ICON_SIZE = 30;
const ICON_PADDING = 1;
export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
@@ -20,9 +22,6 @@ export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>,
let w = bbox.width + 10 * halfPadding;
let h = bbox.height + 8 * halfPadding;
const ICON_SIZE = 30;
const ICON_PADDING = 1;
if (node.icon) {
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 10 * halfPadding;
w = Math.max(w, minWidthWithIcon);
@@ -42,10 +41,9 @@ export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>,
const iconSpace = ICON_SIZE + ICON_PADDING;
const remainingWidth = effectiveWidth - iconSpace;
labelXOffset = -effectiveWidth / 2 + iconSpace + (remainingWidth - bbox.width) / 2;
label.attr('transform', `translate(${labelXOffset}, ${-bbox.height / 2})`);
}
label.attr('transform', `translate(${labelXOffset}, ${-bbox.height / 2})`);
const r = 0.15 * effectiveWidth;
let bangElem;
const path = `M0 0

View File

@@ -6,6 +6,10 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import rough from 'roughjs';
const ICON_SIZE = 30;
const ICON_PADDING = 20;
export async function circle<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node,
@@ -21,27 +25,20 @@ export async function circle<T extends SVGGraphicsElement>(
);
const padding = options?.padding ?? halfPadding;
const ICON_SIZE = 30;
const ICON_PADDING = 15;
let radius = bbox.width / 2 + padding;
let labelXOffset = -bbox.width / 2;
const labelYOffset = -bbox.height / 2;
if (node.icon) {
const totalWidthNeeded = bbox.width + ICON_SIZE + ICON_PADDING * 2;
const minRadiusWithIcon = totalWidthNeeded / 2 + padding;
radius = Math.max(radius, minRadiusWithIcon);
labelXOffset = -radius + ICON_SIZE + ICON_PADDING;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
}
node.width = radius * 2;
node.height = radius * 2;
let labelXOffset = -bbox.width / 2;
if (node.icon) {
labelXOffset = -radius + ICON_SIZE + ICON_PADDING;
}
const labelYOffset = -bbox.height / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
let circleElem;
if (node.look === 'handDrawn') {

View File

@@ -6,6 +6,9 @@ import intersect from '../intersect/index.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
import rough from 'roughjs';
const ICON_SIZE = 30;
const ICON_PADDING = 15;
export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
@@ -16,13 +19,11 @@ export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>
getNodeClasses(node)
);
const ICON_SIZE = 30;
const ICON_PADDING = 15;
let w = bbox.width + 2 * halfPadding;
let h = bbox.height + 2 * halfPadding;
let labelXOffset = -bbox.width / 2;
const labelYOffset = -bbox.height / 2;
if (node.icon) {
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 2 * halfPadding;
w = Math.max(w, minWidthWithIcon);
@@ -33,14 +34,12 @@ export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
} else {
node.width = w;
node.height = h;
}
const labelYOffset = -bbox.height / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
// Cloud radii
const r1 = 0.15 * w;
const r2 = 0.25 * w;

View File

@@ -4,6 +4,9 @@ import intersect from '../intersect/index.js';
import { styles2String } from './handDrawnShapeStyles.js';
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
const ICON_SIZE = 30;
const ICON_PADDING = 15;
export async function defaultMindmapNode<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -20,9 +23,6 @@ export async function defaultMindmapNode<T extends SVGGraphicsElement>(
let w = bbox.width + 8 * halfPadding;
let h = bbox.height + 2 * halfPadding;
const ICON_SIZE = 30;
const ICON_PADDING = 15;
if (node.icon) {
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 8 * halfPadding;
w = Math.max(w, minWidthWithIcon);

View File

@@ -8,6 +8,9 @@ import type { D3Selection } from '../../../types.js';
import { handleUndefinedAttr } from '../../../utils.js';
import type { Bounds, Point } from '../../../types.js';
const ICON_SIZE = 30;
const ICON_PADDING = 10;
export async function drawRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node,
@@ -18,13 +21,11 @@ export async function drawRect<T extends SVGGraphicsElement>(
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const ICON_SIZE = 30;
const ICON_PADDING = 10;
let totalWidth = Math.max(bbox.width + options.labelPaddingX * 2, node?.width || 0);
let totalHeight = Math.max(bbox.height + options.labelPaddingY * 2, node?.height || 0);
let labelXOffset = -bbox.width / 2;
const labelYOffset = -bbox.height / 2;
if (node.icon) {
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + options.labelPaddingX * 2;
totalWidth = Math.max(totalWidth, minWidthWithIcon);
@@ -36,12 +37,11 @@ export async function drawRect<T extends SVGGraphicsElement>(
const availableTextSpace = totalWidth - ICON_SIZE - ICON_PADDING * 2;
labelXOffset =
-totalWidth / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
} else {
node.width = totalWidth;
node.height = totalHeight;
}
const labelYOffset = -bbox.height / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
const x = -totalWidth / 2;
const y = -totalHeight / 2;

View File

@@ -5,6 +5,9 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import type { D3Selection } from '../../../types.js';
import rough from 'roughjs';
const ICON_SIZE = 30;
const ICON_PADDING = 1;
export const createHexagonPathD = (
x: number,
y: number,
@@ -28,12 +31,11 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
node.labelStyle = labelStyles;
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
const ICON_SIZE = 30;
const ICON_PADDING = 1;
let h = bbox.height + (node.padding ?? 0);
let w = bbox.width + (node.padding ?? 0) * 2.5;
let labelXOffset = -bbox.width / 2;
const labelYOffset = -bbox.height / 2;
if (node.icon) {
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + (node.padding ?? 0) * 2.5;
w = Math.max(w, minWidthWithIcon);
@@ -44,12 +46,11 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
} else {
node.width = w;
node.height = h;
}
const labelYOffset = -bbox.height / 2;
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);

View File

@@ -85,6 +85,9 @@ export function generateArcPoints(
return points;
}
const ICON_SIZE = 30;
const ICON_PADDING = 15;
export async function roundedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
@@ -99,9 +102,6 @@ export async function roundedRect<T extends SVGGraphicsElement>(
let w = (node?.width ? node?.width : bbox.width) + labelPaddingX * 2;
let h = (node?.height ? node?.height : bbox.height) + labelPaddingY * 2;
const ICON_SIZE = 30;
const ICON_PADDING = 15;
let labelXOffset = -bbox.width / 2;
if (node.icon) {
@@ -114,10 +114,9 @@ export async function roundedRect<T extends SVGGraphicsElement>(
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
label.attr('transform', `translate(${labelXOffset}, ${-bbox.height / 2})`);
}
label.attr('transform', `translate(${labelXOffset}, ${-bbox.height / 2})`);
const radius = node.radius || 5;
const taper = node.taper || 5; // Taper width for the rounded corners
const { cssStyles } = node;