mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-02 15:16:49 +02:00
chore: Refactor nodes.js to remove global state
This commit is contained in:
@@ -10,12 +10,7 @@ import {
|
|||||||
findNonClusterChild,
|
findNonClusterChild,
|
||||||
sortNodesByHierarchy,
|
sortNodesByHierarchy,
|
||||||
} from './mermaid-graphlib.js';
|
} from './mermaid-graphlib.js';
|
||||||
import {
|
import { Nodes } from '../../rendering-elements/nodes.js';
|
||||||
insertNode,
|
|
||||||
positionNode,
|
|
||||||
clear as clearNodes,
|
|
||||||
setNodeElem,
|
|
||||||
} from '../../rendering-elements/nodes.js';
|
|
||||||
import { insertCluster, clear as clearClusters } from '../../rendering-elements/clusters.js';
|
import { insertCluster, clear as clearClusters } from '../../rendering-elements/clusters.js';
|
||||||
import {
|
import {
|
||||||
insertEdgeLabel,
|
insertEdgeLabel,
|
||||||
@@ -27,8 +22,16 @@ import { log } from '../../../logger.js';
|
|||||||
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js';
|
||||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||||
|
|
||||||
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
const recursiveRender = async (
|
||||||
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
_elem,
|
||||||
|
graph,
|
||||||
|
diagramType,
|
||||||
|
id,
|
||||||
|
parentCluster,
|
||||||
|
siteConfig,
|
||||||
|
nodeData
|
||||||
|
) => {
|
||||||
|
log.debug('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
||||||
const dir = graph.graph().rankdir;
|
const dir = graph.graph().rankdir;
|
||||||
log.trace('Dir in recursive render - dir:', dir);
|
log.trace('Dir in recursive render - dir:', dir);
|
||||||
|
|
||||||
@@ -89,7 +92,8 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
diagramType,
|
diagramType,
|
||||||
id,
|
id,
|
||||||
graph.node(v),
|
graph.node(v),
|
||||||
siteConfig
|
siteConfig,
|
||||||
|
nodeData
|
||||||
);
|
);
|
||||||
const newEl = o.elem;
|
const newEl = o.elem;
|
||||||
updateNodeBounds(node, newEl);
|
updateNodeBounds(node, newEl);
|
||||||
@@ -106,7 +110,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
// node.x,
|
// node.x,
|
||||||
// node.y
|
// node.y
|
||||||
);
|
);
|
||||||
setNodeElem(newEl, node);
|
nodeData.setNodeElem(newEl, node);
|
||||||
} else {
|
} else {
|
||||||
if (graph.children(v).length > 0) {
|
if (graph.children(v).length > 0) {
|
||||||
// This is a cluster but not to be rendered recursively
|
// 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));
|
// insertCluster(clusters, graph.node(v));
|
||||||
} else {
|
} else {
|
||||||
log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
|
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)
|
graph.parent(v)
|
||||||
);
|
);
|
||||||
clusterDb.get(node.id).node = node;
|
clusterDb.get(node.id).node = node;
|
||||||
positionNode(node);
|
nodeData.positionNode(node);
|
||||||
} else {
|
} else {
|
||||||
// A tainted cluster node
|
// A tainted cluster node
|
||||||
if (graph.children(v).length > 0) {
|
if (graph.children(v).length > 0) {
|
||||||
@@ -239,7 +243,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
node
|
node
|
||||||
);
|
);
|
||||||
|
|
||||||
positionNode(node);
|
nodeData.positionNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -264,7 +268,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
|||||||
diff = n.diff;
|
diff = n.diff;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
log.warn('Returning from recursive render XAX', elem, diff);
|
log.debug('Returning from recursive render XAX', elem, diff);
|
||||||
return { elem, diff };
|
return { elem, diff };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,7 +295,6 @@ export const render = async (data4Layout, svg) => {
|
|||||||
});
|
});
|
||||||
const element = svg.select('g');
|
const element = svg.select('g');
|
||||||
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||||
clearNodes();
|
|
||||||
clearEdges();
|
clearEdges();
|
||||||
clearClusters();
|
clearClusters();
|
||||||
clearGraphlib();
|
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);
|
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 siteConfig = getConfig();
|
||||||
|
const nodeData = new Nodes();
|
||||||
await recursiveRender(
|
await recursiveRender(
|
||||||
element,
|
element,
|
||||||
graph,
|
graph,
|
||||||
data4Layout.type,
|
data4Layout.type,
|
||||||
data4Layout.diagramId,
|
data4Layout.diagramId,
|
||||||
undefined,
|
undefined,
|
||||||
siteConfig
|
siteConfig,
|
||||||
|
nodeData
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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;
|
|
||||||
};
|
|
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user