mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-13 11:09:39 +02:00
fix: node label positioning in mindmap nodes
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
This commit is contained in:
@@ -6,6 +6,14 @@
|
|||||||
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"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<style>
|
<style>
|
||||||
svg:not(svg svg) {
|
svg:not(svg svg) {
|
||||||
border: 2px solid darkred;
|
border: 2px solid darkred;
|
||||||
|
@@ -8,6 +8,8 @@ import { handleUndefinedAttr } from '../../../utils.js';
|
|||||||
import type { Bounds, Point } from '../../../types.js';
|
import type { Bounds, Point } from '../../../types.js';
|
||||||
import rough from 'roughjs';
|
import rough from 'roughjs';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 1;
|
||||||
export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
||||||
const { labelStyles, nodeStyles } = styles2String(node);
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
node.labelStyle = labelStyles;
|
node.labelStyle = labelStyles;
|
||||||
@@ -20,9 +22,6 @@ export async function bang<T extends SVGGraphicsElement>(parent: D3Selection<T>,
|
|||||||
let w = bbox.width + 10 * halfPadding;
|
let w = bbox.width + 10 * halfPadding;
|
||||||
let h = bbox.height + 8 * halfPadding;
|
let h = bbox.height + 8 * halfPadding;
|
||||||
|
|
||||||
const ICON_SIZE = 30;
|
|
||||||
const ICON_PADDING = 1;
|
|
||||||
|
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 10 * halfPadding;
|
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 10 * halfPadding;
|
||||||
w = Math.max(w, minWidthWithIcon);
|
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 iconSpace = ICON_SIZE + ICON_PADDING;
|
||||||
const remainingWidth = effectiveWidth - iconSpace;
|
const remainingWidth = effectiveWidth - iconSpace;
|
||||||
labelXOffset = -effectiveWidth / 2 + iconSpace + (remainingWidth - bbox.width) / 2;
|
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;
|
const r = 0.15 * effectiveWidth;
|
||||||
let bangElem;
|
let bangElem;
|
||||||
const path = `M0 0
|
const path = `M0 0
|
||||||
|
@@ -6,6 +6,10 @@ import intersect from '../intersect/index.js';
|
|||||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||||
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
||||||
import rough from 'roughjs';
|
import rough from 'roughjs';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 20;
|
||||||
|
|
||||||
export async function circle<T extends SVGGraphicsElement>(
|
export async function circle<T extends SVGGraphicsElement>(
|
||||||
parent: D3Selection<T>,
|
parent: D3Selection<T>,
|
||||||
node: Node,
|
node: Node,
|
||||||
@@ -21,27 +25,20 @@ export async function circle<T extends SVGGraphicsElement>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const padding = options?.padding ?? halfPadding;
|
const padding = options?.padding ?? halfPadding;
|
||||||
const ICON_SIZE = 30;
|
|
||||||
const ICON_PADDING = 15;
|
|
||||||
|
|
||||||
let radius = bbox.width / 2 + padding;
|
let radius = bbox.width / 2 + padding;
|
||||||
|
let labelXOffset = -bbox.width / 2;
|
||||||
|
const labelYOffset = -bbox.height / 2;
|
||||||
|
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const totalWidthNeeded = bbox.width + ICON_SIZE + ICON_PADDING * 2;
|
const totalWidthNeeded = bbox.width + ICON_SIZE + ICON_PADDING * 2;
|
||||||
const minRadiusWithIcon = totalWidthNeeded / 2 + padding;
|
const minRadiusWithIcon = totalWidthNeeded / 2 + padding;
|
||||||
radius = Math.max(radius, minRadiusWithIcon);
|
radius = Math.max(radius, minRadiusWithIcon);
|
||||||
|
labelXOffset = -radius + ICON_SIZE + ICON_PADDING;
|
||||||
|
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.width = radius * 2;
|
node.width = radius * 2;
|
||||||
node.height = 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;
|
let circleElem;
|
||||||
|
|
||||||
if (node.look === 'handDrawn') {
|
if (node.look === 'handDrawn') {
|
||||||
|
@@ -6,6 +6,9 @@ import intersect from '../intersect/index.js';
|
|||||||
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
||||||
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
||||||
import rough from 'roughjs';
|
import rough from 'roughjs';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 15;
|
||||||
export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>, node: Node) {
|
||||||
const { labelStyles, nodeStyles } = styles2String(node);
|
const { labelStyles, nodeStyles } = styles2String(node);
|
||||||
node.labelStyle = labelStyles;
|
node.labelStyle = labelStyles;
|
||||||
@@ -16,13 +19,11 @@ export async function cloud<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
getNodeClasses(node)
|
getNodeClasses(node)
|
||||||
);
|
);
|
||||||
|
|
||||||
const ICON_SIZE = 30;
|
|
||||||
const ICON_PADDING = 15;
|
|
||||||
|
|
||||||
let w = bbox.width + 2 * halfPadding;
|
let w = bbox.width + 2 * halfPadding;
|
||||||
let h = bbox.height + 2 * halfPadding;
|
let h = bbox.height + 2 * halfPadding;
|
||||||
|
|
||||||
let labelXOffset = -bbox.width / 2;
|
let labelXOffset = -bbox.width / 2;
|
||||||
|
const labelYOffset = -bbox.height / 2;
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 2 * halfPadding;
|
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 2 * halfPadding;
|
||||||
w = Math.max(w, minWidthWithIcon);
|
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;
|
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
|
||||||
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
||||||
|
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
||||||
} else {
|
} else {
|
||||||
node.width = w;
|
node.width = w;
|
||||||
node.height = h;
|
node.height = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelYOffset = -bbox.height / 2;
|
|
||||||
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
|
||||||
|
|
||||||
// Cloud radii
|
// Cloud radii
|
||||||
const r1 = 0.15 * w;
|
const r1 = 0.15 * w;
|
||||||
const r2 = 0.25 * w;
|
const r2 = 0.25 * w;
|
||||||
|
@@ -4,6 +4,9 @@ import intersect from '../intersect/index.js';
|
|||||||
import { styles2String } from './handDrawnShapeStyles.js';
|
import { styles2String } from './handDrawnShapeStyles.js';
|
||||||
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 15;
|
||||||
|
|
||||||
export async function defaultMindmapNode<T extends SVGGraphicsElement>(
|
export async function defaultMindmapNode<T extends SVGGraphicsElement>(
|
||||||
parent: D3Selection<T>,
|
parent: D3Selection<T>,
|
||||||
node: Node
|
node: Node
|
||||||
@@ -20,9 +23,6 @@ export async function defaultMindmapNode<T extends SVGGraphicsElement>(
|
|||||||
let w = bbox.width + 8 * halfPadding;
|
let w = bbox.width + 8 * halfPadding;
|
||||||
let h = bbox.height + 2 * halfPadding;
|
let h = bbox.height + 2 * halfPadding;
|
||||||
|
|
||||||
const ICON_SIZE = 30;
|
|
||||||
const ICON_PADDING = 15;
|
|
||||||
|
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 8 * halfPadding;
|
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + 8 * halfPadding;
|
||||||
w = Math.max(w, minWidthWithIcon);
|
w = Math.max(w, minWidthWithIcon);
|
||||||
|
@@ -8,6 +8,9 @@ import type { D3Selection } from '../../../types.js';
|
|||||||
import { handleUndefinedAttr } from '../../../utils.js';
|
import { handleUndefinedAttr } from '../../../utils.js';
|
||||||
import type { Bounds, Point } from '../../../types.js';
|
import type { Bounds, Point } from '../../../types.js';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 10;
|
||||||
|
|
||||||
export async function drawRect<T extends SVGGraphicsElement>(
|
export async function drawRect<T extends SVGGraphicsElement>(
|
||||||
parent: D3Selection<T>,
|
parent: D3Selection<T>,
|
||||||
node: Node,
|
node: Node,
|
||||||
@@ -18,13 +21,11 @@ export async function drawRect<T extends SVGGraphicsElement>(
|
|||||||
|
|
||||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
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 totalWidth = Math.max(bbox.width + options.labelPaddingX * 2, node?.width || 0);
|
||||||
let totalHeight = Math.max(bbox.height + options.labelPaddingY * 2, node?.height || 0);
|
let totalHeight = Math.max(bbox.height + options.labelPaddingY * 2, node?.height || 0);
|
||||||
|
|
||||||
let labelXOffset = -bbox.width / 2;
|
let labelXOffset = -bbox.width / 2;
|
||||||
|
const labelYOffset = -bbox.height / 2;
|
||||||
|
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + options.labelPaddingX * 2;
|
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + options.labelPaddingX * 2;
|
||||||
totalWidth = Math.max(totalWidth, minWidthWithIcon);
|
totalWidth = Math.max(totalWidth, minWidthWithIcon);
|
||||||
@@ -36,12 +37,11 @@ export async function drawRect<T extends SVGGraphicsElement>(
|
|||||||
const availableTextSpace = totalWidth - ICON_SIZE - ICON_PADDING * 2;
|
const availableTextSpace = totalWidth - ICON_SIZE - ICON_PADDING * 2;
|
||||||
labelXOffset =
|
labelXOffset =
|
||||||
-totalWidth / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
-totalWidth / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
||||||
|
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
||||||
} else {
|
} else {
|
||||||
node.width = totalWidth;
|
node.width = totalWidth;
|
||||||
node.height = totalHeight;
|
node.height = totalHeight;
|
||||||
}
|
}
|
||||||
const labelYOffset = -bbox.height / 2;
|
|
||||||
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
|
||||||
const x = -totalWidth / 2;
|
const x = -totalWidth / 2;
|
||||||
const y = -totalHeight / 2;
|
const y = -totalHeight / 2;
|
||||||
|
|
||||||
|
@@ -5,6 +5,9 @@ import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
|
|||||||
import type { D3Selection } from '../../../types.js';
|
import type { D3Selection } from '../../../types.js';
|
||||||
import rough from 'roughjs';
|
import rough from 'roughjs';
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 1;
|
||||||
|
|
||||||
export const createHexagonPathD = (
|
export const createHexagonPathD = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@@ -28,12 +31,11 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
|
|||||||
node.labelStyle = labelStyles;
|
node.labelStyle = labelStyles;
|
||||||
const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node));
|
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 h = bbox.height + (node.padding ?? 0);
|
||||||
let w = bbox.width + (node.padding ?? 0) * 2.5;
|
let w = bbox.width + (node.padding ?? 0) * 2.5;
|
||||||
|
|
||||||
let labelXOffset = -bbox.width / 2;
|
let labelXOffset = -bbox.width / 2;
|
||||||
|
const labelYOffset = -bbox.height / 2;
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + (node.padding ?? 0) * 2.5;
|
const minWidthWithIcon = bbox.width + ICON_SIZE + ICON_PADDING * 2 + (node.padding ?? 0) * 2.5;
|
||||||
w = Math.max(w, minWidthWithIcon);
|
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;
|
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
|
||||||
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 2;
|
||||||
|
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
||||||
} else {
|
} else {
|
||||||
node.width = w;
|
node.width = w;
|
||||||
node.height = h;
|
node.height = h;
|
||||||
}
|
}
|
||||||
const labelYOffset = -bbox.height / 2;
|
|
||||||
label.attr('transform', `translate(${labelXOffset}, ${labelYOffset})`);
|
|
||||||
const { cssStyles } = node;
|
const { cssStyles } = 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);
|
||||||
|
@@ -85,6 +85,9 @@ export function generateArcPoints(
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ICON_SIZE = 30;
|
||||||
|
const ICON_PADDING = 15;
|
||||||
|
|
||||||
export async function roundedRect<T extends SVGGraphicsElement>(
|
export async function roundedRect<T extends SVGGraphicsElement>(
|
||||||
parent: D3Selection<T>,
|
parent: D3Selection<T>,
|
||||||
node: Node
|
node: Node
|
||||||
@@ -99,9 +102,6 @@ export async function roundedRect<T extends SVGGraphicsElement>(
|
|||||||
let w = (node?.width ? node?.width : bbox.width) + labelPaddingX * 2;
|
let w = (node?.width ? node?.width : bbox.width) + labelPaddingX * 2;
|
||||||
let h = (node?.height ? node?.height : bbox.height) + labelPaddingY * 2;
|
let h = (node?.height ? node?.height : bbox.height) + labelPaddingY * 2;
|
||||||
|
|
||||||
const ICON_SIZE = 30;
|
|
||||||
const ICON_PADDING = 15;
|
|
||||||
|
|
||||||
let labelXOffset = -bbox.width / 2;
|
let labelXOffset = -bbox.width / 2;
|
||||||
|
|
||||||
if (node.icon) {
|
if (node.icon) {
|
||||||
@@ -114,10 +114,9 @@ export async function roundedRect<T extends SVGGraphicsElement>(
|
|||||||
|
|
||||||
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
|
const availableTextSpace = w - ICON_SIZE - ICON_PADDING * 2;
|
||||||
labelXOffset = -w / 2 + ICON_SIZE + ICON_PADDING + availableTextSpace / 2 - bbox.width / 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 radius = node.radius || 5;
|
||||||
const taper = node.taper || 5; // Taper width for the rounded corners
|
const taper = node.taper || 5; // Taper width for the rounded corners
|
||||||
const { cssStyles } = node;
|
const { cssStyles } = node;
|
||||||
|
Reference in New Issue
Block a user