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

View File

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

View File

@ -7,16 +7,14 @@ import {
select as d3select, select as d3select,
scaleOrdinal as d3scaleOrdinal, scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10, schemeTableau10 as d3schemeTableau10,
ContainerElement,
} from 'd3'; } from 'd3';
import { log } from '../../logger.js';
import { BlockDB } from './blockDB.js'; import { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js'; import type { Block } from './blockTypes.js';
// import { diagram as BlockDiagram } from './blockDiagram.js'; // import { diagram as BlockDiagram } from './blockDiagram.js';
import { configureSvgSize } from '../../setupGraphViewbox.js'; import { configureSvgSize } from '../../setupGraphViewbox.js';
import { Uid } from '../../rendering-util/uid.js';
import { pad } from 'lodash';
export const draw = async function ( export const draw = async function (
text: string, text: string,
@ -43,27 +41,28 @@ export const draw = async function (
const nodes = svg.insert('g').attr('class', 'block'); const nodes = svg.insert('g').attr('class', 'block');
await calculateBlockSizes(nodes, bl, db); await calculateBlockSizes(nodes, bl, db);
const bounds = layout(db); const bounds = layout(db);
console.log('Here blocks', bl); log.debug('Here blocks', bl);
await insertBlocks(nodes, bl, db); await insertBlocks(nodes, bl, db);
// console.log('Here', bl); // log.debug('Here', bl);
// Establish svg dimensions and get width and height // Establish svg dimensions and get width and height
// //
// const bounds2 = nodes.node().getBoundingClientRect(); // const bounds2 = nodes.node().getBoundingClientRect();
const bounds2 = bounds;
const padding = 10;
// Why, oh why ???? // Why, oh why ????
if (bounds) {
const bounds2 = bounds;
const magicFactor = Math.max(1, Math.round(0.125 * (bounds2.width / bounds2.height))); const magicFactor = Math.max(1, Math.round(0.125 * (bounds2.width / bounds2.height)));
const height = bounds2.height + magicFactor + 10; const height = bounds2.height + magicFactor + 10;
const width = bounds2.width + 10; const width = bounds2.width + 10;
const useMaxWidth = false; const useMaxWidth = false;
configureSvgSize(svg, height, width, useMaxWidth); configureSvgSize(svg, height, width, useMaxWidth);
console.log('Here Bounds', bounds, bounds2); log.debug('Here Bounds', bounds, bounds2);
svg.attr( svg.attr(
'viewBox', 'viewBox',
`${bounds2.x - 5} ${bounds2.y - 5} ${bounds2.width + 10} ${bounds2.height + 10}` `${bounds2.x - 5} ${bounds2.y - 5} ${bounds2.width + 10} ${bounds2.height + 10}`
); );
}
// svg.attr('viewBox', `${-200} ${-200} ${400} ${400}`); // svg.attr('viewBox', `${-200} ${-200} ${400} ${400}`);
// Prepare data for construction based on diagObj.db // Prepare data for construction based on diagObj.db
@ -83,92 +82,6 @@ export const draw = async function (
y?: number; 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 // Get color scheme for the graph
const colorScheme = d3scaleOrdinal(d3schemeTableau10); const colorScheme = d3scaleOrdinal(d3schemeTableau10);
}; };

View File

@ -39,6 +39,7 @@ export interface Block {
}; };
node?: any; node?: any;
columns?: number; // | TBlockColumnsDefaultValue; columns?: number; // | TBlockColumnsDefaultValue;
classes?: string[];
} }
export interface Link { 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 { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.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) { 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 totalWidth = 0;
const totalHeight = 0; const totalHeight = 0;
let maxWidth = 0; let maxWidth = 0;
@ -17,7 +48,7 @@ function calcBlockSizes(block: Block, db: BlockDB) {
// find max width of children // find max width of children
for (const child of block.children) { for (const child of block.children) {
const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 }; 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) { if (width > maxWidth) {
maxWidth = width; maxWidth = width;
} }
@ -51,105 +82,133 @@ function calcBlockSizes(block: Block, db: BlockDB) {
// } // }
} }
if (block.children?.length > 0) { 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; const numChildren = block.children.length;
block.size = { block.size = {
width: numChildren * (maxWidth + padding) + padding, // width: numChildren * (maxWidth + padding) + padding,
height: maxHeight + 2 * padding, width: xSize * (maxWidth + padding) + padding,
// height: maxHeight + 2 * padding,
height: ySize * (maxHeight + padding) + padding,
x: 0, x: 0,
y: 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) { 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 ( if (
block.children && // find max width of children block.children && // find max width of children
block.children.length > 0 block.children.length > 0
) { ) {
const width = block?.children[0]?.size?.width || 0; const width = block?.children[0]?.size?.width || 0;
const widthOfChildren = block.children.length * width + (block.children.length - 1) * padding; 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 first = true;
let columnPos = -1;
for (const child of block.children) { for (const child of block.children) {
console.log( columnPos++;
'layout blocks (child)',
child.id, // log.debug(
'x:', // 'layout blocks (child)',
child?.size?.x, // child.id,
'width:', // 'x:',
child?.size?.width, // child?.size?.x,
'posX:', // 'width:',
posX, // child?.size?.width,
block?.size?.x, // 'posX:',
widthOfChildren / 2, // posX,
widthOfChildren / 2 // block?.size?.x,
); // widthOfChildren / 2,
// widthOfChildren / 2
// );
if (!child.size) { if (!child.size) {
continue; continue;
} }
const { width, height } = child.size; const { width, height } = child.size;
child.size.x = posX + width / 2; const { px, py } = calculateBlockPosition(columns, columnPos);
posX += width + padding; log.debug(
child.size.y = posY; '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; // posY += height + padding;
if (child.children) { if (child.children) {
layoutBlocks(child, db); layoutBlocks(child, db);
} }
} }
} }
} log.debug(
'layout blocks (<==layoutBlocks)',
function positionBlock(parent: Block, block: Block, db: BlockDB) {
console.log(
'layout position block',
parent.id,
parent?.size?.x,
block.id, block.id,
'x:',
block?.size?.x, block?.size?.x,
'width:', 'width:',
block?.size?.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 minX = 0;
let minY = 0; let minY = 0;
let maxX = 0; let maxX = 0;
@ -160,7 +219,7 @@ function findBounds(block: Block) {
const { x, y, width, height } = block.size; const { x, y, width, height } = block.size;
if (x - width / 2 < minX) { if (x - width / 2 < minX) {
minX = x - width / 2; 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) { if (y - height / 2 < minY) {
minY = y - height / 2; minY = y - height / 2;
@ -180,20 +239,22 @@ function findBounds(block: Block) {
} }
export function layout(db: BlockDB) { export function layout(db: BlockDB) {
const blocks = db.getBlocks(); const root = db.getBlock('root');
const root = { id: 'root', type: 'composite', children: blocks } as Block; if (!root) {
return;
}
calcBlockSizes(root, db); calcBlockSizes(root, db);
layoutBlocks(root, db); layoutBlocks(root, db);
// Position blocks relative to parents // Position blocks relative to parents
// positionBlock(root, root, db); // positionBlock(root, root, db);
console.log('getBlocks', JSON.stringify(db.getBlocks(), null, 2)); log.debug('getBlocks', JSON.stringify(root, null, 2));
minX = 0; minX = 0;
minY = 0; minY = 0;
maxX = 0; maxX = 0;
maxY = 0; maxY = 0;
findBounds(root); findBounds(root);
// console.log('Here maxX', minX, '--', maxX); // log.debug('Here maxX', minX, '--', maxX);
const height = maxY - minY; const height = maxY - minY;
const width = maxX - minX; const width = maxX - minX;
return { x: minX, y: minY, width, height }; return { x: minX, y: minY, width, height };

View File

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