Layout algorithm in place

This commit is contained in:
Knut Sveidqvist 2022-07-24 11:05:54 +02:00
parent 7de68f0bf2
commit 8e5e212c49
8 changed files with 271 additions and 32 deletions

View File

@ -22,6 +22,7 @@
}
.mermaid svg {
/* font-size: 18px !important; */
border: 1px solid red;
}
</style>
</head>
@ -41,7 +42,7 @@ journey
Go downstairs: 5: Me
Sit down: 5: Mee
</div>
<div class="mermaid" style="width: 50%;">
<div class="mermaid2" style="width: 50%;">
mindmap
root[
The root where the things
@ -50,6 +51,52 @@ mindmap
pen!
]
Child1
child2[
Child2<br/>
The second<br/>
The second<br/>
The second<br/>
The second<br/>
The second<br/>
The second<br/>
The second<br/>
]
Other
Child3
GrandChild1
sc1
sc2
sc3
GrandChild2
</div>
<div class="mermaid" style="width: 50%;">
mindmap
root[
The root where the things
happen!
]
Child2
GrandChild1
GrandChild2
Child3
GrandChild3
GrandChild4
Child4
GrandChild5
GrandChild6
Child1
GrandChild1
sc1
sc2
sc3
GrandChild2
Child5
GrandChild7
sc1
sc2
sc3
GrandChild7
</div>
<div class="mermaid2" style="width: 50%;">
pie
@ -62,7 +109,7 @@ mindmap
"Magnesium" : 10.01
"Iron" : 5
</div>
<div class="mermaid" style="width: 50%;">
<div class="mermaid2" style="width: 50%;">
gitGraph TB
commit
commit

View File

@ -63,9 +63,11 @@
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.8",
"fast-clone": "^1.5.13",
"graphlib": "^2.1.8",
"khroma": "^2.0.0",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.0.10"
},
"devDependencies": {
@ -112,7 +114,7 @@
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
},
"resolutions": {
"resolutions": {
"d3": "^7.0.0"
},
"files": [

View File

@ -83,7 +83,6 @@ const detectType = function (text, cnf) {
if (cnf && cnf.flowchart && cnf.flowchart.defaultRenderer === 'dagre-wrapper')
return 'flowchart-v2';
const k = Object.keys(detectors);
console.log('here', k);
for (let i = 0; i < k.length; i++) {
const key = k[i];
console.log('Detecting type for', key);

View File

@ -85,7 +85,7 @@ describe('when parsing a mindmap ', function () {
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
});
@ -100,7 +100,7 @@ describe('when parsing a mindmap ', function () {
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.descr).toEqual('child1');
expect(child.id).toEqual('theId');
expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('should handle an id and type for a node definition', function () {
@ -114,7 +114,7 @@ root
expect(mm.children.length).toEqual(1);
const child = mm.children[0];
expect(child.descr).toEqual('child1');
expect(child.id).toEqual('theId');
expect(child.nodeId).toEqual('theId');
expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
});
it('mutiple types (circle)', function () {
@ -139,7 +139,7 @@ root((the root))
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.icon).toEqual('bomb');
@ -153,7 +153,7 @@ root((the root))
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8');
@ -168,7 +168,7 @@ root((the root))
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('The root');
expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
expect(mm.class).toEqual('m-4 p-8');
@ -182,7 +182,7 @@ root((the root))
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []');
});
it('should be possible to use node syntax in the descriptions in children', function () {
@ -192,7 +192,7 @@ root((the root))
`;
mindmap.parse(str);
const mm = mindmap.yy.getMindmap();
expect(mm.id).toEqual('root');
expect(mm.nodeId).toEqual('root');
expect(mm.descr).toEqual('String containing []');
expect(mm.children.length).toEqual(1);
expect(mm.children[0].descr).toEqual('String containing ()');

View File

@ -5,9 +5,12 @@ var message = '';
var info = false;
const root = {};
let nodes = [];
let cnt = 0;
let elements = {};
export const clear = () => {
nodes = [];
cnt = 0;
elements = {};
};
const getParent = function (level) {
@ -26,7 +29,8 @@ export const getMindmap = () => {
};
export const addNode = (level, id, descr, type) => {
const node = {
id: sanitizeText(id),
id: cnt++,
nodeId: sanitizeText(id),
level,
descr: sanitizeText(descr),
type,
@ -79,6 +83,11 @@ export const getTypeFromStart = (str) => {
return nodeType.DEFAULT;
}
};
export const setElementForId = (id, element) => {
elements[id] = element;
};
export const decorateNode = (decoration) => {
console.log('decorateNode', decoration);
const node = nodes[nodes.length - 1];
@ -96,5 +105,9 @@ export default {
nodeType,
getTypeFromStart,
decorateNode,
setElementForId,
getElementById: (id) => elements[id],
// getNodeById: (id) => nodes.find((node) => node.id === id),
getNodeById: (id) => nodes[id],
// parseError
};

View File

@ -2,7 +2,9 @@
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
import svgDraw from './svgDraw';
import { BoundingBox, Layout, Tree } from 'non-layered-tidy-tree-layout';
import clone from 'fast-clone';
import db from './mindmapDb';
/**
* @param {any} svg The svg element to draw the diagram onto
* @param {object} mindmap The maindmap data and hierarchy
@ -17,17 +19,181 @@ function drawNodes(svg, mindmap, conf) {
}
}
/** @param {any} svg The svg element to draw the diagram onto */
function drawEdges() {}
/**
* @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) {
console.log('transpose', mindmap);
eachNode(mindmap, (node) => {
// node.y = node.y - (node.y - bb.top) * 2 - node.height;
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) {
console.log('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) {
console.log('bottomToUp', 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
* @param conf
*/
function layout(mindmap, dir, conf) {
const bb = new BoundingBox(40, 40);
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 % 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 isRoot
* @param parent
* @param conf
*/
function layoutMindmap(node, isRoot) {}
function layoutMindmap(node, conf) {
// BoundingBox(gap, bottomPadding)
// const bb = new BoundingBox(10, 10);
// const layout = new Layout(bb);
// // const layout = new HorizontalLayout(bb);
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));
}
}
// Merge the trees into a single tree
const result = mergeTrees(node, trees);
// return layout(node, 'BT', conf);
// const res = layout(node, 'BT', conf);
// res.result.children = [];
// trees.forEach((tree) => {
// res.result.children.push(tree.result);
// });
console.log('Trees', trees);
return node;
}
/**
* @param node
* @param isRoot
* @param conf
*/
function positionNodes(node, isRoot) {}
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.
@ -63,16 +229,6 @@ export const draw = (text, id, version, diagObj) => {
const g = svg.append('g');
const mm = diagObj.db.getMindmap();
// mm.x = 0;
// mm.y = 0;
// svgDraw.drawNode(g, mm, getConfig());
// mm.children.forEach((child) => {
// child.x = 200;
// child.y = 200;
// child.width = 200;
// svgDraw.drawNode(g, child, getConfig());
// });
// 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
@ -82,12 +238,14 @@ export const draw = (text, id, version, diagObj) => {
// Next step is to layout the mindmap, giving each node a position
// layoutMindmap(mm, conf);
console.log('Before', mm);
const positionedMindmap = layoutMindmap(mm, conf);
console.log(positionedMindmap);
// After this we can draw, first the edges and the then nodes with the correct position
// drawEdges(svg, mm, conf);
// positionNodes(svg, mm, conf);
positionNodes(positionedMindmap, conf);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.diagramPadding, conf.mindmap.useMaxWidth);

View File

@ -1,5 +1,7 @@
const lineBreakRegex = /<br\s*\/?>/gi;
import { select } from 'd3';
import db from './mindmapDb';
/**
* @param {string} text The text to be wrapped
* @param {number} width The max width of the text
@ -88,13 +90,21 @@ export const drawNode = function (elem, node, conf) {
.call(wrap, node.width);
const bbox = txt.node().getBBox();
node.height = bbox.height + conf.fontSize * 1.1 * 0.5;
r.attr('height', node.height).attr('y', (-1 * node.height) / 2);
r.attr('height', node.height); // .attr('y', (-1 * node.height) / 2);
txt.attr('transform', 'translate( 0,' + (-1 * node.height) / 2 + ')');
// Position the node to its coordinate
if (node.x || node.y) {
nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
}
db.setElementForId(node.id, nodeElem);
return node.height;
};
export default { drawNode };
export const positionNode = function (node, conf) {
const nodeElem = db.getElementById(node.id);
const x = node.x || 0;
const y = node.y || 0;
// Position the node to its coordinate
nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
};
export default { drawNode, positionNode };

View File

@ -5574,6 +5574,11 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
fast-clone@^1.5.13:
version "1.5.13"
resolved "https://registry.yarnpkg.com/fast-clone/-/fast-clone-1.5.13.tgz#7fe17542ae1c872e71bf80d177d00c11f51c2ea7"
integrity sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -8663,6 +8668,11 @@ nomnom@1.5.2:
chalk "~0.4.0"
underscore "~1.6.0"
non-layered-tidy-tree-layout@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"