#3358 Adding support for column statements

This commit is contained in:
Knut Sveidqvist 2023-10-15 22:21:25 +02:00
parent da79b371fe
commit a641fd51e8
7 changed files with 223 additions and 208 deletions

View File

@ -65,27 +65,62 @@
<body>
<pre id="diagram" class="mermaid">
block-beta
id1("Wide 1")
%%id2("2")
block
id3["I am a wide one"]
block
id44("A final one")
id45("B final one")
end
columns 1
id1
id2
id3("Wider then")
end
id4("Another final one")
id4
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
block-beta
block
columns 1
block
columns 3
id1
id2
id2.1
%%id2.2
end
id48
end
id3
%% id3
%% id4
%% block
%% columns 2
%% id2
%% id3
%% end
</pre>
<pre id="diagram" class="mermaid2">
block-beta
block
columns 1
id1
id2
%%id2.1
end
id3
%% id3
%% id4
%% block
%% columns 2
%% id2
%% id3
%% end
</pre>
<pre id="diagram" class="mermaid2">
block-beta
id1
block
id2
end
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
block-beta
id1["Hello"]
block
@ -96,7 +131,7 @@ block-beta
id5["World"]
end
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
block-beta
columns 2
block

View File

@ -1,9 +1,8 @@
// import type { BlockDB } from './blockTypes.js';
import type { DiagramDB } from '../../diagram-api/types.js';
import { BlockConfig, BlockType, Block, Link } from './blockTypes.js';
import type { BlockConfig, BlockType, Block, Link } from './blockTypes.js';
import * as configApi from '../../config.js';
// import common from '../common/common.js';
import {
// setAccTitle,
// getAccTitle,
@ -37,9 +36,8 @@ const populateBlockDatabase = (blockList: Block[], parent: Block): void => {
if (block.children) {
populateBlockDatabase(block.children, block);
}
if (block.type !== 'column-setting') {
children.push(block);
}
children.push(block);
}
}
parent.children = children;
@ -79,9 +77,10 @@ export const generateId = () => {
type ISetHierarchy = (block: Block[]) => void;
const setHierarchy = (block: Block[]): void => {
rootBlock.children = block;
populateBlockDatabase(block, rootBlock);
log.debug('The hierarchy', JSON.stringify(block, null, 2));
blocks = block;
log.debug('The hierarchy', JSON.stringify(rootBlock, null, 2));
blocks = rootBlock.children;
};
type IAddLink = (link: Link) => Link;

View File

@ -7,16 +7,14 @@ import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10,
ContainerElement,
} from 'd3';
import { log } from '../../logger.js';
import { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
// import { diagram as BlockDiagram } from './blockDiagram.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { Uid } from '../../rendering-util/uid.js';
import { pad } from 'lodash';
export const draw = async function (
text: string,
@ -43,27 +41,28 @@ export const draw = async function (
const nodes = svg.insert('g').attr('class', 'block');
await calculateBlockSizes(nodes, bl, db);
const bounds = layout(db);
console.log('Here blocks', bl);
log.debug('Here blocks', bl);
await insertBlocks(nodes, bl, db);
// console.log('Here', bl);
// log.debug('Here', bl);
// Establish svg dimensions and get width and height
//
// const bounds2 = nodes.node().getBoundingClientRect();
const bounds2 = bounds;
const padding = 10;
// Why, oh why ????
const magicFactor = Math.max(1, Math.round(0.125 * (bounds2.width / bounds2.height)));
const height = bounds2.height + magicFactor + 10;
const width = bounds2.width + 10;
const useMaxWidth = false;
configureSvgSize(svg, height, width, useMaxWidth);
console.log('Here Bounds', bounds, bounds2);
svg.attr(
'viewBox',
`${bounds2.x - 5} ${bounds2.y - 5} ${bounds2.width + 10} ${bounds2.height + 10}`
);
if (bounds) {
const bounds2 = bounds;
const magicFactor = Math.max(1, Math.round(0.125 * (bounds2.width / bounds2.height)));
const height = bounds2.height + magicFactor + 10;
const width = bounds2.width + 10;
const useMaxWidth = false;
configureSvgSize(svg, height, width, useMaxWidth);
log.debug('Here Bounds', bounds, bounds2);
svg.attr(
'viewBox',
`${bounds2.x - 5} ${bounds2.y - 5} ${bounds2.width + 10} ${bounds2.height + 10}`
);
}
// svg.attr('viewBox', `${-200} ${-200} ${400} ${400}`);
// Prepare data for construction based on diagObj.db
@ -83,92 +82,6 @@ export const draw = async function (
y?: number;
}
const blocks: LayedBlock[] = [
{
ID: 'ApplicationLayer',
label: 'Application Layer',
x: 0,
y: 0,
children: [
{
ID: 'UserInterface',
label: 'User Interface (WPF, HTML5/CSS3, Swing)',
x: 0,
y: 50,
},
],
},
{
ID: 'PresentationLayer',
label: 'Presentation Layer',
x: 0,
y: 50,
children: [
{
ID: 'Smack',
label: 'J2SE Mobil App (Smack)',
},
{
ID: 'JsJAC',
label: 'Java Script Browser App (JsJAC)',
},
{
ID: 'babelim',
label: '.NET Windows App (Babel-im)',
},
],
},
{
ID: 'SessionLayer',
label: 'Session Layer',
x: 0,
y: 100,
children: [
{
ID: 'XMPP',
label: 'XMPP Component',
},
{
children: [
{
ID: 'Authentication',
label: 'Authentication',
},
{
ID: 'Authorization',
label: 'Authorization',
},
],
},
{
ID: 'LDAP',
label: 'LDAP, DB, POP',
},
],
},
{
ID: 'NetworkLayer',
label: 'Network Layer',
x: 0,
y: 150,
children: [
{ ID: 'HTTP', label: 'HTTP' },
{ ID: 'SOCK', label: 'SOCK' },
],
},
{
ID: 'DataLayer',
label: 'Data Layer',
x: 0,
y: 200,
children: [
{ ID: 'XMPP', label: 'XMPP' },
{ ID: 'BDB', label: 'Business DB' },
{ ID: 'AD', label: 'Active Directory' },
],
},
];
// Get color scheme for the graph
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
};

View File

@ -39,6 +39,7 @@ export interface Block {
};
node?: any;
columns?: number; // | TBlockColumnsDefaultValue;
classes?: string[];
}
export interface Link {

View File

@ -0,0 +1,13 @@
// @ts-ignore: jison doesn't export types
import { calculateBlockPosition } from './layout.js';
describe('Layout', function () {
it('It shoud calulatepositions correctly', () => {
expect(calculateBlockPosition(2, 0)).toEqual({ px: 0, py: 0 });
expect(calculateBlockPosition(2, 1)).toEqual({ px: 1, py: 0 });
expect(calculateBlockPosition(2, 2)).toEqual({ px: 0, py: 1 });
expect(calculateBlockPosition(2, 3)).toEqual({ px: 1, py: 1 });
expect(calculateBlockPosition(2, 4)).toEqual({ px: 0, py: 2 });
expect(calculateBlockPosition(1, 3)).toEqual({ px: 0, py: 2 });
});
});

View File

@ -1,10 +1,41 @@
import { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
import { log } from '../../logger.js';
const padding = 8;
const padding = 10;
interface BlockPosition {
px: number;
py: number;
}
export function calculateBlockPosition(columns: number, position: number): BlockPosition {
// 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)) {
throw new Error('Position must be a non-negative integer.');
}
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);
return { px, py };
}
function calcBlockSizes(block: Block, db: BlockDB) {
console.log('calculateSize (start)', block.id, block?.size?.x, block?.size?.width);
log.debug('calculateSize (start)', block.id, block?.size?.x, block?.size?.width);
const totalWidth = 0;
const totalHeight = 0;
let maxWidth = 0;
@ -17,7 +48,7 @@ function calcBlockSizes(block: Block, db: BlockDB) {
// find max width of children
for (const child of block.children) {
const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 };
// console.log('APA', child.id, width, height, x, y);
// log.debug('APA', child.id, width, height, x, y);
if (width > maxWidth) {
maxWidth = width;
}
@ -51,105 +82,133 @@ function calcBlockSizes(block: Block, db: BlockDB) {
// }
}
if (block.children?.length > 0) {
const columns = block.columns || -1;
const numItems = block.children.length;
// The width and height in number blocks
let xSize = block.children?.length;
if (columns > 0 && columns < numItems) {
xSize = columns;
}
const ySize = Math.ceil(numItems / xSize);
log.debug(
'(calc)',
block.id,
'xSize',
xSize,
'ySize',
ySize,
'columns',
columns,
block.children.length
);
const numChildren = block.children.length;
block.size = {
width: numChildren * (maxWidth + padding) + padding,
height: maxHeight + 2 * padding,
// width: numChildren * (maxWidth + padding) + padding,
width: xSize * (maxWidth + padding) + padding,
// height: maxHeight + 2 * padding,
height: ySize * (maxHeight + padding) + padding,
x: 0,
y: 0,
};
}
console.log('calculateSize APA (done)', block.id, block.size.x, block.size.width);
log.debug('calculateSize APA (done)', block.id, block?.size?.x, block?.size?.width);
}
function layoutBlocks(block: Block, db: BlockDB) {
console.log('layout blocks (block)', block.id, 'x:', block.size.x, 'width:', block.size.width);
log.debug(
'layout blocks (=>layoutBlocks)',
block.id,
'x:',
block?.size?.x,
'width:',
block?.size?.width
);
const columns = block.columns || -1;
log.debug('layoutBlocks columns', block.id, '=>', columns);
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;
let posX = (block?.size?.x || 0) - widthOfChildren / 2;
const posY = 0;
const parentX = block?.size?.x || 0 - block.children.length;
const parentWidth = block?.size?.width || 0;
console.log('widthOfChildren', widthOfChildren, 'posX', posX, 'parentX', parentX);
log.debug('widthOfChildren', widthOfChildren, 'posX');
// let first = true;
let columnPos = -1;
for (const child of block.children) {
console.log(
'layout blocks (child)',
child.id,
'x:',
child?.size?.x,
'width:',
child?.size?.width,
'posX:',
posX,
block?.size?.x,
widthOfChildren / 2,
widthOfChildren / 2
);
columnPos++;
// log.debug(
// 'layout blocks (child)',
// child.id,
// 'x:',
// child?.size?.x,
// 'width:',
// child?.size?.width,
// 'posX:',
// posX,
// block?.size?.x,
// widthOfChildren / 2,
// widthOfChildren / 2
// );
if (!child.size) {
continue;
}
const { width, height } = child.size;
child.size.x = posX + width / 2;
posX += width + padding;
child.size.y = posY;
const { px, py } = calculateBlockPosition(columns, columnPos);
log.debug(
'layout blocks (child) px, py (',
block?.size?.x,
',',
block?.size?.y,
')',
'parent:',
block.id,
width / 2,
padding
);
if (block.size) {
child.size.x =
block.size.x - block.size.width / 2 + px * (width + padding) + width / 2 + padding;
// child.size.x = px * (width + padding) - block.size.width / 2;
// posX += width + padding;
// child.size.y = py * (height + padding) + height / 2 + padding;
child.size.y =
block.size.y - block.size.height / 2 + py * (height + padding) + height / 2 + padding;
log.debug(
'layout blocks (calc) px, py',
'id:',
child.id,
'=>',
'x:',
child.size.x,
'y:',
child.size.y
);
}
// posY += height + padding;
if (child.children) {
layoutBlocks(child, db);
}
}
}
}
function positionBlock(parent: Block, block: Block, db: BlockDB) {
console.log(
'layout position block',
parent.id,
parent?.size?.x,
log.debug(
'layout blocks (<==layoutBlocks)',
block.id,
'x:',
block?.size?.x,
'width:',
block?.size?.width
);
let parentX = 0;
let parentWidth = 0;
let y = 0;
if (parent.id !== 'root') {
parentX = parent?.size?.x || 0;
parentWidth = parent?.size?.width || 0;
y = parent?.size?.y || 0;
}
if (block.size && block.id !== 'root') {
console.log(
'layout position block (calc)',
'x:',
parentX,
parentWidth / 2,
block.id,
'x:',
block.size.x,
block.size.width
);
// block.size.x = parentX + block.size.x + -block.size.width / 2;
block.size.x =
parentX < 0 ? parentX + block.size.x : parentX + block.size.x + -block.size.width / 2;
// block.size.x = parentX - parentWidth + Math.abs(block.size.x) / 2;
block.size.y = block.size.y + y;
}
if (block.children) {
for (const child of block.children) {
positionBlock(block, child, db);
}
}
// console.log('layout position block', block);
}
let minX = 0;
let minY = 0;
let maxX = 0;
@ -160,7 +219,7 @@ function findBounds(block: Block) {
const { x, y, width, height } = block.size;
if (x - width / 2 < minX) {
minX = x - width / 2;
// console.log('Here APA minX', block.id, x, width, minX);
// log.debug('Here APA minX', block.id, x, width, minX);
}
if (y - height / 2 < minY) {
minY = y - height / 2;
@ -180,20 +239,22 @@ function findBounds(block: Block) {
}
export function layout(db: BlockDB) {
const blocks = db.getBlocks();
const root = { id: 'root', type: 'composite', children: blocks } as Block;
const root = db.getBlock('root');
if (!root) {
return;
}
calcBlockSizes(root, db);
layoutBlocks(root, db);
// Position blocks relative to parents
// positionBlock(root, root, db);
console.log('getBlocks', JSON.stringify(db.getBlocks(), null, 2));
log.debug('getBlocks', JSON.stringify(root, null, 2));
minX = 0;
minY = 0;
maxX = 0;
maxY = 0;
findBounds(root);
// console.log('Here maxX', minX, '--', maxX);
// log.debug('Here maxX', minX, '--', maxX);
const height = maxY - minY;
const width = maxX - minX;
return { x: minX, y: minY, width, height };

View File

@ -5,28 +5,23 @@ import { ContainerElement } from 'd3';
import type { Block } from './blockTypes.js';
import { BlockDB } from './blockDB.js';
interface Node {
classes: string;
}
function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
const vertex = block;
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let classStr = 'default';
if ((vertex?.classes?.length || []) > 0) {
classStr = vertex.classes.join(' ');
if ((vertex?.classes?.length || 0) > 0) {
classStr = (vertex?.classes || []).join(' ');
}
classStr = classStr + ' flowchart-label';
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
const labelData = { width: 0, height: 0 };
let radious = 0;
let _shape = '';
let layoutOptions = {};
console.log('This is the type:', vertex.type);
// Set the shape based parameters
switch (vertex.type) {
case 'round':
@ -140,20 +135,18 @@ async function calculateBlockSize(elem: any, block: any, db: any) {
const boundingBox = nodeEl.node().getBBox();
const obj = db.getBlock(node.id);
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
console.log('Here boundsíng', boundingBox.width);
db.setBlock(obj);
nodeEl.remove();
}
export async function insertBlockPositioned(elem: any, block: any, db: any) {
console.log('Here insertBlockPositioned');
const node = getNodeFromBlock(block, db, true);
// if (node.type === 'composite') {
// return;
// }
// Add the element to the DOM to size it
const obj = db.getBlock(node.id);
const nodeEl = await insertNode(elem, node);
// const obj = db.getBlock(node.id);
// const nodeEl = await insertNode(elem, node);
positionNode(node);
}