diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index 283c316fb..84808e44f 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -3,6 +3,7 @@ name: Preview release on: pull_request: branches: [develop] + types: [opened, synchronize, labeled, ready_for_review] concurrency: group: ${{ github.workflow }}-${{ github.event.number }} diff --git a/cypress/integration/rendering/newShapes.spec.ts b/cypress/integration/rendering/newShapes.spec.ts index 0f539f09c..67d862ea4 100644 --- a/cypress/integration/rendering/newShapes.spec.ts +++ b/cypress/integration/rendering/newShapes.spec.ts @@ -60,7 +60,7 @@ looks.forEach((look) => { it(`without label`, () => { let flowchartCode = `flowchart ${direction}\n`; newShapesSet.forEach((newShape, index) => { - flowchartCode += ` n${index} --> n${index}${index}{ shape: ${newShape} }\n`; + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`; }); imgSnapshotTest(flowchartCode, { look }); }); diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index d570e60d1..39ad5dcda 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -44,7 +44,7 @@ export const render = async ( // Add the element to the DOM if (!node.isGroup) { - const childNodeEl = await insertNode(nodeEl, node, { config }); + const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir }); boundingBox = childNodeEl.node().getBBox(); child.domId = childNodeEl; child.width = boundingBox.width; diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index 76f3113df..86ae7e284 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -87,7 +87,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit // insertCluster(clusters, graph.node(v)); } else { log.info('Node - the non recursive path', v, node.id, node); - await insertNode(nodes, graph.node(v), { config: siteConfig }); + await insertNode(nodes, graph.node(v), { config: siteConfig, dir }); } } }) diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index 4a3a4da56..d8a27ad6f 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -146,7 +146,7 @@ export const addVertex = function ( } if (doc?.icon) { vertex.icon = doc?.icon; - if (!doc.label) { + if (!doc.label?.trim() && vertex.text === id) { vertex.text = ''; } } @@ -158,7 +158,7 @@ export const addVertex = function ( } if (doc?.img) { vertex.img = doc?.img; - if (!doc.label) { + if (!doc.label?.trim() && vertex.text === id) { vertex.text = ''; } } @@ -814,17 +814,17 @@ export const lex = { }; const getTypeFromVertex = (vertex: FlowVertex) => { - if (vertex?.img) { + if (vertex.img) { return 'imageSquare'; } - if (vertex?.icon) { - if (vertex?.form === 'circle') { + if (vertex.icon) { + if (vertex.form === 'circle') { return 'iconCircle'; } - if (vertex?.form === 'square') { + if (vertex.form === 'square') { return 'iconSquare'; } - if (vertex?.form === 'rounded') { + if (vertex.form === 'rounded') { return 'iconRounded'; } return 'icon'; diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js b/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js index ced98f91b..c117fe6c2 100644 --- a/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js +++ b/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js @@ -125,7 +125,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit // insertCluster(clusters, graph.node(v)); } else { log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir); - await insertNode(nodes, graph.node(v), { config: siteConfig }); + await insertNode(nodes, graph.node(v), { config: siteConfig, dir }); } } }) diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts index 2492412dc..c6a34e929 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/forkJoin.ts @@ -1,11 +1,15 @@ import { getNodeClasses, updateNodeBounds } from './util.js'; import intersect from '../intersect/index.js'; -import type { Node } from '../../types.js'; +import type { Node, RenderOptions } from '../../types.js'; import type { SVG } from '../../../diagram-api/types.js'; import rough from 'roughjs'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; -export const forkJoin = (parent: SVG, node: Node, dir: string) => { +export const forkJoin = ( + parent: SVG, + node: Node, + { dir, config: { state, themeVariables } }: RenderOptions +) => { const { nodeStyles } = styles2String(node); node.label = ''; const shapeSvg = parent @@ -27,7 +31,10 @@ export const forkJoin = (parent: SVG, node: Node, dir: string) => { // @ts-ignore TODO: Fix rough typings const rc = rough.svg(shapeSvg); - const options = userNodeOverrides(node, {}); + const options = userNodeOverrides(node, { + stroke: themeVariables.lineColor, + fill: themeVariables.lineColor, + }); if (node.look !== 'handDrawn') { options.roughness = 0; @@ -47,7 +54,11 @@ export const forkJoin = (parent: SVG, node: Node, dir: string) => { } updateNodeBounds(node, shape); - + const padding = state?.padding ?? 0; + if (node.width && node.height) { + node.width += padding / 2 || 0; + node.height += padding / 2 || 0; + } node.intersect = function (point) { return intersect.rect(node, point); }; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/icon.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/icon.ts index 051ae3119..d7ad58de9 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/icon.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/icon.ts @@ -19,18 +19,20 @@ export const icon = async ( const iconSize = Math.max(assetHeight, assetWidth); const defaultWidth = flowchart?.wrappingWidth; node.width = Math.max(iconSize, defaultWidth ?? 0); - const { nodeBorder } = themeVariables; - const { stylesMap } = compileStyles(node); const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'icon-shape default'); const topLabel = node.pos === 't'; const height = iconSize; - const width = Math.max(iconSize, bbox.width); + const width = iconSize; + const { nodeBorder } = themeVariables; + const { stylesMap } = compileStyles(node); const x = -width / 2; const y = -height / 2; + const labelPadding = node.label ? 8 : 0; + // @ts-ignore - rough is not typed const rc = rough.svg(shapeSvg); const options = userNodeOverrides(node, { stroke: 'none', fill: 'none' }); @@ -42,7 +44,17 @@ export const icon = async ( const iconNode = rc.rectangle(x, y, width, height, options); + const outerWidth = Math.max(width, bbox.width); + const outerHeight = height + bbox.height + labelPadding; + + const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, { + ...options, + fill: 'none', + stroke: 'none', + }); + const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); + const outerShape = shapeSvg.insert(() => outerNode); if (node.icon) { const iconElem = shapeSvg.append('g'); @@ -52,21 +64,26 @@ export const icon = async ( const iconBBox = iconElem.node().getBBox(); const iconWidth = iconBBox.width; const iconHeight = iconBBox.height; + const iconX = iconBBox.x; + const iconY = iconBBox.y; iconElem.attr( 'transform', - `translate(${-iconWidth / 2},${topLabel ? height / 2 - iconHeight + bbox.height / 2 : -height / 2 - bbox.height / 2})` + `translate(${-iconWidth / 2 - iconX},${topLabel ? outerHeight / 2 - iconHeight - iconY : -outerHeight / 2 - iconY})` ); iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder); } label.attr( 'transform', - `translate(${-width / 2 + width / 2 - bbox.width / 2},${topLabel ? -height / 2 - 5 - bbox.height / 2 : height / 2 - bbox.height / 2})` + `translate(${-bbox.width / 2},${topLabel ? -height / 2 - bbox.height / 2 - labelPadding / 2 : height / 2 - bbox.height / 2 + labelPadding / 2})` ); - iconShape.attr('transform', `translate(${0},${topLabel ? bbox.height / 2 : -bbox.height / 2})`); + iconShape.attr( + 'transform', + `translate(${0},${topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2})` + ); - updateNodeBounds(node, shapeSvg); + updateNodeBounds(node, outerShape); node.intersect = function (point) { log.info('iconSquare intersect', node, point); @@ -75,51 +92,34 @@ export const icon = async ( } const dx = node.x ?? 0; const dy = node.y ?? 0; - const nodeWidth = node.width ?? 0; const nodeHeight = node.height ?? 0; - + let points = []; if (topLabel) { - const points = [ - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, + points = [ + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } else { - const points = [ - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy - nodeHeight / 2 }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, y: dy - nodeHeight / 2 }, - { - x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, - y: dy - nodeHeight / 2 + height, - }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + height }, - { x: dx + nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 - bbox.height }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy + nodeHeight / 2 - bbox.height }, + points = [ + { x: dx - width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + height }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } + + const pos = intersect.polygon(node, points, point); + return pos; }; return shapeSvg; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts index 2d0072d11..e7551ca65 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconCircle.ts @@ -19,15 +19,13 @@ export const iconCircle = async ( const iconSize = Math.max(assetHeight, assetWidth); const defaultWidth = flowchart?.wrappingWidth; node.width = Math.max(iconSize, defaultWidth ?? 0); - const { shapeSvg, bbox, halfPadding, label } = await labelHelper( - parent, - node, - 'icon-shape default' - ); + const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'icon-shape default'); + + const padding = 20; + const labelPadding = node.label ? 8 : 0; const topLabel = node.pos === 't'; - const diameter = iconSize + halfPadding * 2; const { nodeBorder, mainBkg } = themeVariables; const { stylesMap } = compileStyles(node); // @ts-ignore - rough is not typed @@ -39,32 +37,48 @@ export const iconCircle = async ( options.fillStyle = 'solid'; } - const iconNode = rc.circle(0, 0, diameter, options); - - const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); const iconElem = shapeSvg.append('g'); if (node.icon) { iconElem.html( `${await getIconSVG(node.icon, { height: iconSize, fallbackPrefix: '' })}` ); - const iconBBox = iconElem.node().getBBox(); - const iconWidth = iconBBox.width; - const iconHeight = iconBBox.height; - iconElem.attr( - 'transform', - `translate(${-iconWidth / 2},${topLabel ? diameter / 2 - iconHeight - halfPadding + bbox.height / 2 : -diameter / 2 + halfPadding - bbox.height / 2})` - ); - iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder); } + const iconBBox = iconElem.node().getBBox(); + const iconWidth = iconBBox.width; + const iconHeight = iconBBox.height; + const iconX = iconBBox.x; + const iconY = iconBBox.y; + const diameter = Math.max(iconWidth, iconHeight) + padding * 2; + const iconNode = rc.circle(0, 0, diameter, options); + + const outerWidth = Math.max(diameter, bbox.width); + const outerHeight = diameter + bbox.height + labelPadding; + + const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, { + ...options, + fill: 'none', + stroke: 'none', + }); + + const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); + const outerShape = shapeSvg.insert(() => outerNode); + iconElem.attr( + 'transform', + `translate(${-iconWidth / 2 - iconX},${topLabel ? diameter / 2 - iconHeight - padding + bbox.height / 2 - iconY : -diameter / 2 + padding - bbox.height / 2 - labelPadding / 2 - iconY})` + ); + iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder); label.attr( 'transform', - `translate(${-diameter / 2 + diameter / 2 - bbox.width / 2},${topLabel ? -diameter / 2 - bbox.height / 2 : diameter / 2 - bbox.height / 2})` + `translate(${-bbox.width / 2},${topLabel ? -diameter / 2 - bbox.height / 2 : diameter / 2 - bbox.height / 2 + labelPadding / 2})` ); - iconShape.attr('transform', `translate(${0},${topLabel ? bbox.height / 2 : -bbox.height / 2})`); + iconShape.attr( + 'transform', + `translate(${0},${topLabel ? bbox.height / 2 : -bbox.height / 2 - labelPadding / 2})` + ); - updateNodeBounds(node, shapeSvg); + updateNodeBounds(node, outerShape); node.intersect = function (point) { log.info('iconSquare intersect', node, point); diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts index 8aca67682..0f5d6199d 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconRounded.ts @@ -1,6 +1,6 @@ import { log } from '../../../logger.js'; import { labelHelper, updateNodeBounds } from './util.js'; -import type { Node, RenderOptions } from '../../types.js'; +import type { Node, RenderOptions } from '../../types.d.ts'; import type { SVG } from '../../../diagram-api/types.js'; import { compileStyles, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import rough from 'roughjs'; @@ -36,6 +36,8 @@ export const iconRounded = async ( const x = -width / 2; const y = -height / 2; + const labelPadding = node.label ? 8 : 0; + // @ts-ignore - rough is not typed const rc = rough.svg(shapeSvg); const options = userNodeOverrides(node, { stroke: stylesMap.get('fill') || mainBkg }); @@ -47,7 +49,17 @@ export const iconRounded = async ( const iconNode = rc.path(createRoundedRectPathD(x, y, width, height, 5), options); + const outerWidth = Math.max(width, bbox.width); + const outerHeight = height + bbox.height + labelPadding; + + const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, { + ...options, + fill: 'none', + stroke: 'none', + }); + const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); + const outerShape = shapeSvg.insert(() => outerNode); if (node.icon) { const iconElem = shapeSvg.append('g'); @@ -57,21 +69,26 @@ export const iconRounded = async ( const iconBBox = iconElem.node().getBBox(); const iconWidth = iconBBox.width; const iconHeight = iconBBox.height; + const iconX = iconBBox.x; + const iconY = iconBBox.y; iconElem.attr( 'transform', - `translate(${-iconWidth / 2},${topLabel ? height / 2 - iconHeight - halfPadding + bbox.height / 2 : -height / 2 + halfPadding - bbox.height / 2})` + `translate(${-iconWidth / 2 - iconX},${topLabel ? outerHeight / 2 - iconHeight - halfPadding - iconY : -outerHeight / 2 + halfPadding - iconY})` ); iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder); } label.attr( 'transform', - `translate(${-width / 2 + width / 2 - bbox.width / 2},${topLabel ? -height / 2 - 5 - bbox.height / 2 : height / 2 - bbox.height / 2})` + `translate(${-bbox.width / 2},${topLabel ? -height / 2 - bbox.height / 2 - labelPadding / 2 : height / 2 - bbox.height / 2 + labelPadding / 2})` ); - iconShape.attr('transform', `translate(${0},${topLabel ? bbox.height / 2 : -bbox.height / 2})`); + iconShape.attr( + 'transform', + `translate(${0},${topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2})` + ); - updateNodeBounds(node, shapeSvg); + updateNodeBounds(node, outerShape); node.intersect = function (point) { log.info('iconSquare intersect', node, point); @@ -80,51 +97,34 @@ export const iconRounded = async ( } const dx = node.x ?? 0; const dy = node.y ?? 0; - const nodeWidth = node.width ?? 0; const nodeHeight = node.height ?? 0; - + let points = []; if (topLabel) { - const points = [ - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, + points = [ + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } else { - const points = [ - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy - nodeHeight / 2 }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, y: dy - nodeHeight / 2 }, - { - x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, - y: dy - nodeHeight / 2 + height, - }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + height }, - { x: dx + nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 - bbox.height }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy + nodeHeight / 2 - bbox.height }, + points = [ + { x: dx - width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + height }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } + + const pos = intersect.polygon(node, points, point); + return pos; }; return shapeSvg; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts index 9d0375874..cb465c031 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/iconSquare.ts @@ -35,6 +35,8 @@ export const iconSquare = async ( const x = -width / 2; const y = -height / 2; + const labelPadding = node.label ? 8 : 0; + // @ts-ignore - rough is not typed const rc = rough.svg(shapeSvg); const options = userNodeOverrides(node, { stroke: stylesMap.get('fill') || mainBkg }); @@ -46,7 +48,17 @@ export const iconSquare = async ( const iconNode = rc.rectangle(x, y, width, height, options); + const outerWidth = Math.max(width, bbox.width); + const outerHeight = height + bbox.height + labelPadding; + + const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, { + ...options, + fill: 'none', + stroke: 'none', + }); + const iconShape = shapeSvg.insert(() => iconNode, ':first-child'); + const outerShape = shapeSvg.insert(() => outerNode); if (node.icon) { const iconElem = shapeSvg.append('g'); @@ -56,21 +68,26 @@ export const iconSquare = async ( const iconBBox = iconElem.node().getBBox(); const iconWidth = iconBBox.width; const iconHeight = iconBBox.height; + const iconX = iconBBox.x; + const iconY = iconBBox.y; iconElem.attr( 'transform', - `translate(${-iconWidth / 2},${topLabel ? height / 2 - iconHeight - halfPadding + bbox.height / 2 : -height / 2 + halfPadding - bbox.height / 2})` + `translate(${-iconWidth / 2 - iconX},${topLabel ? outerHeight / 2 - iconHeight - halfPadding - iconY : -outerHeight / 2 + halfPadding - iconY})` ); iconElem.selectAll('path').attr('fill', stylesMap.get('stroke') || nodeBorder); } label.attr( 'transform', - `translate(${-width / 2 + width / 2 - bbox.width / 2},${topLabel ? -height / 2 - 5 - bbox.height / 2 : height / 2 - bbox.height / 2})` + `translate(${-bbox.width / 2},${topLabel ? -height / 2 - bbox.height / 2 - labelPadding / 2 : height / 2 - bbox.height / 2 + labelPadding / 2})` ); - iconShape.attr('transform', `translate(${0},${topLabel ? bbox.height / 2 : -bbox.height / 2})`); + iconShape.attr( + 'transform', + `translate(${0},${topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2})` + ); - updateNodeBounds(node, shapeSvg); + updateNodeBounds(node, outerShape); node.intersect = function (point) { log.info('iconSquare intersect', node, point); @@ -79,51 +96,34 @@ export const iconSquare = async ( } const dx = node.x ?? 0; const dy = node.y ?? 0; - const nodeWidth = node.width ?? 0; const nodeHeight = node.height ?? 0; - + let points = []; if (topLabel) { - const points = [ - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 - bbox.height / 2 }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy + nodeHeight / 2 - bbox.height / 2, - }, - { - x: dx + nodeWidth / 2 - (bbox.width - width) / 2 - width, - y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2, - }, - { x: dx - nodeWidth / 2, y: dy - nodeHeight / 2 + bbox.height - bbox.height / 2 }, + points = [ + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy + nodeHeight / 2 }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } else { - const points = [ - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy - nodeHeight / 2 }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, y: dy - nodeHeight / 2 }, - { - x: dx - nodeWidth / 2 + (bbox.width - width) / 2 + width, - y: dy - nodeHeight / 2 + height, - }, - { x: dx + nodeWidth / 2, y: dy - nodeHeight / 2 + height }, - { x: dx + nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 }, - { x: dx - nodeWidth / 2, y: dy + nodeHeight / 2 - bbox.height }, - { x: dx - nodeWidth / 2 + (bbox.width - width) / 2, y: dy + nodeHeight / 2 - bbox.height }, + points = [ + { x: dx - width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 }, + { x: dx + width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + height }, + { x: dx - width / 2, y: dy - nodeHeight / 2 + height }, ]; - const pos = intersect.polygon(node, points, point); - return pos; } + + const pos = intersect.polygon(node, points, point); + return pos; }; return shapeSvg; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts index 2e2690c50..551223ef4 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts @@ -1,15 +1,16 @@ import { log } from '../../../logger.js'; import { labelHelper, updateNodeBounds } from './util.js'; -import type { Node } from '../../types.js'; +import type { Node, RenderOptions } from '../../types.d.ts'; import type { SVG } from '../../../diagram-api/types.js'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; import rough from 'roughjs'; import intersect from '../intersect/index.js'; -import { createPathFromPoints } from './util.js'; -import { getConfig } from '../../../diagram-api/diagramAPI.js'; -export const imageSquare = async (parent: SVG, node: Node) => { - //image dimensions +export const imageSquare = async ( + parent: SVG, + node: Node, + { config: { flowchart } }: RenderOptions +) => { const img = new Image(); img.src = node?.img ?? ''; await img.decode(); @@ -17,49 +18,26 @@ export const imageSquare = async (parent: SVG, node: Node) => { const imageNaturalWidth = Number(img.naturalWidth.toString().replace('px', '')); const imageNaturalHeight = Number(img.naturalHeight.toString().replace('px', '')); - const defaultWidth = getConfig().flowchart?.wrappingWidth; + const { labelStyles } = styles2String(node); + + node.labelStyle = labelStyles; + + const defaultWidth = flowchart?.wrappingWidth; + const imageWidth = Math.max( node.label ? (defaultWidth ?? 0) : 0, node?.assetWidth ?? imageNaturalWidth ); const imageHeight = node?.assetHeight ?? imageNaturalHeight; - - const imagePoints = [ - { x: -imageWidth / 2, y: -imageHeight }, - { x: imageWidth / 2, y: -imageHeight }, - { x: imageWidth / 2, y: 0 }, - { x: -imageWidth / 2, y: 0 }, - ]; - - //label dimensions - const { labelStyles, nodeStyles } = styles2String(node); - node.labelStyle = labelStyles; - - // const { shapeSvg, bbox, halfPadding, label } = await labelHelper( - // parent, - // node, - // 'icon-shape default' - // ); - - const { cssStyles } = node; - // const defaultHeight = bbox.height; - // node.height = Math.max(node.height ?? 0, node.label ? (defaultHeight ?? 0) : 0, imageHeight); - const labelWidth = Math.max(node.width ?? 0, node.label ? (defaultWidth ?? 0) : 0, imageWidth); - node.width = node.label ? labelWidth : 0; + node.width = Math.max(imageWidth, defaultWidth ?? 0); const { shapeSvg, bbox, label } = await labelHelper(parent, node, 'image-shape default'); - // const width = Math.max(bbox.width + (node.padding ?? 0), node?.width ?? 0); - const height = Math.max(bbox.height + (node.padding ?? 0), node?.height ?? 0); - const labelHeight = node.label ? height : 0; + const topLabel = node.pos === 't'; - const imagePosition = node?.pos ?? 'b'; + const x = -imageWidth / 2; + const y = -imageHeight / 2; - const labelPoints = [ - { x: -labelWidth / 2, y: 0 }, - { x: labelWidth / 2, y: 0 }, - { x: labelWidth / 2, y: labelHeight }, - { x: -labelWidth / 2, y: labelHeight }, - ]; + const labelPadding = node.label ? 8 : 0; // @ts-ignore - rough is not typed const rc = rough.svg(shapeSvg); @@ -70,18 +48,20 @@ export const imageSquare = async (parent: SVG, node: Node) => { options.fillStyle = 'solid'; } - const imagePath = createPathFromPoints(imagePoints); - const imagePathNode = rc.path(imagePath, options); + const imageNode = rc.rectangle(x, y, imageWidth, imageHeight, options); - const linePath = createPathFromPoints(labelPoints); - const lineNode = rc.path(linePath, { ...options }); + const outerWidth = Math.max(imageWidth, bbox.width); + const outerHeight = imageHeight + bbox.height + labelPadding; - const imageShape = shapeSvg.insert(() => lineNode, ':first-child').attr('opacity', 0); - imageShape.insert(() => imagePathNode, ':first-child'); + const outerNode = rc.rectangle(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight, { + ...options, + fill: 'none', + stroke: 'none', + }); - imageShape.attr('transform', `translate(${0},${(imageHeight - labelHeight) / 2})`); + const iconShape = shapeSvg.insert(() => imageNode, ':first-child'); + const outerShape = shapeSvg.insert(() => outerNode); - // Image operations if (node.img) { const image = shapeSvg.append('image'); @@ -91,34 +71,58 @@ export const imageSquare = async (parent: SVG, node: Node) => { image.attr('height', imageHeight); image.attr('preserveAspectRatio', 'none'); - const yPos = - imagePosition === 'b' ? -imageHeight / 2 - labelHeight / 2 : (-imageHeight + labelHeight) / 2; - image.attr('transform', `translate(${-imageWidth / 2}, ${yPos})`); + image.attr( + 'transform', + `translate(${-imageWidth / 2},${topLabel ? outerHeight / 2 - imageHeight : -outerHeight / 2})` + ); } - if (cssStyles && node.look !== 'handDrawn') { - imageShape.selectAll('path').attr('style', cssStyles); - } + label.attr( + 'transform', + `translate(${-bbox.width / 2},${topLabel ? -imageHeight / 2 - bbox.height / 2 - labelPadding / 2 : imageHeight / 2 - bbox.height / 2 + labelPadding / 2})` + ); - if (nodeStyles && node.look !== 'handDrawn') { - imageShape.selectAll('path').attr('style', nodeStyles); - } + iconShape.attr( + 'transform', + `translate(${0},${topLabel ? bbox.height / 2 + labelPadding / 2 : -bbox.height / 2 - labelPadding / 2})` + ); - const yPos = - imagePosition === 'b' - ? (imageHeight + labelHeight) / 2 - bbox.height - (bbox.y - (bbox.top ?? 0)) - : -(imageHeight + labelHeight) / 2 + (node?.padding ?? 0) / 2 - (bbox.y - (bbox.top ?? 0)); - - label.attr('transform', `translate(${-bbox.width / 2 - (bbox.x - (bbox.left ?? 0))},${yPos})`); - - updateNodeBounds(node, imageShape); + updateNodeBounds(node, outerShape); node.intersect = function (point) { - log.info('imageSquare intersect', node, point); + log.info('iconSquare intersect', node, point); + if (!node.label) { + return intersect.rect(node, point); + } + const dx = node.x ?? 0; + const dy = node.y ?? 0; + const nodeHeight = node.height ?? 0; + let points = []; + if (topLabel) { + points = [ + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + imageWidth / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx + imageWidth / 2, y: dy + nodeHeight / 2 }, + { x: dx - imageWidth / 2, y: dy + nodeHeight / 2 }, + { x: dx - imageWidth / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + bbox.height + labelPadding }, + ]; + } else { + points = [ + { x: dx - imageWidth / 2, y: dy - nodeHeight / 2 }, + { x: dx + imageWidth / 2, y: dy - nodeHeight / 2 }, + { x: dx + imageWidth / 2, y: dy - nodeHeight / 2 + imageHeight }, + { x: dx + bbox.width / 2, y: dy - nodeHeight / 2 + imageHeight }, + { x: dx + bbox.width / 2 / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy + nodeHeight / 2 }, + { x: dx - bbox.width / 2, y: dy - nodeHeight / 2 + imageHeight }, + { x: dx - imageWidth / 2, y: dy - nodeHeight / 2 + imageHeight }, + ]; + } - const combinedPoints = [...imagePoints, ...labelPoints]; - - const pos = intersect.polygon(node, combinedPoints, point); + const pos = intersect.polygon(node, points, point); return pos; }; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts index 516713ddc..bd36c7261 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts @@ -1,11 +1,15 @@ -import { log } from '../../../logger.js'; import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js'; import intersect from '../intersect/index.js'; import type { Node } from '../../types.js'; import rough from 'roughjs'; import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; +import type { RenderOptions } from '../../types.js'; -export const note = async (parent: SVGAElement, node: Node) => { +export const note = async ( + parent: SVGAElement, + node: Node, + { config: { themeVariables } }: RenderOptions +) => { const { labelStyles, nodeStyles } = styles2String(node); node.labelStyle = labelStyles; const { shapeSvg, bbox } = await labelHelper(parent, node, getNodeClasses(node)); @@ -19,11 +23,13 @@ export const note = async (parent: SVGAElement, node: Node) => { node.centerLabel = true; } - log.info('Classes = ', node.cssClasses); // add the rect // @ts-ignore TODO: Fix rough typings const rc = rough.svg(shapeSvg); - const options = userNodeOverrides(node, {}); + const options = userNodeOverrides(node, { + fill: themeVariables.noteBkgColor, + stroke: themeVariables.noteBorderColor, + }); if (node.look !== 'handDrawn') { options.roughness = 0; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts index 1fc554657..9c547f30f 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/stateEnd.ts @@ -3,7 +3,7 @@ import intersect from '../intersect/index.js'; import type { Node, RenderOptions } from '../../types.js'; import type { SVG } from '../../../diagram-api/types.js'; import rough from 'roughjs'; -import { solidStateFill, styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; +import { styles2String, userNodeOverrides } from './handDrawnShapeStyles.js'; export const stateEnd = ( parent: SVG, @@ -13,7 +13,7 @@ export const stateEnd = ( const { labelStyles, nodeStyles } = styles2String(node); node.labelStyle = labelStyles; const { cssStyles } = node; - const { lineColor } = themeVariables; + const { lineColor, stateBorder, nodeBorder } = themeVariables; const shapeSvg = parent .insert('g') .attr('class', 'node default') @@ -29,14 +29,17 @@ export const stateEnd = ( } const roughNode = rc.circle(0, 0, 14, { - ...solidStateFill(lineColor), - roughness: 0.5, ...options, + stroke: lineColor, + strokeWidth: 2, }); + const innerFill = stateBorder ?? nodeBorder; const roughInnerNode = rc.circle(0, 0, 5, { - ...solidStateFill(lineColor), - fillStyle: 'solid', ...options, + fill: innerFill, + stroke: innerFill, + strokeWidth: 2, + fillStyle: 'solid', }); const circle = shapeSvg.insert(() => roughNode, ':first-child'); circle.insert(() => roughInnerNode); diff --git a/packages/mermaid/src/rendering-util/types.ts b/packages/mermaid/src/rendering-util/types.ts index 0d24f6fdf..510e94b94 100644 --- a/packages/mermaid/src/rendering-util/types.ts +++ b/packages/mermaid/src/rendering-util/types.ts @@ -143,4 +143,5 @@ export type LayoutMethod = export interface RenderOptions { config: MermaidConfig; + dir: string; }