mermaid/packages/mermaid/src/diagrams/block/layout.ts

417 lines
10 KiB
TypeScript
Raw Normal View History

import type { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
import { log } from '../../logger.js';
const padding = 8;
interface BlockPosition {
px: number;
py: number;
}
export function calculateBlockPosition(columns: number, position: number): BlockPosition {
2024-01-18 14:28:14 +01:00
// log.debug('calculateBlockPosition abc89', columns, position);
// Ensure that columns is a positive integer
if (columns === 0 || !Number.isInteger(columns)) {
throw new Error('Columns must be an integer !== 0.');
}
// Ensure that position is a non-negative integer
if (position < 0 || !Number.isInteger(position)) {
2024-01-08 15:48:59 +01:00
throw new Error('Position must be a non-negative integer.' + position);
}
if (columns < 0) {
// Auto coulumns is set
return { px: position, py: 0 };
}
if (columns === 1) {
// Auto coulumns is set
return { px: 0, py: position };
}
// Calculate posX and posY
const px = position % columns;
const py = Math.floor(position / columns);
2024-01-18 14:28:14 +01:00
// log.debug('calculateBlockPosition abc89', columns, position, '=> (', px, py, ')');
return { px, py };
}
2023-10-03 20:12:33 +02:00
const getMaxChildSize = (block: Block) => {
let maxWidth = 0;
let maxHeight = 0;
// find max width of children
2024-01-18 14:28:14 +01:00
// log.debug('getMaxChildSize abc95 (start) parent:', block.id);
for (const child of block.children) {
const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 };
2024-01-18 14:28:14 +01:00
log.debug(
'getMaxChildSize abc95 child:',
child.id,
'width:',
width,
'height:',
height,
'x:',
x,
'y:',
2024-01-18 14:28:14 +01:00
y,
child.type
);
2024-01-18 14:28:14 +01:00
if (child.type === 'space') {
continue;
}
if (width > maxWidth) {
maxWidth = width / (block.w || 1);
}
if (height > maxHeight) {
maxHeight = height;
}
}
return { width: maxWidth, height: maxHeight };
};
2024-01-18 15:44:16 +01:00
function setBlockSizes(block: Block, db: BlockDB, sieblingWidth = 0, sieblingHeight = 0) {
2024-01-18 14:28:14 +01:00
log.debug(
'setBlockSizes abc95 (start)',
block.id,
block?.size?.x,
'block width =',
2024-01-18 14:28:14 +01:00
block?.size,
'sieblingWidth',
sieblingWidth
);
2024-01-18 14:28:14 +01:00
if (!block?.size?.width) {
block.size = {
width: sieblingWidth,
height: sieblingHeight,
x: 0,
y: 0,
};
}
2023-10-03 14:19:08 +02:00
const totalWidth = 0;
const totalHeight = 0;
let maxWidth = 0;
let maxHeight = 0;
if (block.children?.length > 0) {
for (const child of block.children) {
setBlockSizes(child, db);
}
// find max width of children
const childSize = getMaxChildSize(block);
maxWidth = childSize.width;
maxHeight = childSize.height;
2024-01-18 14:28:14 +01:00
log.debug('setBlockSizes abc95 maxWidth of', block.id, ':s children is ', maxWidth, maxHeight);
// set width of block to max width of children
for (const child of block.children) {
if (child.size) {
2024-01-18 14:28:14 +01:00
log.debug(
'abc95 Setting size of children of',
block.id,
'id=',
child.id,
maxWidth,
maxHeight,
child.size
);
2024-01-18 15:44:16 +01:00
child.size.width = maxWidth * (child.w || 1) + padding * ((child.w || 1) - 1);
child.size.height = maxHeight;
2023-10-03 20:12:33 +02:00
child.size.x = 0;
child.size.y = 0;
2024-01-18 14:28:14 +01:00
log.debug(
'abc95 updating size of ',
block.id,
' children child:',
child.id,
'maxWidth:',
maxWidth,
'maxHeight:',
maxHeight
);
}
}
for (const child of block.children) {
2024-01-18 14:28:14 +01:00
// log.debug('abc95 fin 2 Setting size', child.id, maxWidth, maxHeight, child.size);
2024-01-08 15:48:59 +01:00
setBlockSizes(child, db, maxWidth, maxHeight);
2024-01-18 14:28:14 +01:00
// log.debug('abc95 fin 3 Setting size', child.id, maxWidth, maxHeight, child.size);
}
const columns = block.columns || -1;
const numItems = block.children.length;
// The width and height in number blocks
2024-01-08 15:48:59 +01:00
let xSize = block.children.length;
if (columns > 0 && columns < numItems) {
xSize = columns;
}
const w = block.w || 1;
const ySize = Math.ceil(numItems / xSize);
let width = xSize * (maxWidth + padding) + padding;
2024-01-08 15:48:59 +01:00
let height = ySize * (maxHeight + padding) + padding;
// If maxWidth
if (width < sieblingWidth) {
2024-01-18 14:28:14 +01:00
log.debug(
'Detected to small siebling: abc95',
block.id,
'sieblingWidth',
sieblingWidth,
2024-01-08 15:48:59 +01:00
'sieblingHeight',
sieblingHeight,
'width',
width
);
width = sieblingWidth;
2024-01-08 15:48:59 +01:00
height = sieblingHeight;
const childWidth = (sieblingWidth - xSize * padding - padding) / xSize;
2024-01-08 15:48:59 +01:00
const childHeight = (sieblingHeight - ySize * padding - padding) / ySize;
log.debug('Size indata abc88', block.id, 'childWidth', childWidth, 'maxWidth', maxWidth);
2024-01-08 15:48:59 +01:00
log.debug('Size indata abc88', block.id, 'childHeight', childHeight, 'maxHeight', maxHeight);
log.debug('Size indata abc88 xSize', xSize, 'paddiong', padding);
2024-01-08 15:48:59 +01:00
// set width of block to max width of children
for (const child of block.children) {
if (child.size) {
child.size.width = childWidth;
2024-01-08 15:48:59 +01:00
child.size.height = childHeight;
child.size.x = 0;
child.size.y = 0;
}
}
}
2024-01-18 14:28:14 +01:00
log.debug(
'abc95 (finale calc)',
block.id,
'xSize',
xSize,
'ySize',
ySize,
'columns',
columns,
block.children.length,
'width=',
Math.max(width, block.size?.width || 0)
);
if (width < (block?.size?.width || 0)) {
width = block?.size?.width || 0;
// Grow children to fit
const num = block.children.length;
if (num > 0) {
const childWidth = (width - num * padding - padding) / num;
2024-01-18 14:28:14 +01:00
// log.debug('abc95 (finale calc) width', block.id, width, block.size?.width, childWidth);
for (const child of block.children) {
if (child.size) {
child.size.width = childWidth;
}
}
}
}
2023-10-03 14:19:08 +02:00
block.size = {
width,
2024-01-08 15:48:59 +01:00
height,
2023-10-03 14:19:08 +02:00
x: 0,
y: 0,
};
2023-10-03 12:56:47 +02:00
}
2024-01-18 14:28:14 +01:00
log.debug(
'setBlockSizes abc94 (done)',
2024-01-08 15:48:59 +01:00
block.id,
block?.size?.x,
block?.size?.width,
block?.size?.y,
block?.size?.height
);
2023-10-03 20:12:33 +02:00
}
function layoutBlocks(block: Block, db: BlockDB) {
log.debug(
2024-01-18 14:28:14 +01:00
'abc85 layout blocks (=>layoutBlocks)',
block.id,
'x:',
block?.size?.x,
2024-01-08 15:48:59 +01:00
'y:',
block?.size?.y,
'width:',
block?.size?.width
);
const columns = block.columns || -1;
2024-01-18 14:28:14 +01:00
log.debug('layoutBlocks columns abc95', block.id, '=>', columns, block);
2023-10-03 20:12:33 +02:00
if (
block.children && // find max width of children
block.children.length > 0
) {
const width = block?.children[0]?.size?.width || 0;
const widthOfChildren = block.children.length * width + (block.children.length - 1) * padding;
2024-01-08 15:48:59 +01:00
log.debug('widthOfChildren 88', widthOfChildren, 'posX');
2023-10-03 20:12:33 +02:00
// let first = true;
2024-01-08 15:48:59 +01:00
let columnPos = 0;
2024-01-18 14:28:14 +01:00
log.debug('abc91 block?.size?.x', block.id, block?.size?.x);
let startingPosX = block?.size?.x ? block?.size?.x + (-block?.size?.width / 2 || 0) : -padding;
let rowPos = 0;
2023-10-03 20:12:33 +02:00
for (const child of block.children) {
const parent = block;
2023-10-03 20:12:33 +02:00
if (!child.size) {
continue;
}
const { width, height } = child.size;
const { px, py } = calculateBlockPosition(columns, columnPos);
if (py != rowPos) {
rowPos = py;
startingPosX = block?.size?.x || -padding;
}
2024-01-18 14:28:14 +01:00
log.debug(
'abc89 layout blocks (child) id:',
2024-01-08 15:48:59 +01:00
child.id,
'Pos:',
columnPos,
2024-01-08 15:48:59 +01:00
' (px, py)',
px,
py,
' (',
parent?.size?.x,
',',
parent?.size?.y,
')',
'parent:',
parent.id,
'width:',
width,
padding
);
if (parent.size) {
// child.size.x =
// block.size.x -
// block.size.width / 2 +
// px * (child?.w || 1) * (width + padding) +
// width / 2 +
// padding;
const halfWidth = width / 2;
child.size.x = startingPosX + padding + halfWidth;
2024-01-18 14:28:14 +01:00
log.debug(
'abc91 layout blocks (calc) px, py',
'id:',
child.id,
'startingPosX',
startingPosX,
'new startingPosX',
child.size.x + halfWidth,
'padding',
padding,
'width=',
width,
'halfWidth',
halfWidth,
'=>',
'x:',
child.size.x,
'y:',
child.size.y,
child.w,
'(width * (child?.w || 1)) / 2',
(width * (child?.w || 1)) / 2
);
startingPosX = child.size.x + halfWidth;
child.size.y =
parent.size.y - parent.size.height / 2 + py * (height + padding) + height / 2 + padding;
2024-01-18 14:28:14 +01:00
log.debug(
2024-01-08 15:48:59 +01:00
'abc88 layout blocks (calc) px, py',
'id:',
child.id,
'startingPosX',
startingPosX,
padding,
halfWidth,
'=>',
'x:',
child.size.x,
'y:',
child.size.y,
child.w,
'(width * (child?.w || 1)) / 2',
(width * (child?.w || 1)) / 2
);
}
2023-10-03 20:12:33 +02:00
// posY += height + padding;
if (child.children) {
layoutBlocks(child, db);
}
columnPos += child?.w || 1;
2024-01-18 14:28:14 +01:00
log.debug('abc88 columnsPos', child, columnPos);
2023-10-03 20:12:33 +02:00
}
}
log.debug(
'layout blocks (<==layoutBlocks)',
2023-10-03 20:12:33 +02:00
block.id,
'x:',
2023-10-03 20:12:33 +02:00
block?.size?.x,
2024-01-08 15:48:59 +01:00
'y:',
block?.size?.y,
2023-10-03 20:12:33 +02:00
'width:',
block?.size?.width
);
}
let minX = 0;
let minY = 0;
let maxX = 0;
let maxY = 0;
function findBounds(block: Block) {
2023-10-03 20:12:33 +02:00
if (block.size && block.id !== 'root') {
const { x, y, width, height } = block.size;
2023-09-14 10:11:43 +02:00
if (x - width / 2 < minX) {
minX = x - width / 2;
// log.debug('Here APA minX', block.id, x, width, minX);
}
2023-09-14 10:11:43 +02:00
if (y - height / 2 < minY) {
minY = y - height / 2;
}
2023-09-14 10:11:43 +02:00
if (x + width / 2 > maxX) {
maxX = x + width / 2;
}
2023-09-14 10:11:43 +02:00
if (y + height / 2 > maxY) {
maxY = y + height / 2;
}
}
if (block.children) {
for (const child of block.children) {
findBounds(child);
}
}
}
export function layout(db: BlockDB) {
const root = db.getBlock('root');
if (!root) {
return;
}
2024-01-08 15:48:59 +01:00
setBlockSizes(root, db, 0, 0);
2023-10-03 20:12:33 +02:00
layoutBlocks(root, db);
2023-10-03 12:56:47 +02:00
// Position blocks relative to parents
2023-10-03 20:12:33 +02:00
// positionBlock(root, root, db);
log.debug('getBlocks', JSON.stringify(root, null, 2));
minX = 0;
minY = 0;
maxX = 0;
maxY = 0;
findBounds(root);
// log.debug('Here maxX', minX, '--', maxX);
const height = maxY - minY;
const width = maxX - minX;
return { x: minX, y: minY, width, height };
}