From 4f6586873fb0563c4a3cba81604b1cbb0888709e Mon Sep 17 00:00:00 2001 From: Ashish Jain Date: Mon, 27 May 2024 15:17:59 +0200 Subject: [PATCH] wip: Broke out flowchar shapes for common rendering --- cypress/platform/flowchart-refactor.html | 775 ++++++++++++++++++ cypress/platform/flowchart-sate.html | 183 +++++ .../mermaid/src/diagrams/flowchart/flowDb.ts | 4 +- .../flowchart/flowRenderer-v3-unified.ts | 2 +- .../rendering-elements/nodes.js | 37 +- .../rendering-elements/shapes/circle.ts | 58 ++ .../rendering-elements/shapes/cylinder.ts | 116 +++ .../rendering-elements/shapes/doubleCircle.ts | 81 ++ .../rendering-elements/shapes/drawRect.ts | 11 +- .../rendering-elements/shapes/hexagon.ts | 105 +++ .../shapes/insertPolygonShape.ts | 25 + .../shapes/invertedTrapezoid.ts | 96 +++ .../rendering-elements/shapes/leanLeft.ts | 96 +++ .../rendering-elements/shapes/leanRight.ts | 96 +++ .../rendering-elements/shapes/question.ts | 90 ++ .../shapes/rectLeftInvArrow.ts | 94 +++ .../rendering-elements/shapes/stadium.ts | 111 +++ .../rendering-elements/shapes/subroutine.ts | 97 +++ .../rendering-elements/shapes/trapezoid.ts | 96 +++ .../mermaid/src/rendering-util/types.d.ts | 4 + 20 files changed, 2173 insertions(+), 4 deletions(-) create mode 100644 cypress/platform/flowchart-refactor.html create mode 100644 cypress/platform/flowchart-sate.html create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/circle.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/doubleCircle.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/insertPolygonShape.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/invertedTrapezoid.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/leanLeft.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/leanRight.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/rectLeftInvArrow.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts diff --git a/cypress/platform/flowchart-refactor.html b/cypress/platform/flowchart-refactor.html new file mode 100644 index 000000000..7eb2e545a --- /dev/null +++ b/cypress/platform/flowchart-refactor.html @@ -0,0 +1,775 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DagreDagre with roughELKELK with rough
+ +
+
+
+      flowchart LR
+    id1([This is the text in the box])
+
+  
+
+
+
+flowchart LR
+    id1([This is the text in the box])
+
+      
+
+
+%%{init: {"look": "handdrawn"} }%%
+flowchart LR
+    id1([This is the text in the box])
+
+
+      
+
+
+%%{init: {"handdrawn": false, "layout": "elk"} }%%
+flowchart LR
+    id1([This is the text in the box])
+
+
+      
+
+
+%%{init: {"look": "handdrawn", "layout": "elk"} }%%
+flowchart LR
+    id1([This is the text in the box])
+
+
+      
+
+ +
+
+
+      flowchart LR
+    id1[[This is the text in the box]]
+    
+
+
+
+flowchart LR
+    id1[[This is the text in the box]]
+      
+
+
+%%{init: {"look": "handdrawn"} }%%
+flowchart LR
+    id1[[This is the text in the box]]
+      
+
+
+%%{init: {"handdrawn": false, "layout": "elk"} }%%
+flowchart LR
+    id1[[This is the text in the box]]
+      
+
+
+%%{init: {"look": "handdrawn", "layout": "elk"} }%%
+flowchart LR
+    id1[[This is the text in the box]]
+      
+
+ +
+
+
+              flowchart LR
+    id1[(Database)]
+    
+
+
+
+          flowchart LR
+    id1[(Database)]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1[(Database)]
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1[(Database)]
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1[(Database)]
+      
+
+ +
+
+
+              flowchart LR
+    id1((This is the text in the circle))
+    
+
+
+
+          flowchart LR
+    id1((This is the text in the circle))
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1((This is the text in the circle))
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1((This is the text in the circle))
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1((This is the text in the circle))
+      
+
+ +
+
+
+              flowchart TD
+    id1(((This is the text in the circle)))
+    
+
+
+
+          flowchart TD
+    id1(((This is the text in the circle)))
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart TD
+    id1(((This is the text in the circle)))
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart TD
+    id1(((This is the text in the circle)))
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart TD
+    id1(((This is the text in the circle)))
+      
+
+ +
+
+
+              flowchart LR
+    id1>This is the text in the box]
+    
+
+
+
+          flowchart LR
+    id1>This is the text in the box]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1>This is the text in the box]  
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1>This is the text in the box]  
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1>This is the text in the box]
+      
+
+ +
+
+
+              flowchart LR
+    id1{This is the text in the box}
+    
+
+
+
+          flowchart LR
+    id1{This is the text in the box}
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1{This is the text in the box}
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1{This is the text in the box}
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1{This is the text in the box}
+      
+
+ +
+
+
+              flowchart LR
+    id1{{This is the text in the box}}
+    
+
+
+
+          flowchart LR
+    id1{{This is the text in the box}}
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1{{This is the text in the box}}
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1{{This is the text in the box}}
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1{{This is the text in the box}}
+      
+
+ +
+
+
+              flowchart TD
+    id1[/This is the text in the box/]
+    
+
+
+
+          flowchart TD
+    id1[/This is the text in the box/]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart TD
+    id1[/This is the text in the box/]  
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart TD
+    id1[/This is the text in the box/] 
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart TD
+    id1[/This is the text in the box/]
+      
+
+ +
+
+
+              flowchart TD
+    id1[\This is the text in the box\]
+    
+
+
+
+          flowchart TD
+    id1[\This is the text in the box\]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart TD
+    id1[\This is the text in the box\]
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart TD
+    id1[\This is the text in the box\]
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart TD
+    id1[\This is the text in the box\]
+
+      
+
+ +
+
+
+              flowchart TD
+    A[/Christmas\]
+    
+
+
+
+          flowchart TD
+    A[/Christmas\]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart TD
+    A[/Christmas\]
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart TD
+    A[/Christmas\]
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart TD
+    A[/Christmas\]
+      
+
+ +
+
+
+              flowchart TD
+    A[\Christmas/]
+    
+
+
+
+          flowchart TD
+    A[\Christmas/]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart TD
+    A[\Christmas/]
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart TD
+    A[\Christmas/]
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart TD
+    A[\Christmas/]  
+      
+
+ +
+
+
+              flowchart LR
+    id1(This is the text in the box)
+    
+
+
+
+          flowchart LR
+    id1(This is the text in the box)
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1(This is the text in the box)
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1(This is the text in the box)
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1(This is the text in the box) 
+      
+
+ +
+
+
+              flowchart LR
+    id1[This is the text in the box]
+    
+
+
+
+          flowchart LR
+    id1[This is the text in the box]
+      
+
+
+          %%{init: {"look": "handdrawn"} }%%
+          flowchart LR
+    id1[This is the text in the box]
+      
+
+
+          %%{init: {"handdrawn": false, "layout": "elk"} }%%
+          flowchart LR
+    id1[This is the text in the box]
+      
+
+
+          %%{init: {"look": "handdrawn", "layout": "elk"} }%%
+          flowchart LR
+    id1[This is the text in the box]
+      
+
+ + + + + + \ No newline at end of file diff --git a/cypress/platform/flowchart-sate.html b/cypress/platform/flowchart-sate.html new file mode 100644 index 000000000..83e46363a --- /dev/null +++ b/cypress/platform/flowchart-sate.html @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DagreDagre with roughELKELK with rough
+ +
+
+
+      flowchart LR
+    id1([This is the text in the box])
+
+  
+
+
+
+%%{init: {"look": "handdrawn"} }%%
+stateDiagram-v2 
+    stateA
+
+      
+
+
+%%{init: {"look": "handdrawn"} }%%
+flowchart LR
+    id1([This is the text in the box])
+
+
+      
+
+ + + + + + \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index ef4ae0a76..605e8d5ea 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -774,6 +774,7 @@ export const getData = () => { // extract(getRootDocV2()); // const diagramStates = getStates(); + const useRough = config.look === 'handdrawn'; const n = getVertices(); n.forEach((vertex) => { const node: Node = { @@ -788,11 +789,12 @@ export const getData = () => { domId: vertex.domId, type: undefined, isGroup: false, + useRough, }; nodes.push(node); }); - const useRough = config.look === 'handdrawn'; + //const useRough = config.look === 'handdrawn'; return { nodes, edges, other: {}, config }; }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts index bf2d92176..01bf79fcb 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v3-unified.ts @@ -3,7 +3,7 @@ import type { DiagramStyleClassDef } from '../../diagram-api/types.js'; import type { LayoutData, LayoutMethod } from '../../rendering-util/types.js'; import { getConfig } from '../../diagram-api/diagramAPI.js'; import { render } from '../../rendering-util/render.js'; -import { getDiagramElements } from '../../rendering-util/inserElementsForSize.js'; +import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js'; import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js'; import { getDirection } from './flowDb.js'; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js index 52219ac08..d03fd257a 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js @@ -7,8 +7,19 @@ import { stateEnd } from './shapes/stateEnd.ts'; import { forkJoin } from './shapes/forkJoin.ts'; import { choice } from './shapes/choice.ts'; import { note } from './shapes/note.ts'; +import { stadium } from './shapes/stadium.js'; import { getConfig } from '$root/diagram-api/diagramAPI.js'; - +import { subroutine } from './shapes/subroutine.js'; +import { cylinder } from './shapes/cylinder.js'; +import { circle } from './shapes/circle.js'; +import { doublecircle } from './shapes/doubleCircle.js'; +import { rect_left_inv_arrow } from './shapes/rectLeftInvArrow.js'; +import { question } from './shapes/question.js'; +import { hexagon } from './shapes/hexagon.js'; +import { lean_right } from './shapes/leanRight.js'; +import { lean_left } from './shapes/leanLeft.js'; +import { trapezoid } from './shapes/trapezoid.js'; +import { inv_trapezoid } from './shapes/invertedTrapezoid.js'; const formatClass = (str) => { if (str) { return ' ' + str; @@ -26,6 +37,18 @@ const shapes = { note, roundedRect, squareRect, + stadium, + subroutine, + cylinder, + circle, + doublecircle, + odd: rect_left_inv_arrow, + diamond: question, + hexagon, + lean_right, + lean_left, + trapezoid, + inv_trapezoid, }; let nodeElems = {}; @@ -34,6 +57,18 @@ export const insertNode = async (elem, node, dir) => { let newEl; let el; + if (node) { + console.log('BLA: rect node', JSON.stringify(node)); + } + //special check for rect shape (with or without rounded corners) + if (node.shape === 'rect') { + if (node.rx && node.ry) { + node.shape = 'roundedRect'; + } else { + node.shape = 'squareRect'; + } + } + // Add link when appropriate if (node.link) { let target; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/circle.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/circle.ts new file mode 100644 index 000000000..0574ab6cb --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/circle.ts @@ -0,0 +1,58 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; + +export const circle = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const radius = bbox.width / 2 + halfPadding; + let circleElem; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Circle: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.9, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const roughNode = rc.circle(0, 0, radius * 2, options); + + circleElem = shapeSvg.insert(() => roughNode, ':first-child'); + circleElem.attr('class', 'basic label-container').attr('style', cssStyles); + } else { + circleElem = shapeSvg + .insert('circle', ':first-child') + .attr('class', 'basic label-container') + .attr('style', cssStyles) + .attr('r', radius) + .attr('cx', 0) + .attr('cy', 0); + } + + updateNodeBounds(node, circleElem); + + node.intersect = function (point) { + log.info('Circle intersect', node, radius, point); + return intersect.circle(node, radius, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts new file mode 100644 index 000000000..cd12792c6 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/cylinder.ts @@ -0,0 +1,116 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; + +/** + * Creates an SVG path for a cylindrical shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the cylinder. + * @param {number} height - The height of the cylinder. + * @param {number} rx - The x-radius of the cylinder's ends. + * @param {number} ry - The y-radius of the cylinder's ends. + * @returns {string} The path data for the cylindrical shape. + */ +export const createCylinderPathD = ( + x: number, + y: number, + width: number, + height: number, + rx: number, + ry: number +): string => { + return [ + `M${x},${y + ry}`, + `a${rx},${ry} 0,0,0 ${width},0`, + `a${rx},${ry} 0,0,0 ${-width},0`, + `l0,${height}`, + `a${rx},${ry} 0,0,0 ${width},0`, + `l0,${-height}`, + ].join(' '); +}; + +export const cylinder = async (parent: SVGAElement, node: Node) => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, // + ' ' + node.class, + true + ); + + const w = bbox.width + node.padding; + const rx = w / 2; + const ry = rx / (2.5 + w / 50); + const h = bbox.height + ry + node.padding; + + let cylinder: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Cylinder: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createCylinderPathD(0, 0, w, h, rx, ry); + const roughNode = rc.path(pathData, options); + + cylinder = shapeSvg.insert(() => roughNode, ':first-child'); + cylinder.attr('class', 'basic label-container'); + if (cssStyles) { + cylinder.attr('style', cssStyles); + } + } else { + const pathData = createCylinderPathD(0, 0, w, h, rx, ry); + cylinder = shapeSvg + .insert('path', ':first-child') + .attr('d', pathData) + .attr('class', 'basic label-container') + .attr('style', cssStyles); + } + + cylinder.attr('label-offset-y', ry); + cylinder.attr('transform', `translate(${-w / 2}, ${-(h / 2 + ry)})`); + + updateNodeBounds(node, cylinder); + + node.intersect = function (point) { + const pos = intersect.rect(node, point); + const x = pos.x - (node.x ?? 0); + + if ( + rx != 0 && + (Math.abs(x) < (node.width ?? 0) / 2 || + (Math.abs(x) == (node.width ?? 0) / 2 && + Math.abs(pos.y - (node.y ?? 0)) > (node.height ?? 0) / 2 - ry)) + ) { + let y = ry * ry * (1 - (x * x) / (rx * rx)); + if (y != 0) { + y = Math.sqrt(y); + } + y = ry - y; + if (point.y - (node.y ?? 0) > 0) { + y = -y; + } + + pos.y += y; + } + + return pos; + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/doubleCircle.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/doubleCircle.ts new file mode 100644 index 000000000..49e4051c3 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/doubleCircle.ts @@ -0,0 +1,81 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +//import d3 from 'd3'; + +export const doublecircle = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + const gap = 5; + const outerRadius = bbox.width / 2 + halfPadding + gap; + const innerRadius = bbox.width / 2 + halfPadding; + + let circleGroup; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('DoubleCircle: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const outerOptions = userNodeOverrides(node, { + roughness: 0.9, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + + const innerOptions = { ...outerOptions, fill: mainBkg }; + const outerRoughNode = rc.circle(0, 0, outerRadius * 2, outerOptions); + const innerRoughNode = rc.circle(0, 0, innerRadius * 2, innerOptions); + + circleGroup = shapeSvg.insert('g', ':first-child'); + circleGroup.attr('class', node.cssClasses).attr('style', cssStyles); + // d3.select(outerRoughNode).attr('class', 'outer-circle').attr('style', cssStyles); + // d3.select(innerRoughNode).attr('class', 'inner-circle').attr('style', cssStyles); + + circleGroup.node()?.appendChild(outerRoughNode); + circleGroup.node()?.appendChild(innerRoughNode); + } else { + circleGroup = shapeSvg.insert('g', ':first-child'); + const outerCircle = circleGroup.insert('circle', ':first-child'); + const innerCircle = circleGroup.insert('circle', ':first-child'); + + circleGroup.attr('class', 'basic label-container').attr('style', cssStyles); + + outerCircle + .attr('class', 'outer-circle') + .attr('style', cssStyles) + .attr('r', outerRadius) + .attr('cx', 0) + .attr('cy', 0); + + innerCircle + .attr('class', 'inner-circle') + .attr('style', cssStyles) + .attr('r', innerRadius) + .attr('cx', 0) + .attr('cy', 0); + } + + updateNodeBounds(node, circleGroup); + + node.intersect = function (point) { + log.info('DoubleCircle intersect', node, outerRadius, point); + return intersect.circle(node, outerRadius, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/drawRect.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/drawRect.ts index ac1b690c1..70b3fa6a1 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/drawRect.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/drawRect.ts @@ -68,7 +68,14 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt const y = -bbox.height / 2 - halfPadding; let rect; - const { rx, ry, cssStyles, useRough } = node; + let { rx, ry, cssStyles, useRough } = node; + + //use options rx, ry overrides if present + if (options && options.rx && options.ry) { + rx = options.rx; + ry = options.ry; + } + if (useRough) { // @ts-ignore TODO: Fix rough typings const rc = rough.svg(shapeSvg); @@ -81,6 +88,8 @@ export const drawRect = async (parent: SVGAElement, node: Node, options: RectOpt stroke: nodeBorder, seed: handdrawnSeed, }); + + console.log('rect options: ', options); const roughNode = rx || ry ? rc.path(createRoundedRectPathD(x, y, totalWidth, totalHeight, rx || 0), options) diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts new file mode 100644 index 000000000..e9070d8d3 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/hexagon.ts @@ -0,0 +1,105 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; + +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a hexagon shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the hexagon. + * @param {number} height - The height of the hexagon. + * @param {number} m - The margin size for the hexagon. + * @returns {string} The path data for the hexagon shape. + */ +export const createHexagonPathD = ( + x: number, + y: number, + width: number, + height: number, + m: number +): string => { + return [ + `M${x + m},${y}`, + `L${x + width - m},${y}`, + `L${x + width},${y - height / 2}`, + `L${x + width - m},${y - height}`, + `L${x + m},${y - height}`, + `L${x},${y - height / 2}`, + 'Z', + ].join(' '); +}; + +export const hexagon = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const f = 4; + const h = bbox.height + node.padding; + const m = h / f; + const w = bbox.width + 2 * m + node.padding; + const points = [ + { 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 }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Hexagon: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createHexagonPathD(0, 0, w, h, m); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + + node.width = w; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/insertPolygonShape.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/insertPolygonShape.ts new file mode 100644 index 000000000..6d121b3e1 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/insertPolygonShape.ts @@ -0,0 +1,25 @@ +/** + * @param parent + * @param w + * @param h + * @param points + */ +export function insertPolygonShape( + parent: any, + w: number, + h: number, + points: { x: number; y: number }[] +) { + return parent + .insert('polygon', ':first-child') + .attr( + 'points', + points + .map(function (d) { + return d.x + ',' + d.y; + }) + .join(' ') + ) + .attr('class', 'label-container') + .attr('transform', 'translate(' + -w / 2 + ',' + h / 2 + ')'); +} diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/invertedTrapezoid.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/invertedTrapezoid.ts new file mode 100644 index 000000000..809d6dd07 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/invertedTrapezoid.ts @@ -0,0 +1,96 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for an inverted trapezoid shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @returns {string} The path data for the inverted trapezoid shape. + */ +export const createInvertedTrapezoidPathD = ( + x: number, + y: number, + width: number, + height: number +): string => { + return [ + `M${x + height / 6},${y}`, + `L${x + width - height / 6},${y}`, + `L${x + width + (2 * height) / 6},${y - height}`, + `L${x - (2 * height) / 6},${y - height}`, + 'Z', + ].join(' '); +}; + +export const inv_trapezoid = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const points = [ + { x: h / 6, y: 0 }, + { x: w - h / 6, y: 0 }, + { x: w + (2 * h) / 6, y: -h }, + { x: (-2 * h) / 6, y: -h }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Inverted Trapezoid: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createInvertedTrapezoidPathD(0, 0, w, h); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + + node.width = w; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanLeft.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanLeft.ts new file mode 100644 index 000000000..0cfaf7c01 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanLeft.ts @@ -0,0 +1,96 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a lean left shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @returns {string} The path data for the lean left shape. + */ +export const createLeanLeftPathD = ( + x: number, + y: number, + width: number, + height: number +): string => { + return [ + `M${x + (2 * height) / 6},${y}`, + `L${x + width + height / 6},${y}`, + `L${x + width - (2 * height) / 6},${y - height}`, + `L${x - height / 6},${y - height}`, + 'Z', + ].join(' '); +}; + +export const lean_left = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const points = [ + { x: (2 * h) / 6, y: 0 }, + { x: w + h / 6, y: 0 }, + { x: w - (2 * h) / 6, y: -h }, + { x: -h / 6, y: -h }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Lean Left: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createLeanLeftPathD(0, 0, w, h); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + + node.width = w; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanRight.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanRight.ts new file mode 100644 index 000000000..711be91f7 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/leanRight.ts @@ -0,0 +1,96 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a lean right shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @returns {string} The path data for the lean right shape. + */ +export const createLeanRightPathD = ( + x: number, + y: number, + width: number, + height: number +): string => { + return [ + `M${x - (2 * height) / 6},${y}`, + `L${x + width - height / 6},${y}`, + `L${x + width + (2 * height) / 6},${y - height}`, + `L${x + height / 6},${y - height}`, + 'Z', + ].join(' '); +}; + +export const lean_right = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const points = [ + { x: (-2 * h) / 6, y: 0 }, + { x: w - h / 6, y: 0 }, + { x: w + (2 * h) / 6, y: -h }, + { x: h / 6, y: -h }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Lean Right: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createLeanRightPathD(0, 0, w, h); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + + node.width = w; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts new file mode 100644 index 000000000..fd68766e5 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/question.ts @@ -0,0 +1,90 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a decision box shape (question shape). + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} size - The size of the shape. + * @returns {string} The path data for the decision box shape. + */ +export const createDecisionBoxPathD = (x: number, y: number, size: number): string => { + return [ + `M${x + size / 2},${y}`, + `L${x + size},${y - size / 2}`, + `L${x + size / 2},${y - size}`, + `L${x},${y - size / 2}`, + 'Z', + ].join(' '); +}; + +export const question = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const s = w + h; + + const points = [ + { x: s / 2, y: 0 }, + { x: s, y: -s / 2 }, + { x: s / 2, y: -s }, + { x: 0, y: -s / 2 }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('DecisionBox: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createDecisionBoxPathD(0, 0, s); + const roughNode = rc.path(pathData, options); + + polygon = shapeSvg + .insert(() => roughNode, ':first-child') + .attr('transform', `translate(${-s / 2}, ${s / 2})`); + + if (cssStyles) { + polygon.attr('style', cssStyles); + } + } else { + polygon = insertPolygonShape(shapeSvg, s, s, points); + } + + if (cssStyles) { + polygon.attr('style', cssStyles); + } + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + log.warn('Intersect called'); + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/rectLeftInvArrow.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/rectLeftInvArrow.ts new file mode 100644 index 000000000..afcc1fb7d --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/rectLeftInvArrow.ts @@ -0,0 +1,94 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a special polygon shape with a left-inverted arrow. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @returns {string} The path data for the special polygon shape. + */ +export const createPolygonPathD = (x: number, y: number, width: number, height: number): string => { + return [ + `M${x - height / 2},${y}`, + `L${x + width},${y}`, + `L${x + width},${y - height}`, + `L${x - height / 2},${y - height}`, + `L${x},${y - height / 2}`, + 'Z', + ].join(' '); +}; + +export const rect_left_inv_arrow = async ( + parent: SVGAElement, + node: Node +): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const points = [ + { x: -h / 2, y: 0 }, + { x: w, y: 0 }, + { x: w, y: -h }, + { x: -h / 2, y: -h }, + { x: 0, y: -h / 2 }, + ]; + + let polygon; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Polygon: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createPolygonPathD(0, 0, w, h); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + node.width = w + h; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts new file mode 100644 index 000000000..423f2392a --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stadium.ts @@ -0,0 +1,111 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { createRoundedRectPathD } from './roundedRectPath.js'; + +export const createStadiumPathD = ( + x: number, + y: number, + totalWidth: number, + totalHeight: number +) => { + const radius = totalHeight / 2; + return [ + 'M', + x + radius, + y, // Move to the start of the top-left arc + 'H', + x + totalWidth - radius, // Draw horizontal line to the start of the top-right arc + 'A', + radius, + radius, + 0, + 0, + 1, + x + totalWidth, + y + radius, // Draw top-right arc + 'H', + x, // Draw horizontal line to the start of the bottom-right arc + 'A', + radius, + radius, + 0, + 0, + 1, + x + totalWidth - radius, + y + totalHeight, // Draw bottom-right arc + 'H', + x + radius, // Draw horizontal line to the start of the bottom-left arc + 'A', + radius, + radius, + 0, + 0, + 1, + x, + y + radius, // Draw bottom-left arc + 'Z', // Close the path + ].join(' '); +}; + +export const stadium = async (parent: SVGAElement, node: Node) => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, // + ' ' + node.class, + true + ); + + const h = bbox.height + node.padding; + const w = bbox.width + h / 4 + node.padding; + + let rect; + const { cssStyles, useRough } = node; + if (useRough) { + console.log('Stadium:Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', // solid fill + fillWeight: 3.5, + stroke: nodeBorder, + seed: handdrawnSeed, + // strokeWidth: 1, + }); + + console.log('Stadium options: ', options); + const pathData = createRoundedRectPathD(-w / 2, -h / 2, w, h, h / 3); + const roughNode = rc.path(pathData, options); + + rect = shapeSvg.insert(() => roughNode, ':first-child'); + rect.attr('class', 'basic label-container').attr('style', cssStyles); + } else { + rect = shapeSvg.insert('rect', ':first-child'); + + rect + .attr('class', 'basic label-container') + .attr('style', cssStyles) + .attr('rx', h / 2) + .attr('ry', h / 2) + .attr('x', -w / 2) + .attr('y', -h / 2) + .attr('width', w) + .attr('height', h); + } + + updateNodeBounds(node, rect); + + node.intersect = function (point) { + return intersect.rect(node, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts new file mode 100644 index 000000000..eacd53e27 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/subroutine.ts @@ -0,0 +1,97 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +export const createSubroutinePathD = ( + x: number, + y: number, + width: number, + height: number +): string => { + const offset = 8; + return [ + `M${x - offset},${y}`, + `H${x + width + offset}`, + `V${y + height}`, + `H${x - offset}`, + `V${y}`, + 'M', + x, + y, + 'H', + x + width, + 'V', + y + height, + 'H', + x, + 'Z', + ].join(' '); +}; + +export const subroutine = async (parent: SVGAElement, node: Node) => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, // + ' ' + node.class, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + + let rect; + const { cssStyles, useRough } = node; + if (useRough) { + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + stroke: nodeBorder, + seed: handdrawnSeed, + strokeWidth: 1, + }); + const pathData = createSubroutinePathD(-w / 2, -h / 2, w, h); + const roughNode = rc.path(pathData, options); + + rect = shapeSvg.insert(() => roughNode, ':first-child'); + rect.attr('class', 'basic label-container').attr('style', cssStyles); + } + + const points = [ + { x: 0, y: 0 }, + { x: w, y: 0 }, + { x: w, y: -h }, + { x: 0, y: -h }, + { x: 0, y: 0 }, + { x: -8, y: 0 }, + { x: w + 8, y: 0 }, + { x: w + 8, y: -h }, + { x: -8, y: -h }, + { x: -8, y: 0 }, + ]; + + const el = insertPolygonShape(shapeSvg, w, h, points); + if (cssStyles) { + el.attr('style', cssStyles); + } + + updateNodeBounds(node, el); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; + +export default subroutine; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts new file mode 100644 index 000000000..6a26f83e1 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/trapezoid.ts @@ -0,0 +1,96 @@ +import { log } from '$root/logger.js'; +import { labelHelper, updateNodeBounds } from './util.js'; +import intersect from '../intersect/index.js'; +import { getConfig } from '$root/diagram-api/diagramAPI.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; +import rough from 'roughjs'; +import { insertPolygonShape } from './insertPolygonShape.js'; + +/** + * Creates an SVG path for a trapezoid shape. + * @param {number} x - The x coordinate of the top-left corner. + * @param {number} y - The y coordinate of the top-left corner. + * @param {number} width - The width of the shape. + * @param {number} height - The height of the shape. + * @returns {string} The path data for the trapezoid shape. + */ +export const createTrapezoidPathD = ( + x: number, + y: number, + width: number, + height: number +): string => { + return [ + `M${x - (2 * height) / 6},${y}`, + `L${x + width + (2 * height) / 6},${y}`, + `L${x + width - height / 6},${y - height}`, + `L${x + height / 6},${y - height}`, + 'Z', + ].join(' '); +}; + +export const trapezoid = async (parent: SVGAElement, node: Node): Promise => { + const { themeVariables, handdrawnSeed } = getConfig(); + const { nodeBorder, mainBkg } = themeVariables; + + const { shapeSvg, bbox, halfPadding } = await labelHelper( + parent, + node, + 'node ' + node.cssClasses, + true + ); + + const w = bbox.width + node.padding; + const h = bbox.height + node.padding; + const points = [ + { x: (-2 * h) / 6, y: 0 }, + { x: w + (2 * h) / 6, y: 0 }, + { x: w - h / 6, y: -h }, + { x: h / 6, y: -h }, + ]; + + let polygon: d3.Selection; + const { cssStyles, useRough } = node; + + if (useRough) { + console.log('Trapezoid: Inside use useRough'); + const rc = rough.svg(shapeSvg); + const options = userNodeOverrides(node, { + roughness: 0.7, + fill: mainBkg, + fillStyle: 'hachure', + fillWeight: 1.5, + //stroke: nodeBorder, + seed: handdrawnSeed, + //strokeWidth: 1, + }); + const pathData = createTrapezoidPathD(0, 0, w, h); + const roughNode = rc.path(pathData, options); + + 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 (cssStyles) { + polygon.attr('style', cssStyles); + } + + node.width = w; + node.height = h; + + updateNodeBounds(node, polygon); + + node.intersect = function (point) { + return intersect.polygon(node, points, point); + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/types.d.ts b/packages/mermaid/src/rendering-util/types.d.ts index 997d93d62..4b8750725 100644 --- a/packages/mermaid/src/rendering-util/types.d.ts +++ b/packages/mermaid/src/rendering-util/types.d.ts @@ -57,6 +57,10 @@ interface Node { borderStyle?: string; borderWidth?: number; labelTextColor?: string; + + // Flowchart specific properties + x?: number; + y?: number; } // Common properties for any edge in the system