diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js index ce3c890fe..45e47b8a5 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js @@ -46,6 +46,7 @@ import { lightningBolt } from './shapes/lightningBolt.js'; import { filledCircle } from './shapes/filledCircle.js'; import { multiWaveEdgedRectangle } from './shapes/multiWaveEdgedRectangle.js'; import { windowPane } from './shapes/windowPane.js'; +import { linedWaveEdgedRect } from './shapes/linedWaveEdgedRect.js'; const shapes = { state, @@ -95,6 +96,7 @@ const shapes = { filledCircle, multiWaveEdgedRectangle, windowPane, + linedWaveEdgedRect, }; const nodeElems = new Map(); diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/linedWaveEdgedRect.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/linedWaveEdgedRect.ts new file mode 100644 index 000000000..2b8110e38 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/linedWaveEdgedRect.ts @@ -0,0 +1,87 @@ +import { + labelHelper, + updateNodeBounds, + getNodeClasses, + generateFullSineWavePoints, + createPathFromPoints, +} 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 const linedWaveEdgedRect = 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 = 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 { cssStyles } = node; + + // @ts-ignore - rough is not typed + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, {}); + + if (node.look !== 'handDrawn') { + options.roughness = 0; + options.fillStyle = 'solid'; + } + + const points = [ + { x: -w / 2 - (w / 2) * 0.1, y: finalH / 2 }, + ...generateFullSineWavePoints( + -w / 2 - (w / 2) * 0.1, + finalH / 2, + w / 2 + (w / 2) * 0.1, + finalH / 2, + waveAmplitude, + 0.8 + ), + { x: w / 2 + (w / 2) * 0.1, y: -finalH / 2 }, + { x: -w / 2 - (w / 2) * 0.1, y: -finalH / 2 }, + ]; + + const x = -w / 2; + const y = -finalH / 2; + + const innerPathPoints = [ + { y: y * 1.2, x: x }, + { y: -y * 0.9, x: x }, + ]; + + const waveEdgeRectPath = createPathFromPoints(points); + const waveEdgeRectNode = rc.path(waveEdgeRectPath, options); + + const innerSecondPath = createPathFromPoints(innerPathPoints); + const innerSecondNode = rc.path(innerSecondPath, options); + + const waveEdgeRect = shapeSvg + .insert(() => innerSecondNode, ':first-child') + .insert(() => waveEdgeRectNode, ':first-child'); + + waveEdgeRect.attr('class', 'basic label-container'); + + if (cssStyles && node.look !== 'handDrawn') { + waveEdgeRect.selectAll('path').attr('style', cssStyles); + } + + if (nodeStyles && node.look !== 'handDrawn') { + waveEdgeRect.selectAll('path').attr('style', nodeStyles); + } + + waveEdgeRect.attr('transform', `translate(0,${-waveAmplitude / 2})`); + label.attr( + 'transform', + `translate(${-w / 2 + (w / 2) * 0.05 + (node.padding ?? 0)},${-h / 2 + (node.padding ?? 0) - waveAmplitude / 2})` + ); + + updateNodeBounds(node, waveEdgeRect); + node.intersect = function (point) { + const pos = intersect.polygon(node, points, point); + return pos; + }; + + return shapeSvg; +};