fix review comments

This commit is contained in:
Ashish Jain 2024-07-19 14:09:31 +02:00
parent 2718cdae51
commit 86e4642046
9 changed files with 106 additions and 261 deletions

View File

@ -97,7 +97,7 @@ export const openURLAndVerifyRendering = (
const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
cy.visit(url);
// cy.window().should('have.property', 'rendered', true);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
if (validation) {

View File

@ -236,7 +236,7 @@ describe('Block diagram', () => {
);
});
it('BL16: width alignment - blocks shold be equal in width', () => {
it('BL17: width alignment - blocks shold be equal in width', () => {
imgSnapshotTest(
`block-beta
A("This is the text")
@ -247,7 +247,7 @@ describe('Block diagram', () => {
);
});
it('BL17: block types 1 - square, rounded and circle', () => {
it('BL18: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@ -258,7 +258,7 @@ describe('Block diagram', () => {
);
});
it('BL18: block types 2 - odd, diamond and hexagon', () => {
it('BL19: block types 2 - odd, diamond and hexagon', () => {
imgSnapshotTest(
`block-beta
A>"rect_left_inv_arrow"]
@ -269,7 +269,7 @@ describe('Block diagram', () => {
);
});
it('BL19: block types 3 - stadium', () => {
it('BL20: block types 3 - stadium', () => {
imgSnapshotTest(
`block-beta
A(["stadium"])
@ -278,7 +278,7 @@ describe('Block diagram', () => {
);
});
it('BL20: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
it('BL21: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
imgSnapshotTest(
`block-beta
A[/"lean right"/]
@ -290,7 +290,7 @@ describe('Block diagram', () => {
);
});
it('BL21: block types 1 - square, rounded and circle', () => {
it('BL22: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@ -301,7 +301,7 @@ describe('Block diagram', () => {
);
});
it('BL22: sizing - it should be possible to make a block wider', () => {
it('BL23: sizing - it should be possible to make a block wider', () => {
imgSnapshotTest(
`block-beta
A("rounded"):2
@ -312,7 +312,7 @@ describe('Block diagram', () => {
);
});
it('BL23: sizing - it should be possible to make a composite block wider', () => {
it('BL24: sizing - it should be possible to make a composite block wider', () => {
imgSnapshotTest(
`block-beta
block:2
@ -324,7 +324,7 @@ describe('Block diagram', () => {
);
});
it('BL24: block in the middle with space on each side', () => {
it('BL25: block in the middle with space on each side', () => {
imgSnapshotTest(
`block-beta
columns 3
@ -335,7 +335,7 @@ describe('Block diagram', () => {
{}
);
});
it('BL25: space and an edge', () => {
it('BL26: space and an edge', () => {
imgSnapshotTest(
`block-beta
columns 5
@ -345,7 +345,7 @@ describe('Block diagram', () => {
{}
);
});
it('BL26: block sizes for regular blocks', () => {
it('BL27: block sizes for regular blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
@ -354,7 +354,7 @@ describe('Block diagram', () => {
{}
);
});
it('BL27: composite block with a set width - f should use the available space', () => {
it('BL28: composite block with a set width - f should use the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
@ -363,11 +363,12 @@ describe('Block diagram', () => {
f
end
g
`,
`,
{}
);
});
it('BL23: composite block with a set width - f and g should split the available space', () => {
it('BL29: composite block with a set width - f and g should split the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
@ -379,7 +380,7 @@ describe('Block diagram', () => {
h
i
j
`,
`,
{}
);
});

View File

@ -1,7 +1,7 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('C4 diagram', () => {
it('should render a simple C4Context diagram', () => {
it('C4.1 should render a simple C4Context diagram', () => {
imgSnapshotTest(
`
C4Context
@ -31,7 +31,7 @@ describe('C4 diagram', () => {
{}
);
});
it('should render a simple C4Container diagram', () => {
it('C4.2 should render a simple C4Container diagram', () => {
imgSnapshotTest(
`
C4Container
@ -50,7 +50,7 @@ describe('C4 diagram', () => {
{}
);
});
it('should render a simple C4Component diagram', () => {
it('C4.3 should render a simple C4Component diagram', () => {
imgSnapshotTest(
`
C4Component
@ -68,7 +68,7 @@ describe('C4 diagram', () => {
{}
);
});
it('should render a simple C4Dynamic diagram', () => {
it('C4.4 should render a simple C4Dynamic diagram', () => {
imgSnapshotTest(
`
C4Dynamic
@ -91,7 +91,7 @@ describe('C4 diagram', () => {
{}
);
});
it('should render a simple C4Deployment diagram', () => {
it('C4.5 should render a simple C4Deployment diagram', () => {
imgSnapshotTest(
`
C4Deployment

View File

@ -76,7 +76,7 @@ describe('Class diagram V2', () => {
);
});
it('should render a simple class diagram with different visibilities', () => {
it('2.1 should render a simple class diagram with different visibilities', () => {
imgSnapshotTest(
`
classDiagram-v2
@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
);
});
it('should render multiple class diagrams', () => {
it('3: should render multiple class diagrams', () => {
imgSnapshotTest(
[
`

View File

@ -1,5 +1,4 @@
import mermaid from './mermaid.esm.mjs';
// import flowchartELK from './mermaid-flowchart-elk.esm.mjs';
import { layouts } from './mermaid-layout-elk.esm.mjs';
import externalExample from './mermaid-example-diagram.esm.mjs';
import zenUml from './mermaid-zenuml.esm.mjs';
@ -48,7 +47,6 @@ const contentLoaded = async function () {
document.getElementsByTagName('body')[0].appendChild(div);
}
// await mermaid.registerExternalDiagrams([externalExample, zenUml, flowchartELK]);
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
mermaid.registerLayoutLoaders(layouts);

View File

@ -198,9 +198,6 @@ function getClassesFromDbInfo(dbInfoItem) {
* Get classes from the db for the info item.
* If there aren't any or if dbInfoItem isn't defined, return an empty string.
* Else create 1 string from the list of classes found
*
* @param {undefined | null | object} dbInfoItem
* @returns {string}
*/
function getStylesFromDbInfo(dbInfoItem) {
if (dbInfoItem === undefined || dbInfoItem === null) {

View File

@ -121,7 +121,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
graph
);
log.info(findNonClusterChild(node.id, graph));
clusterDb[node.id] = { id: findNonClusterChild(node.id, graph), node };
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
// insertCluster(clusters, graph.node(v));
} else {
log.trace('Node - the non recursive path XAX', v, node.id, node);
@ -138,7 +138,16 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
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, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
log.info(
'Fix',
clusterDb,
'ids:',
e.v,
e.w,
'Translating: ',
clusterDb.get(e.v),
clusterDb.get(e.w)
);
await insertEdgeLabel(edgeLabels, edge);
});
@ -147,20 +156,6 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
await processEdges();
// // 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
// await graph.edges().forEach(async 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, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
// await insertEdgeLabel(edgeLabels, edge);
// });
log.info('Graph before layout:', JSON.stringify(graphlibJson.write(graph)));
log.info('############################################# XXX');
@ -198,7 +193,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
node.y,
graph.parent(v)
);
clusterDb[node.id].node = node;
clusterDb.get(node.id).node = node;
positionNode(node);
} else {
// A tainted cluster node
@ -222,7 +217,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
await insertCluster(clusters, node);
// A cluster in the non-recursive way
clusterDb[node.id].node = node;
clusterDb.get(node.id).node = node;
} else {
// Regular node
const parent = graph.node(node.parentId);
@ -255,7 +250,6 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
// OBS HERE
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
const startNode = graph.node(e.v);
var endNode = graph.node(e.w);
@ -273,17 +267,8 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
log.warn('Returning from recursive render XAX', elem, diff);
return { elem, diff };
};
/**
* ###############################################################
* Render the graph
* ###############################################################
* @param data4Layout
* @param svg
* @param element
*/
export const render = async (data4Layout, svg, element) => {
// Create the input mermaid.graph
// console.log('XYZ data4Layout', data4Layout);
const graph = new graphlib.Graph({
multigraph: true,
compound: true,
@ -305,15 +290,12 @@ export const render = async (data4Layout, svg, element) => {
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 });
if (node.parentId) {

View File

@ -3,39 +3,40 @@ import { log } from '$root/logger.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
export let clusterDb = {};
let descendants = {};
let parents = {};
export let clusterDb = new Map();
let descendants = new Map();
let parents = new Map();
export const clear = () => {
descendants = {};
parents = {};
clusterDb = {};
descendants.clear();
parents.clear();
clusterDb.clear();
};
const isDescendant = (id, ancestorId) => {
log.trace('In isDescendant', ancestorId, ' ', id, ' = ', descendants[ancestorId].includes(id));
return descendants[ancestorId].includes(id);
const ancestorDescendants = descendants.get(ancestorId) || [];
log.trace('In isDescendant', ancestorId, ' ', id, ' = ', ancestorDescendants.includes(id));
return ancestorDescendants.includes(id);
};
const edgeInCluster = (edge, clusterId) => {
log.info('Descendants of ', clusterId, ' is ', descendants[clusterId]);
const clusterDescendants = descendants.get(clusterId) || [];
log.info('Descendants of ', clusterId, ' is ', clusterDescendants);
log.info('Edge is ', edge);
// Edges to/from the cluster is not in the cluster, they are in the parent
if (edge.v === clusterId || edge.w === clusterId) {
return false;
}
if (!descendants[clusterId]) {
if (!clusterDescendants) {
log.debug('Tilt, ', clusterId, ',not in descendants');
return false;
}
return (
descendants[clusterId].includes(edge.v) ||
clusterDescendants.includes(edge.v) ||
isDescendant(edge.v, clusterId) ||
isDescendant(edge.w, clusterId) ||
descendants[clusterId].includes(edge.w)
clusterDescendants.includes(edge.w)
);
};
@ -51,7 +52,6 @@ const copy = (clusterId, graph, newGraph, rootId) => {
);
const nodes = graph.children(clusterId) || [];
// Include cluster node if it is not the root
if (clusterId !== rootId) {
nodes.push(clusterId);
}
@ -63,7 +63,7 @@ const copy = (clusterId, graph, newGraph, rootId) => {
copy(node, graph, newGraph, rootId);
} else {
const data = graph.node(node);
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId); //,node, data, ' parent is ', clusterId);
log.info('cp ', node, ' to ', rootId, ' with parent ', clusterId);
newGraph.setNode(node, data);
if (rootId !== graph.parent(node)) {
log.warn('Setting parent', node, graph.parent(node));
@ -91,7 +91,6 @@ const copy = (clusterId, graph, newGraph, rootId) => {
const data = graph.edge(edge.v, edge.w, edge.name);
log.info('Edge data', data, rootId);
try {
// Do not copy edges in and out of the root cluster, they belong to the parent graph
if (edgeInCluster(edge, rootId)) {
log.info('Copying as ', edge.v, edge.w, data, edge.name);
newGraph.setEdge(edge.v, edge.w, data, edge.name);
@ -117,25 +116,19 @@ const copy = (clusterId, graph, newGraph, rootId) => {
graph.removeNode(node);
});
};
export const extractDescendants = (id, graph) => {
// log.debug('Extracting ', id);
const children = graph.children(id);
let res = [...children];
for (const child of children) {
parents[child] = id;
parents.set(child, id);
res = [...res, ...extractDescendants(child, graph)];
}
return res;
};
/**
* Validates the graph, checking that all parent child relation points to existing nodes and that
* edges between nodes also ia correct. When not correct the function logs the discrepancies.
*
* @param graph
*/
export const validate = (graph) => {
const edges = graph.edges();
log.trace('Edges: ', edges);
@ -168,13 +161,6 @@ const findCommonEdges = (graph, id1, id2) => {
return result;
};
/**
* Finds a child that is not a cluster. When faking an edge between a node and a cluster.
*
* @param id
* @param {any} graph
* @param {string} clusterId
*/
export const findNonClusterChild = (id, graph, clusterId) => {
const children = graph.children(id);
log.trace('Searching children of id ', id, children);
@ -185,19 +171,10 @@ export const findNonClusterChild = (id, graph, clusterId) => {
for (const child of children) {
const _id = findNonClusterChild(child, graph, clusterId);
// Edge chase where the cluster has an edge to a node and the selected
// child has a link to the same node
const commonEdges = findCommonEdges(graph, clusterId, _id);
if (_id) {
if (commonEdges.length > 0) {
// console.log(
// '\x1B[44;93;4m abc24 The replacement also has an edge',
// clusterId,
// ' => ',
// _id,
// graph.edges()
// );
reserve = _id;
} else {
return _id;
@ -208,17 +185,15 @@ export const findNonClusterChild = (id, graph, clusterId) => {
};
const getAnchorId = (id) => {
if (!clusterDb[id]) {
if (!clusterDb.has(id)) {
return id;
}
// If the cluster has no external connections
if (!clusterDb[id].externalConnections) {
if (!clusterDb.get(id).externalConnections) {
return id;
}
// Return the replacement node
if (clusterDb[id]) {
return clusterDb[id].id;
if (clusterDb.has(id)) {
return clusterDb.get(id).id;
}
return id;
};
@ -230,8 +205,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
} else {
log.debug('Opting in, graph ');
}
// Go through the nodes and for each cluster found, save a replacement node, this can be used when
// faking a link to a cluster
graph.nodes().forEach(function (id) {
const children = graph.children(id);
if (children.length > 0) {
@ -241,34 +215,24 @@ export const adjustClustersAndEdges = (graph, depth) => {
' Replacement id in edges: ',
findNonClusterChild(id, graph, id)
);
descendants[id] = extractDescendants(id, graph);
clusterDb[id] = { id: findNonClusterChild(id, graph, id), clusterData: graph.node(id) };
descendants.set(id, extractDescendants(id, graph));
clusterDb.set(id, { id: findNonClusterChild(id, graph, id), clusterData: graph.node(id) });
}
});
// Check incoming and outgoing edges for each cluster
graph.nodes().forEach(function (id) {
const children = graph.children(id);
const edges = graph.edges();
if (children.length > 0) {
log.debug('Cluster identified', id, descendants);
edges.forEach((edge) => {
// log.debug('Edge, descendants: ', edge, descendants[id]);
const d1 = isDescendant(edge.v, id);
const d2 = isDescendant(edge.w, id);
// Check if any edge leaves the cluster (not the actual cluster, that's a link from the box)
if (edge.v !== id && edge.w !== id) {
// Any edge where either the one of the nodes is descending to the cluster but not the other
// if (descendants[id].indexOf(edge.v) < 0 && descendants[id].indexOf(edge.w) < 0) {
const d1 = isDescendant(edge.v, id);
const d2 = isDescendant(edge.w, id);
// d1 xor d2 - if either d1 is true and d2 is false or the other way around
if (d1 ^ d2) {
log.warn('Edge: ', edge, ' leaves cluster ', id);
log.warn('Descendants of XXX ', id, ': ', descendants[id]);
clusterDb[id].externalConnections = true;
}
if (d1 ^ d2) {
log.warn('Edge: ', edge, ' leaves cluster ', id);
log.warn('Descendants of XXX ', id, ': ', descendants.get(id));
clusterDb.get(id).externalConnections = true;
}
});
} else {
@ -276,18 +240,15 @@ export const adjustClustersAndEdges = (graph, depth) => {
}
});
for (let id of Object.keys(clusterDb)) {
const nonClusterChild = clusterDb[id].id;
for (let id of clusterDb.keys()) {
const nonClusterChild = clusterDb.get(id).id;
const parent = graph.parent(nonClusterChild);
// Change replacement node of id to parent of current replacement node if valid
if (parent !== id && clusterDb[parent] && !clusterDb[parent].externalConnections) {
clusterDb[id].id = parent;
if (parent !== id && clusterDb.has(parent) && !clusterDb.get(parent).externalConnections) {
clusterDb.get(id).id = parent;
}
}
// For clusters with incoming and/or outgoing edges translate those edges to a real node
// in the cluster in order to fake the edge
graph.edges().forEach(function (e) {
const edge = graph.edge(e);
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
@ -295,7 +256,6 @@ export const adjustClustersAndEdges = (graph, depth) => {
let v = e.v;
let w = e.w;
// Check if link is either from or to a cluster
log.warn(
'Fix XXX',
clusterDb,
@ -303,14 +263,13 @@ export const adjustClustersAndEdges = (graph, depth) => {
e.v,
e.w,
'Translating: ',
clusterDb[e.v],
clusterDb.get(e.v),
' --- ',
clusterDb[e.w]
clusterDb.get(e.w)
);
if (clusterDb[e.v] && clusterDb[e.w] && clusterDb[e.v] === clusterDb[e.w]) {
// cspell:ignore trixing
log.warn('Fixing and trixing link to self - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
@ -352,19 +311,19 @@ export const adjustClustersAndEdges = (graph, depth) => {
graph.setEdge(v, specialId1, edge1, e.name + '-cyclic-special-0');
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
} else if (clusterDb[e.v] || clusterDb[e.w]) {
log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
} else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
if (v !== e.v) {
const parent = graph.parent(v);
clusterDb[parent].externalConnections = true;
clusterDb.get(parent).externalConnections = true;
edge.fromCluster = e.v;
}
if (w !== e.w) {
const parent = graph.parent(w);
clusterDb[parent].externalConnections = true;
clusterDb.get(parent).externalConnections = true;
edge.toCluster = e.w;
}
log.warn('Fix Replacing with XXX', v, w, e.name);
@ -390,9 +349,6 @@ export const extractor = (graph, depth) => {
log.error('Bailing out');
return;
}
// For clusters without incoming and/or outgoing edges, create a new cluster-node
// containing the nodes and edges in the custer in a new graph
// for (let i = 0;)
let nodes = graph.nodes();
let hasChildren = false;
for (const node of nodes) {
@ -404,30 +360,23 @@ export const extractor = (graph, depth) => {
log.debug('Done, no node has children', graph.nodes());
return;
}
// const clusters = Object.keys(clusterDb);
// clusters.forEach(clusterId => {
log.debug('Nodes = ', nodes, depth);
for (const node of nodes) {
log.debug(
'Extracting node',
node,
clusterDb,
clusterDb[node] && !clusterDb[node].externalConnections,
clusterDb.has(node) && !clusterDb.get(node).externalConnections,
!graph.parent(node),
graph.node(node),
graph.children('D'),
' Depth ',
depth
);
// Note that the node might have been removed after the Object.keys call so better check
// that it still is in the game
if (!clusterDb[node]) {
// Skip if the node is not a cluster
if (!clusterDb.has(node)) {
log.debug('Not a cluster', node, depth);
// break;
} else if (
!clusterDb[node].externalConnections &&
// !graph.parent(node) &&
!clusterDb.get(node).externalConnections &&
graph.children(node) &&
graph.children(node).length > 0
) {
@ -439,9 +388,9 @@ export const extractor = (graph, depth) => {
const graphSettings = graph.graph();
let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB';
if (clusterDb[node]?.clusterData?.dir) {
dir = clusterDb[node].clusterData.dir;
log.warn('Fixing dir', clusterDb[node].clusterData.dir, dir);
if (clusterDb.get(node)?.clusterData?.dir) {
dir = clusterDb.get(node).clusterData.dir;
log.warn('Fixing dir', clusterDb.get(node).clusterData.dir, dir);
}
const clusterGraph = new graphlib.Graph({
@ -449,7 +398,7 @@ export const extractor = (graph, depth) => {
compound: true,
})
.setGraph({
rankdir: dir, // Todo: set proper spacing
rankdir: dir,
nodesep: 50,
ranksep: 50,
marginx: 8,
@ -464,8 +413,8 @@ export const extractor = (graph, depth) => {
graph.setNode(node, {
clusterNode: true,
id: node,
clusterData: clusterDb[node].clusterData,
label: clusterDb[node].label,
clusterData: clusterDb.get(node).clusterData,
label: clusterDb.get(node).label,
graph: clusterGraph,
});
log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph));
@ -475,7 +424,7 @@ export const extractor = (graph, depth) => {
'Cluster ** ',
node,
' **not meeting the criteria !externalConnections:',
!clusterDb[node].externalConnections,
!clusterDb.get(node).externalConnections,
' no parent: ',
!graph.parent(node),
' children ',
@ -502,7 +451,7 @@ const sorter = (graph, nodes) => {
if (nodes.length === 0) {
return [];
}
let result = Object.assign(nodes);
let result = Object.assign([], nodes);
nodes.forEach((node) => {
const children = graph.children(node);
const sorted = sorter(graph, children);

View File

@ -154,7 +154,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x;
let y = edge.y;
if (path) {
// // debugger;
const pos = utils.calcLabelPosition(path);
log.debug(
'Moving label ' + edge.label + ' from (',
@ -175,13 +174,11 @@ export const positionEdgeLabel = (edge, paths) => {
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
}
//let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
if (edge.startLabelLeft) {
const el = terminalLabels.get(edge.id).startLeft;
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeStart ? 10 : 0, 'start_left', path);
x = pos.x;
y = pos.y;
@ -193,7 +190,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(
edge.arrowTypeStart ? 10 : 0,
'start_right',
@ -209,7 +205,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x;
let y = edge.y;
if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_left', path);
x = pos.x;
y = pos.y;
@ -275,21 +270,18 @@ export const intersection = (node, outsidePoint, insidePoint) => {
res.y = outsidePoint.y;
}
log.debug(`abc89 topp/bott calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res); // cspell: disable-line
log.debug(`abc89 top/bottom calc, Q ${Q}, q ${q}, R ${R}, r ${r}`, res);
return res;
} else {
// Intersection onn sides of rect
// Intersection on sides of rect
if (insidePoint.x < outsidePoint.x) {
r = outsidePoint.x - w - x;
} else {
r = x - w - outsidePoint.x;
}
let q = (Q * r) / R;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x + dx - w;
// OK let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
// let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : outsidePoint.x + r;
let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
if (r === 0) {
@ -306,49 +298,34 @@ export const intersection = (node, outsidePoint, insidePoint) => {
return { x: _x, y: _y };
}
};
/**
* This function will page a path and node where the last point(s) in the path is inside the node
* and return an update path ending by the border of the node.
*
* @param {Array} _points
* @param {any} boundaryNode
* @returns {Array} Points
*/
const cutPathAtIntersect = (_points, boundaryNode) => {
log.warn('abc88 cutPathAtIntersect', _points, boundaryNode);
let points = [];
let lastPointOutside = _points[0];
let isInside = false;
_points.forEach((point) => {
// const node = clusterDb[edge.toCluster].node;
log.info('abc88 checking point', point, boundaryNode);
// check if point is inside the boundary rect
if (!outsideNode(boundaryNode, point) && !isInside) {
// First point inside the rect found
// Calc the intersection coord between the point anf the last point outside the rect
const inter = intersection(boundaryNode, lastPointOutside, point);
log.debug('abc88 inside', point, lastPointOutside, inter);
log.debug('abc88 intersection', inter, boundaryNode);
// // Check case where the intersection is the same as the last point
let pointPresent = false;
points.forEach((p) => {
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
});
// // if (!pointPresent) {
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
points.push(inter);
} else {
log.warn('abc88 no intersect', inter, points);
}
// points.push(inter);
isInside = true;
} else {
// Outside
log.warn('abc88 outside', point, lastPointOutside);
lastPointOutside = point;
// points.push(point);
if (!isInside) {
points.push(point);
}
@ -358,13 +335,6 @@ const cutPathAtIntersect = (_points, boundaryNode) => {
return points;
};
/**
* Given an edge, this function will return the corner points of the edge. This is defined as:
* one point that has a previous point and a next point such as the angle between the previous
* point and the next point is 90 degrees. Meaning that the previous point has the same x coordinate
* as the center point and at the same time the next point has the same y coordinate or vice versa.
* @param points
*/
function extractCornerPoints(points) {
const cornerPoints = [];
const cornerPointPositions = [];
@ -401,11 +371,6 @@ const findAdjacentPoint = function (pointA, pointB, distance) {
return { x: pointB.x - ratio * xDiff, y: pointB.y - ratio * yDiff };
};
/**
* Given an array of points, this function will return a new array of points where the corners have been removed and replaced with
* adjacent points in each direction. SO a corder will be replaced with a point before and the point after the corner.
*/
const fixCorners = function (lineData) {
const { cornerPointPositions } = extractCornerPoints(lineData);
const newLineData = [];
@ -415,7 +380,6 @@ const fixCorners = function (lineData) {
const nextPoint = lineData[i + 1];
const cornerPoint = lineData[i];
// Find a new point on the line point 5 points back and push it to the new array
const newPrevPoint = findAdjacentPoint(prevPoint, cornerPoint, 5);
const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5);
@ -466,9 +430,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
var head = endNode;
if (head.intersect && tail.intersect) {
// log.info('abc88 InsertEdge: 0.5', edge.start, '-->', edge.end, JSON.stringify(points));
points = points.slice(1, edge.points.length - 1);
// log.info('abc88 InsertEdge APA12: 0.7', edge.start, '-->', edge.end, JSON.stringify(points));
points.unshift(tail.intersect(points[0]));
log.debug(
'Last point APA12',
@ -481,59 +443,43 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
);
points.push(head.intersect(points[points.length - 1]));
}
// log.info('abc88 InsertEdge 2 SPLIT: ', points);
if (edge.toCluster) {
log.info('to cluster abc88', clusterDb[edge.toCluster]);
points = cutPathAtIntersect(edge.points, clusterDb[edge.toCluster].node);
log.info('to cluster abc88', clusterDb.get(edge.toCluster));
points = cutPathAtIntersect(edge.points, clusterDb.get(edge.toCluster).node);
pointsHasChanged = true;
}
if (edge.fromCluster) {
log.debug('from cluster abc88', clusterDb[edge.fromCluster], JSON.stringify(points, null, 2));
points = cutPathAtIntersect(points.reverse(), clusterDb[edge.fromCluster].node).reverse();
// log.info('from cluster abc88 fixed', JSON.stringify(points, null, 2));
log.debug(
'from cluster abc88',
clusterDb.get(edge.fromCluster),
JSON.stringify(points, null, 2)
);
points = cutPathAtIntersect(points.reverse(), clusterDb.get(edge.fromCluster).node).reverse();
pointsHasChanged = true;
}
// The data for our line
let lineData = points.filter((p) => !Number.isNaN(p.y));
// const { cornerPoints, cornerPointPositions } = extractCornerPoints(lineData);
lineData = fixCorners(lineData);
let lastPoint = lineData[lineData.length - 1];
if (lineData.length > 1) {
lastPoint = lineData[lineData.length - 1];
const secondLastPoint = lineData[lineData.length - 2];
// Calculate the mid point of the last two points
const diffX = (lastPoint.x - secondLastPoint.x) / 2;
const diffY = (lastPoint.y - secondLastPoint.y) / 2;
const midPoint = { x: secondLastPoint.x + diffX, y: secondLastPoint.y + diffY };
lineData.splice(-1, 0, midPoint);
}
// This is the accessor function we talked about above
let curve = curveBasis;
// curve = curveCardinal;
// curve = curveLinear;
// curve = curveNatural;
// curve = curveCatmullRom.alpha(0.5);
// curve = curveCatmullRom;
// curve = curveCardinal.tension(0.7);
// curve = curveMonotoneY;
// let curve = interpolateToCurve([5], curveNatural, 0.01, 10);
// 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
// have not been thought through
if (edge.curve) {
curve = edge.curve;
}
const { x, y } = getLineFunctionsWithOffset(edge);
// const lineFunction = edge.curve ? line().x(x).y(y).curve(curve) : roundedCornersLine;
const lineFunction = line().x(x).y(y).curve(curve);
// Construct stroke classes based on properties
let strokeClasses;
switch (edge.thickness) {
case 'normal':
@ -563,16 +509,11 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
}
let svgPath;
let linePath = lineFunction(lineData);
//convert to array (if not already)
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
if (edge.look === 'handdrawn') {
const rc = rough.svg(elem);
Object.assign([], lineData);
// const svgPathNode = rc.path(lineFunction(ld.splice(0, ld.length-1)), {
// const svgPathNode = rc.path(lineFunction(ld), {
// roughness: 0.3,
// seed: handdrawnSeed,
// });
const svgPathNode = rc.path(linePath, {
roughness: 0.3,
seed: handdrawnSeed,
@ -582,7 +523,6 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
svgPath = select(svgPathNode)
.select('path')
// .attr('d', lineFunction(lineData))
.attr('id', edge.id)
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
@ -597,29 +537,8 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
}
// DEBUG code, adds a red circle at each edge coordinate
// cornerPoints.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'blue')
// .style('fill', 'blue')
// .attr('r', 3)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
// lineData.forEach((point) => {
// elem
// .append('circle')
// .style('stroke', 'red')
// .style('fill', 'red')
// .attr('r', 1)
// .attr('cx', point.x)
// .attr('cy', point.y);
// });
let url = '';
// // TODO: Can we load this config only from the rendered graph type?
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url =
window.location.protocol +
@ -627,8 +546,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
window.location.host +
window.location.pathname +
window.location.search;
url = url.replace(/\(/g, '\\(');
url = url.replace(/\)/g, '\\)');
url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
}
log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd);