#5237 Recusive structuture refactoring for subgraphs

This commit is contained in:
Knut Sveidqvist
2024-05-03 15:58:39 +02:00
parent 024bffd683
commit b2c286ff1d
5 changed files with 244 additions and 169 deletions

View File

@@ -74,19 +74,24 @@
<body> <body>
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
stateDiagram-v2 stateDiagram-v2
[*] --> First Chimp --> A
state First {
[*] --> second
second --> [*]
}
</pre </pre
> >
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid2">
stateDiagram-v2 stateDiagram-v2
Second state First {
second --> third
}
</pre
>
<pre id="diagram" class="mermaid2">
stateDiagram-v2
[*] --> First
state First {
[*] --> second
second --> [*]
}
</pre </pre
> >
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid2">

View File

@@ -574,16 +574,6 @@ const setDirection = (dir) => {
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim()); const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, useRough) => { const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, useRough) => {
console.log(
'abc88 parent, parsedItemm, diagramStates, nodes, edges, altFlag, useRough:',
parent,
parsedItem,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
const itemId = parsedItem.id; const itemId = parsedItem.id;
const classStr = getClassesFromDbInfo(diagramStates[itemId]); const classStr = getClassesFromDbInfo(diagramStates[itemId]);
@@ -758,6 +748,11 @@ const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, u
} }
}; };
/**
*
* @param nodes
* @param nodeData
*/
function insertOrUpdateNode(nodes, nodeData) { function insertOrUpdateNode(nodes, nodeData) {
const existingNodeData = nodes.find((node) => node.id === nodeData.id); const existingNodeData = nodes.find((node) => node.id === nodeData.id);
if (existingNodeData) { if (existingNodeData) {

View File

@@ -87,8 +87,8 @@ export const draw = async function (text: string, id: string, _version: string,
// performRender(data4Rendering); // performRender(data4Rendering);
data4Layout.type = diag.type; data4Layout.type = diag.type;
data4Layout.layoutAlgorithm = 'dagre-wrapper'; // data4Layout.layoutAlgorithm = 'dagre-wrapper';
//data4Layout.layoutAlgorithm = 'elk'; data4Layout.layoutAlgorithm = 'elk';
data4Layout.direction = DIR; data4Layout.direction = DIR;
data4Layout.nodeSpacing = conf.nodeSpacing || 50; data4Layout.nodeSpacing = conf.nodeSpacing || 50;
data4Layout.rankSpacing = conf.rankSpacing || 50; data4Layout.rankSpacing = conf.rankSpacing || 50;

View File

@@ -21,6 +21,7 @@ import { log } from '$root/logger.js';
import ELK from 'elkjs/lib/elk.bundled.js'; import ELK from 'elkjs/lib/elk.bundled.js';
const nodeDb = {}; const nodeDb = {};
let portPos = {};
let clusterDb = {}; let clusterDb = {};
const addSubGraphs = function (db) { const addSubGraphs = function (db) {
@@ -46,6 +47,150 @@ const addSubGraphs = function (db) {
return parentLookupDb; return parentLookupDb;
}; };
export const addVertex = async (nodeEl, graph, nodeArr, node) => {
console.log('addVertex abc88', node.id);
// const node = vert[id];
// /**
// * Variable for storing the classes for the vertex
// *
// * @type {string}
// */
// let classStr = 'default';
// if (node.classes.length > 0) {
// classStr = node.classes.join(' ');
// }
// classStr = classStr + ' flowchart-label';
// const styles = getStylesFromArray(node.styles);
// // Use vertex id as text in the box if no text is provided by the graph definition
// let vertexText = node.text !== undefined ? node.text : node.id;
// // We create a SVG label, either by delegating to addHtmlLabel or manually
// let vertexNode;
// const labelData = { width: 0, height: 0 };
const ports = [
{
id: node.id + '-west',
layoutOptions: {
'port.side': 'WEST',
},
},
{
id: node.id + '-east',
layoutOptions: {
'port.side': 'EAST',
},
},
{
id: node.id + '-south',
layoutOptions: {
'port.side': 'SOUTH',
},
},
{
id: node.id + '-north',
layoutOptions: {
'port.side': 'NORTH',
},
},
];
let boundingBox;
const child = {
...node,
ports: node.shape === 'diamond' ? ports : [],
};
graph.children.push(child);
// // Add the element to the DOM
if (node.type !== 'group') {
const childNodeEl = await insertNode(nodeEl, node, node.dir);
boundingBox = childNodeEl.node().getBBox();
child.domId = childNodeEl;
child.width = boundingBox.width;
child.height = boundingBox.height;
} else {
child.children = [];
await addVertices(nodeEl, nodeArr, child, node.id);
}
// else {
// const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
// // svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
// // const rows = vertexText.split(common.lineBreakRegex);
// // for (const row of rows) {
// // const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
// // tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
// // tspan.setAttribute('dy', '1em');
// // tspan.setAttribute('x', '1');
// // tspan.textContent = row;
// // svgLabel.appendChild(tspan);
// // }
// // vertexNode = svgLabel;
// // const bbox = vertexNode.getBBox();
// const { shapeSvg, bbox } = await labelHelper(nodes, node, undefined, true);
// labelData.width = bbox.width;
// labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
// labelData.height = bbox.height;
// labelData.labelNode = shapeSvg.node();
// node.labelData = labelData;
// }
// // const { shapeSvg, bbox } = await labelHelper(svg, node, undefined, true);
// const data = {
// id: node.id,
// ports: node.shape === 'diamond' ? ports : [],
// // labelStyle: styles.labelStyle,
// // shape: _shape,
// layoutOptions,
// labelText: vertexText,
// labelData,
// // labels: [{ text: vertexText }],
// // rx: radius,
// // ry: radius,
// // class: classStr,
// // style: styles.style,
// // link: vertex.link,
// // linkTarget: vertex.linkTarget,
// // tooltip: diagObj.db.getTooltip(vertex.id) || '',
// domId: diagObj.db.lookUpDomId(node.id),
// // haveCallback: vertex.haveCallback,
// width: boundingBox?.width,
// height: boundingBox?.height,
// // dir: vertex.dir,
// type: node.shape,
// // props: vertex.props,
// // padding: getConfig().flowchart.padding,
// // boundingBox,
// el: nodeEl,
// parent: parentLookupDb.parentById[node.id],
// };
// // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
// // graph.children.push({
// // ...data,
// // });
// // }
// nodeDb[node.id] = data;
// // log.trace('setNode', {
// // labelStyle: styles.labelStyle,
// // shape: _shape,
// // labelText: vertexText,
// // rx: radius,
// // ry: radius,
// // class: classStr,
// // style: styles.style,
// // id: vertex.id,
// // domId: diagObj.db.lookUpDomId(vertex.id),
// // width: vertex.type === 'group' ? 500 : undefined,
// // type: vertex.type,
// // dir: vertex.dir,
// // props: vertex.props,
// // padding: getConfig().flowchart.padding,
// // parent: parentLookupDb.parentById[vertex.id],
// // });
};
// /** // /**
// * Function that adds the vertices found during parsing to the graph to be rendered. // * Function that adds the vertices found during parsing to the graph to be rendered.
// * // *
@@ -56,148 +201,13 @@ const addSubGraphs = function (db) {
// * @param doc // * @param doc
// * @param diagObj // * @param diagObj
// */ // */
export const addVertices = async function (svg, data4Layout, parentLookupDb, graph) { export const addVertices = async function (nodeEl, nodeArr, graph, parentId) {
const nodes = svg.insert('g').attr('class', 'nodes'); const siblings = nodeArr.filter((node) => node.parentId === parentId);
log.info('addVertices abc88', siblings, parentId);
console.log('data4Layout (node)', data4Layout);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
await Promise.all( await Promise.all(
data4Layout.nodes.map(async (node) => { siblings.map(async (node) => {
console.log('node', node); await addVertex(nodeEl, graph, nodeArr, node);
// const node = vert[id];
// /**
// * Variable for storing the classes for the vertex
// *
// * @type {string}
// */
// let classStr = 'default';
// if (node.classes.length > 0) {
// classStr = node.classes.join(' ');
// }
// classStr = classStr + ' flowchart-label';
// const styles = getStylesFromArray(node.styles);
// // Use vertex id as text in the box if no text is provided by the graph definition
// let vertexText = node.text !== undefined ? node.text : node.id;
// // We create a SVG label, either by delegating to addHtmlLabel or manually
// let vertexNode;
// const labelData = { width: 0, height: 0 };
const ports = [
{
id: node.id + '-west',
layoutOptions: {
'port.side': 'WEST',
},
},
{
id: node.id + '-east',
layoutOptions: {
'port.side': 'EAST',
},
},
{
id: node.id + '-south',
layoutOptions: {
'port.side': 'SOUTH',
},
},
{
id: node.id + '-north',
layoutOptions: {
'port.side': 'NORTH',
},
},
];
let boundingBox;
let nodeEl;
// // Add the element to the DOM
if (node.type !== 'group') {
nodeEl = await insertNode(nodes, node, node.dir);
boundingBox = nodeEl.node().getBBox();
graph.children.push({
...node,
domId: nodeEl,
});
}
// else {
// const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
// // svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
// // const rows = vertexText.split(common.lineBreakRegex);
// // for (const row of rows) {
// // const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
// // tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
// // tspan.setAttribute('dy', '1em');
// // tspan.setAttribute('x', '1');
// // tspan.textContent = row;
// // svgLabel.appendChild(tspan);
// // }
// // vertexNode = svgLabel;
// // const bbox = vertexNode.getBBox();
// const { shapeSvg, bbox } = await labelHelper(nodes, node, undefined, true);
// labelData.width = bbox.width;
// labelData.wrappingWidth = getConfig().flowchart.wrappingWidth;
// labelData.height = bbox.height;
// labelData.labelNode = shapeSvg.node();
// node.labelData = labelData;
// }
// // const { shapeSvg, bbox } = await labelHelper(svg, node, undefined, true);
// const data = {
// id: node.id,
// ports: node.type === 'diamond' ? ports : [],
// // labelStyle: styles.labelStyle,
// // shape: _shape,
// layoutOptions,
// labelText: vertexText,
// labelData,
// // labels: [{ text: vertexText }],
// // rx: radius,
// // ry: radius,
// // class: classStr,
// // style: styles.style,
// // link: vertex.link,
// // linkTarget: vertex.linkTarget,
// // tooltip: diagObj.db.getTooltip(vertex.id) || '',
// domId: diagObj.db.lookUpDomId(node.id),
// // haveCallback: vertex.haveCallback,
// width: boundingBox?.width,
// height: boundingBox?.height,
// // dir: vertex.dir,
// type: node.type,
// // props: vertex.props,
// // padding: getConfig().flowchart.padding,
// // boundingBox,
// el: nodeEl,
// parent: parentLookupDb.parentById[node.id],
// };
// // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) {
// // graph.children.push({
// // ...data,
// // });
// // }
// nodeDb[node.id] = data;
// // log.trace('setNode', {
// // labelStyle: styles.labelStyle,
// // shape: _shape,
// // labelText: vertexText,
// // rx: radius,
// // ry: radius,
// // class: classStr,
// // style: styles.style,
// // id: vertex.id,
// // domId: diagObj.db.lookUpDomId(vertex.id),
// // width: vertex.type === 'group' ? 500 : undefined,
// // type: vertex.type,
// // dir: vertex.dir,
// // props: vertex.props,
// // padding: getConfig().flowchart.padding,
// // parent: parentLookupDb.parentById[vertex.id],
// // });
}) })
); );
return graph; return graph;
@@ -235,9 +245,18 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, depth) => {
// ); // );
// label.node().appendChild(node.labelData.labelNode); // label.node().appendChild(node.labelData.labelNode);
// log.info('Id (UGH)= ', node.type, node.labels); // log.info('Id (UGH)= ', node.shape, node.labels);
// } else { // } else {
log.info('Id (UGH)= ', node.id); log.info(
'Id (UGH)= ',
node.id,
node.x,
node.y,
relX,
relY,
node.domId.node(),
`translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
);
node.domId.attr( node.domId.attr(
'transform', 'transform',
`translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})` `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})`
@@ -246,12 +265,61 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, depth) => {
// } // }
// }); // });
// nodeArray.forEach(function (node) { // nodeArray.forEach(function (node) {
// if (node && node.type === 'group') { // if (node && node.shape === 'group') {
// drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj, depth + 1); // drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj, depth + 1);
// } // }
}); });
}; };
const getNextPort = (node, edgeDirection, graphDirection) => {
log.info('getNextPort abc88', { node, edgeDirection, graphDirection });
if (!portPos[node]) {
switch (graphDirection) {
case 'TB':
case 'TD':
portPos[node] = {
inPosition: 'north',
outPosition: 'south',
};
break;
case 'BT':
portPos[node] = {
inPosition: 'south',
outPosition: 'north',
};
break;
case 'RL':
portPos[node] = {
inPosition: 'east',
outPosition: 'west',
};
break;
case 'LR':
portPos[node] = {
inPosition: 'west',
outPosition: 'east',
};
break;
}
}
const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition;
if (edgeDirection === 'in') {
portPos[node].inPosition = getNextPosition(
portPos[node].inPosition,
edgeDirection,
graphDirection
);
} else {
portPos[node].outPosition = getNextPosition(
portPos[node].outPosition,
edgeDirection,
graphDirection
);
}
return result;
};
const getEdgeStartEndPoint = (edge, dir) => { const getEdgeStartEndPoint = (edge, dir) => {
let source = edge.start; let source = edge.start;
let target = edge.end; let target = edge.end;
@@ -267,11 +335,11 @@ const getEdgeStartEndPoint = (edge, dir) => {
return { source, target }; return { source, target };
} }
if (startNode.type === 'diamond') { if (startnode.shape === 'diamond') {
source = `${source}-${getNextPort(source, 'out', dir)}`; source = `${source}-${getNextPort(source, 'out', dir)}`;
} }
if (endNode.type === 'diamond') { if (endnode.shape === 'diamond') {
target = `${target}-${getNextPort(target, 'in', dir)}`; target = `${target}-${getNextPort(target, 'in', dir)}`;
} }
@@ -514,7 +582,9 @@ export const render = async (data4Layout, svg, element) => {
// that is not in the dom so we need to add it to the dom, get the size // that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs // we will position the nodes when we get the layout from elkjs
const parentLookupDb = {}; const parentLookupDb = {};
graph = await addVertices(svg, data4Layout, parentLookupDb, graph); const nodeEl = svg.insert('g').attr('class', 'nodes');
graph = await addVertices(nodeEl, data4Layout.nodes, graph);
console.log('after addVertices abc88', JSON.stringify(graph, null, 2));
console.log('graph', graph, data4Layout); console.log('graph', graph, data4Layout);
@@ -596,11 +666,13 @@ export const render = async (data4Layout, svg, element) => {
// }); // });
// insertChildren(graph.children, parentLookupDb); // insertChildren(graph.children, parentLookupDb);
// log.info('after layout', JSON.stringify(graph, null, 2));
// console.log('before layout abc88', JSON.stringify(graph, null, 2));
const g = await elk.layout(graph); const g = await elk.layout(graph);
log.info('after layout', JSON.stringify(graph, null, 2));
console.log('after layout abc88', g);
// drawNodes(0, 0, g.children, svg, subGraphsEl, 0); // drawNodes(0, 0, g.children, svg, subGraphsEl, 0);
drawNodes(0, 0, g.children, svg, null, 0); drawNodes(0, 0, g.children, svg, null, 0);
console.log('after layout', g);
g.edges?.map((edge) => { g.edges?.map((edge) => {
// (elem, edge, clusterDb, diagramType, graph, id) // (elem, edge, clusterDb, diagramType, graph, id)
edge.start = nodeDb[edge.sources[0]]; edge.start = nodeDb[edge.sources[0]];

View File

@@ -419,7 +419,10 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, graph, i
// Currently only flowcharts get the curve from the settings, perhaps this should // Currently only flowcharts get the curve from the settings, perhaps this should
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that // be expanded to a common setting? Restricting it for now in order not to cause side-effects that
// have not been thought through // have not been thought through
if (edge.curve && (diagramType === 'graph' || diagramType === 'flowchart')) { if (
edge.curve &&
(diagramType === 'graph' || diagramType === 'flowchart' || diagramType === 'stateDiagram')
) {
curve = edge.curve; curve = edge.curve;
} }