Compare commits

..

6 Commits

11 changed files with 65 additions and 222 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
revert: restore original hexagon and roundedRect implementations

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
chore: restore original hexagon and roundedRect implementations

View File

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

View File

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

View File

@@ -85,17 +85,6 @@ 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.
*
@@ -219,7 +208,6 @@ 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;
@@ -279,7 +267,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 = this.sanitizeNodeLabelType(linkTextObj.type);
edge.labelType = linkTextObj.type;
}
if (type !== undefined) {
@@ -714,7 +702,7 @@ You have to call mermaid.initialize.`
title: title.trim(),
classes: [],
dir,
labelType: this.sanitizeNodeLabelType(_title?.type),
labelType: _title.type,
};
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
@@ -1024,7 +1012,6 @@ You have to call mermaid.initialize.`
const baseNode = {
id: vertex.id,
label: vertex.text,
labelType: vertex.labelType,
labelStyle: '',
parentId,
padding: config.flowchart?.padding || 8,
@@ -1101,7 +1088,6 @@ 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),
@@ -1133,7 +1119,6 @@ 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: 'markdown' | 'string' | 'text';
labelType: 'text';
link?: string;
linkTarget?: string;
props?: any;
@@ -62,7 +62,7 @@ export interface FlowEdge {
style?: string[];
length?: number;
text: string;
labelType: 'markdown' | 'string' | 'text';
labelType: 'text';
classes: string[];
id?: string;
animation?: 'fast' | 'slow';

View File

@@ -1,8 +1,9 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import { insertPolygonShape } from './insertPolygonShape.js';
import type { D3Selection } from '../../../types.js';
export const createHexagonPathD = (
@@ -28,50 +29,42 @@ export async function hexagon<T extends SVGGraphicsElement>(parent: D3Selection<
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const h = bbox.height + (node.padding ?? 0);
const w = bbox.width + (node.padding ?? 0) * 2.5;
const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
let halfWidth = w / 2;
const m = halfWidth / 6; // Margin for label
halfWidth = halfWidth + m; // Adjusted half width for hexagon
const halfHeight = h / 2;
const fixedLength = halfHeight / 2;
const deducedWidth = halfWidth - fixedLength;
const f = 4;
const h = bbox.height + node.padding;
const m = h / f;
const w = bbox.width + 2 * m + node.padding;
const points = [
{ x: -deducedWidth, y: -halfHeight },
{ x: 0, y: -halfHeight },
{ x: deducedWidth, y: -halfHeight },
{ x: halfWidth, y: 0 },
{ x: deducedWidth, y: halfHeight },
{ x: 0, y: halfHeight },
{ x: -deducedWidth, y: halfHeight },
{ x: -halfWidth, y: 0 },
{ x: m, y: 0 },
{ x: w - m, y: 0 },
{ x: w, y: -h / 2 },
{ x: w - m, y: -h },
{ x: m, y: -h },
{ x: 0, y: -h / 2 },
];
const pathData = createPathFromPoints(points);
const shapeNode = rc.path(pathData, options);
let polygon: D3Selection<SVGGElement> | Awaited<ReturnType<typeof insertPolygonShape>>;
const { cssStyles } = node;
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
polygon.attr('class', 'basic label-container');
if (node.look === 'handDrawn') {
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
const pathData = createHexagonPathD(0, 0, w, h, m);
const roughNode = rc.path(pathData, options);
if (cssStyles && node.look !== 'handDrawn') {
polygon.selectChildren('path').attr('style', cssStyles);
polygon = shapeSvg
.insert(() => roughNode, ':first-child')
.attr('transform', `translate(${-w / 2}, ${h / 2})`);
if (cssStyles) {
polygon.attr('style', cssStyles);
}
} else {
polygon = insertPolygonShape(shapeSvg, w, h, points);
}
if (nodeStyles && node.look !== 'handDrawn') {
polygon.selectChildren('path').attr('style', nodeStyles);
if (nodeStyles) {
polygon.attr('style', nodeStyles);
}
node.width = w;

View File

@@ -1,161 +1,18 @@
import { labelHelper, updateNodeBounds, getNodeClasses, createPathFromPoints } from './util.js';
import intersect from '../intersect/index.js';
import type { Node } from '../../types.js';
import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js';
import rough from 'roughjs';
import type { Node, RectOptions } from '../../types.js';
import type { D3Selection } from '../../../types.js';
/**
* Generates evenly spaced points along an elliptical arc connecting two points.
*
* @param x1 - x-coordinate of the start point of the arc
* @param y1 - y-coordinate of the start point of the arc
* @param x2 - x-coordinate of the end point of the arc
* @param y2 - y-coordinate of the end point of the arc
* @param rx - horizontal radius of the ellipse
* @param ry - vertical radius of the ellipse
* @param clockwise - direction of the arc; true for clockwise, false for counterclockwise
* @returns Array of points `{ x, y }` along the elliptical arc
*
* @throws Error if the given radii are too small to draw an arc between the points
*/
export function generateArcPoints(
x1: number,
y1: number,
x2: number,
y2: number,
rx: number,
ry: number,
clockwise: boolean
) {
const numPoints = 20;
// Calculate midpoint
const midX = (x1 + x2) / 2;
const midY = (y1 + y2) / 2;
// Calculate the angle of the line connecting the points
const angle = Math.atan2(y2 - y1, x2 - x1);
// Calculate transformed coordinates for the ellipse
const dx = (x2 - x1) / 2;
const dy = (y2 - y1) / 2;
// Scale to unit circle
const transformedX = dx / rx;
const transformedY = dy / ry;
// Calculate the distance between points on the unit circle
const distance = Math.sqrt(transformedX ** 2 + transformedY ** 2);
// Check if the ellipse can be drawn with the given radii
if (distance > 1) {
throw new Error('The given radii are too small to create an arc between the points.');
}
// Calculate the distance from the midpoint to the center of the ellipse
const scaledCenterDistance = Math.sqrt(1 - distance ** 2);
// Calculate the center of the ellipse
const centerX = midX + scaledCenterDistance * ry * Math.sin(angle) * (clockwise ? -1 : 1);
const centerY = midY - scaledCenterDistance * rx * Math.cos(angle) * (clockwise ? -1 : 1);
// Calculate the start and end angles on the ellipse
const startAngle = Math.atan2((y1 - centerY) / ry, (x1 - centerX) / rx);
const endAngle = Math.atan2((y2 - centerY) / ry, (x2 - centerX) / rx);
// Adjust angles for clockwise/counterclockwise
let angleRange = endAngle - startAngle;
if (clockwise && angleRange < 0) {
angleRange += 2 * Math.PI;
}
if (!clockwise && angleRange > 0) {
angleRange -= 2 * Math.PI;
}
// Generate points
const points = [];
for (let i = 0; i < numPoints; i++) {
const t = i / (numPoints - 1);
const angle = startAngle + t * angleRange;
const x = centerX + rx * Math.cos(angle);
const y = centerY + ry * Math.sin(angle);
points.push({ x, y });
}
return points;
}
import { drawRect } from './drawRect.js';
export async function roundedRect<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: Node
) {
const { labelStyles, nodeStyles } = styles2String(node);
node.labelStyle = labelStyles;
const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node));
const options = {
rx: 5,
ry: 5,
classes: '',
labelPaddingX: (node?.padding || 0) * 1,
labelPaddingY: (node?.padding || 0) * 1,
} as RectOptions;
const labelPaddingX = node?.padding ?? 0;
const labelPaddingY = node?.padding ?? 0;
const w = (node?.width ? node?.width : bbox.width) + labelPaddingX * 2;
const h = (node?.height ? node?.height : bbox.height) + labelPaddingY * 2;
const radius = node.radius || 5;
const taper = node.taper || 5; // Taper width for the rounded corners
const { cssStyles } = node;
// @ts-expect-error -- Passing a D3.Selection seems to work for some reason
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {});
if (node.stroke) {
options.stroke = node.stroke;
}
if (node.look !== 'handDrawn') {
options.roughness = 0;
options.fillStyle = 'solid';
}
const points = [
// Top edge (left to right)
{ x: -w / 2 + taper, y: -h / 2 }, // Top-left corner start (1)
{ x: w / 2 - taper, y: -h / 2 }, // Top-right corner start (2)
...generateArcPoints(w / 2 - taper, -h / 2, w / 2, -h / 2 + taper, radius, radius, true), // Top-left arc (2 to 3)
// Right edge (top to bottom)
{ x: w / 2, y: -h / 2 + taper }, // Top-right taper point (3)
{ x: w / 2, y: h / 2 - taper }, // Bottom-right taper point (4)
...generateArcPoints(w / 2, h / 2 - taper, w / 2 - taper, h / 2, radius, radius, true), // Top-left arc (4 to 5)
// Bottom edge (right to left)
{ x: w / 2 - taper, y: h / 2 }, // Bottom-right corner start (5)
{ x: -w / 2 + taper, y: h / 2 }, // Bottom-left corner start (6)
...generateArcPoints(-w / 2 + taper, h / 2, -w / 2, h / 2 - taper, radius, radius, true), // Top-left arc (4 to 5)
// Left edge (bottom to top)
{ x: -w / 2, y: h / 2 - taper }, // Bottom-left taper point (7)
{ x: -w / 2, y: -h / 2 + taper }, // Top-left taper point (8)
...generateArcPoints(-w / 2, -h / 2 + taper, -w / 2 + taper, -h / 2, radius, radius, true), // Top-left arc (4 to 5)
];
const pathData = createPathFromPoints(points);
const shapeNode = rc.path(pathData, options);
const polygon = shapeSvg.insert(() => shapeNode, ':first-child');
polygon.attr('class', 'basic label-container outer-path');
if (cssStyles && node.look !== 'handDrawn') {
polygon.selectChildren('path').attr('style', cssStyles);
}
if (nodeStyles && node.look !== 'handDrawn') {
polygon.selectChildren('path').attr('style', nodeStyles);
}
updateNodeBounds(node, polygon);
node.intersect = function (point) {
const pos = intersect.polygon(node, points, point);
return pos;
};
return shapeSvg;
return drawRect(parent, node, options);
}

View File

@@ -13,7 +13,7 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
_classes?: string
) => {
let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.flowchart?.htmlLabels);
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig()?.htmlLabels);
if (!_classes) {
cssClasses = 'node default';
} else {
@@ -48,7 +48,6 @@ 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,7 +1,6 @@
export interface NodeMetaData {
shape?: string;
label?: string;
labelType?: string;
icon?: string;
form?: string;
pos?: 't' | 'b';