Rendering, tmp commit before refactoring

This commit is contained in:
Knut Sveidqvist
2023-09-05 11:13:27 +02:00
parent 5f1cfc7519
commit 1e864a508d
10 changed files with 1033 additions and 78 deletions

View File

@@ -24,6 +24,9 @@
h1 {
color: grey;
}
.mermaid {
border: 1px solid #ddd;
}
.mermaid2 {
display: none;
}
@@ -59,16 +62,17 @@
<body>
<pre id="diagram" class="mermaid">
block-beta
id</pre
>
id1("Wide 1")
id2("2")
id3("3")
id4("A final one")
</pre>
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
a1 -- l2 --> a2
end
id
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
flowchart RL
subgraph "`one`"
a1 -- l1 --> a2
@@ -93,11 +97,11 @@ flowchart LR
way`"]
</pre
>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
classDiagram-v2
note "I love this diagram!\nDo you love it?"
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
stateDiagram-v2
State1: The state with a note with minus - and plus + in it
note left of State1
@@ -142,7 +146,7 @@ mindmap
शान्तिः سلام 和平 `"]
</pre>
<pre id="diagram" class="mermaid">
<pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TB
%% I could not figure out how to use double quotes in labels in Mermaid

View File

@@ -1037,14 +1037,14 @@ export const positionNode = (node) => {
const padding = 8;
const diff = node.diff || 0;
if (node.clusterNode) {
el.attr(
'transform',
'translate(' +
(node.x + diff - node.width / 2) +
', ' +
(node.y - node.height / 2 - padding) +
')'
);
el.attr(
'transform',
'translate(' +
(node.x + diff - node.width / 2) +
', ' +
(node.y - node.height / 2 - padding) +
')'
);
} else {
el.attr('transform', 'translate(' + node.x + ', ' + node.y + ')');
}

View File

@@ -106,6 +106,16 @@ const getBlocks: IGetBlocks = () => {
log.info('Block in test', blocks, blocks[0].id);
return blocks || [];
};
type IGetBlock = (id: string) => Block | undefined;
const getBlock: IGetBlock = (id: string) => {
log.info('Block in test', blocks, blocks[0].id);
return blockDatabase[id];
};
type ISetBlock = (block: Block) => void;
const setBlock: ISetBlock = (block: Block) => {
log.info('Block in test', blocks, blocks[0].id);
blockDatabase[block.id] = block;
};
type IGetLinks = () => Link[];
const getLinks: IGetLinks = () => links;
@@ -119,6 +129,8 @@ export interface BlockDB extends DiagramDB {
addLink: IAddLink;
getLogger: IGetLogger;
getBlocks: IGetBlocks;
getBlock: IGetBlock;
setBlock: ISetBlock;
getLinks: IGetLinks;
getColumns: IGetColumns;
typeStr2Type: ITypeStr2Type;
@@ -134,6 +146,8 @@ const db: BlockDB = {
getBlocks,
getLinks,
setHierarchy,
getBlock,
setBlock,
// getAccTitle,
// setAccTitle,
// getAccDescription,

View File

@@ -2,6 +2,7 @@ import { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: jison doesn't export types
import parser from './parser/block.jison';
import db from './blockDB.js';
import flowStyles from './styles.js';
import renderer from './blockRenderer.js';
// TODO: do we need this?
@@ -14,4 +15,5 @@ export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles: flowStyles,
};

View File

@@ -1,20 +1,30 @@
import { Diagram } from '../../Diagram.js';
import * as configApi from '../../config.js';
import { calculateBlockSizes } from './renderHelpers.js';
import { layout } from './layout.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import {
select as d3select,
scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10,
ContainerElement,
} from 'd3';
import { BlockDB, Block } from './blockDB.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';
export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void {
const { securityLevel } = configApi.getConfig();
export const draw = async function (
text: string,
id: string,
_version: string,
diagObj: Diagram
): Promise<void> {
const { securityLevel, flowchart: conf } = configApi.getConfig();
const db = diagObj.db as BlockDB;
let sandboxElement: any;
if (securityLevel === 'sandbox') {
sandboxElement = d3select('#i' + id);
@@ -27,12 +37,23 @@ export const draw = function (text: string, id: string, _version: string, diagOb
// @ts-ignore TODO root.select is not callable
const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
const bl = db.getBlocks();
const nodes = svg.insert('g').attr('class', 'block');
await calculateBlockSizes(nodes, bl, db);
const bounds = layout(db);
console.log('Here', bl);
// Establish svg dimensions and get width and height
//
const height = 400;
const width = 600;
//
// const bounds = nodes.node().getBoundingClientRect();
const height = bounds.height;
const width = bounds.width;
const useMaxWidth = false;
configureSvgSize(svg, height, width, useMaxWidth);
console.log('Here Bounds', bounds);
svg.attr('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`);
// Prepare data for construction based on diagObj.db
// This must be a mutable object with `nodes` and `links` properties:
@@ -53,107 +74,92 @@ export const draw = function (text: string, id: string, _version: string, diagOb
const blocks: LayedBlock[] = [
{
ID: "ApplicationLayer",
label: "Application Layer",
ID: 'ApplicationLayer',
label: 'Application Layer',
x: 0,
y: 0,
children: [
{
ID: "UserInterface",
label: "User Interface (WPF, HTML5/CSS3, Swing)",
ID: 'UserInterface',
label: 'User Interface (WPF, HTML5/CSS3, Swing)',
x: 0,
y: 50,
}
y: 50,
},
],
},
{
ID: "PresentationLayer",
label: "Presentation Layer",
ID: 'PresentationLayer',
label: 'Presentation Layer',
x: 0,
y: 50,
children: [
{
ID: "Smack",
label: "J2SE Mobil App (Smack)"
ID: 'Smack',
label: 'J2SE Mobil App (Smack)',
},
{
ID: "JsJAC",
label: "Java Script Browser App (JsJAC)",
ID: 'JsJAC',
label: 'Java Script Browser App (JsJAC)',
},
{
ID: "babelim",
label: ".NET Windows App (Babel-im)",
ID: 'babelim',
label: '.NET Windows App (Babel-im)',
},
]
],
},
{
ID: "SessionLayer",
label: "Session Layer",
ID: 'SessionLayer',
label: 'Session Layer',
x: 0,
y: 100,
children: [
{
ID: "XMPP",
label: "XMPP Component"
ID: 'XMPP',
label: 'XMPP Component',
},
{
children: [
{
ID: "Authentication",
label: "Authentication",
ID: 'Authentication',
label: 'Authentication',
},
{
ID: "Authorization",
label: "Authorization",
ID: 'Authorization',
label: 'Authorization',
},
]
],
},
{
ID: "LDAP",
label: "LDAP, DB, POP",
ID: 'LDAP',
label: 'LDAP, DB, POP',
},
]
],
},
{
ID: "NetworkLayer",
label: "Network Layer",
ID: 'NetworkLayer',
label: 'Network Layer',
x: 0,
y: 150,
children: [
{ ID: "HTTP", label: "HTTP" },
{ ID: "SOCK", label: "SOCK" },
]
{ ID: 'HTTP', label: 'HTTP' },
{ ID: 'SOCK', label: 'SOCK' },
],
},
{
ID: "DataLayer",
label: "Data Layer",
ID: 'DataLayer',
label: 'Data Layer',
x: 0,
y: 200,
children: [
{ ID: "XMPP", label: "XMPP" },
{ ID: "BDB", label: "Business DB" },
{ ID: "AD", label: "Active Directory" },
]
{ ID: 'XMPP', label: 'XMPP' },
{ ID: 'BDB', label: 'Business DB' },
{ ID: 'AD', label: 'Active Directory' },
],
},
];
// Get color scheme for the graph
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
svg
.append('g')
.attr('class', 'block')
.selectAll('.block')
.data(blocks)
.join('rect')
.attr('x', (d: any) => d.x || 0)
.attr('y', (d: any) => d.y || 0)
.attr('class', 'block')
.attr('stroke', 'black')
.attr('height', (d: any) => 50)
.attr('width', (d: any) => 100)
.attr('fill', (d: any) => colorScheme(d.ID));
};
export default {

View File

@@ -31,6 +31,13 @@ export interface Block {
parent?: Block;
type?: BlockType;
children: Block[];
size?: {
width: number;
height: number;
x: number;
y: number;
};
node?: any;
columns?: number; // | TBlockColumnsDefaultValue;
}

View File

@@ -0,0 +1,108 @@
import { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
function layoutBLock(block: Block, db: BlockDB) {
if (block.children) {
for (const child of block.children) {
layoutBLock(child, db);
}
// find max width of children
let maxWidth = 0;
let maxHeight = 0;
for (const child of block.children) {
const { width, height, x, y } = child.size || { width: 0, height: 0, x: 0, y: 0 };
if (width > maxWidth) {
maxWidth = width;
}
if (height > maxHeight) {
maxHeight = height;
}
}
// set width of block to max width of children
for (const child of block.children) {
if (child.size) {
child.size.width = maxWidth;
child.size.height = maxHeight;
}
}
// Position items
let x = 0;
let y = 0;
const padding = 10;
for (const child of block.children) {
if (child.size) {
child.size.x = x;
child.size.y = y;
x += maxWidth + padding;
}
}
}
}
function positionBlock(block: Block, db: BlockDB) {
console.log('Here Positioning', block?.size?.node);
// const o = db.getBlock(block.id);
// const node;
if (block?.size?.node) {
const node = block?.size?.node;
const size = block?.size;
console.log('Here as well', node);
if (node) {
node.attr(
'transform',
'translate(' + (size.x - size.width / 2) + ', ' + (size.y - size.height / 2) + ')'
);
}
}
if (block.children) {
for (const child of block.children) {
positionBlock(child, db);
}
}
}
let minX = 0;
let minY = 0;
let maxX = 0;
let maxY = 0;
function findBounds(block: Block) {
if (block.size) {
const { x, y, width, height } = block.size;
console.log('Here', minX, minY, x, y, width, height);
if (x - width < minX) {
minX = x - width;
}
if (y - height < minY) {
minY = y - height;
}
if (x > maxX) {
maxX = x;
}
if (y > maxY) {
maxY = y;
}
}
if (block.children) {
for (const child of block.children) {
findBounds(child);
}
}
}
export function layout(db: BlockDB) {
const blocks = db.getBlocks();
const root = { id: 'root', type: 'composite', children: blocks } as Block;
layoutBLock(root, db);
positionBlock(root, db);
minX = 0;
minY = 0;
maxX = 0;
maxY = 0;
findBounds(root);
const height = maxY - minY;
const width = maxX - minX;
return { x: minX, y: minY, width, height };
}

View File

@@ -0,0 +1,270 @@
import { getStylesFromArray } from '../../utils.js';
import { insertNode } from '../../dagre-wrapper/nodes.js';
import { getConfig } from '../../config.js';
import { ContainerElement } from 'd3';
import type { Block } from './blockTypes.js';
import { BlockDB } from './blockDB.js';
function getNodeFromBlock(block: Block, db: BlockDB) {
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(' ');
}
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 = {};
// Set the shape based parameters
switch (vertex.type) {
case 'round':
radious = 5;
_shape = 'rect';
break;
case 'square':
_shape = 'rect';
break;
case 'diamond':
_shape = 'question';
layoutOptions = {
portConstraints: 'FIXED_SIDE',
};
break;
case 'hexagon':
_shape = 'hexagon';
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 'odd_right':
_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);
const styles = getStylesFromArray([]);
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.label;
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
// labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
id: vertex.id,
// link: vertex.link,
// linkTarget: vertex.linkTarget,
// tooltip: diagObj.db.getTooltip(vertex.id) || '',
// domId: diagObj.db.lookUpDomId(vertex.id),
// haveCallback: vertex.haveCallback,
// width: vertex.type === 'group' ? 500 : undefined,
// dir: vertex.dir,
type: vertex.type,
// props: vertex.props,
padding: getConfig()?.flowchart?.padding || 0,
};
return node;
}
async function calculateBlockSize(elem: any, block: any, db: any) {
console.log('Here befoire 3');
const node = getNodeFromBlock(block, db);
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);
console.log('Here el', nodeEl);
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
db.setBlock(obj);
// nodeEl.remove();
}
export async function calculateBlockSizes(elem: ContainerElement, blocks: Block[], db: BlockDB) {
console.log('Here before 2');
for (const block of blocks) {
await calculateBlockSize(elem, block, db);
if (block.children) {
await calculateBlockSizes(elem, block.children, db);
}
}
}
export async function insertBlockPositioned(elem: any, block: any, db: any) {
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(' ');
}
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 = {};
// Set the shape based parameters
switch (vertex.type) {
case 'round':
radious = 5;
_shape = 'rect';
break;
case 'square':
_shape = 'rect';
break;
case 'diamond':
_shape = 'question';
layoutOptions = {
portConstraints: 'FIXED_SIDE',
};
break;
case 'hexagon':
_shape = 'hexagon';
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 'odd_right':
_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);
const styles = getStylesFromArray([]);
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.label;
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
id: vertex.id,
link: vertex.link,
linkTarget: vertex.linkTarget,
// tooltip: diagObj.db.getTooltip(vertex.id) || '',
// domId: diagObj.db.lookUpDomId(vertex.id),
haveCallback: vertex.haveCallback,
width: vertex.width,
height: vertex.height,
dir: vertex.dir,
type: vertex.type,
props: vertex.props,
padding: getConfig()?.flowchart?.padding || 0,
};
let boundingBox;
let nodeEl;
// Add the element to the DOM
if (node.type !== 'group') {
nodeEl = await insertNode(elem, node, vertex.dir);
// nodeEl.remove();
boundingBox = nodeEl.node().getBBox();
if (node.id) {
const obj = db.getBlock(node.id);
obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl };
db.setBlock(obj);
}
}
}

View File

@@ -0,0 +1,144 @@
// import khroma from 'khroma';
import * as khroma from 'khroma';
/** Returns the styles given options */
export interface FlowChartStyleOptions {
arrowheadColor: string;
border2: string;
clusterBkg: string;
clusterBorder: string;
edgeLabelBackground: string;
fontFamily: string;
lineColor: string;
mainBkg: string;
nodeBorder: string;
nodeTextColor: string;
tertiaryColor: string;
textColor: string;
titleColor: string;
}
const fade = (color: string, opacity: number) => {
// @ts-ignore TODO: incorrect types from khroma
const channel = khroma.channel;
const r = channel(color, 'r');
const g = channel(color, 'g');
const b = channel(color, 'b');
// @ts-ignore incorrect types from khroma
return khroma.rgba(r, g, b, opacity);
};
const getStyles = (options: FlowChartStyleOptions) =>
`.label {
font-family: ${options.fontFamily};
color: ${options.nodeTextColor || options.textColor};
}
.cluster-label text {
fill: ${options.titleColor};
}
.cluster-label span,p {
color: ${options.titleColor};
}
.label text,span,p {
fill: ${options.nodeTextColor || options.textColor};
color: ${options.nodeTextColor || options.textColor};
}
.node rect,
.node circle,
.node ellipse,
.node polygon,
.node path {
fill: ${options.mainBkg};
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.flowchart-label text {
text-anchor: middle;
}
// .flowchart-label .text-outer-tspan {
// text-anchor: middle;
// }
// .flowchart-label .text-inner-tspan {
// text-anchor: start;
// }
.node .label {
text-align: center;
}
.node.clickable {
cursor: pointer;
}
.arrowheadPath {
fill: ${options.arrowheadColor};
}
.edgePath .path {
stroke: ${options.lineColor};
stroke-width: 2.0px;
}
.flowchart-link {
stroke: ${options.lineColor};
fill: none;
}
.edgeLabel {
background-color: ${options.edgeLabelBackground};
rect {
opacity: 0.5;
background-color: ${options.edgeLabelBackground};
fill: ${options.edgeLabelBackground};
}
text-align: center;
}
/* For html labels only */
.labelBkg {
background-color: ${fade(options.edgeLabelBackground, 0.5)};
// background-color:
}
.cluster rect {
fill: ${options.clusterBkg};
stroke: ${options.clusterBorder};
stroke-width: 1px;
}
.cluster text {
fill: ${options.titleColor};
}
.cluster span,p {
color: ${options.titleColor};
}
/* .cluster div {
color: ${options.titleColor};
} */
div.mermaidTooltip {
position: absolute;
text-align: center;
max-width: 200px;
padding: 2px;
font-family: ${options.fontFamily};
font-size: 12px;
background: ${options.tertiaryColor};
border: 1px solid ${options.border2};
border-radius: 2px;
pointer-events: none;
z-index: 100;
}
.flowchartTitleText {
text-anchor: middle;
font-size: 18px;
fill: ${options.textColor};
}
`;
export default getStyles;

View File

@@ -0,0 +1,400 @@
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select, curveLinear, selectAll } from 'd3';
import { swimlaneLayout } from './swimlane-layout.js';
import { insertNode } from '../../../dagre-wrapper/nodes.js';
import flowDb from '../flowDb.js';
import { getConfig } from '../../../config.js';
import { getStylesFromArray } from '../../../utils.js';
import setupGraph, { addEdges, addVertices } from './setup-graph.js';
import { render } from '../../../dagre-wrapper/index.js';
import { log } from '../../../logger.js';
import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
import common, { evaluate } from '../../common/common.js';
import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { insertEdge, positionEdgeLabel } from '../../../dagre-wrapper/edges.js';
import {
clear as clearGraphlib,
clusterDb,
adjustClustersAndEdges,
findNonClusterChild,
sortNodesByHierarchy,
} from '../../../dagre-wrapper/mermaid-graphlib.js';
const conf = {};
export const setConf = function (cnf) {
const keys = Object.keys(cnf);
for (const key of keys) {
conf[key] = cnf[key];
}
};
/**
*
* @param element
* @param graph
* @param layout
* @param elem
* @param conf
*/
async function swimlaneRender(layout, vert, elem, g, id, conf) {
let max;
// draw nodes from layout.graph to element
const nodes = layout.graph.nodes();
// lanes are the swimlanes
const lanes = layout.lanes;
const nodesElements = elem.insert('g').attr('class', 'nodes');
// for each node, draw a rect, with a child text inside as label
for (const node of nodes) {
const nodeFromLayout = layout.graph.node(node);
const vertex = vert[node];
//Initialise the node
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let classStr = 'default';
if (vertex.classes.length > 0) {
classStr = vertex.classes.join(' ');
}
classStr = classStr + ' swimlane-label';
const styles = getStylesFromArray(vertex.styles);
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
// We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode;
log.info('vertex', vertex, vertex.labelType);
if (vertex.labelType === 'markdown') {
log.info('vertex', vertex, vertex.labelType);
} else {
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
const node = {
label: vertexText.replace(
/fa[blrs]?:fa-[\w-]+/g,
(s) => `<i class='${s.replace(':', ' ')}'></i>`
),
};
vertexNode = addHtmlLabel(elem, node).node();
vertexNode.parentNode.removeChild(vertexNode);
} else {
const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:'));
const rows = vertexText.split(common.lineBreakRegex);
for (const row of rows) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
tspan.setAttribute('dy', '1em');
tspan.setAttribute('x', '1');
tspan.textContent = row;
svgLabel.appendChild(tspan);
}
vertexNode = svgLabel;
}
}
let radious = 0;
let _shape = '';
// Set the shape based parameters
switch (vertex.type) {
case 'round':
radious = 5;
_shape = 'rect';
break;
case 'square':
_shape = 'rect';
break;
case 'diamond':
_shape = 'question';
break;
case 'hexagon':
_shape = 'hexagon';
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 'odd_right':
_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';
}
// Add the node
let nodeObj = {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
labelType: vertex.labelType,
rx: radious,
ry: radious,
class: classStr,
style: styles.style,
id: vertex.id,
link: vertex.link,
linkTarget: vertex.linkTarget,
// tooltip: diagObj.db.getTooltip(vertex.id) || '',
// domId: diagObj.db.lookUpDomId(vertex.id),
haveCallback: vertex.haveCallback,
width: vertex.type === 'group' ? 500 : undefined,
dir: vertex.dir,
type: vertex.type,
props: vertex.props,
padding: getConfig().flowchart.padding,
x: nodeFromLayout.x,
y: nodeFromLayout.y,
};
let boundingBox;
let nodeEl;
// Add the element to the DOM
nodeEl = await insertNode(nodesElements, nodeObj, vertex.dir);
boundingBox = nodeEl.node().getBBox();
nodeEl.attr('transform', `translate(${nodeObj.x}, ${nodeObj.y / 2})`);
}
return elem;
}
/**
* Returns the all the styles from classDef statements in the graph definition.
*
* @param text
* @param diagObj
* @returns {object} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
log.info('Extracting classes');
diagObj.db.clear();
try {
// Parse the graph definition
diagObj.parse(text);
return diagObj.db.getClasses();
} catch (e) {
return;
}
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
*
* @param text
* @param id
*/
export const draw = async function (text, id, _version, diagObj) {
log.info('Drawing flowchart');
diagObj.db.clear();
flowDb.setGen('gen-2');
// Parse the graph definition
diagObj.parser.parse(text);
const { securityLevel, flowchart: conf } = getConfig();
// Handle root and document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
// create g as a graphlib graph using setupGraph from setup-graph.js
const g = setupGraph(diagObj, id, root, doc);
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
log.info('Subgraph - ', subG);
diagObj.db.addVertex(
subG.id,
{ text: subG.title, type: subG.labelType },
'group',
undefined,
subG.classes,
subG.dir
);
}
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
const edges = diagObj.db.getEdges();
log.info('Edges', edges);
const svg = root.select('#' + id);
svg.append('g');
// Run the renderer. This is what draws the final graph.
// const element = root.select('#' + id + ' g');
console.log('diagObj', diagObj);
console.log('subGraphs', diagObj.db.getSubGraphs());
const layout = swimlaneLayout(g, diagObj);
console.log('custom layout', layout);
// draw lanes as vertical lines
const lanesElements = svg.insert('g').attr('class', 'lanes');
let laneCount = 0;
for (const lane of layout.lanes) {
laneCount++;
//draw lane header as rectangle with lane title centered in it
const laneHeader = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
// Set attributes for the rectangle
laneHeader.setAttribute('x', lane.x); // x-coordinate of the top-left corner
laneHeader.setAttribute('y', -50); // y-coordinate of the top-left corner
laneHeader.setAttribute('width', lane.width); // width of the rectangle
laneHeader.setAttribute('height', '50'); // height of the rectangle
if (laneCount % 2 == 0) {
//set light blue color for even lanes
laneHeader.setAttribute('fill', 'blue'); // fill color of the rectangle
} else {
//set white color odd lanes
laneHeader.setAttribute('fill', 'grey'); // fill color of the rectangle
}
laneHeader.setAttribute('stroke', 'black'); // color of the stroke/border
laneHeader.setAttribute('stroke-width', '2'); // width of the stroke/border
// Append the rectangle to the SVG element
lanesElements.node().appendChild(laneHeader);
//draw lane title
const laneTitle = document.createElementNS('http://www.w3.org/2000/svg', 'text');
// Set attributes for the rectangle
laneTitle.setAttribute('x', lane.x + lane.width / 2); // x-coordinate of the top-left corner
laneTitle.setAttribute('y', -50 + 50 / 2); // y-coordinate of the top-left corner
laneTitle.setAttribute('width', lane.width); // width of the rectangle
laneTitle.setAttribute('height', '50'); // height of the rectangle
laneTitle.setAttribute('fill', 'white'); // fill color of the rectangle
laneTitle.setAttribute('stroke-width', '1'); // width of the stroke/border
laneTitle.setAttribute('text-anchor', 'middle'); // width of the stroke/border
laneTitle.setAttribute('alignment-baseline', 'middle'); // width of the stroke/border
laneTitle.setAttribute('font-size', '20'); // width of the stroke/border
laneTitle.textContent = lane.title;
// Append the rectangle to the SVG element
lanesElements.node().appendChild(laneTitle);
//draw lane
// Create a <rect> element
const rectangle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
// Set attributes for the rectangle
rectangle.setAttribute('x', lane.x); // x-coordinate of the top-left corner
rectangle.setAttribute('y', 0); // y-coordinate of the top-left corner
rectangle.setAttribute('width', lane.width); // width of the rectangle
rectangle.setAttribute('height', '500'); // height of the rectangle
if (laneCount % 2 == 0) {
//set light blue color for even lanes
rectangle.setAttribute('fill', 'lightblue'); // fill color of the rectangle
} else {
//set white color odd lanes
rectangle.setAttribute('fill', '#ffffff'); // fill color of the rectangle
}
rectangle.setAttribute('stroke', 'black'); // color of the stroke/border
rectangle.setAttribute('stroke-width', '2'); // width of the stroke/border
// Append the rectangle to the SVG element
lanesElements.node().appendChild(rectangle);
}
// append lanesElements to elem
svg.node().appendChild(lanesElements.node());
// add lane headers
const laneHeaders = svg.insert('g').attr('class', 'laneHeaders');
addEdges(edges, g, diagObj);
g.edges().forEach(function (e) {
const edge = g.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
const edgePaths = svg.insert('g').attr('class', 'edgePaths');
//create edge points based on start and end node
//get start node x, y coordinates
const sourceNode = layout.graph.node(e.v);
//get end node x, y coordinates
sourceNode.x = sourceNode.x;
sourceNode.y = sourceNode.y;
const targetNode = layout.graph.node(e.w);
targetNode.x = targetNode.x;
targetNode.y = targetNode.y;
edge.points = [];
edge.points.push({ x: sourceNode.x, y: sourceNode.y / 2 });
edge.points.push({ x: targetNode.x, y: targetNode.y / 2 });
const paths = insertEdge(edgePaths, e, edge, clusterDb, 'flowchart', g);
//positionEdgeLabel(edge, paths);
});
await swimlaneRender(layout, vert, svg, g, id, conf);
// utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
};
export default {
setConf,
addVertices,
addEdges,
getClasses,
draw,
};