262 lines
6.7 KiB
TypeScript
262 lines
6.7 KiB
TypeScript
import { getStylesFromArray } from '../../utils.js';
|
|
import { insertNode, positionNode } from '../../dagre-wrapper/nodes.js';
|
|
import { insertEdge, insertEdgeLabel, positionEdgeLabel } from '../../dagre-wrapper/edges.js';
|
|
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
|
|
import { getConfig } from '../../config.js';
|
|
import type { ContainerElement } from 'd3';
|
|
import type { Block } from './blockTypes.js';
|
|
import type { BlockDB } from './blockDB.js';
|
|
|
|
interface Node {
|
|
classes: string;
|
|
}
|
|
|
|
function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
|
|
const vertex = block;
|
|
|
|
let classStr = 'default';
|
|
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 radius = 0;
|
|
let shape = '';
|
|
let layoutOptions = {};
|
|
let padding;
|
|
// Set the shape based parameters
|
|
switch (vertex.type) {
|
|
case 'round':
|
|
radius = 5;
|
|
shape = 'rect';
|
|
break;
|
|
case 'composite':
|
|
radius = 0;
|
|
shape = 'composite';
|
|
padding = 0;
|
|
break;
|
|
case 'square':
|
|
shape = 'rect';
|
|
break;
|
|
case 'diamond':
|
|
shape = 'question';
|
|
layoutOptions = {
|
|
portConstraints: 'FIXED_SIDE',
|
|
};
|
|
break;
|
|
case 'hexagon':
|
|
shape = 'hexagon';
|
|
break;
|
|
case 'block_arrow':
|
|
shape = 'block_arrow';
|
|
break;
|
|
case 'odd':
|
|
shape = 'rect_left_inv_arrow';
|
|
break;
|
|
case 'lean_right':
|
|
shape = 'lean_right';
|
|
break;
|
|
case 'lean_left':
|
|
shape = 'lean_left';
|
|
break;
|
|
case 'trapezoid':
|
|
shape = 'trapezoid';
|
|
break;
|
|
case 'inv_trapezoid':
|
|
shape = 'inv_trapezoid';
|
|
break;
|
|
case 'rect_left_inv_arrow':
|
|
shape = 'rect_left_inv_arrow';
|
|
break;
|
|
case 'circle':
|
|
shape = 'circle';
|
|
break;
|
|
case 'ellipse':
|
|
shape = 'ellipse';
|
|
break;
|
|
case 'stadium':
|
|
shape = 'stadium';
|
|
break;
|
|
case 'subroutine':
|
|
shape = 'subroutine';
|
|
break;
|
|
case 'cylinder':
|
|
shape = 'cylinder';
|
|
break;
|
|
case 'group':
|
|
shape = 'rect';
|
|
break;
|
|
case 'doublecircle':
|
|
shape = 'doublecircle';
|
|
break;
|
|
default:
|
|
shape = 'rect';
|
|
}
|
|
|
|
const styles = getStylesFromArray(vertex?.styles || []);
|
|
|
|
// Use vertex id as text in the box if no text is provided by the graph definition
|
|
const vertexText = vertex.label;
|
|
|
|
const bounds = vertex.size || { width: 0, height: 0, x: 0, y: 0 };
|
|
// Add the node
|
|
const node = {
|
|
labelStyle: styles.labelStyle,
|
|
shape: shape,
|
|
labelText: vertexText,
|
|
rx: radius,
|
|
ry: radius,
|
|
class: classStr,
|
|
style: styles.style,
|
|
id: vertex.id,
|
|
directions: vertex.directions,
|
|
width: bounds.width,
|
|
height: bounds.height,
|
|
x: bounds.x,
|
|
y: bounds.y,
|
|
positioned,
|
|
intersect: undefined,
|
|
type: vertex.type,
|
|
padding: padding ?? (getConfig()?.block?.padding || 0),
|
|
};
|
|
return node;
|
|
}
|
|
async function calculateBlockSize(
|
|
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
|
block: any,
|
|
db: any
|
|
) {
|
|
const node = getNodeFromBlock(block, db, false);
|
|
if (node.type === 'group') {
|
|
return;
|
|
}
|
|
|
|
// Add the element to the DOM to size it
|
|
const nodeEl = await insertNode(elem, node);
|
|
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 };
|
|
db.setBlock(obj);
|
|
nodeEl.remove();
|
|
}
|
|
type ActionFun = typeof calculateBlockSize;
|
|
|
|
export async function insertBlockPositioned(elem: any, block: Block, db: any) {
|
|
const node = getNodeFromBlock(block, db, true);
|
|
// Add the element to the DOM to size it
|
|
const obj = db.getBlock(node.id);
|
|
if (obj.type !== 'space') {
|
|
const nodeEl = await insertNode(elem, node);
|
|
block.intersect = node?.intersect;
|
|
positionNode(node);
|
|
}
|
|
}
|
|
|
|
export async function performOperations(
|
|
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
|
blocks: Block[],
|
|
db: BlockDB,
|
|
operation: ActionFun
|
|
) {
|
|
for (const block of blocks) {
|
|
await operation(elem, block, db);
|
|
if (block.children) {
|
|
await performOperations(elem, block.children, db, operation);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function calculateBlockSizes(elem: any, blocks: Block[], db: BlockDB) {
|
|
await performOperations(elem, blocks, db, calculateBlockSize);
|
|
}
|
|
|
|
export async function insertBlocks(
|
|
elem: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
|
|
blocks: Block[],
|
|
db: BlockDB
|
|
) {
|
|
await performOperations(elem, blocks, db, insertBlockPositioned);
|
|
}
|
|
|
|
export async function insertEdges(
|
|
elem: any,
|
|
edges: Block[],
|
|
blocks: Block[],
|
|
db: BlockDB,
|
|
id: string
|
|
) {
|
|
const g = new graphlib.Graph({
|
|
multigraph: true,
|
|
compound: true,
|
|
});
|
|
g.setGraph({
|
|
rankdir: 'TB',
|
|
nodesep: 10,
|
|
ranksep: 10,
|
|
marginx: 8,
|
|
marginy: 8,
|
|
});
|
|
|
|
for (const block of blocks) {
|
|
if (block.size) {
|
|
g.setNode(block.id, {
|
|
width: block.size.width,
|
|
height: block.size.height,
|
|
intersect: block.intersect,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const edge of edges) {
|
|
// elem, e, edge, clusterDb, diagramType, graph;
|
|
if (edge.start && edge.end) {
|
|
const startBlock = db.getBlock(edge.start);
|
|
const endBlock = db.getBlock(edge.end);
|
|
|
|
if (startBlock?.size && endBlock?.size) {
|
|
const start = startBlock.size;
|
|
const end = endBlock.size;
|
|
const points = [
|
|
{ x: start.x, y: start.y },
|
|
{ x: start.x + (end.x - start.x) / 2, y: start.y + (end.y - start.y) / 2 },
|
|
{ x: end.x, y: end.y },
|
|
];
|
|
// edge.points = points;
|
|
await insertEdge(
|
|
elem,
|
|
{ v: edge.start, w: edge.end, name: edge.id },
|
|
{
|
|
...edge,
|
|
arrowTypeEnd: edge.arrowTypeEnd,
|
|
arrowTypeStart: edge.arrowTypeStart,
|
|
points,
|
|
classes: 'edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1',
|
|
},
|
|
undefined,
|
|
'block',
|
|
g,
|
|
id
|
|
);
|
|
if (edge.label) {
|
|
await insertEdgeLabel(elem, {
|
|
...edge,
|
|
label: edge.label,
|
|
labelStyle: 'stroke: #333; stroke-width: 1.5px;fill:none;',
|
|
arrowTypeEnd: edge.arrowTypeEnd,
|
|
arrowTypeStart: edge.arrowTypeStart,
|
|
points,
|
|
classes: 'edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1',
|
|
});
|
|
await positionEdgeLabel(
|
|
{ ...edge, x: points[1].x, y: points[1].y },
|
|
{
|
|
originalPath: points,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|