diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/multiWaveEdgedRectangle.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/multiWaveEdgedRectangle.ts index 9f3519375..c0c7f0b2a 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/multiWaveEdgedRectangle.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/multiWaveEdgedRectangle.ts @@ -1,91 +1,48 @@ -import { labelHelper, updateNodeBounds, getNodeClasses } from './util.js'; +import { + labelHelper, + updateNodeBounds, + getNodeClasses, + createPathFromPoints, + generateFullSineWavePoints, +} from './util.js'; import intersect from '../intersect/index.js'; import type { Node } from '$root/rendering-util/types.d.ts'; import rough from 'roughjs'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; -export function createMultiWaveEdgedRectanglePathD2(width: number, height: number) { - const offset = -5; - const offsetX = -offset; - const offsetY = offset; - - // Calculate control points (same for each layer) - const rightX = width; - const midX = width / 2; - const controlY1 = height * 0.8; - const controlY2 = height * 1.15; - const endY = height * 0.94; - - // Construct the path for the current layer with an offset in the opposite direction - const path = `M${offsetX} ${offsetY} - H${rightX + offsetX} - V${controlY1 + offsetY} - C${midX + offsetX} ${controlY1 + offsetY}, ${midX + offsetX} ${controlY2 + offsetY}, ${offsetX} ${endY + offsetY} - Z`; - return path; -} - -export function createMultiWaveEdgedRectanglePathD(width: number, height: number) { - // Calculate control points - const offset = 0; - const offsetX = offset; - const offsetY = offset; - - const rightX = width; - const midX = width / 2; - const controlY1 = height * 0.8; - const controlY2 = height * 1.15; - const endY = height * 0.94; - - const path = `M${offsetX} ${offsetY} - H${rightX + offsetX} - V${controlY1 + offsetY} - C${midX + offsetX} ${controlY1 + offsetY}, ${midX + offsetX} ${controlY2 + offsetY}, ${offsetX} ${endY + offsetY} - Z`; - return path; -} - -export function createMultiWaveEdgedRectanglePathD3(width: number, height: number) { - const offset = 5; - const offsetX = -offset; - const offsetY = offset; - - const rightX = width; - const midX = width / 2; - const controlY1 = height * 0.8; - const controlY2 = height * 1.15; - const endY = height * 0.94; - - const path = `M${offsetX} ${offsetY} - H${rightX + offsetX} - V${controlY1 + offsetY} - C${midX + offsetX} ${controlY1 + offsetY}, ${midX + offsetX} ${controlY2 + offsetY}, ${offsetX} ${endY + offsetY} - Z`; - - return path; -} - export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) => { const { labelStyles, nodeStyles } = styles2String(node); node.labelStyle = labelStyles; const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); - const w = bbox.width + node.padding; - const h = bbox.height + node.padding + 20; + const w = Math.max(bbox.width + (node.padding ?? 0) * 2, node?.width ?? 0); + const h = Math.max(bbox.height + (node.padding ?? 0) * 2, node?.height ?? 0); + const waveAmplitude = h / 4; + const finalH = h + waveAmplitude; + const x = -w / 2; + const y = -finalH / 2; + const rectOffset = 5; const { cssStyles } = node; - const rectOffset = 5; - const x = 0, - y = 0; + const wavePoints = generateFullSineWavePoints( + x - rectOffset, + y + finalH + rectOffset, + x + w - rectOffset, + y + finalH + rectOffset, + waveAmplitude, + 0.8 + ); - const points = [ + const lastWavePoint = wavePoints?.[wavePoints.length - 1]; + + const outerPathPoints = [ { x: x - rectOffset, y: y + rectOffset }, - { x: x - rectOffset, y: y + h + rectOffset }, - { x: x + w - rectOffset, y: y + h + rectOffset }, - { x: x + w - rectOffset, y: y + h }, - { x: x + w, y: y + h }, - { x: x + w, y: y + h - rectOffset }, - { x: x + w + rectOffset, y: y + h - rectOffset }, + { x: x - rectOffset, y: y + finalH + rectOffset }, + ...wavePoints, + { x: x + w - rectOffset, y: lastWavePoint.y - rectOffset }, + { x: x + w, y: lastWavePoint.y - rectOffset }, + { x: x + w, y: lastWavePoint.y - 2 * rectOffset }, + { x: x + w + rectOffset, y: lastWavePoint.y - 2 * rectOffset }, { x: x + w + rectOffset, y: y - rectOffset }, { x: x + rectOffset, y: y - rectOffset }, { x: x + rectOffset, y: y }, @@ -93,9 +50,14 @@ export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) = { x, y: y + rectOffset }, ]; - const pathData = createMultiWaveEdgedRectanglePathD(w, h); - const pathData2 = createMultiWaveEdgedRectanglePathD2(w, h); - const pathData3 = createMultiWaveEdgedRectanglePathD3(w, h); + const innerPathPoints = [ + { x, y: y + rectOffset }, + { x: x + w - rectOffset, y: y + rectOffset }, + { x: x + w - rectOffset, y: lastWavePoint.y - rectOffset }, + { x: x + w, y: lastWavePoint.y - rectOffset }, + { x: x + w, y }, + { x, y }, + ]; // @ts-ignore - rough is not typed const rc = rough.svg(shapeSvg); @@ -105,14 +67,14 @@ export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) = options.roughness = 0; options.fillStyle = 'solid'; } - const shapeNode = rc.path(pathData, options); - const shapeNode2 = rc.path(pathData2, options); - const shapeNode3 = rc.path(pathData3, options); - const shape = shapeSvg.insert('g', ':first-child'); - shape.insert(() => shapeNode3, ':first-child'); - shape.insert(() => shapeNode, ':first-child'); - shape.insert(() => shapeNode2, ':first-child'); + const outerPath = createPathFromPoints(outerPathPoints); + const outerNode = rc.path(outerPath, options); + const innerPath = createPathFromPoints(innerPathPoints); + const innerNode = rc.path(innerPath, options); + + const shape = shapeSvg.insert(() => outerNode, ':first-child'); + shape.insert(() => innerNode); shape.attr('class', 'basic label-container'); @@ -124,14 +86,17 @@ export const multiWaveEdgedRectangle = async (parent: SVGAElement, node: Node) = shape.attr('style', nodeStyles); } - shape.attr('transform', `translate(${-w / 2}, ${-h / 2})`); + shape.attr('transform', `translate(0,${-waveAmplitude / 2})`); + + label.attr( + 'transform', + `translate(${-(bbox.width / 2) - rectOffset - (bbox.x - (bbox.left ?? 0))}, ${-(bbox.height / 2) + rectOffset - waveAmplitude / 2 - 4 * (bbox.y - (bbox.top ?? 0))})` + ); updateNodeBounds(node, shape); - label.attr('transform', `translate(${-(w + 10) / 2}, ${-(h - rectOffset) * 0.3})`); - node.intersect = function (point) { - const pos = intersect.polygon(node, points, point); + const pos = intersect.polygon(node, outerPathPoints, point); return pos; };