From 30e4ab99d9909a9038d8fd87cadeb6953d7978d9 Mon Sep 17 00:00:00 2001 From: omkarht Date: Fri, 6 Sep 2024 11:40:06 +0530 Subject: [PATCH] Added image shape icon --- .../mermaid/src/diagrams/flowchart/flowDb.ts | 10 ++ .../mermaid/src/diagrams/flowchart/types.ts | 1 + .../rendering-elements/nodes.js | 2 + .../rendering-elements/shapes/imageSquare.ts | 105 ++++++++++++++++++ .../mermaid/src/rendering-util/types.d.ts | 1 + packages/mermaid/src/types.ts | 1 + 6 files changed, 120 insertions(+) create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index 020350a12..1e37f9cf0 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -156,6 +156,12 @@ export const addVertex = function ( if (doc?.pos) { vertex.pos = doc?.pos; } + if (doc?.img) { + vertex.img = doc?.img; + } + if (doc?.pos) { + vertex.pos = doc?.pos; + } } }; @@ -804,6 +810,9 @@ export const lex = { }; const getTypeFromVertex = (vertex: FlowVertex) => { + if (vertex?.img) { + return 'imageSquare'; + } if (vertex?.icon) { if (vertex.form === 'circle') { return 'iconCircle'; @@ -880,6 +889,7 @@ const addNodeFromVertex = ( tooltip: getTooltip(vertex.id), icon: vertex.icon, pos: vertex.pos, + img: vertex.img, }); } }; diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts index a1778e161..a29eeacc2 100644 --- a/packages/mermaid/src/diagrams/flowchart/types.ts +++ b/packages/mermaid/src/diagrams/flowchart/types.ts @@ -14,6 +14,7 @@ export interface FlowVertex { icon?: string; form?: string; pos?: 't' | 'b'; + img?: string; } export interface FlowText { diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js index 271144b91..338239a84 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js @@ -54,6 +54,7 @@ import { curlyBraces } from './shapes/curlyBraces.js'; import { iconSquare } from './shapes/iconSquare.js'; import { iconCircle } from './shapes/iconCircle.js'; import { icon } from './shapes/icon.js'; +import { imageSquare } from './shapes/imageSquare.js'; //Use these names as the left side to render shapes. const shapes = { @@ -230,6 +231,7 @@ const shapes = { iconSquare, iconCircle, icon, + imageSquare, }; const nodeElems = new Map(); diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts new file mode 100644 index 000000000..64886bcf6 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/imageSquare.ts @@ -0,0 +1,105 @@ +import { log } from '$root/logger.js'; +import { getNodeClasses, labelHelper, updateNodeBounds } from './util.js'; +import type { Node } from '$root/rendering-util/types.d.ts'; +import type { SVG } from '$root/diagram-api/types.js'; +import { + styles2String, + userNodeOverrides, +} from '$root/rendering-util/rendering-elements/shapes/handDrawnShapeStyles.js'; +import rough from 'roughjs'; +import intersect from '../intersect/index.js'; +import { createPathFromPoints } from './util.js'; + +export const imageSquare = async (parent: SVG, node: Node) => { + const { labelStyles, nodeStyles } = styles2String(node); + node.labelStyle = labelStyles; + const { shapeSvg, bbox, label } = await labelHelper(parent, node, getNodeClasses(node)); + const { cssStyles } = node; + + const imageWidth = node.width ?? 50; + const imageHeight = node.height ?? 50; + + // Ensure width and height accommodate the text (bbox) and image + const width = Math.max(bbox.width + (node.padding ?? 0), imageWidth); + const height = Math.max(bbox.height + (node.padding ?? 0), imageHeight); + + // const imageSizeWidth = width / 2; // Image will be smaller than the full width + const imageSizeHeight = height / 2; // Image will be smaller than the full height + + const skeletonWidth = width; // The shape's width matches the text width + const skeletonHeight = height + imageSizeHeight + bbox.height; // The height includes space for the label and image + + const imagePosition = node?.pos ?? 'b'; + + // Define the points for the rectangle (shape) + const points = [ + { x: 0, y: 0 }, + { x: skeletonWidth, y: 0 }, + { x: skeletonWidth, y: skeletonHeight }, + { x: 0, y: skeletonHeight }, + ]; + + // @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'; + } + + // Create the path for the rectangle shape + const linePath = createPathFromPoints(points); + const lineNode = rc.path(linePath, options); + + const iconShape = shapeSvg.insert(() => lineNode, ':first-child'); + if (node.img) { + const image = shapeSvg.append('image'); + + // Set the image attributes + image.attr('href', node.img); + image.attr('width', width); + image.attr('height', height); + + // Center the image horizontally and position it at the bottom + // image.attr('x', -(skeletonWidth / 2 )); + // image.attr('y', -(skeletonHeight / 2 - height - imageSizeHeight /2)); // Position at the bottom + + // Apply transformation if needed to fine-tune + const yPos = + imagePosition === 't' + ? -(skeletonHeight / 2 - imageSizeHeight / 2) + : skeletonHeight / 2 - height - imageSizeHeight / 2; + image.attr('transform', `translate(${-(skeletonWidth / 2)}, ${yPos})`); + } + + // Apply styles for the shape + if (cssStyles && node.look !== 'handDrawn') { + iconShape.selectAll('path').attr('style', cssStyles); + } + + if (nodeStyles && node.look !== 'handDrawn') { + iconShape.selectAll('path').attr('style', nodeStyles); + } + + // Position the shape at the center + iconShape.attr('transform', `translate(${-skeletonWidth / 2},${-skeletonHeight / 2})`); + + // Position the label at the top center of the shape + const yPos = + imagePosition === 't' ? imageSizeHeight / 2 : -skeletonHeight / 2 + (node?.padding ?? 0) / 2; + + label.attr('transform', `translate(${-bbox.width / 2},${yPos})`); + + updateNodeBounds(node, iconShape); + + // Add the image inside the shape + + node.intersect = function (point) { + log.info('iconSquare intersect', node, point); + const pos = intersect.polygon(node, points, point); + return pos; + }; + + return shapeSvg; +}; diff --git a/packages/mermaid/src/rendering-util/types.d.ts b/packages/mermaid/src/rendering-util/types.d.ts index adef02d6d..a5180fba6 100644 --- a/packages/mermaid/src/rendering-util/types.d.ts +++ b/packages/mermaid/src/rendering-util/types.d.ts @@ -67,6 +67,7 @@ interface Node { look?: string; icon?: string; pos?: 't' | 'b'; + img?: string; } // Common properties for any edge in the system diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts index 8181f59ca..7fd98830d 100644 --- a/packages/mermaid/src/types.ts +++ b/packages/mermaid/src/types.ts @@ -4,6 +4,7 @@ export interface NodeMetaData { icon?: string; form?: string; pos?: 't' | 'b'; + img?: string; } export interface Point { x: number;