From e1a71296febb96584ccb5a594034a7923d79a757 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 4 Oct 2024 00:22:56 +0530 Subject: [PATCH] chore: Refactor nodes.js to remove global state --- .../layout-algorithms/dagre/index.js | 41 +++++---- .../rendering-elements/nodes.js | 82 ------------------ .../rendering-elements/nodes.ts | 83 +++++++++++++++++++ 3 files changed, 106 insertions(+), 100 deletions(-) delete mode 100644 packages/mermaid/src/rendering-util/rendering-elements/nodes.js create mode 100644 packages/mermaid/src/rendering-util/rendering-elements/nodes.ts 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 6f1fa7d3b..82e2c5546 100644 --- a/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js +++ b/packages/mermaid/src/rendering-util/layout-algorithms/dagre/index.js @@ -10,12 +10,7 @@ import { findNonClusterChild, sortNodesByHierarchy, } from './mermaid-graphlib.js'; -import { - insertNode, - positionNode, - clear as clearNodes, - setNodeElem, -} from '../../rendering-elements/nodes.js'; +import { Nodes } from '../../rendering-elements/nodes.js'; import { insertCluster, clear as clearClusters } from '../../rendering-elements/clusters.js'; import { insertEdgeLabel, @@ -27,8 +22,16 @@ import { log } from '../../../logger.js'; import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js'; import { getConfig } from '../../../diagram-api/diagramAPI.js'; -const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => { - log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster); +const recursiveRender = async ( + _elem, + graph, + diagramType, + id, + parentCluster, + siteConfig, + nodeData +) => { + log.debug('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster); const dir = graph.graph().rankdir; log.trace('Dir in recursive render - dir:', dir); @@ -89,7 +92,8 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit diagramType, id, graph.node(v), - siteConfig + siteConfig, + nodeData ); const newEl = o.elem; updateNodeBounds(node, newEl); @@ -106,7 +110,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit // node.x, // node.y ); - setNodeElem(newEl, node); + nodeData.setNodeElem(newEl, node); } else { if (graph.children(v).length > 0) { // This is a cluster but not to be rendered recursively @@ -125,7 +129,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, dir }); + await nodeData.insertNode(nodes, graph.node(v), { config: siteConfig, dir }); } } }) @@ -194,7 +198,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit graph.parent(v) ); clusterDb.get(node.id).node = node; - positionNode(node); + nodeData.positionNode(node); } else { // A tainted cluster node if (graph.children(v).length > 0) { @@ -239,7 +243,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit node ); - positionNode(node); + nodeData.positionNode(node); } } }) @@ -264,7 +268,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit diff = n.diff; } }); - log.warn('Returning from recursive render XAX', elem, diff); + log.debug('Returning from recursive render XAX', elem, diff); return { elem, diff }; }; @@ -291,7 +295,6 @@ export const render = async (data4Layout, svg) => { }); const element = svg.select('g'); insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId); - clearNodes(); clearEdges(); clearClusters(); clearGraphlib(); @@ -362,16 +365,18 @@ export const render = async (data4Layout, svg) => { } }); - log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph))); + log.debug('Graph at first:', JSON.stringify(graphlibJson.write(graph))); adjustClustersAndEdges(graph); - log.warn('Graph after XAX:', JSON.stringify(graphlibJson.write(graph))); + log.debug('Graph after XAX:', JSON.stringify(graphlibJson.write(graph))); const siteConfig = getConfig(); + const nodeData = new Nodes(); await recursiveRender( element, graph, data4Layout.type, data4Layout.diagramId, undefined, - siteConfig + siteConfig, + nodeData ); }; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js b/packages/mermaid/src/rendering-util/rendering-elements/nodes.js deleted file mode 100644 index 2d2b5008f..000000000 --- a/packages/mermaid/src/rendering-util/rendering-elements/nodes.js +++ /dev/null @@ -1,82 +0,0 @@ -import { log } from '../../logger.js'; -import { shapes } from './shapes.js'; - -const nodeElems = new Map(); - -export const insertNode = async (elem, node, renderOptions) => { - let newEl; - let el; - - //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'; - } - } - - const shapeHandler = shapes[node.shape]; - - if (!shapeHandler) { - throw new Error(`No such shape: ${node.shape}. Please check your syntax.`); - } - - if (node.link) { - // Add link when appropriate - let target; - if (renderOptions.config.securityLevel === 'sandbox') { - target = '_top'; - } else if (node.linkTarget) { - target = node.linkTarget || '_blank'; - } - newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target); - el = await shapeHandler(newEl, node, renderOptions); - } else { - el = await shapeHandler(elem, node, renderOptions); - newEl = el; - } - if (node.tooltip) { - el.attr('title', node.tooltip); - } - - nodeElems.set(node.id, newEl); - - if (node.haveCallback) { - nodeElems.get(node.id).attr('class', nodeElems.get(node.id).attr('class') + ' clickable'); - } - return newEl; -}; - -export const setNodeElem = (elem, node) => { - nodeElems.set(node.id, elem); -}; - -export const clear = () => { - nodeElems.clear(); -}; - -export const positionNode = (node) => { - const el = nodeElems.get(node.id); - log.trace( - 'Transforming node', - node.diff, - node, - 'translate(' + (node.x - node.width / 2 - 5) + ', ' + node.width / 2 + ')' - ); - const padding = 8; - const diff = node.diff || 0; - if (node.clusterNode) { - el.attr( - 'transform', - 'translate(' + - (node.x + diff - node.width / 2) + - ', ' + - (node.y - node.height / 2 - padding) + - ')' - ); - } else { - el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')'); - } - return diff; -}; diff --git a/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts b/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts new file mode 100644 index 000000000..5e42f20d8 --- /dev/null +++ b/packages/mermaid/src/rendering-util/rendering-elements/nodes.ts @@ -0,0 +1,83 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { log } from '../../logger.js'; +import type { SVG } from '../../mermaid.js'; +import type { Node, ShapeRenderOptions } from '../types.js'; +import { shapes } from './shapes.js'; + +// TODO: Need a better name for the class. +export class Nodes { + private readonly nodeElems = new Map(); + positionNode = (node: any) => { + const el = this.nodeElems.get(node.id); + log.trace( + 'Transforming node', + node.diff, + node, + 'translate(' + (node.x - node.width / 2 - 5) + ', ' + node.width / 2 + ')' + ); + const padding = 8; + const diff = node.diff || 0; + if (node.clusterNode) { + el.attr( + 'transform', + 'translate(' + + (node.x + diff - node.width / 2) + + ', ' + + (node.y - node.height / 2 - padding) + + ')' + ); + } else { + el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')'); + } + return diff; + }; + setNodeElem = (elem: any, node: any) => { + this.nodeElems.set(node.id, elem); + }; + insertNode = async (elem: SVG, node: Node, renderOptions: ShapeRenderOptions) => { + let newEl: any; + let el: any; + + //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'; + } + } + + if (!node.shape) { + throw new Error(`No shape defined for node ${node.id}. Please check your syntax.`); + } + + const shapeHandler = shapes[node.shape]; + + if (!shapeHandler) { + throw new Error(`No such shape: ${node.shape}. Please check your syntax.`); + } + + if (node.link) { + // Add link when appropriate + const target = + renderOptions.config.securityLevel === 'sandbox' ? '_top' : (node.linkTarget ?? '_blank'); + newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target); + el = await shapeHandler(newEl, node, renderOptions); + } else { + el = await shapeHandler(elem, node, renderOptions); + newEl = el; + } + if (node.tooltip) { + el.attr('title', node.tooltip); + } + + this.nodeElems.set(node.id, newEl); + + if (node.haveCallback) { + this.nodeElems + .get(node.id) + .attr('class', this.nodeElems.get(node.id).attr('class') + ' clickable'); + } + return newEl; + }; +}