Merge branch '5237-unified-layout-common-renderer' into knsv/new-shapes

This commit is contained in:
Knut Sveidqvist 2024-07-19 16:09:47 +02:00
commit 82c247bb11
10 changed files with 106 additions and 262 deletions

View File

@ -97,7 +97,7 @@ export const openURLAndVerifyRendering = (
const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-'); const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
cy.visit(url); cy.visit(url);
// cy.window().should('have.property', 'rendered', true); cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible'); cy.get('svg').should('be.visible');
if (validation) { 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( imgSnapshotTest(
`block-beta `block-beta
A("This is the text") 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( imgSnapshotTest(
`block-beta `block-beta
A["square"] 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( imgSnapshotTest(
`block-beta `block-beta
A>"rect_left_inv_arrow"] 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( imgSnapshotTest(
`block-beta `block-beta
A(["stadium"]) 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( imgSnapshotTest(
`block-beta `block-beta
A[/"lean right"/] 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( imgSnapshotTest(
`block-beta `block-beta
A["square"] 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( imgSnapshotTest(
`block-beta `block-beta
A("rounded"):2 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( imgSnapshotTest(
`block-beta `block-beta
block:2 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( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@ -335,7 +335,7 @@ describe('Block diagram', () => {
{} {}
); );
}); });
it('BL25: space and an edge', () => { it('BL26: space and an edge', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 5 columns 5
@ -345,7 +345,7 @@ describe('Block diagram', () => {
{} {}
); );
}); });
it('BL26: block sizes for regular blocks', () => { it('BL27: block sizes for regular blocks', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 3 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( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@ -363,11 +363,12 @@ describe('Block diagram', () => {
f f
end end
g 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( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@ -379,7 +380,7 @@ describe('Block diagram', () => {
h h
i i
j j
`, `,
{} {}
); );
}); });

View File

@ -1,7 +1,7 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('C4 diagram', () => { describe('C4 diagram', () => {
it('should render a simple C4Context diagram', () => { it('C4.1 should render a simple C4Context diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Context 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( imgSnapshotTest(
` `
C4Container 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( imgSnapshotTest(
` `
C4Component 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( imgSnapshotTest(
` `
C4Dynamic 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( imgSnapshotTest(
` `
C4Deployment 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( imgSnapshotTest(
` `
classDiagram-v2 classDiagram-v2
@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
); );
}); });
it('should render multiple class diagrams', () => { it('3: should render multiple class diagrams', () => {
imgSnapshotTest( imgSnapshotTest(
[ [
` `

View File

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

View File

@ -925,7 +925,6 @@ export const intersection = (node, outsidePoint, insidePoint) => {
} }
}; };
const outsideNode = (node, point) => { const outsideNode = (node, point) => {
console.log('Checking bounds ', node, point);
const x = node.x; const x = node.x;
const y = node.y; const y = node.y;
const dx = Math.abs(point.x - x); const dx = Math.abs(point.x - x);

View File

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

View File

@ -121,7 +121,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
graph graph
); );
log.info(findNonClusterChild(node.id, 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)); // insertCluster(clusters, graph.node(v));
} else { } else {
log.trace('Node - the non recursive path XAX', v, node.id, node); 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))); log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster // 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); await insertEdgeLabel(edgeLabels, edge);
}); });
@ -147,20 +156,6 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
await processEdges(); 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('Graph before layout:', JSON.stringify(graphlibJson.write(graph)));
log.info('############################################# XXX'); log.info('############################################# XXX');
@ -198,7 +193,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
node.y, node.y,
graph.parent(v) graph.parent(v)
); );
clusterDb[node.id].node = node; clusterDb.get(node.id).node = node;
positionNode(node); positionNode(node);
} else { } else {
// A tainted cluster node // A tainted cluster node
@ -222,7 +217,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
await insertCluster(clusters, node); await insertCluster(clusters, node);
// A cluster in the non-recursive way // A cluster in the non-recursive way
clusterDb[node.id].node = node; clusterDb.get(node.id).node = node;
} else { } else {
// Regular node // Regular node
const parent = graph.node(node.parentId); const parent = graph.node(node.parentId);
@ -255,7 +250,6 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
const edge = graph.edge(e); const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge); log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
// OBS HERE
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2)); edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
const startNode = graph.node(e.v); const startNode = graph.node(e.v);
var endNode = graph.node(e.w); 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); log.warn('Returning from recursive render XAX', elem, diff);
return { elem, diff }; return { elem, diff };
}; };
/**
* ###############################################################
* Render the graph
* ###############################################################
* @param data4Layout
* @param svg
* @param element
*/
export const render = async (data4Layout, svg, element) => { export const render = async (data4Layout, svg, element) => {
// Create the input mermaid.graph
// console.log('XYZ data4Layout', data4Layout);
const graph = new graphlib.Graph({ const graph = new graphlib.Graph({
multigraph: true, multigraph: true,
compound: true, compound: true,
@ -305,15 +290,12 @@ export const render = async (data4Layout, svg, element) => {
return {}; return {};
}); });
// Org
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId); insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
clearNodes(); clearNodes();
clearEdges(); clearEdges();
clearClusters(); clearClusters();
clearGraphlib(); clearGraphlib();
// Add the nodes and edges to the graph
data4Layout.nodes.forEach((node) => { data4Layout.nodes.forEach((node) => {
graph.setNode(node.id, { ...node }); graph.setNode(node.id, { ...node });
if (node.parentId) { 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 graphlib from 'dagre-d3-es/src/graphlib/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js'; import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
export let clusterDb = {}; export let clusterDb = new Map();
let descendants = {}; let descendants = new Map();
let parents = {}; let parents = new Map();
export const clear = () => { export const clear = () => {
descendants = {}; descendants.clear();
parents = {}; parents.clear();
clusterDb = {}; clusterDb.clear();
}; };
const isDescendant = (id, ancestorId) => { const isDescendant = (id, ancestorId) => {
log.trace('In isDescendant', ancestorId, ' ', id, ' = ', descendants[ancestorId].includes(id)); const ancestorDescendants = descendants.get(ancestorId) || [];
return descendants[ancestorId].includes(id); log.trace('In isDescendant', ancestorId, ' ', id, ' = ', ancestorDescendants.includes(id));
return ancestorDescendants.includes(id);
}; };
const edgeInCluster = (edge, clusterId) => { 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); 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) { if (edge.v === clusterId || edge.w === clusterId) {
return false; return false;
} }
if (!descendants[clusterId]) { if (!clusterDescendants) {
log.debug('Tilt, ', clusterId, ',not in descendants'); log.debug('Tilt, ', clusterId, ',not in descendants');
return false; return false;
} }
return ( return (
descendants[clusterId].includes(edge.v) || clusterDescendants.includes(edge.v) ||
isDescendant(edge.v, clusterId) || isDescendant(edge.v, clusterId) ||
isDescendant(edge.w, 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) || []; const nodes = graph.children(clusterId) || [];
// Include cluster node if it is not the root
if (clusterId !== rootId) { if (clusterId !== rootId) {
nodes.push(clusterId); nodes.push(clusterId);
} }
@ -63,7 +63,7 @@ const copy = (clusterId, graph, newGraph, rootId) => {
copy(node, graph, newGraph, rootId); copy(node, graph, newGraph, rootId);
} else { } else {
const data = graph.node(node); 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); newGraph.setNode(node, data);
if (rootId !== graph.parent(node)) { if (rootId !== graph.parent(node)) {
log.warn('Setting parent', node, 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); const data = graph.edge(edge.v, edge.w, edge.name);
log.info('Edge data', data, rootId); log.info('Edge data', data, rootId);
try { try {
// Do not copy edges in and out of the root cluster, they belong to the parent graph
if (edgeInCluster(edge, rootId)) { if (edgeInCluster(edge, rootId)) {
log.info('Copying as ', edge.v, edge.w, data, edge.name); log.info('Copying as ', edge.v, edge.w, data, edge.name);
newGraph.setEdge(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); graph.removeNode(node);
}); });
}; };
export const extractDescendants = (id, graph) => { export const extractDescendants = (id, graph) => {
// log.debug('Extracting ', id);
const children = graph.children(id); const children = graph.children(id);
let res = [...children]; let res = [...children];
for (const child of children) { for (const child of children) {
parents[child] = id; parents.set(child, id);
res = [...res, ...extractDescendants(child, graph)]; res = [...res, ...extractDescendants(child, graph)];
} }
return res; 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) => { export const validate = (graph) => {
const edges = graph.edges(); const edges = graph.edges();
log.trace('Edges: ', edges); log.trace('Edges: ', edges);
@ -168,13 +161,6 @@ const findCommonEdges = (graph, id1, id2) => {
return result; 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) => { export const findNonClusterChild = (id, graph, clusterId) => {
const children = graph.children(id); const children = graph.children(id);
log.trace('Searching children of id ', id, children); log.trace('Searching children of id ', id, children);
@ -185,19 +171,10 @@ export const findNonClusterChild = (id, graph, clusterId) => {
for (const child of children) { for (const child of children) {
const _id = findNonClusterChild(child, graph, clusterId); 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); const commonEdges = findCommonEdges(graph, clusterId, _id);
if (_id) { if (_id) {
if (commonEdges.length > 0) { if (commonEdges.length > 0) {
// console.log(
// '\x1B[44;93;4m abc24 The replacement also has an edge',
// clusterId,
// ' => ',
// _id,
// graph.edges()
// );
reserve = _id; reserve = _id;
} else { } else {
return _id; return _id;
@ -208,17 +185,15 @@ export const findNonClusterChild = (id, graph, clusterId) => {
}; };
const getAnchorId = (id) => { const getAnchorId = (id) => {
if (!clusterDb[id]) { if (!clusterDb.has(id)) {
return id; return id;
} }
// If the cluster has no external connections if (!clusterDb.get(id).externalConnections) {
if (!clusterDb[id].externalConnections) {
return id; return id;
} }
// Return the replacement node if (clusterDb.has(id)) {
if (clusterDb[id]) { return clusterDb.get(id).id;
return clusterDb[id].id;
} }
return id; return id;
}; };
@ -230,8 +205,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
} else { } else {
log.debug('Opting in, graph '); 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) { graph.nodes().forEach(function (id) {
const children = graph.children(id); const children = graph.children(id);
if (children.length > 0) { if (children.length > 0) {
@ -241,34 +215,24 @@ export const adjustClustersAndEdges = (graph, depth) => {
' Replacement id in edges: ', ' Replacement id in edges: ',
findNonClusterChild(id, graph, id) findNonClusterChild(id, graph, id)
); );
descendants[id] = extractDescendants(id, graph); descendants.set(id, extractDescendants(id, graph));
clusterDb[id] = { id: findNonClusterChild(id, graph, id), clusterData: graph.node(id) }; 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) { graph.nodes().forEach(function (id) {
const children = graph.children(id); const children = graph.children(id);
const edges = graph.edges(); const edges = graph.edges();
if (children.length > 0) { if (children.length > 0) {
log.debug('Cluster identified', id, descendants); log.debug('Cluster identified', id, descendants);
edges.forEach((edge) => { 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 (d1 ^ d2) {
if (edge.v !== id && edge.w !== id) { log.warn('Edge: ', edge, ' leaves cluster ', id);
// Any edge where either the one of the nodes is descending to the cluster but not the other log.warn('Descendants of XXX ', id, ': ', descendants.get(id));
// if (descendants[id].indexOf(edge.v) < 0 && descendants[id].indexOf(edge.w) < 0) { clusterDb.get(id).externalConnections = true;
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;
}
} }
}); });
} else { } else {
@ -276,18 +240,15 @@ export const adjustClustersAndEdges = (graph, depth) => {
} }
}); });
for (let id of Object.keys(clusterDb)) { for (let id of clusterDb.keys()) {
const nonClusterChild = clusterDb[id].id; const nonClusterChild = clusterDb.get(id).id;
const parent = graph.parent(nonClusterChild); const parent = graph.parent(nonClusterChild);
// Change replacement node of id to parent of current replacement node if valid if (parent !== id && clusterDb.has(parent) && !clusterDb.get(parent).externalConnections) {
if (parent !== id && clusterDb[parent] && !clusterDb[parent].externalConnections) { clusterDb.get(id).id = parent;
clusterDb[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) { graph.edges().forEach(function (e) {
const edge = graph.edge(e); const edge = graph.edge(e);
log.warn('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(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 v = e.v;
let w = e.w; let w = e.w;
// Check if link is either from or to a cluster
log.warn( log.warn(
'Fix XXX', 'Fix XXX',
clusterDb, clusterDb,
@ -303,14 +263,13 @@ export const adjustClustersAndEdges = (graph, depth) => {
e.v, e.v,
e.w, e.w,
'Translating: ', '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]) { if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
// cspell:ignore trixing log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trixing link to self - removing XXX', e.v, e.w, e.name); log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
log.warn('Fixing and trixing - removing XXX', e.v, e.w, e.name);
v = getAnchorId(e.v); v = getAnchorId(e.v);
w = getAnchorId(e.w); w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name); 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(v, specialId1, edge1, e.name + '-cyclic-special-0');
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1'); graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2'); graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
} else if (clusterDb[e.v] || clusterDb[e.w]) { } else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
log.warn('Fixing and trixing - 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); v = getAnchorId(e.v);
w = getAnchorId(e.w); w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name); graph.removeEdge(e.v, e.w, e.name);
if (v !== e.v) { if (v !== e.v) {
const parent = graph.parent(v); const parent = graph.parent(v);
clusterDb[parent].externalConnections = true; clusterDb.get(parent).externalConnections = true;
edge.fromCluster = e.v; edge.fromCluster = e.v;
} }
if (w !== e.w) { if (w !== e.w) {
const parent = graph.parent(w); const parent = graph.parent(w);
clusterDb[parent].externalConnections = true; clusterDb.get(parent).externalConnections = true;
edge.toCluster = e.w; edge.toCluster = e.w;
} }
log.warn('Fix Replacing with XXX', v, w, e.name); log.warn('Fix Replacing with XXX', v, w, e.name);
@ -390,9 +349,6 @@ export const extractor = (graph, depth) => {
log.error('Bailing out'); log.error('Bailing out');
return; 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 nodes = graph.nodes();
let hasChildren = false; let hasChildren = false;
for (const node of nodes) { for (const node of nodes) {
@ -404,30 +360,23 @@ export const extractor = (graph, depth) => {
log.debug('Done, no node has children', graph.nodes()); log.debug('Done, no node has children', graph.nodes());
return; return;
} }
// const clusters = Object.keys(clusterDb);
// clusters.forEach(clusterId => {
log.debug('Nodes = ', nodes, depth); log.debug('Nodes = ', nodes, depth);
for (const node of nodes) { for (const node of nodes) {
log.debug( log.debug(
'Extracting node', 'Extracting node',
node, node,
clusterDb, clusterDb,
clusterDb[node] && !clusterDb[node].externalConnections, clusterDb.has(node) && !clusterDb.get(node).externalConnections,
!graph.parent(node), !graph.parent(node),
graph.node(node), graph.node(node),
graph.children('D'), graph.children('D'),
' Depth ', ' Depth ',
depth depth
); );
// Note that the node might have been removed after the Object.keys call so better check if (!clusterDb.has(node)) {
// that it still is in the game
if (!clusterDb[node]) {
// Skip if the node is not a cluster
log.debug('Not a cluster', node, depth); log.debug('Not a cluster', node, depth);
// break;
} else if ( } else if (
!clusterDb[node].externalConnections && !clusterDb.get(node).externalConnections &&
// !graph.parent(node) &&
graph.children(node) && graph.children(node) &&
graph.children(node).length > 0 graph.children(node).length > 0
) { ) {
@ -439,9 +388,9 @@ export const extractor = (graph, depth) => {
const graphSettings = graph.graph(); const graphSettings = graph.graph();
let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB'; let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB';
if (clusterDb[node]?.clusterData?.dir) { if (clusterDb.get(node)?.clusterData?.dir) {
dir = clusterDb[node].clusterData.dir; dir = clusterDb.get(node).clusterData.dir;
log.warn('Fixing dir', clusterDb[node].clusterData.dir, dir); log.warn('Fixing dir', clusterDb.get(node).clusterData.dir, dir);
} }
const clusterGraph = new graphlib.Graph({ const clusterGraph = new graphlib.Graph({
@ -449,7 +398,7 @@ export const extractor = (graph, depth) => {
compound: true, compound: true,
}) })
.setGraph({ .setGraph({
rankdir: dir, // Todo: set proper spacing rankdir: dir,
nodesep: 50, nodesep: 50,
ranksep: 50, ranksep: 50,
marginx: 8, marginx: 8,
@ -464,8 +413,8 @@ export const extractor = (graph, depth) => {
graph.setNode(node, { graph.setNode(node, {
clusterNode: true, clusterNode: true,
id: node, id: node,
clusterData: clusterDb[node].clusterData, clusterData: clusterDb.get(node).clusterData,
label: clusterDb[node].label, label: clusterDb.get(node).label,
graph: clusterGraph, graph: clusterGraph,
}); });
log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph)); log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph));
@ -475,7 +424,7 @@ export const extractor = (graph, depth) => {
'Cluster ** ', 'Cluster ** ',
node, node,
' **not meeting the criteria !externalConnections:', ' **not meeting the criteria !externalConnections:',
!clusterDb[node].externalConnections, !clusterDb.get(node).externalConnections,
' no parent: ', ' no parent: ',
!graph.parent(node), !graph.parent(node),
' children ', ' children ',
@ -502,7 +451,7 @@ const sorter = (graph, nodes) => {
if (nodes.length === 0) { if (nodes.length === 0) {
return []; return [];
} }
let result = Object.assign(nodes); let result = Object.assign([], nodes);
nodes.forEach((node) => { nodes.forEach((node) => {
const children = graph.children(node); const children = graph.children(node);
const sorted = sorter(graph, children); const sorted = sorter(graph, children);

View File

@ -154,7 +154,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x; let x = edge.x;
let y = edge.y; let y = edge.y;
if (path) { if (path) {
// // debugger;
const pos = utils.calcLabelPosition(path); const pos = utils.calcLabelPosition(path);
log.debug( log.debug(
'Moving label ' + edge.label + ' from (', 'Moving label ' + edge.label + ' from (',
@ -175,13 +174,11 @@ export const positionEdgeLabel = (edge, paths) => {
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`); el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
} }
//let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
if (edge.startLabelLeft) { if (edge.startLabelLeft) {
const el = terminalLabels.get(edge.id).startLeft; const el = terminalLabels.get(edge.id).startLeft;
let x = edge.x; let x = edge.x;
let y = edge.y; let y = edge.y;
if (path) { if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeStart ? 10 : 0, 'start_left', path); const pos = utils.calcTerminalLabelPosition(edge.arrowTypeStart ? 10 : 0, 'start_left', path);
x = pos.x; x = pos.x;
y = pos.y; y = pos.y;
@ -193,7 +190,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x; let x = edge.x;
let y = edge.y; let y = edge.y;
if (path) { if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition( const pos = utils.calcTerminalLabelPosition(
edge.arrowTypeStart ? 10 : 0, edge.arrowTypeStart ? 10 : 0,
'start_right', 'start_right',
@ -209,7 +205,6 @@ export const positionEdgeLabel = (edge, paths) => {
let x = edge.x; let x = edge.x;
let y = edge.y; let y = edge.y;
if (path) { if (path) {
// debugger;
const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_left', path); const pos = utils.calcTerminalLabelPosition(edge.arrowTypeEnd ? 10 : 0, 'end_left', path);
x = pos.x; x = pos.x;
y = pos.y; y = pos.y;
@ -275,21 +270,18 @@ export const intersection = (node, outsidePoint, insidePoint) => {
res.y = outsidePoint.y; 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; return res;
} else { } else {
// Intersection onn sides of rect // Intersection on sides of rect
if (insidePoint.x < outsidePoint.x) { if (insidePoint.x < outsidePoint.x) {
r = outsidePoint.x - w - x; r = outsidePoint.x - w - x;
} else { } else {
r = x - w - outsidePoint.x; r = x - w - outsidePoint.x;
} }
let q = (Q * r) / R; 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 : 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; 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 }); log.debug(`sides calc abc89, Q ${Q}, q ${q}, R ${R}, r ${r}`, { _x, _y });
if (r === 0) { if (r === 0) {
@ -306,49 +298,34 @@ export const intersection = (node, outsidePoint, insidePoint) => {
return { x: _x, y: _y }; 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) => { const cutPathAtIntersect = (_points, boundaryNode) => {
log.warn('abc88 cutPathAtIntersect', _points, boundaryNode); log.warn('abc88 cutPathAtIntersect', _points, boundaryNode);
let points = []; let points = [];
let lastPointOutside = _points[0]; let lastPointOutside = _points[0];
let isInside = false; let isInside = false;
_points.forEach((point) => { _points.forEach((point) => {
// const node = clusterDb[edge.toCluster].node;
log.info('abc88 checking point', point, boundaryNode); log.info('abc88 checking point', point, boundaryNode);
// check if point is inside the boundary rect
if (!outsideNode(boundaryNode, point) && !isInside) { 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); const inter = intersection(boundaryNode, lastPointOutside, point);
log.debug('abc88 inside', point, lastPointOutside, inter); log.debug('abc88 inside', point, lastPointOutside, inter);
log.debug('abc88 intersection', inter, boundaryNode); log.debug('abc88 intersection', inter, boundaryNode);
// // Check case where the intersection is the same as the last point
let pointPresent = false; let pointPresent = false;
points.forEach((p) => { points.forEach((p) => {
pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y); pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y);
}); });
// // if (!pointPresent) {
if (!points.some((e) => e.x === inter.x && e.y === inter.y)) { if (!points.some((e) => e.x === inter.x && e.y === inter.y)) {
points.push(inter); points.push(inter);
} else { } else {
log.warn('abc88 no intersect', inter, points); log.warn('abc88 no intersect', inter, points);
} }
// points.push(inter);
isInside = true; isInside = true;
} else { } else {
// Outside
log.warn('abc88 outside', point, lastPointOutside); log.warn('abc88 outside', point, lastPointOutside);
lastPointOutside = point; lastPointOutside = point;
// points.push(point);
if (!isInside) { if (!isInside) {
points.push(point); points.push(point);
} }
@ -358,13 +335,6 @@ const cutPathAtIntersect = (_points, boundaryNode) => {
return points; 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) { function extractCornerPoints(points) {
const cornerPoints = []; const cornerPoints = [];
const cornerPointPositions = []; const cornerPointPositions = [];
@ -401,11 +371,6 @@ const findAdjacentPoint = function (pointA, pointB, distance) {
return { x: pointB.x - ratio * xDiff, y: pointB.y - ratio * yDiff }; 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 fixCorners = function (lineData) {
const { cornerPointPositions } = extractCornerPoints(lineData); const { cornerPointPositions } = extractCornerPoints(lineData);
const newLineData = []; const newLineData = [];
@ -415,7 +380,6 @@ const fixCorners = function (lineData) {
const nextPoint = lineData[i + 1]; const nextPoint = lineData[i + 1];
const cornerPoint = lineData[i]; 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 newPrevPoint = findAdjacentPoint(prevPoint, cornerPoint, 5);
const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5); const newNextPoint = findAdjacentPoint(nextPoint, cornerPoint, 5);
@ -466,9 +430,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
var head = endNode; var head = endNode;
if (head.intersect && tail.intersect) { 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); 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])); points.unshift(tail.intersect(points[0]));
log.debug( log.debug(
'Last point APA12', 'Last point APA12',
@ -481,59 +443,43 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
); );
points.push(head.intersect(points[points.length - 1])); points.push(head.intersect(points[points.length - 1]));
} }
// log.info('abc88 InsertEdge 2 SPLIT: ', points);
if (edge.toCluster) { if (edge.toCluster) {
log.info('to cluster abc88', clusterDb[edge.toCluster]); log.info('to cluster abc88', clusterDb.get(edge.toCluster));
points = cutPathAtIntersect(edge.points, clusterDb[edge.toCluster].node); points = cutPathAtIntersect(edge.points, clusterDb.get(edge.toCluster).node);
pointsHasChanged = true; pointsHasChanged = true;
} }
if (edge.fromCluster) { if (edge.fromCluster) {
log.debug('from cluster abc88', clusterDb[edge.fromCluster], JSON.stringify(points, null, 2)); log.debug(
points = cutPathAtIntersect(points.reverse(), clusterDb[edge.fromCluster].node).reverse(); 'from cluster abc88',
// log.info('from cluster abc88 fixed', JSON.stringify(points, null, 2)); clusterDb.get(edge.fromCluster),
JSON.stringify(points, null, 2)
);
points = cutPathAtIntersect(points.reverse(), clusterDb.get(edge.fromCluster).node).reverse();
pointsHasChanged = true; pointsHasChanged = true;
} }
// The data for our line
let lineData = points.filter((p) => !Number.isNaN(p.y)); let lineData = points.filter((p) => !Number.isNaN(p.y));
// const { cornerPoints, cornerPointPositions } = extractCornerPoints(lineData);
lineData = fixCorners(lineData); lineData = fixCorners(lineData);
let lastPoint = lineData[lineData.length - 1]; let lastPoint = lineData[lineData.length - 1];
if (lineData.length > 1) { if (lineData.length > 1) {
lastPoint = lineData[lineData.length - 1]; lastPoint = lineData[lineData.length - 1];
const secondLastPoint = lineData[lineData.length - 2]; const secondLastPoint = lineData[lineData.length - 2];
// Calculate the mid point of the last two points
const diffX = (lastPoint.x - secondLastPoint.x) / 2; const diffX = (lastPoint.x - secondLastPoint.x) / 2;
const diffY = (lastPoint.y - secondLastPoint.y) / 2; const diffY = (lastPoint.y - secondLastPoint.y) / 2;
const midPoint = { x: secondLastPoint.x + diffX, y: secondLastPoint.y + diffY }; const midPoint = { x: secondLastPoint.x + diffX, y: secondLastPoint.y + diffY };
lineData.splice(-1, 0, midPoint); lineData.splice(-1, 0, midPoint);
} }
// This is the accessor function we talked about above
let curve = curveBasis; 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) { if (edge.curve) {
curve = edge.curve; curve = edge.curve;
} }
const { x, y } = getLineFunctionsWithOffset(edge); 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); const lineFunction = line().x(x).y(y).curve(curve);
// Construct stroke classes based on properties
let strokeClasses; let strokeClasses;
switch (edge.thickness) { switch (edge.thickness) {
case 'normal': case 'normal':
@ -563,16 +509,11 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
} }
let svgPath; let svgPath;
let linePath = lineFunction(lineData); let linePath = lineFunction(lineData);
//convert to array (if not already)
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style]; const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
if (edge.look === 'handdrawn') { if (edge.look === 'handdrawn') {
const rc = rough.svg(elem); const rc = rough.svg(elem);
Object.assign([], lineData); 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, { const svgPathNode = rc.path(linePath, {
roughness: 0.3, roughness: 0.3,
seed: handdrawnSeed, seed: handdrawnSeed,
@ -582,7 +523,6 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
svgPath = select(svgPathNode) svgPath = select(svgPathNode)
.select('path') .select('path')
// .attr('d', lineFunction(lineData))
.attr('id', edge.id) .attr('id', edge.id)
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '')) .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : ''); .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('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : ''); .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 = ''; let url = '';
// // TODO: Can we load this config only from the rendered graph type?
if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) { if (getConfig().flowchart.arrowMarkerAbsolute || getConfig().state.arrowMarkerAbsolute) {
url = url =
window.location.protocol + window.location.protocol +
@ -627,8 +546,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
window.location.host + window.location.host +
window.location.pathname + window.location.pathname +
window.location.search; window.location.search;
url = url.replace(/\(/g, '\\('); url = url.replace(/\(/g, '\\(').replace(/\)/g, '\\)');
url = url.replace(/\)/g, '\\)');
} }
log.info('arrowTypeStart', edge.arrowTypeStart); log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd); log.info('arrowTypeEnd', edge.arrowTypeEnd);