mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
diagram-v2: store results of stateDb.extract(), apply class to state; code cleanup
This commit is contained in:
parent
3c0727c744
commit
ba71afcce5
@ -7,7 +7,23 @@ import { configureSvgSize } from '../../setupGraphViewbox';
|
|||||||
import common from '../common/common';
|
import common from '../common/common';
|
||||||
import addSVGAccessibilityFields from '../../accessibility';
|
import addSVGAccessibilityFields from '../../accessibility';
|
||||||
|
|
||||||
|
const DEFAULT_DIR = 'TD';
|
||||||
|
|
||||||
|
// When information is parsed and processed (extracted) by stateDb.extract()
|
||||||
|
// These are globals so the information can be accessed as needed (e.g. in setUpNode, etc.)
|
||||||
|
let diagramStates = [];
|
||||||
|
let diagramClasses = [];
|
||||||
|
|
||||||
|
// List of nodes created from the parsed diagram statement items
|
||||||
|
let nodeDb = {};
|
||||||
|
|
||||||
|
let graphItemCount = 0; // used to construct ids, etc.
|
||||||
|
|
||||||
|
// Configuration
|
||||||
const conf = {};
|
const conf = {};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
export const setConf = function (cnf) {
|
export const setConf = function (cnf) {
|
||||||
const keys = Object.keys(cnf);
|
const keys = Object.keys(cnf);
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
@ -15,150 +31,187 @@ export const setConf = function (cnf) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let nodeDb = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the all the styles from classDef statements in the graph definition.
|
* Returns the all the styles from classDef statements in the graph definition.
|
||||||
*
|
*
|
||||||
* @param {any} text
|
* @param {string} text - the diagram text to be parsed
|
||||||
* @param diag
|
* @param {Diagram} diagramObj
|
||||||
* @returns {object} ClassDef styles
|
* @returns {object} ClassDef styles
|
||||||
*/
|
*/
|
||||||
export const getClasses = function (text, diag) {
|
export const getClasses = function (text, diagramObj) {
|
||||||
log.trace('Extracting classes');
|
log.trace('Extracting classes');
|
||||||
diag.sb.clear();
|
if (diagramClasses.length > 0) return diagramClasses; // we have already extracted the classes
|
||||||
|
|
||||||
// Parse the graph definition
|
diagramObj.db.clear();
|
||||||
diag.parser.parse(text);
|
try {
|
||||||
return diag.sb.getClasses();
|
// Parse the graph definition
|
||||||
|
diagramObj.parser.parse(text);
|
||||||
|
// must run extract() to turn the parsed statements into states, relationships, classes, etc.
|
||||||
|
diagramObj.db.extract(diagramObj.db.getRootDocV2());
|
||||||
|
|
||||||
|
return diagramObj.db.getClasses();
|
||||||
|
} catch (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupNode = (g, parent, node, altFlag) => {
|
/**
|
||||||
// Add the node
|
* Get classes from the db info item.
|
||||||
if (node.id !== 'root') {
|
* If there aren't any or if dbInfoItem isn't defined, return an empty string.
|
||||||
|
* Else create 1 string from the list of classes found
|
||||||
|
*
|
||||||
|
* @param {undefined | null | object} dbInfoItem
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getClassesFromDbInfo(dbInfoItem) {
|
||||||
|
if (typeof dbInfoItem === 'undefined' || dbInfoItem === null) return '';
|
||||||
|
else {
|
||||||
|
if (dbInfoItem.classes) {
|
||||||
|
return dbInfoItem.classes.join(' ');
|
||||||
|
} else return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a graph node based on the statement information
|
||||||
|
*
|
||||||
|
* @param g - graph
|
||||||
|
* @param {object} parent
|
||||||
|
* @param {object} parsedItem - parsed statement item
|
||||||
|
* @param {object} diagramDb
|
||||||
|
* @param {boolean} altFlag - for clusters, add the "statediagram-cluster-alt" CSS class
|
||||||
|
* @todo This duplicates some of what is done in stateDb.js extract method
|
||||||
|
*/
|
||||||
|
const setupNode = (g, parent, parsedItem, diagramDb, altFlag) => {
|
||||||
|
const itemId = parsedItem.id;
|
||||||
|
const classStr = getClassesFromDbInfo(diagramStates[itemId]);
|
||||||
|
|
||||||
|
if (itemId !== 'root') {
|
||||||
let shape = 'rect';
|
let shape = 'rect';
|
||||||
if (node.start === true) {
|
if (parsedItem.start === true) {
|
||||||
shape = 'start';
|
shape = 'start';
|
||||||
}
|
}
|
||||||
if (node.start === false) {
|
if (parsedItem.start === false) {
|
||||||
shape = 'end';
|
shape = 'end';
|
||||||
}
|
}
|
||||||
if (node.type !== 'default') {
|
if (parsedItem.type !== 'default') {
|
||||||
shape = node.type;
|
shape = parsedItem.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nodeDb[node.id]) {
|
// Add the node to our list (nodeDb)
|
||||||
nodeDb[node.id] = {
|
if (!nodeDb[itemId]) {
|
||||||
id: node.id,
|
nodeDb[itemId] = {
|
||||||
|
id: itemId,
|
||||||
shape,
|
shape,
|
||||||
description: common.sanitizeText(node.id, getConfig()),
|
description: common.sanitizeText(itemId, getConfig()),
|
||||||
classes: 'statediagram-state',
|
classes: classStr + ' statediagram-state',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build of the array of description strings accordinging
|
const newNode = nodeDb[itemId];
|
||||||
if (node.description) {
|
|
||||||
if (Array.isArray(nodeDb[node.id].description)) {
|
// Build of the array of description strings
|
||||||
|
if (parsedItem.description) {
|
||||||
|
if (Array.isArray(newNode.description)) {
|
||||||
// There already is an array of strings,add to it
|
// There already is an array of strings,add to it
|
||||||
nodeDb[node.id].shape = 'rectWithTitle';
|
newNode.shape = 'rectWithTitle';
|
||||||
nodeDb[node.id].description.push(node.description);
|
newNode.description.push(parsedItem.description);
|
||||||
} else {
|
} else {
|
||||||
if (nodeDb[node.id].description.length > 0) {
|
if (newNode.description.length > 0) {
|
||||||
// if there is a description already transformit to an array
|
// if there is a description already transform it to an array
|
||||||
nodeDb[node.id].shape = 'rectWithTitle';
|
newNode.shape = 'rectWithTitle';
|
||||||
if (nodeDb[node.id].description === node.id) {
|
if (newNode.description === itemId) {
|
||||||
// If the previous description was the is, remove it
|
// If the previous description was the is, remove it
|
||||||
nodeDb[node.id].description = [node.description];
|
newNode.description = [parsedItem.description];
|
||||||
} else {
|
} else {
|
||||||
nodeDb[node.id].description = [nodeDb[node.id].description, node.description];
|
newNode.description = [newNode.description, parsedItem.description];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nodeDb[node.id].shape = 'rect';
|
newNode.shape = 'rect';
|
||||||
nodeDb[node.id].description = node.description;
|
newNode.description = parsedItem.description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodeDb[node.id].description = common.sanitizeTextOrArray(
|
newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig());
|
||||||
nodeDb[node.id].description,
|
|
||||||
getConfig()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// update the node shape
|
||||||
if (nodeDb[node.id].description.length === 1 && nodeDb[node.id].shape === 'rectWithTitle') {
|
if (newNode.description.length === 1 && newNode.shape === 'rectWithTitle') {
|
||||||
nodeDb[node.id].shape = 'rect';
|
newNode.shape = 'rect';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save data for description and group so that for instance a statement without description overwrites
|
// Save data for description and group so that for instance a statement without description overwrites
|
||||||
// one with description
|
// one with description
|
||||||
|
|
||||||
// group
|
// group
|
||||||
if (!nodeDb[node.id].type && node.doc) {
|
if (!newNode.type && parsedItem.doc) {
|
||||||
log.info('Setting cluster for ', node.id, getDir(node));
|
log.info('Setting cluster for ', itemId, getDir(parsedItem));
|
||||||
nodeDb[node.id].type = 'group';
|
newNode.type = 'group';
|
||||||
nodeDb[node.id].dir = getDir(node);
|
newNode.dir = getDir(parsedItem);
|
||||||
nodeDb[node.id].shape = node.type === 'divider' ? 'divider' : 'roundedWithTitle';
|
newNode.shape = parsedItem.type === 'divider' ? 'divider' : 'roundedWithTitle';
|
||||||
nodeDb[node.id].classes =
|
|
||||||
nodeDb[node.id].classes +
|
newNode.classes =
|
||||||
|
newNode.classes +
|
||||||
' ' +
|
' ' +
|
||||||
(altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster');
|
(altFlag ? 'statediagram-cluster statediagram-cluster-alt' : 'statediagram-cluster');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is what will be added to the graph
|
||||||
const nodeData = {
|
const nodeData = {
|
||||||
labelStyle: '',
|
labelStyle: '',
|
||||||
shape: nodeDb[node.id].shape,
|
shape: newNode.shape,
|
||||||
labelText: nodeDb[node.id].description,
|
labelText: newNode.description,
|
||||||
// typeof nodeDb[node.id].description === 'object'
|
// typeof newNode.description === 'object'
|
||||||
// ? nodeDb[node.id].description[0]
|
// ? newNode.description[0]
|
||||||
// : nodeDb[node.id].description,
|
// : newNode.description,
|
||||||
classes: nodeDb[node.id].classes, //classStr,
|
classes: newNode.classes,
|
||||||
style: '', //styles.style,
|
style: '', //styles.style,
|
||||||
id: node.id,
|
id: itemId,
|
||||||
dir: nodeDb[node.id].dir,
|
dir: newNode.dir,
|
||||||
domId: 'state-' + node.id + '-' + cnt,
|
domId: 'state-' + itemId + '-' + graphItemCount,
|
||||||
type: nodeDb[node.id].type,
|
type: newNode.type,
|
||||||
padding: 15, //getConfig().flowchart.padding
|
padding: 15, //getConfig().flowchart.padding
|
||||||
};
|
};
|
||||||
|
|
||||||
if (node.note) {
|
if (parsedItem.note) {
|
||||||
// Todo: set random id
|
// Todo: set random id
|
||||||
const noteData = {
|
const noteData = {
|
||||||
labelStyle: '',
|
labelStyle: '',
|
||||||
shape: 'note',
|
shape: 'note',
|
||||||
labelText: node.note.text,
|
labelText: parsedItem.note.text,
|
||||||
classes: 'statediagram-note', //classStr,
|
classes: 'statediagram-note', //classStr,
|
||||||
style: '', //styles.style,
|
style: '', // styles.style,
|
||||||
id: node.id + '----note-' + cnt,
|
id: itemId + '----note-' + graphItemCount,
|
||||||
domId: 'state-' + node.id + '----note-' + cnt,
|
domId: 'state-' + itemId + '----note-' + graphItemCount,
|
||||||
type: nodeDb[node.id].type,
|
type: newNode.type,
|
||||||
padding: 15, //getConfig().flowchart.padding
|
padding: 15, //getConfig().flowchart.padding
|
||||||
};
|
};
|
||||||
const groupData = {
|
const groupData = {
|
||||||
labelStyle: '',
|
labelStyle: '',
|
||||||
shape: 'noteGroup',
|
shape: 'noteGroup',
|
||||||
labelText: node.note.text,
|
labelText: parsedItem.note.text,
|
||||||
classes: nodeDb[node.id].classes, //classStr,
|
classes: newNode.classes, //classStr,
|
||||||
style: '', //styles.style,
|
style: '', // styles.style,
|
||||||
id: node.id + '----parent',
|
id: itemId + '----parent',
|
||||||
domId: 'state-' + node.id + '----parent-' + cnt,
|
domId: 'state-' + itemId + '----parent-' + graphItemCount,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
padding: 0, //getConfig().flowchart.padding
|
padding: 0, //getConfig().flowchart.padding
|
||||||
};
|
};
|
||||||
cnt++;
|
graphItemCount++;
|
||||||
|
|
||||||
g.setNode(node.id + '----parent', groupData);
|
g.setNode(itemId + '----parent', groupData);
|
||||||
|
|
||||||
g.setNode(noteData.id, noteData);
|
g.setNode(noteData.id, noteData);
|
||||||
g.setNode(node.id, nodeData);
|
g.setNode(itemId, nodeData);
|
||||||
|
|
||||||
g.setParent(node.id, node.id + '----parent');
|
g.setParent(itemId, itemId + '----parent');
|
||||||
g.setParent(noteData.id, node.id + '----parent');
|
g.setParent(noteData.id, itemId + '----parent');
|
||||||
|
|
||||||
let from = node.id;
|
let from = itemId;
|
||||||
let to = noteData.id;
|
let to = noteData.id;
|
||||||
|
|
||||||
if (node.note.position === 'left of') {
|
if (parsedItem.note.position === 'left of') {
|
||||||
from = noteData.id;
|
from = noteData.id;
|
||||||
to = node.id;
|
to = itemId;
|
||||||
}
|
}
|
||||||
g.setEdge(from, to, {
|
g.setEdge(from, to, {
|
||||||
arrowhead: 'none',
|
arrowhead: 'none',
|
||||||
@ -172,66 +225,92 @@ const setupNode = (g, parent, node, altFlag) => {
|
|||||||
thickness: 'normal',
|
thickness: 'normal',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
g.setNode(node.id, nodeData);
|
g.setNode(itemId, nodeData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
if (parent.id !== 'root') {
|
if (parent.id !== 'root') {
|
||||||
log.trace('Setting node ', node.id, ' to be child of its parent ', parent.id);
|
log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
|
||||||
g.setParent(node.id, parent.id);
|
g.setParent(itemId, parent.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.doc) {
|
if (parsedItem.doc) {
|
||||||
log.trace('Adding nodes children ');
|
log.trace('Adding nodes children ');
|
||||||
setupDoc(g, node, node.doc, !altFlag);
|
setupDoc(g, parsedItem, parsedItem.doc, diagramDb, !altFlag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let cnt = 0;
|
|
||||||
const setupDoc = (g, parent, doc, altFlag) => {
|
/**
|
||||||
// cnt = 0;
|
* Turn parsed statements (item.stmt) into nodes, relationships, etc. for a document.
|
||||||
|
* (A document may be nested within others.)
|
||||||
|
*
|
||||||
|
* @param g
|
||||||
|
* @param parentParsedItem - parsed Item that is the parent of this document (doc)
|
||||||
|
* @param doc - the document to set up
|
||||||
|
* @param diagramDb
|
||||||
|
* @param altFlag
|
||||||
|
* @todo This duplicates some of what is done in stateDb.js extract method
|
||||||
|
*/
|
||||||
|
const setupDoc = (g, parentParsedItem, doc, diagramDb, altFlag) => {
|
||||||
|
// graphItemCount = 0;
|
||||||
log.trace('items', doc);
|
log.trace('items', doc);
|
||||||
doc.forEach((item) => {
|
doc.forEach((item) => {
|
||||||
if (item.stmt === 'state' || item.stmt === 'default') {
|
switch (item.stmt) {
|
||||||
setupNode(g, parent, item, altFlag);
|
case 'state':
|
||||||
} else if (item.stmt === 'relation') {
|
setupNode(g, parentParsedItem, item, diagramDb, altFlag);
|
||||||
setupNode(g, parent, item.state1, altFlag);
|
break;
|
||||||
setupNode(g, parent, item.state2, altFlag);
|
case 'default':
|
||||||
const edgeData = {
|
setupNode(g, parentParsedItem, item, diagramDb, altFlag);
|
||||||
id: 'edge' + cnt,
|
break;
|
||||||
arrowhead: 'normal',
|
case 'relation':
|
||||||
arrowTypeEnd: 'arrow_barb',
|
{
|
||||||
style: 'fill:none',
|
setupNode(g, parentParsedItem, item.state1, diagramDb, altFlag);
|
||||||
labelStyle: '',
|
setupNode(g, parentParsedItem, item.state2, diagramDb, altFlag);
|
||||||
label: common.sanitizeText(item.description, getConfig()),
|
const edgeData = {
|
||||||
arrowheadStyle: 'fill: #333',
|
id: 'edge' + graphItemCount,
|
||||||
labelpos: 'c',
|
arrowhead: 'normal',
|
||||||
labelType: 'text',
|
arrowTypeEnd: 'arrow_barb',
|
||||||
thickness: 'normal',
|
style: 'fill:none',
|
||||||
classes: 'transition',
|
labelStyle: '',
|
||||||
};
|
label: common.sanitizeText(item.description, getConfig()),
|
||||||
let startId = item.state1.id;
|
arrowheadStyle: 'fill: #333',
|
||||||
let endId = item.state2.id;
|
labelpos: 'c',
|
||||||
|
labelType: 'text',
|
||||||
g.setEdge(startId, endId, edgeData, cnt);
|
thickness: 'normal',
|
||||||
cnt++;
|
classes: 'transition',
|
||||||
|
};
|
||||||
|
g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount);
|
||||||
|
graphItemCount++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const getDir = (nodes, defaultDir) => {
|
|
||||||
let dir = defaultDir || 'TB';
|
/**
|
||||||
if (nodes.doc) {
|
* Get the direction from the statement items. Default is TB (top to bottom).
|
||||||
for (let i = 0; i < nodes.doc.length; i++) {
|
* Look through all of the documents (docs) in the parsedItems
|
||||||
const node = nodes.doc[i];
|
*
|
||||||
if (node.stmt === 'dir') {
|
* @param {object[]} parsedItem - the parsed statement item to look through
|
||||||
dir = node.value;
|
* @param [defaultDir='TB'] - the direction to use if none is found
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
const getDir = (parsedItem, defaultDir = DEFAULT_DIR) => {
|
||||||
|
let dir = defaultDir;
|
||||||
|
if (parsedItem.doc) {
|
||||||
|
for (let i = 0; i < parsedItem.doc.length; i++) {
|
||||||
|
const parsedItemDoc = parsedItem.doc[i];
|
||||||
|
if (parsedItemDoc.stmt === 'dir') {
|
||||||
|
dir = parsedItemDoc.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dir;
|
return dir;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
* Draws a state diagram in the tag with id: id based on the graph definition in text.
|
||||||
*
|
*
|
||||||
* @param {any} text
|
* @param {any} text
|
||||||
* @param {any} id
|
* @param {any} id
|
||||||
@ -244,18 +323,21 @@ export const draw = function (text, id, _version, diag) {
|
|||||||
nodeDb = {};
|
nodeDb = {};
|
||||||
// Fetch the default direction, use TD if none was found
|
// Fetch the default direction, use TD if none was found
|
||||||
let dir = diag.db.getDirection();
|
let dir = diag.db.getDirection();
|
||||||
if (typeof dir === 'undefined') {
|
if (typeof dir === 'undefined') dir = DEFAULT_DIR;
|
||||||
dir = 'LR';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { securityLevel, state: conf } = getConfig();
|
const { securityLevel, state: conf } = getConfig();
|
||||||
const nodeSpacing = conf.nodeSpacing || 50;
|
const nodeSpacing = conf.nodeSpacing || 50;
|
||||||
const rankSpacing = conf.rankSpacing || 50;
|
const rankSpacing = conf.rankSpacing || 50;
|
||||||
|
|
||||||
log.info(diag.db.getRootDocV2());
|
log.info(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
// This parses the diagram text and sets the classes, relations, styles, classDefs, etc.
|
||||||
diag.db.extract(diag.db.getRootDocV2());
|
diag.db.extract(diag.db.getRootDocV2());
|
||||||
log.info(diag.db.getRootDocV2());
|
log.info(diag.db.getRootDocV2());
|
||||||
|
|
||||||
|
diagramStates = diag.db.getStates();
|
||||||
|
diagramClasses = diag.db.getClasses();
|
||||||
|
|
||||||
// Create the input mermaid.graph
|
// Create the input mermaid.graph
|
||||||
const g = new graphlib.Graph({
|
const g = new graphlib.Graph({
|
||||||
multigraph: true,
|
multigraph: true,
|
||||||
@ -272,7 +354,7 @@ export const draw = function (text, id, _version, diag) {
|
|||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
setupNode(g, undefined, diag.db.getRootDocV2(), true);
|
setupNode(g, undefined, diag.db.getRootDocV2(), diag.db, true);
|
||||||
|
|
||||||
// Set up an SVG group so that we can translate the final graph.
|
// Set up an SVG group so that we can translate the final graph.
|
||||||
let sandboxElement;
|
let sandboxElement;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user