mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-12-05 03:54:35 +01:00
Rendering, tmp commit before refactoring
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 + ')');
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
108
packages/mermaid/src/diagrams/block/layout.ts
Normal file
108
packages/mermaid/src/diagrams/block/layout.ts
Normal 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 };
|
||||
}
|
||||
270
packages/mermaid/src/diagrams/block/renderHelpers.ts
Normal file
270
packages/mermaid/src/diagrams/block/renderHelpers.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
packages/mermaid/src/diagrams/block/styles.ts
Normal file
144
packages/mermaid/src/diagrams/block/styles.ts
Normal 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;
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user