Mindmap cleanup

This commit is contained in:
Knut Sveidqvist
2022-10-03 11:08:55 +02:00
parent 5079fa93b4
commit 0a2804c641
6 changed files with 15 additions and 395 deletions

View File

@@ -69,10 +69,9 @@ mindmap
::icon(mdi mdi-fire) ::icon(mdi mdi-fire)
gc7((grand<br/>grand<br/>child 8)) gc7((grand<br/>grand<br/>child 8))
</pre> </pre>
<div id="cy"></div> <!-- <div id="cy"></div> -->
<script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script> <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script>
<!-- <script src="./mermaid-example-diagram-detector.js"></script> --> <!-- <script src="./mermaid-example-diagram-detector.js"></script> -->
<!-- <script src="http://localhost:9000/packages/mermaid-mindmap/dist/mermaid-example-diagram-detector.js"></script> -->
<script src="./mermaid.js"></script> <script src="./mermaid.js"></script>
<script> <script>
mermaid.parseError = function (err, hash) { mermaid.parseError = function (err, hash) {

View File

@@ -5,12 +5,6 @@ import mindmapRenderer from './mindmapRenderer';
import mindmapStyles from './styles'; import mindmapStyles from './styles';
import { injectUtils } from './mermaidUtils'; import { injectUtils } from './mermaidUtils';
// const getBaseFolder = (path: string) => {
// const parts = path.split('/');
// parts.pop();
// return parts.join('/');
// };
window.mermaid.connectDiagram( window.mermaid.connectDiagram(
'mindmap', 'mindmap',
{ {

View File

@@ -132,11 +132,7 @@ export const type2Str = (type) => {
} }
}; };
export let parseError; // = (str, hash) export let parseError;
// => {
// const error = { str, hash };
// throw error;
// };
export const setErrorHandler = (handler) => { export const setErrorHandler = (handler) => {
parseError = handler; parseError = handler;
}; };
@@ -146,17 +142,3 @@ export const getLogger = () => log;
export const getNodeById = (id) => nodes[id]; export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id]; export const getElementById = (id) => elements[id];
// export default {
// // getMindmap,
// // addNode,
// // clear,
// // nodeType,
// // getType,
// // decorateNode,
// // setElementForId,
// getElementById: (id) => elements[id],
// // getNodeById: (id) => nodes.find((node) => node.id === id),
// getNodeById: (id) => nodes[id],
// // type2Str,
// // parseError
// };

View File

@@ -2,14 +2,11 @@
import { select } from 'd3'; import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils'; import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import svgDraw from './svgDraw'; import svgDraw from './svgDraw';
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
import cytoscape from 'cytoscape'; import cytoscape from 'cytoscape';
import coseBilkent from 'cytoscape-cose-bilkent'; import coseBilkent from 'cytoscape-cose-bilkent';
import fcose from 'cytoscape-fcose';
import clone from 'fast-clone';
import * as db from './mindmapDb'; import * as db from './mindmapDb';
cytoscape.use(fcose); // Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent); cytoscape.use(coseBilkent);
/** /**
@@ -35,38 +32,15 @@ function drawNodes(svg, mindmap, section, conf) {
* @param section * @param section
* @param edgesEl * @param edgesEl
* @param cy * @param cy
* @param conf
*/ */
// edgesElem, cy, conf function drawEdges(edgesEl, cy) {
function drawEdges(edgesEl, cy, conf) {
cy.edges().map((edge, id) => { cy.edges().map((edge, id) => {
const data = edge.data(); const data = edge.data();
if (edge[0]._private.bodyBounds) { if (edge[0]._private.bodyBounds) {
const bounds = edge[0]._private.rscratch; const bounds = edge[0]._private.rscratch;
log.info( log.trace('Edge: ', id, data);
id,
// 'x:',
// edge.controlPoints(),
// 'y:',
// edge[0]._private.rscratch
// 'w:',
// edge.boundingbox().w,
// 'h:',
// edge.boundingbox().h,
// edge.midPoint()
data
);
// data.el.attr('transform', `translate(${node.position().x}, ${node.position().y})`);
// edgesEl
// .insert('line')
// .attr('x1', bounds.startX)
// .attr('y1', bounds.startY)
// .attr('x2', bounds.endX)
// .attr('y2', bounds.endY)
// .attr('class', 'path');
edgesEl edgesEl
.insert('path') .insert('path')
// Todo use regular line function
.attr( .attr(
'd', 'd',
`M ${bounds.startX},${bounds.startY} L ${bounds.midX},${bounds.midY} L${bounds.endX},${bounds.endY} ` `M ${bounds.startX},${bounds.startY} L ${bounds.midX},${bounds.midY} L${bounds.endX},${bounds.endY} `
@@ -76,19 +50,6 @@ function drawEdges(edgesEl, cy, conf) {
}); });
} }
/**
* @param mindmap
* @param callback
*/
function eachNode(mindmap, callback) {
callback(mindmap);
if (mindmap.children) {
mindmap.children.forEach((child) => {
eachNode(child, callback);
});
}
}
/** /**
* @param {any} svg The svg element to draw the diagram onto * @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The maindmap data and hierarchy * @param {object} mindmap The maindmap data and hierarchy
@@ -98,7 +59,7 @@ function eachNode(mindmap, callback) {
* @param level * @param level
*/ */
function addNodes(mindmap, cy, conf, level) { function addNodes(mindmap, cy, conf, level) {
const node = cy.add({ cy.add({
group: 'nodes', group: 'nodes',
data: { data: {
id: mindmap.id, id: mindmap.id,
@@ -116,9 +77,9 @@ function addNodes(mindmap, cy, conf, level) {
}, },
}); });
if (mindmap.children) { if (mindmap.children) {
mindmap.children.forEach((child, index) => { mindmap.children.forEach((child) => {
addNodes(child, cy, conf, level + 1); addNodes(child, cy, conf, level + 1);
const edge = cy.add({ cy.add({
group: 'edges', group: 'edges',
data: { data: {
id: `${mindmap.id}_${child.id}`, id: `${mindmap.id}_${child.id}`,
@@ -139,85 +100,45 @@ function addNodes(mindmap, cy, conf, level) {
*/ */
function layoutMindmap(node, conf) { function layoutMindmap(node, conf) {
return new Promise((resolve) => { return new Promise((resolve) => {
// BoundingBox(gap, bottomPadding)
// const bb = new BoundingBox(10, 10);
// const layout = new Layout(bb);
// // const layout = new HorizontalLayout(bb);
if (node.children.length === 0) { if (node.children.length === 0) {
return node; return node;
} }
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
const cy = cytoscape({ const cy = cytoscape({
// styleEnabled: false,
// animate: false,
// ready: function () {
// log.info('Ready', this);
// },
container: document.getElementById('cy'), // container to render in container: document.getElementById('cy'), // container to render in
style: [ style: [
// the stylesheet for the graph
{
selector: 'node',
style: {
'background-color': '#666',
label: 'data(labelText)',
},
},
{ {
selector: 'edge', selector: 'edge',
style: { style: {
width: 3,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier', 'curve-style': 'bezier',
label: 'data(id)',
}, },
}, },
], ],
}); });
// Remove element after layout
renderEl.remove();
addNodes(node, cy, conf, 0); addNodes(node, cy, conf, 0);
// Make cytoscape care about the dimensisions of the nodes // Make cytoscape care about the dimensisions of the nodes
cy.nodes().forEach(function (n) { cy.nodes().forEach(function (n) {
n.layoutDimensions = () => { n.layoutDimensions = () => {
const data = n.data(); const data = n.data();
// console.log(
// 'id',
// data.id,
// ' node',
// data.nodeId,
// ' layoutDimensions',
// data.width,
// 'x',
// data.height
// );
return { w: data.width, h: data.height }; return { w: data.width, h: data.height };
}; };
}); });
// // Merge the trees into a single tree
// mergeTrees(node, trees);
cy.layout({ cy.layout({
// name: 'grid',
// name: 'circle',
// name: 'cose',
// name: 'fcose',
name: 'cose-bilkent', name: 'cose-bilkent',
quality: 'proof', quality: 'proof',
// randomize: false,
// seed: 2,
// name: 'breadthfirst',
// headless: true, // headless: true,
styleEnabled: false, styleEnabled: false,
animate: false, animate: false,
}).run(); }).run();
cy.ready((e) => { cy.ready((e) => {
log.info('Ready', e); log.info('Ready', e);
resolve(cy);
resolve({ positionedMindmap: node, cy });
}); });
}); });
} }
@@ -227,7 +148,7 @@ function layoutMindmap(node, conf) {
* @param positionedMindmap * @param positionedMindmap
* @param conf * @param conf
*/ */
function positionNodes(cy, conf) { function positionNodes(cy) {
cy.nodes().map((node, id) => { cy.nodes().map((node, id) => {
const data = node.data(); const data = node.data();
data.x = node.position().x; data.x = node.position().x;
@@ -290,7 +211,7 @@ export const draw = async (text, id, version, diagObj) => {
// Next step is to layout the mindmap, giving each node a position // Next step is to layout the mindmap, giving each node a position
const { positionedMindmap, cy } = await layoutMindmap(mm, conf); const cy = await layoutMindmap(mm, conf);
// // After this we can draw, first the edges and the then nodes with the correct position // // After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, cy, conf); drawEdges(edgesElem, cy, conf);

View File

@@ -1,269 +0,0 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import svgDraw from './svgDraw';
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
import clone from 'fast-clone';
import * as db from './mindmapDb';
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The maindmap data and hierarchy
* @param section
* @param {object} conf The configuration object
*/
function drawNodes(svg, mindmap, section, conf) {
svgDraw.drawNode(svg, mindmap, section, conf);
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawNodes(svg, child, section < 0 ? index : section, conf);
});
}
}
/**
* @param edgesElem
* @param mindmap
* @param parent
* @param depth
* @param section
* @param conf
*/
function drawEdges(edgesElem, mindmap, parent, depth, section, conf) {
if (parent) {
svgDraw.drawEdge(edgesElem, mindmap, parent, depth, section, conf);
}
if (mindmap.children) {
mindmap.children.forEach((child, index) => {
drawEdges(edgesElem, child, mindmap, depth + 1, section < 0 ? index : section, conf);
});
}
}
/**
* @param mindmap
* @param callback
*/
function eachNode(mindmap, callback) {
callback(mindmap);
if (mindmap.children) {
mindmap.children.forEach((child) => {
eachNode(child, callback);
});
}
}
/** @param {object} mindmap */
function transpose(mindmap) {
eachNode(mindmap, (node) => {
const orgWidth = node.width;
const orgX = node.x;
node.width = node.height;
node.height = orgWidth;
node.x = node.y;
node.y = orgX;
});
return mindmap;
}
/** @param {object} mindmap */
function bottomToUp(mindmap) {
log.debug('bottomToUp', mindmap);
eachNode(mindmap.result, (node) => {
// node.y = node.y - (node.y - bb.top) * 2 - node.height;
node.y = node.y - (node.y - 0) * 2 - node.height;
});
return mindmap;
}
/** @param {object} mindmap The mindmap hierarchy */
function rightToLeft(mindmap) {
eachNode(mindmap.result, (node) => {
// node.y = node.y - (node.y - bb.top) * 2 - node.height;
node.x = node.x - (node.x - 0) * 2 - node.width;
});
return mindmap;
}
/**
* @param mindmap
* @param dir
*/
function layout(mindmap, dir) {
const bb = new BoundingBox(30, 60);
const layout = new Layout(bb);
switch (dir) {
case 'TB':
return layout.layout(mindmap);
case 'BT':
return bottomToUp(layout.layout(mindmap));
case 'RL': {
transpose(mindmap);
let newRes = layout.layout(mindmap);
transpose(newRes.result);
return rightToLeft(newRes);
}
case 'LR': {
transpose(mindmap);
let newRes = layout.layout(mindmap);
transpose(newRes.result);
return newRes;
}
default:
}
}
const dirFromIndex = (index) => {
const dirNum = (index + 2) % 4;
switch (dirNum) {
case 0:
return 'LR';
case 1:
return 'RL';
case 2:
return 'TB';
case 3:
return 'BT';
default:
return 'TB';
}
};
const mergeTrees = (node, trees) => {
node.x = trees[0].result.x;
node.y = trees[0].result.y;
trees.forEach((tree) => {
tree.result.children.forEach((child) => {
const dx = node.x - tree.result.x;
const dy = node.y - tree.result.y;
eachNode(child, (childNode) => {
const orgNode = db.getNodeById(childNode.id);
if (orgNode) {
orgNode.x = childNode.x + dx;
orgNode.y = childNode.y + dy;
}
});
});
});
return node;
};
/**
* @param node
* @param conf
*/
function layoutMindmap(node, conf) {
// BoundingBox(gap, bottomPadding)
// const bb = new BoundingBox(10, 10);
// const layout = new Layout(bb);
// // const layout = new HorizontalLayout(bb);
if (node.children.length === 0) {
return node;
}
const trees = [];
// node.children.forEach((child, index) => {
// const tree = clone(node);
// tree.children = [tree.children[index]];
// trees.push(layout(tree, dirFromIndex(index), conf));
// });
let cnt = 0;
// For each direction, create a new tree with the same root, and add a ubset of the children to it.
for (let i = 0; i < 4; i++) {
// Calculate the number of the children of the root node that will be used in this direction
const numChildren =
Math.floor(node.children.length / 4) + (node.children.length % 4 > i ? 1 : 0);
// Copy the original root node
const tree = clone(node);
// Setup the new copy with the children to be rendered in this direction
tree.children = [];
for (let j = 0; j < numChildren; j++) {
tree.children.push(node.children[cnt]);
cnt++;
}
if (tree.children.length > 0) {
trees.push(layout(tree, dirFromIndex(i), conf));
}
}
// Let each node know the direct of its tree for when we draw the branches.
trees.forEach((tree, index) => {
tree.result.direction = dirFromIndex(index);
eachNode(tree.result, (node) => {
node.direction = dirFromIndex(index);
});
});
// Merge the trees into a single tree
mergeTrees(node, trees);
return node;
}
/**
* @param node
* @param conf
*/
function positionNodes(node, conf) {
svgDraw.positionNode(node, conf);
if (node.children) {
node.children.forEach((child) => {
positionNodes(child, conf);
});
}
}
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw = (text, id, version, diagObj) => {
const conf = getConfig();
// This is done only for throwing the error if the text is not valid.
diagObj.db.clear();
// Parse the graph definition
diagObj.parser.parse(text);
log.debug('Renering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
const svg = root.select('#' + id);
svg.append('g');
const mm = diagObj.db.getMindmap();
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
const edgesElem = svg.append('g');
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const positionedMindmap = layoutMindmap(mm, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, positionedMindmap, null, 0, -1, conf);
positionNodes(positionedMindmap, conf);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
};
export default {
draw,
};

View File

@@ -1,24 +1,17 @@
// @ts-ignore: TODO Fix ts errors // @ts-ignore: TODO Fix ts errors
import { mindmapDetector } from './mindmapDetector'; import { mindmapDetector } from './mindmapDetector';
// const getBaseFolder = () => {
const scriptElement = document.currentScript as HTMLScriptElement; const scriptElement = document.currentScript as HTMLScriptElement;
const path = scriptElement.src; const path = scriptElement.src;
const lastSlash = path.lastIndexOf('/'); const lastSlash = path.lastIndexOf('/');
const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1); const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1);
// };
// console.log('Current script', getBaseFolder(scriptElement !== null ? scriptElement.src : '')); // eslint-disable-line no-console
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
if (window.mermaid && typeof window.mermaid.detectors === 'object') { if (window.mermaid && typeof window.mermaid.detectors === 'object') {
window.mermaid.detectors.push({ id: 'mindmap', detector: mindmapDetector }); window.mermaid.detectors.push({ id: 'mindmap', detector: mindmapDetector });
} else { } else {
// console.error('window.mermaid.detectors was not found!'); // eslint-disable-line no-console
window.mermaid = {}; window.mermaid = {};
window.mermaid.detectors = [{ id: 'mindmap', detector: mindmapDetector }]; window.mermaid.detectors = [{ id: 'mindmap', detector: mindmapDetector }];
// console.error('Detectors now:', window.mermaid.detectors); // eslint-disable-line no-console
} }
/*! /*!