mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
#815 Styling subgraphs with color shades
This commit is contained in:
parent
14f7756fdb
commit
bb9b0b015e
@ -83,6 +83,7 @@
|
||||
"treemap",
|
||||
"ts-nocheck",
|
||||
"tuleap",
|
||||
"ugge",
|
||||
"unist",
|
||||
"verdana",
|
||||
"viewports",
|
||||
|
@ -56,7 +56,7 @@
|
||||
<body>
|
||||
<div>Security check</div>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
cyto TD
|
||||
cyto TB
|
||||
%% I could not figure out how to use double quotes in labels in Mermaid
|
||||
subgraph ibm[IBM Espresso CPU]
|
||||
core0[IBM PowerPC Broadway Core 0]
|
||||
@ -111,8 +111,35 @@ cyto TD
|
||||
>
|
||||
<pre id="diagram" class="mermaid">
|
||||
cyto LR
|
||||
B1 --be be--> B2
|
||||
B1 --bo bo--> B3
|
||||
B1 --be be--x B2
|
||||
B1 --bo bo--o B3
|
||||
subgraph Ugge
|
||||
B2
|
||||
B3
|
||||
subgraph inner
|
||||
B4
|
||||
B5
|
||||
end
|
||||
subgraph inner2
|
||||
subgraph deeper
|
||||
C4
|
||||
C5
|
||||
end
|
||||
C6
|
||||
end
|
||||
|
||||
B4 --> C4
|
||||
|
||||
B3 -- X --> B4
|
||||
B2 --> inner
|
||||
|
||||
C4 --> C5
|
||||
end
|
||||
|
||||
subgraph outer
|
||||
B6
|
||||
end
|
||||
B6 --> B5
|
||||
</pre
|
||||
>
|
||||
inside1 --> inside2 & inside3 & inside4 & inside5 & inside6 a(letter a<br />a) ---> b(letter
|
||||
@ -159,7 +186,7 @@ cyto LR
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
mermaid.initialize({
|
||||
theme: 'base',
|
||||
theme: 'dark',
|
||||
startOnLoad: true,
|
||||
logLevel: 0,
|
||||
flowchart: {
|
||||
|
@ -4,7 +4,8 @@ import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
|
||||
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
|
||||
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
|
||||
import createLabel from '../../mermaid/src/dagre-wrapper/createLabel';
|
||||
import dagre from 'cytoscape-dagre';
|
||||
import { insertEdgeLabel, positionEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
|
||||
import { findCommonAncestor } from './render-utils';
|
||||
// Replace with other function to avoid dependency to dagre-d3
|
||||
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
|
||||
|
||||
@ -26,7 +27,7 @@ export const setConf = function (cnf) {
|
||||
}
|
||||
};
|
||||
|
||||
const nodeDb = {};
|
||||
let nodeDb = {};
|
||||
|
||||
// /**
|
||||
// * Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
@ -381,10 +382,9 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
edgeData.id = linkId;
|
||||
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
|
||||
|
||||
const labelEl = createLabel(edgeData.label, edgeData.labelStyle);
|
||||
labelsEl.node().appendChild(labelEl);
|
||||
const labelBox = labelEl.firstChild.getBoundingClientRect();
|
||||
// console.log('labelEl', labelEl);
|
||||
const edgesNode = select(edges);
|
||||
const labelEl = insertEdgeLabel(labelsEl, edgeData);
|
||||
// console.log('labelEl', labelEl, edgeData.width);
|
||||
// Add the edge to the graph
|
||||
graph.edges.push({
|
||||
id: 'e' + edge.start + edge.end,
|
||||
@ -393,11 +393,11 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
labelEl: labelEl,
|
||||
labels: [
|
||||
{
|
||||
width: labelBox.width,
|
||||
width: edgeData.width,
|
||||
// width: 80,
|
||||
height: labelBox.height,
|
||||
orgWidth: labelBox.width,
|
||||
orgHeight: labelBox.height,
|
||||
height: edgeData.height,
|
||||
orgWidth: edgeData.width,
|
||||
orgHeight: edgeData.height,
|
||||
text: edgeData.label,
|
||||
layoutOptions: {
|
||||
'edgeLabels.inline': 'true',
|
||||
@ -413,9 +413,19 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
return graph;
|
||||
};
|
||||
|
||||
const addmarkers = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute) {
|
||||
// // TODO: Can we load this config only from the rendered graph type?
|
||||
let url;
|
||||
// TODO: break out and share with dagre wrapper. The current code in dagre wrapper also adds
|
||||
// adds the line to the graph, but we don't need that here. This is why we cant use the dagre
|
||||
// wrapper directly for this
|
||||
/**
|
||||
* Add the markers to the edge depending on the type of arrow is
|
||||
* @param svgPath
|
||||
* @param edgeData
|
||||
* @param diagramType
|
||||
* @param arrowMarkerAbsolute
|
||||
*/
|
||||
const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute) {
|
||||
let url = '';
|
||||
// Check configuration for absolute path
|
||||
if (arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
@ -426,6 +436,8 @@ const addmarkers = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
// look in edge data and decide which marker to use
|
||||
switch (edgeData.arrowTypeStart) {
|
||||
case 'arrow_cross':
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
|
||||
@ -526,114 +538,104 @@ const addSubGraphs = function (db) {
|
||||
if (parentLookupDb.parentById[subgraph.id] !== undefined) {
|
||||
data.parent = parentLookupDb.parentById[subgraph.id];
|
||||
}
|
||||
// cy.add({
|
||||
// group: 'nodes',
|
||||
// data,
|
||||
// });
|
||||
});
|
||||
return parentLookupDb;
|
||||
};
|
||||
|
||||
/* Reverse engineered with trial and error */
|
||||
const calcOffset = function (src, dest, sourceId, targetId) {
|
||||
if (src === dest) {
|
||||
const calcOffsetOld = function (src, dest, sourceId, targetId, srcDepth, targetDepth, so, to) {
|
||||
// if (src === dest) {
|
||||
// return src;
|
||||
// }
|
||||
// if (sourceId === 'B6') {
|
||||
// return 0;
|
||||
// }
|
||||
// if (sourceId === 'B4') {
|
||||
// return 318;
|
||||
// }
|
||||
if (srcDepth < targetDepth) {
|
||||
return src;
|
||||
}
|
||||
if (srcDepth > targetDepth) {
|
||||
return dest;
|
||||
}
|
||||
if (srcDepth === targetDepth) {
|
||||
return src;
|
||||
}
|
||||
// if (src < dest) {
|
||||
// return dest + src;
|
||||
// }
|
||||
return 0;
|
||||
};
|
||||
|
||||
const insertEdge = function (edgesEl, edge, edgeData, diagObj) {
|
||||
const calcOffset = function (src, dest, parentLookupDb) {
|
||||
const ancestor = findCommonAncestor(src, dest, parentLookupDb);
|
||||
if (ancestor === undefined || ancestor === 'root') {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
const ancestoprOffset = nodeDb[ancestor].offset;
|
||||
return { x: ancestoprOffset.posX, y: ancestoprOffset.posY };
|
||||
};
|
||||
|
||||
const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
|
||||
const srcOffset = nodeDb[edge.sources[0]].offset;
|
||||
const targetOffset = nodeDb[edge.targets[0]].offset;
|
||||
const offset = {
|
||||
x: calcOffset(
|
||||
srcOffset.x,
|
||||
targetOffset.x,
|
||||
nodeDb[edge.sources[0]].id,
|
||||
nodeDb[edge.targets[0]].id
|
||||
),
|
||||
y: calcOffset(
|
||||
srcOffset.y,
|
||||
targetOffset.y,
|
||||
nodeDb[edge.sources[0]].id,
|
||||
nodeDb[edge.targets[0]].id
|
||||
),
|
||||
};
|
||||
// console.log('srcOffset', srcOffset.x, targetOffset.x, srcOffset.y, targetOffset.y);
|
||||
const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb);
|
||||
|
||||
const src = edge.sections[0].startPoint;
|
||||
const dest = edge.sections[0].endPoint;
|
||||
const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : [];
|
||||
// const dest = edge.target().position();
|
||||
// const dest = edge.targetEndpoint();
|
||||
|
||||
const segPoints = segments.map((segment) => [segment.x + offset.x, segment.y + offset.y]);
|
||||
const points = [
|
||||
[src.x + offset.x, src.y + offset.y],
|
||||
...segPoints,
|
||||
[dest.x + offset.x, dest.y + offset.y],
|
||||
];
|
||||
// console.log('Edge ctrl points:', edge.segmentPoints(), 'Bounds:', bounds, edge.source(), points);
|
||||
// console.log('Edge ctrl points:', points);
|
||||
// const curve = line().curve(curveCardinal);
|
||||
|
||||
// const curve = line().curve(curveBasis);
|
||||
const curve = line().curve(curveLinear);
|
||||
const edgePath = edgesEl
|
||||
.insert('path')
|
||||
.attr('d', curve(points))
|
||||
// .attr('d', points))
|
||||
.attr('class', 'path')
|
||||
.attr('fill', 'none');
|
||||
const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel');
|
||||
const edgeEl = select(edgeG.node().appendChild(edge.labelEl));
|
||||
// console.log('Edge label', edgeEl, edge);
|
||||
const box = edgeEl.node().firstChild.getBoundingClientRect();
|
||||
edgeEl.attr('width', box.width);
|
||||
edgeEl.attr('height', box.height);
|
||||
// edgeEl.height = 24;
|
||||
const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl));
|
||||
const box = edgeWithLabel.node().firstChild.getBoundingClientRect();
|
||||
edgeWithLabel.attr('width', box.width);
|
||||
edgeWithLabel.attr('height', box.height);
|
||||
|
||||
edgeG.attr(
|
||||
'transform',
|
||||
`translate(${edge.labels[0].x - box.width / 2}, ${edge.labels[0].y - box.height / 2})`
|
||||
`translate(${edge.labels[0].x + offset.x}, ${edge.labels[0].y + offset.y})`
|
||||
);
|
||||
addmarkers(edgesEl, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute);
|
||||
// edgesEl
|
||||
// .append('circle')
|
||||
// .style('stroke', 'red')
|
||||
// .style('fill', 'red')
|
||||
// .attr('r', 1)
|
||||
// .attr('cx', src.x)
|
||||
// .attr('cy', src.y);
|
||||
// edgesEl
|
||||
// .append('circle')
|
||||
// .style('stroke', 'white')
|
||||
// .style('fill', 'white')
|
||||
// .attr('r', 1)
|
||||
// .attr('cx', segments[0].x)
|
||||
// .attr('cy', segments[0].y);
|
||||
// edgesEl
|
||||
// .append('circle')
|
||||
// .style('stroke', 'pink')
|
||||
// .style('fill', 'pink')
|
||||
// .attr('r', 1)
|
||||
// .attr('cx', dest.x)
|
||||
// .attr('cy', dest.y);
|
||||
addMarkersToEdge(edgePath, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Recursive function that iterates over an array of nodes and inserts the children of each node.
|
||||
* It also recursively populates the inserts the children of the children and so on.
|
||||
* @param {*} graph
|
||||
* @param nodeArray
|
||||
* @param parentLookupDb
|
||||
*/
|
||||
const insertChildren = (nodeArray, parentLookupDb) => {
|
||||
nodeArray.forEach((node) => {
|
||||
// Check if we have reached the end of the tree
|
||||
if (!node.children) {
|
||||
node.children = [];
|
||||
}
|
||||
// Check if the node has children
|
||||
const childIds = parentLookupDb.childrenById[node.id];
|
||||
// console.log('UGH', node.id, childIds);
|
||||
// If the node has children, add them to the node
|
||||
if (childIds) {
|
||||
childIds.forEach((childId) => {
|
||||
node.children.push(nodeDb[childId]);
|
||||
});
|
||||
}
|
||||
// Recursive call
|
||||
insertChildren(node.children, parentLookupDb);
|
||||
});
|
||||
};
|
||||
@ -648,6 +650,7 @@ const insertChildren = (nodeArray, parentLookupDb) => {
|
||||
export const draw = function (text, id, _version, diagObj) {
|
||||
// Add temporary render element
|
||||
diagObj.db.clear();
|
||||
nodeDb = {};
|
||||
diagObj.db.setGen('gen-2');
|
||||
// Parse the graph definition
|
||||
diagObj.parser.parse(text);
|
||||
@ -659,12 +662,13 @@ export const draw = function (text, id, _version, diagObj) {
|
||||
id: 'root',
|
||||
layoutOptions: {
|
||||
'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
|
||||
// 'elk.hierarchyHandling': 'SEPARATE_CHILDREN',
|
||||
'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]',
|
||||
// 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 120,
|
||||
// 'elk.layered.spacing.nodeNodeBetweenLayers': '140',
|
||||
'elk.layered.spacing.edgeNodeBetweenLayers': '30',
|
||||
// 'elk.algorithm': 'layered',
|
||||
'elk.direction': 'WEST',
|
||||
'elk.direction': 'DOWN',
|
||||
// 'elk.port.side': 'SOUTH',
|
||||
// 'nodePlacement.strategy': 'SIMPLE',
|
||||
// 'org.eclipse.elk.spacing.labelLabel': 120,
|
||||
@ -679,14 +683,27 @@ export const draw = function (text, id, _version, diagObj) {
|
||||
edges: [],
|
||||
};
|
||||
log.info('Drawing flowchart using v3 renderer');
|
||||
|
||||
// Set the direction,
|
||||
// Fetch the default direction, use TD if none was found
|
||||
let dir = diagObj.db.getDirection();
|
||||
if (dir === undefined) {
|
||||
dir = 'TD';
|
||||
switch (dir) {
|
||||
case 'BT':
|
||||
graph.layoutOptions['elk.direction'] = 'UP';
|
||||
break;
|
||||
case 'TB':
|
||||
graph.layoutOptions['elk.direction'] = 'DOWN';
|
||||
break;
|
||||
case 'LR':
|
||||
graph.layoutOptions['elk.direction'] = 'RIGHT';
|
||||
break;
|
||||
case 'RL':
|
||||
graph.layoutOptions['elk.direction'] = 'LEFT';
|
||||
break;
|
||||
}
|
||||
|
||||
const { securityLevel, flowchart: conf } = getConfig();
|
||||
|
||||
// Find the root dom node to ne used in rendering
|
||||
// Handle root and document for when rendering in sandbox mode
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
@ -699,25 +716,46 @@ export const draw = function (text, id, _version, diagObj) {
|
||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||
|
||||
const svg = root.select(`[id="${id}"]`);
|
||||
|
||||
// Define the supported markers for the diagram
|
||||
const markers = ['point', 'circle', 'cross'];
|
||||
|
||||
// Add the marker definitions to the svg as marker tags
|
||||
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const vert = diagObj.db.getVertices();
|
||||
|
||||
// Setup nodes from the subgraphs with type group, these will be used
|
||||
// as nodes with children in the subgraph
|
||||
let subG;
|
||||
const subGraphs = diagObj.db.getSubGraphs();
|
||||
log.info('Subgraphs - ', subGraphs);
|
||||
for (let i = subGraphs.length - 1; i >= 0; i--) {
|
||||
subG = subGraphs[i];
|
||||
log.info('Subgraph - ', subG);
|
||||
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
|
||||
}
|
||||
|
||||
// Add an element in the svg to be used to hold the subgraphs container
|
||||
// elements
|
||||
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
|
||||
|
||||
// Create the lookup db for the subgraphs and their children to used when creating
|
||||
// the tree structured graph
|
||||
const parentLookupDb = addSubGraphs(diagObj.db);
|
||||
|
||||
// Add the nodes to the graph, this will entail creating the actual nodes
|
||||
// in order to get the size of the node. You can't get the size of a node
|
||||
// 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
|
||||
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
|
||||
|
||||
// Time for the edges, we start with adding an element in the node to hold the edges
|
||||
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
|
||||
// Fetch the edges form the parsed graph definition
|
||||
const edges = diagObj.db.getEdges();
|
||||
|
||||
// Add the edges to the graph, this will entail creating the actual edges
|
||||
graph = addEdges(edges, diagObj, graph, svg);
|
||||
|
||||
// Iterate through all nodes and add the top level nodes to the graph
|
||||
@ -748,46 +786,21 @@ export const draw = function (text, id, _version, diagObj) {
|
||||
}
|
||||
});
|
||||
insertChildren(graph.children, parentLookupDb);
|
||||
// console.log('Graph (UGH)- ', JSON.parse(JSON.stringify(graph)), JSON.stringify(graph));
|
||||
// const graph2 = {
|
||||
// id: 'root',
|
||||
// layoutOptions: { 'elk.algorithm': 'layered' },
|
||||
// children: [
|
||||
// {
|
||||
// id: 'N1',
|
||||
// width: 30,
|
||||
// height: 30,
|
||||
// padding: 12,
|
||||
// children: [
|
||||
// { id: 'n1', width: 30, height: 30 },
|
||||
// { id: 'n2', width: 30, height: 30 },
|
||||
// { id: 'n3', width: 30, height: 30 },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// edges: [
|
||||
// { id: 'e1', sources: ['n1'], targets: ['n2'] },
|
||||
// { id: 'e2', sources: ['n1'], targets: ['n3'] },
|
||||
// ],
|
||||
// };
|
||||
elk.layout(graph).then(function (g) {
|
||||
// elk.layout(graph2).then(function (g) {
|
||||
// console.log('Layout (UGH)- ', g, JSON.stringify(g));
|
||||
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj);
|
||||
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
|
||||
|
||||
g.edges.map((edge, id) => {
|
||||
// console.log('Edge (UGH)- ', edge);
|
||||
insertEdge(edgesEl, edge, edge.edgeData, diagObj);
|
||||
insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
|
||||
});
|
||||
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
|
||||
resolve();
|
||||
});
|
||||
// Remove element after layout
|
||||
// renderEl.remove();
|
||||
renderEl.remove();
|
||||
});
|
||||
};
|
||||
|
||||
const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj) => {
|
||||
const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {
|
||||
nodeArray.forEach(function (node) {
|
||||
if (node) {
|
||||
nodeDb[node.id].offset = {
|
||||
@ -795,13 +808,15 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj) => {
|
||||
posY: node.y + relY,
|
||||
x: relX,
|
||||
y: relY,
|
||||
depth,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
};
|
||||
if (node.type === 'group') {
|
||||
const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph');
|
||||
subgraphEl
|
||||
.insert('rect')
|
||||
.attr('class', 'subgraph node')
|
||||
.attr('style', 'fill:#ccc;stroke:black;stroke-width:1')
|
||||
.attr('class', 'subgraph subgraph-lvl-' + (depth % 5) + ' node')
|
||||
.attr('x', node.x + relX)
|
||||
.attr('y', node.y + relY)
|
||||
.attr('width', node.width)
|
||||
@ -825,7 +840,7 @@ const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj) => {
|
||||
});
|
||||
nodeArray.forEach(function (node) {
|
||||
if (node && node.type === 'group') {
|
||||
drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj);
|
||||
drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj, depth + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
119
packages/mermaid-flowchart-v3/src/render-utils.js
Normal file
119
packages/mermaid-flowchart-v3/src/render-utils.js
Normal file
@ -0,0 +1,119 @@
|
||||
export const findCommonAncestorCoPilot = (id1, id2, treeData) => {
|
||||
const { parentById, childrenById } = treeData;
|
||||
const parents1 = [];
|
||||
const parents2 = [];
|
||||
let cnt = 0;
|
||||
let currentId = id1;
|
||||
while (currentId) {
|
||||
parents1.push(currentId);
|
||||
currentId = parentById[currentId];
|
||||
cnt++;
|
||||
if (cnt > 200) {
|
||||
throw new Error('Infinite loop detected!');
|
||||
}
|
||||
}
|
||||
currentId = id2;
|
||||
while (currentId) {
|
||||
parents2.push(currentId);
|
||||
currentId = parentById[currentId];
|
||||
cnt++;
|
||||
if (cnt > 200) {
|
||||
throw new Error('Infinite loop detected!');
|
||||
}
|
||||
}
|
||||
let commonAncestor = 'root';
|
||||
while (parents1.length && parents2.length) {
|
||||
cnt++;
|
||||
if (cnt > 200) {
|
||||
throw new Error('Infinite loop detected!');
|
||||
}
|
||||
const p1 = parents1.pop();
|
||||
const p2 = parents2.pop();
|
||||
if (p1 === p2) {
|
||||
commonAncestor = p1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return commonAncestor;
|
||||
};
|
||||
|
||||
export const findCommonAncestor = (id1, id2, treeData) => {
|
||||
const { parentById } = treeData;
|
||||
const visited = new Set();
|
||||
let currentId = id1;
|
||||
while (currentId) {
|
||||
visited.add(currentId);
|
||||
if (currentId === id2) {
|
||||
return currentId;
|
||||
}
|
||||
currentId = parentById[currentId];
|
||||
}
|
||||
currentId = id2;
|
||||
while (currentId) {
|
||||
if (visited.has(currentId)) {
|
||||
return currentId;
|
||||
}
|
||||
currentId = parentById[currentId];
|
||||
}
|
||||
return 'root';
|
||||
};
|
||||
|
||||
export const findCommonAncestorKnut = (id1, id2, treeData) => {
|
||||
const { parentById, childrenById } = treeData;
|
||||
const parents1 = [];
|
||||
const parents2 = [];
|
||||
let cnt = 0;
|
||||
let currentId = id1;
|
||||
while (currentId) {
|
||||
parents1.push(currentId);
|
||||
currentId = parentById[currentId];
|
||||
cnt++;
|
||||
if (cnt > 200) {
|
||||
throw new Error('Infinite loop detected!');
|
||||
}
|
||||
}
|
||||
currentId = id2;
|
||||
while (currentId) {
|
||||
parents2.push(currentId);
|
||||
currentId = parentById[currentId];
|
||||
if (currentId === 'root') {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
if (parents1.includes(currentId)) {
|
||||
return currentId;
|
||||
}
|
||||
|
||||
cnt++;
|
||||
if (cnt > 200) {
|
||||
throw new Error('Infinite loop detected!');
|
||||
}
|
||||
}
|
||||
return 'root';
|
||||
};
|
||||
|
||||
export const findCommonAncestorRecursive = (id1, id2, treeData) => {
|
||||
const { parentById, childrenById } = treeData;
|
||||
|
||||
// Base case: return the current node if it is the common ancestor
|
||||
if (id1 === id2) {
|
||||
return id1;
|
||||
}
|
||||
|
||||
// Recursive case: search for the common ancestor in the parent nodes
|
||||
const parent1 = parentById[id1];
|
||||
const parent2 = parentById[id2];
|
||||
if (parent1 && parent2) {
|
||||
return findCommonAncestor(parent1, parent2, treeData);
|
||||
}
|
||||
|
||||
// Edge case: one of the nodes is the root of the tree
|
||||
if (parent1) {
|
||||
return parent1;
|
||||
}
|
||||
if (parent2) {
|
||||
return parent2;
|
||||
}
|
||||
return 'root';
|
||||
};
|
39
packages/mermaid-flowchart-v3/src/render-utils.spec.js
Normal file
39
packages/mermaid-flowchart-v3/src/render-utils.spec.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { findCommonAncestor } from './render-utils';
|
||||
describe('when rendering a flowchart using elk ', function () {
|
||||
let lookupDb;
|
||||
beforeEach(function () {
|
||||
/**
|
||||
* root:
|
||||
* B1
|
||||
* outer
|
||||
* B6
|
||||
* Ugge
|
||||
* B2
|
||||
* B3
|
||||
* inner
|
||||
* B4
|
||||
* B5
|
||||
* inner2
|
||||
* C4
|
||||
* C5
|
||||
*/
|
||||
lookupDb = JSON.parse(
|
||||
'{"parentById":{"B4":"inner","B5":"inner","C4":"inner2","C5":"inner2","B2":"Ugge","B3":"Ugge","inner":"Ugge","inner2":"Ugge","B6":"outer"},"childrenById":{"inner":["B4","B5"],"inner2":["C4","C5"],"Ugge":["B2","B3","inner","inner2"],"outer":["B6"]}}'
|
||||
);
|
||||
});
|
||||
it('Sieblings in a subgraph', function () {
|
||||
expect(findCommonAncestor('B4', 'B5', lookupDb)).toBe('inner');
|
||||
});
|
||||
it('Find an uncle', function () {
|
||||
expect(findCommonAncestor('B4', 'B2', lookupDb)).toBe('Ugge');
|
||||
});
|
||||
it('Find a cousin', function () {
|
||||
expect(findCommonAncestor('B4', 'C4', lookupDb)).toBe('Ugge');
|
||||
});
|
||||
it('Find a grandparent', function () {
|
||||
expect(findCommonAncestor('B4', 'B6', lookupDb)).toBe('root');
|
||||
});
|
||||
it('Sieblings in the root', function () {
|
||||
expect(findCommonAncestor('B1', 'outer', lookupDb)).toBe('root');
|
||||
});
|
||||
});
|
@ -15,6 +15,20 @@ export interface FlowChartStyleOptions {
|
||||
titleColor: string;
|
||||
}
|
||||
|
||||
const genSections = (options) => {
|
||||
let sections = '';
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
sections += `
|
||||
.subgraph-lvl-${i} {
|
||||
fill: ${options[`surface${i}`]};
|
||||
stroke: ${options[`surfacePeer${i}`]};
|
||||
}
|
||||
`;
|
||||
}
|
||||
return sections;
|
||||
};
|
||||
|
||||
const getStyles = (options: FlowChartStyleOptions) =>
|
||||
`.label {
|
||||
font-family: ${options.fontFamily};
|
||||
@ -109,6 +123,15 @@ const getStyles = (options: FlowChartStyleOptions) =>
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
.subgraph {
|
||||
stroke-width:2;
|
||||
rx:3;
|
||||
}
|
||||
// .subgraph-lvl-1 {
|
||||
// fill:#ccc;
|
||||
// // stroke:black;
|
||||
// }
|
||||
${genSections(options)}
|
||||
`;
|
||||
|
||||
export default getStyles;
|
||||
|
@ -107,6 +107,7 @@ export const insertEdgeLabel = (elem, edge) => {
|
||||
terminalLabels[edge.id].endRight = endEdgeLabelRight;
|
||||
setTerminalWidth(fo, edge.endLabelRight);
|
||||
}
|
||||
return labelElement;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -463,7 +464,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
|
||||
.attr('style', edge.style);
|
||||
|
||||
// DEBUG code, adds a red circle at each edge coordinate
|
||||
// edge.points.forEach(point => {
|
||||
// edge.points.forEach((point) => {
|
||||
// elem
|
||||
// .append('circle')
|
||||
// .style('stroke', 'red')
|
||||
|
@ -13,7 +13,6 @@ class Theme {
|
||||
* deducing colors for instance line color. Default value is #f4f4f4.
|
||||
*/
|
||||
this.background = '#f4f4f4';
|
||||
this.darkMode = false;
|
||||
|
||||
this.primaryColor = '#fff4dd';
|
||||
|
||||
@ -169,6 +168,16 @@ class Theme {
|
||||
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor;
|
||||
}
|
||||
|
||||
const multiplier = this.darkMode ? -4 : -1;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this['surface' + i] =
|
||||
this['surface' + i] ||
|
||||
adjust(this.mainBkg, { h: 180, s: -15, l: multiplier * (5 + i * 3) });
|
||||
this['surfacePeer' + i] =
|
||||
this['surfacePeer' + i] ||
|
||||
adjust(this.mainBkg, { h: 180, s: -15, l: multiplier * (8 + i * 3) });
|
||||
}
|
||||
|
||||
/* class */
|
||||
this.classText = this.classText || this.textColor;
|
||||
|
||||
|
@ -196,6 +196,13 @@ class Theme {
|
||||
this['cScalePeer' + i] = this['cScalePeer' + i] || lighten(this['cScale' + i], 10);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this['surface' + i] =
|
||||
this['surface' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(-10 + i * 4) });
|
||||
this['surfacePeer' + i] =
|
||||
this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(-7 + i * 4) });
|
||||
}
|
||||
|
||||
// Setup teh label color for the set
|
||||
this.scaleLabelColor = this.scaleLabelColor || (this.darkMode ? 'black' : this.labelTextColor);
|
||||
|
||||
|
@ -147,6 +147,11 @@ class Theme {
|
||||
this['cScaleInv' + i] = this['cScaleInv' + i] || adjust(this['cScale' + i], { h: 180 });
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this['surface' + i] = this['surface' + i] || adjust(this.mainBkg, { h: 30, l: -(5 + i * 5) });
|
||||
this['surfacePeer' + i] =
|
||||
this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, l: -(7 + i * 5) });
|
||||
}
|
||||
// Setup the label color for the set
|
||||
this.scaleLabelColor =
|
||||
this.scaleLabelColor !== 'calculated' && this.scaleLabelColor
|
||||
|
@ -130,6 +130,13 @@ class Theme {
|
||||
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this['surface' + i] =
|
||||
this['surface' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(5 + i * 5) });
|
||||
this['surfacePeer' + i] =
|
||||
this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(8 + i * 5) });
|
||||
}
|
||||
|
||||
/* Flowchart variables */
|
||||
|
||||
this.nodeBkg = this.mainBkg;
|
||||
|
@ -147,6 +147,12 @@ class Theme {
|
||||
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this['surface' + i] = this['surface' + i] || adjust(this.mainBkg, { l: -(5 + i * 5) });
|
||||
this['surfacePeer' + i] =
|
||||
this['surfacePeer' + i] || adjust(this.mainBkg, { l: -(8 + i * 5) });
|
||||
}
|
||||
|
||||
/* Flowchart variables */
|
||||
|
||||
this.nodeBkg = this.mainBkg;
|
||||
|
Loading…
x
Reference in New Issue
Block a user