Skip to content

Commit

Permalink
#5237 WIP, testing common layoutAndRender
Browse files Browse the repository at this point in the history
  • Loading branch information
knsv committed Apr 23, 2024
1 parent 6b7e122 commit 8205e36
Show file tree
Hide file tree
Showing 9 changed files with 692 additions and 518 deletions.
9 changes: 8 additions & 1 deletion cypress/platform/knsv2.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,17 @@
</head>
<body>
<pre id="diagram" class="mermaid">
stateDiagram-v2
Second
</pre
>
<pre id="diagram" class="mermaid2">
stateDiagram-v2
state First {
Second
} </pre>
}
</pre
>
<pre id="diagram" class="mermaid2">
flowchart TB
subgraph First
Expand Down
209 changes: 209 additions & 0 deletions packages/mermaid/src/dagre-wrapper/index-refactored.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import insertMarkers from './markers.js';
import { updateNodeBounds } from './shapes/util.js';
import {
clear as clearGraphlib,
clusterDb,
adjustClustersAndEdges,
findNonClusterChild,
sortNodesByHierarchy,
} from './mermaid-graphlib.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './nodes.js';
import { insertCluster, clear as clearClusters } from './clusters.js';
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js';
import { log } from '../logger.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
// import type { LayoutData, LayoutMethod } from '../rendering-util/types.js';
// import type { MermaidConfig } from '../config.type.js';

const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster, siteConfig) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);

const elem = _elem.insert('g').attr('class', 'root');
if (!graph.nodes()) {
log.info('No nodes found for', graph);
} else {
log.info('Recursive render XXX', graph.nodes());
}
if (graph.edges().length > 0) {
log.trace('Recursive edges', graph.edge(graph.edges()[0]));
}
const clusters = elem.insert('g').attr('class', 'clusters');
const edgePaths = elem.insert('g').attr('class', 'edgePaths');
const edgeLabels = elem.insert('g').attr('class', 'edgeLabels');
const nodes = elem.insert('g').attr('class', 'nodes');

// Insert nodes, this will insert them into the dom and each node will get a size. The size is updated
// to the abstract node and is later used by dagre for the layout
await Promise.all(
graph.nodes().map(async function (v) {
const node = graph.node(v);
if (parentCluster !== undefined) {
const data = JSON.parse(JSON.stringify(parentCluster.clusterData));
// data.clusterPositioning = true;
log.info('Setting data for cluster XXX (', v, ') ', data, parentCluster);
graph.setNode(parentCluster.id, data);
if (!graph.parent(v)) {
log.trace('Setting parent', v, parentCluster.id);
graph.setParent(v, parentCluster.id, data);
}
}
log.info('(Insert) Node XXX' + v + ': ' + JSON.stringify(graph.node(v)));
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified', v, node.width, graph.node(v));
const o = await recursiveRender(
nodes,
node.graph,
diagramtype,
id,
graph.node(v),
siteConfig
);
const newEl = o.elem;
updateNodeBounds(node, newEl);
node.diff = o.diff || 0;
log.info('Node bounds (abc123)', v, node, node.width, node.x, node.y);
setNodeElem(newEl, node);

log.warn('Recursive render complete ', newEl, node);
} else {
if (graph.children(v).length > 0) {
// This is a cluster but not to be rendered recursively
// Render as before
log.info('Cluster - the non recursive path XXX', v, node.id, node, graph);
log.info(findNonClusterChild(node.id, graph));
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
// insertCluster(clusters, graph.node(v));
} else {
log.info('Node - the non recursive path', v, node.id, node);
await insertNode(nodes, graph.node(v), dir);
}
}
})
);

// Insert labels, this will insert them into the dom so that the width can be calculated
// Also figure out which edges point to/from clusters and adjust them accordingly
// Edges from/to clusters really points to the first child in the cluster.
// TODO: pick optimal child in the cluster to us as link anchor
graph.edges().forEach(function (e) {
const edge = graph.edge(e.v, e.w, e.name);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));

// Check if link is either from or to a cluster
log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translateing: ', clusterDb[e.v], clusterDb[e.w]);
insertEdgeLabel(edgeLabels, edge);
});

graph.edges().forEach(function (e) {
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
});
log.info('#############################################');
log.info('### Layout ###');
log.info('#############################################');
log.info(graph);
dagreLayout(graph);
log.info('Graph after layout:', graphlibJson.write(graph));
// Move the nodes to the correct place
let diff = 0;
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
sortNodesByHierarchy(graph).forEach(function (v) {
const node = graph.node(v);
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
log.info(
'Position ' + v + ': (' + node.x,
',' + node.y,
') width: ',
node.width,
' height: ',
node.height
);
if (node && node.clusterNode) {
// clusterDb[node.id].node = node;
node.y += subGraphTitleTotalMargin;
positionNode(node);
} else {
// Non cluster node
if (graph.children(v).length > 0) {
// A cluster in the non-recursive way
// positionCluster(node);
node.height += subGraphTitleTotalMargin;
insertCluster(clusters, node);
clusterDb[node.id].node = node;
} else {
node.y += subGraphTitleTotalMargin / 2;
positionNode(node);
}
}
});

// Move the edge labels to the correct place after layout
graph.edges().forEach(function (e) {
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);

edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id);
positionEdgeLabel(edge, paths);
});

graph.nodes().forEach(function (v) {
const n = graph.node(v);
log.info(v, n.type, n.diff);
if (n.type === 'group') {
diff = n.diff;
}
});
return { elem, diff };
};

export const render = async (data4Layout, svg, element) => {
// Create the input mermaid.graph
const graph = new graphlib.Graph({
multigraph: true,
compound: true,
})
.setGraph({
rankdir: data4Layout.direction,
nodesep: data4Layout.nodeSpacing,
ranksep: data4Layout.rankSpacing,
marginx: 8,
marginy: 8,
})
.setDefaultEdgeLabel(function () {
return {};
});

// Org

insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
clearNodes();
clearEdges();
clearClusters();
clearGraphlib();

// Add the nodes and edges to the graph
data4Layout.nodes.forEach((node) => {
graph.setNode(node.id, { ...node });
});

log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
const siteConfig = getConfig();
await recursiveRender(
element,
graph,
data4Layout.type,
data4Layout.diagramId,
undefined,
siteConfig
);
};
29 changes: 21 additions & 8 deletions packages/mermaid/src/diagrams/state/stateDb.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,12 +545,26 @@ const dataFetcher = (parentId, doc, nodes, edges) => {
doc.forEach((item) => {
switch (item.stmt) {
case STMT_STATE:
if(parentId) {
nodes.push({...item, labelText: item.id, labelType:'text', parentId});
if (parentId) {
nodes.push({ ...item, labelText: item.id, labelType: 'text', parentId, shape: 'rect' });
} else {
nodes.push({...item, labelText: item.id, labelType:'text'});
nodes.push({
...item,
labelText: item.id,
// description: item.id,
labelType: 'text',
labelStyle: '',
shape: 'rect',
domId: 'state-bla-bla-bla',
x: 100,
y: 100,
height: 100,
width: 100,
padding: 15,
classes: ' statediagram-state',
});
}
if(item.doc) {
if (item.doc) {
dataFetcher(item.id, item.doc, nodes, edges);
}
break;
Expand All @@ -559,7 +573,7 @@ const dataFetcher = (parentId, doc, nodes, edges) => {
break;
}
});
}
};
export const getData = () => {
const nodes = [];
const edges = [];
Expand All @@ -571,9 +585,8 @@ export const getData = () => {
// }
dataFetcher(undefined, rootDoc, nodes, edges);


return {nodes, edges, other: {}};
}
return { nodes, edges, other: {} };
};

export default {
getConfig: () => getConfig().state,
Expand Down
4 changes: 2 additions & 2 deletions packages/mermaid/src/diagrams/state/stateDiagram-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
import parser from './parser/stateDiagram.jison';
import db from './stateDb.js';
import styles from './styles.js';
// import renderer from './stateRenderer-v2.js';
import renderer from './stateRenderer-v3-unified.js';
import renderer from './stateRenderer-v2.js';
// import renderer from './stateRenderer-v3-unified.js';

export const diagram: DiagramDefinition = {
parser,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { log } from '../../logger.js';
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
import type { LayoutData, LayoutMethod } from '../../rendering-util/types.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import doLayout from '../../rendering-util/doLayout.js';
import performRender from '../../rendering-util/performRender.js';
import insertElementsForSize, {
getDiagramElements,
} from '../../rendering-util/inserElementsForSize.js';

// Configuration
const conf: Record<string, any> = {};

export const setConf = function (cnf: Record<string, any>) {
const keys = Object.keys(cnf);
for (const key of keys) {
conf[key] = cnf[key];
}
};

export const getClasses = function (
text: string,
diagramObj: any
): Record<string, DiagramStyleClassDef> {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses();
};

export const draw = async function (text: string, id: string, _version: string, diag: any) {
log.info('Drawing state diagram (v2)', id);
const { securityLevel, state: conf } = getConfig();

// Extracting the data from the parsed structure into a more usable form
// Not related to the refactoring, but this is the first step in the rendering process
diag.db.extract(diag.db.getRootDocV2());

// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
// into the Layout data format
const data4Layout = diag.db.getData() as LayoutData;
// Create the root SVG
const { svg, element } = getDiagramElements(id, securityLevel);
// For some diagrams this call is not needed, but in the state diagram it is
await insertElementsForSize(element, data4Layout);

console.log('data4Layout:', data4Layout);

Check failure on line 45 in packages/mermaid/src/diagrams/state/stateRenderer-v3-unified-step-by-step.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Unexpected console statement

// Now we have layout data with real sizes, we can perform the layout
const data4Rendering = doLayout(data4Layout, id, _version, 'dagre-wrapper');

// The performRender method provided in all supported diagrams is used to render the data
performRender(data4Rendering);
};

export default {
setConf,
getClasses,
draw,
};
Loading

0 comments on commit 8205e36

Please sign in to comment.