Layout tweaks
This commit is contained in:
parent
6659fc9e96
commit
3dff4b5d51
|
@ -1,21 +1,34 @@
|
||||||
|
|
||||||
|
const {
|
||||||
|
filter,
|
||||||
|
} = require('ramda');
|
||||||
|
|
||||||
const { size } = require('../../constants/view');
|
const { size } = require('../../constants/view');
|
||||||
|
|
||||||
|
const plusMinus = require('../../utils/plus-minus');
|
||||||
|
|
||||||
const margin = size / 10;
|
const margin = size / 10;
|
||||||
const step = size + (2 * margin);
|
const step = size + (2 * margin);
|
||||||
|
|
||||||
function nodesIntersect(a, b) {
|
const offsetY = 1080 / 2;
|
||||||
if (a.x === undefined || a.y === undefined || b.x === undefined || b.y === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (((a.x - size - margin) < b.x) && (b.x < (a.x + size + margin))) &&
|
const centerColumnsCount = 5;
|
||||||
(((a.y - size - margin) < b.y) && (b.y < (a.y + size + margin)));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = class LayoutEngine {
|
module.exports = class LayoutEngine {
|
||||||
constructor(graphViewProps) {
|
constructor() {
|
||||||
this.graphViewProps = graphViewProps;
|
Object.assign(this, {
|
||||||
|
size,
|
||||||
|
margin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesIntersect(a, b) {
|
||||||
|
if (a.x === undefined || a.y === undefined || b.x === undefined || b.y === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (((a.x - size - margin) < b.x) && (b.x < (a.x + size + margin))) &&
|
||||||
|
(((a.y - size - margin) < b.y) && (b.y < (a.y + size + margin)));
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatePosition(node) {
|
calculatePosition(node) {
|
||||||
|
@ -23,36 +36,76 @@ module.exports = class LayoutEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustNodes(nodes) {
|
adjustNodes(nodes) {
|
||||||
nodes.forEach(node => {
|
const targetClientsColumnHeight = Math.round(filter(
|
||||||
|
x => x.type === 'sink' || x.type === 'source',
|
||||||
|
nodes,
|
||||||
|
).length * 0.75);
|
||||||
|
|
||||||
|
const estimatedColumnHeights = {
|
||||||
|
total: 0,
|
||||||
|
|
||||||
|
get(k) {
|
||||||
|
return this[k] || 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
inc(k) {
|
||||||
|
this[k] = this[k] || 0;
|
||||||
|
this[k] += 1;
|
||||||
|
this.total += 1;
|
||||||
|
return this[k];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeColumn = node => Math.round(node.x / step) - 2;
|
||||||
|
|
||||||
|
const unpositionedNodes = nodes.filter(node => {
|
||||||
if (node.type === 'satellite') {
|
if (node.type === 'satellite') {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.x !== undefined) {
|
if (node.x !== undefined) {
|
||||||
return;
|
estimatedColumnHeights.inc(nodeColumn(node));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
unpositionedNodes.forEach(node => {
|
||||||
if (node.type === 'source') {
|
if (node.type === 'source') {
|
||||||
node.x = 0 * step;
|
node.x = 0 * step;
|
||||||
} else if (node.type === 'sink') {
|
} else if (node.type === 'sink') {
|
||||||
node.x = 8 * step;
|
node.x = 8 * step;
|
||||||
} else {
|
} else {
|
||||||
node.x = (2 * step) + ((node.index % 5) * step);
|
let targetCol = node.index % centerColumnsCount;
|
||||||
|
if (estimatedColumnHeights.get(2) < targetClientsColumnHeight) {
|
||||||
|
targetCol = 2;
|
||||||
|
}
|
||||||
|
node.x = (2 * step) + (targetCol * step);
|
||||||
}
|
}
|
||||||
|
|
||||||
node.y = 0;
|
const columnHeight = estimatedColumnHeights.inc(nodeColumn(node));
|
||||||
|
|
||||||
for (const otherNode of nodes) {
|
const direction = Math.sign(plusMinus(node.index));
|
||||||
if (otherNode.type === 'satellite') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherNode === node) {
|
node.y = offsetY + (direction * (Math.floor(columnHeight / 2) - 1) * (size + margin));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodesIntersect(node, otherNode)) {
|
let intersected = true;
|
||||||
node.y += size + margin;
|
while (intersected) {
|
||||||
|
intersected = false;
|
||||||
|
for (const otherNode of nodes) {
|
||||||
|
if (otherNode.type === 'satellite') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherNode === node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nodesIntersect(node, otherNode)) {
|
||||||
|
node.y += direction * (size + margin);
|
||||||
|
intersected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
30
components/graph/layout-engine.test.js
Normal file
30
components/graph/layout-engine.test.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
import test from 'ava';
|
||||||
|
|
||||||
|
import LayoutEngine from './layout-engine';
|
||||||
|
|
||||||
|
const n = (x, y) => ({ x, y });
|
||||||
|
|
||||||
|
test('nodesIntersect', t => {
|
||||||
|
const l = new LayoutEngine();
|
||||||
|
const { size, margin } = l;
|
||||||
|
|
||||||
|
const true_ = (x1, y1, x2, y2) => t.true(l.nodesIntersect(n(x1, y1), n(x2, y2)));
|
||||||
|
const false_ = (x1, y1, x2, y2) => t.false(l.nodesIntersect(n(x1, y1), n(x2, y2)));
|
||||||
|
|
||||||
|
[
|
||||||
|
[ 0, 0 ],
|
||||||
|
[ 500, 500 ],
|
||||||
|
[ -500, -500 ],
|
||||||
|
].forEach(([ x0, y0 ]) => {
|
||||||
|
true_(x0, y0, x0, y0);
|
||||||
|
|
||||||
|
false_(x0, y0, x0 + size + margin, y0);
|
||||||
|
false_(x0, y0, x0, y0 + size + margin);
|
||||||
|
|
||||||
|
true_(x0, y0, x0 + size + margin - 1, y0);
|
||||||
|
true_(x0, y0, x0, y0 + size + margin - 1);
|
||||||
|
|
||||||
|
true_(x0, y0, x0 + size + margin - 1, y0 + size + margin - 1);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user