#5237 Removed style, class, labelText and props from node

This commit is contained in:
Knut Sveidqvist 2024-05-20 14:21:01 +02:00
parent 4463e9d7a3
commit b6ef7367c2
12 changed files with 1204 additions and 642 deletions

View File

@ -76,7 +76,7 @@
</head>
<body>
<pre id="diagram" class="mermaid">
%%{init: {"layout": "stress"} }%%
%%{init: {"layout": "elk"} }%%
stateDiagram
[*] --> T1
T1 --> T2
@ -531,7 +531,7 @@ mindmap
mermaid.initialize({
// theme: 'dark',
handdrawnSeed: 12,
look: 'handdrawn',
// look: 'handdrawn',
// layout: 'dagre',
layout: 'elk',
flowchart: { titleTopMargin: 10 },

View File

@ -0,0 +1,392 @@
import { log } from '../../logger.js';
import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import {
DEFAULT_DIAGRAM_DIRECTION,
STMT_STATE,
STMT_RELATION,
STMT_CLASSDEF,
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
G_EDGE_STYLE,
G_EDGE_ARROWHEADSTYLE,
G_EDGE_LABELPOS,
G_EDGE_LABELTYPE,
G_EDGE_THICKNESS,
CSS_EDGE,
DEFAULT_NESTED_DOC_DIR,
SHAPE_DIVIDER,
SHAPE_GROUP,
CSS_DIAGRAM_CLUSTER,
CSS_DIAGRAM_CLUSTER_ALT,
CSS_DIAGRAM_STATE,
SHAPE_STATE_WITH_DESC,
SHAPE_STATE,
SHAPE_START,
SHAPE_END,
SHAPE_NOTE,
SHAPE_NOTEGROUP,
CSS_DIAGRAM_NOTE,
DOMID_TYPE_SPACER,
DOMID_STATE,
NOTE_ID,
PARENT_ID,
NOTE,
PARENT,
CSS_EDGE_NOTE_EDGE,
} from './stateCommon.js';
// List of nodes created from the parsed diagram statement items
let nodeDb = {};
let graphItemCount = 0; // used to construct ids, etc.
/**
* Create a standard string for the dom ID of an item.
* If a type is given, insert that before the counter, preceded by the type spacer
*
* @param itemId
* @param counter
* @param {string | null} type
* @param typeSpacer
* @returns {string}
*/
export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) {
const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : '';
return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
}
const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, useRough) => {
// graphItemCount = 0;
log.trace('items', doc);
doc.forEach((item) => {
switch (item.stmt) {
case STMT_STATE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case DEFAULT_STATE_TYPE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case STMT_RELATION:
{
dataFetcher(
parentParsedItem,
item.state1,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
dataFetcher(
parentParsedItem,
item.state2,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
const edgeData = {
id: 'edge' + graphItemCount,
start: item.state1.id,
end: item.state2.id,
arrowhead: 'normal',
arrowTypeEnd: 'arrow_barb',
style: G_EDGE_STYLE,
labelStyle: '',
label: common.sanitizeText(item.description, getConfig()),
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
classes: CSS_EDGE,
useRough,
};
edges.push(edgeData);
//g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount);
graphItemCount++;
}
break;
}
});
};
/**
* Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param {object[]} parsedItem - the parsed statement item to look through
* @param [defaultDir] - the direction to use if none is found
* @returns {string}
*/
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_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;
};
/**
* Returns a new list of classes.
* In the future, this can be replaced with a class common to all diagrams.
* ClassDef information = { id: id, styles: [], textStyles: [] }
*
* @returns {{}}
*/
function newClassesList() {
return {};
}
// let direction = DEFAULT_DIAGRAM_DIRECTION;
// let rootDoc = [];
let cssClasses = newClassesList(); // style classes defined by a classDef
/**
*
* @param nodes
* @param nodeData
*/
function insertOrUpdateNode(nodes, nodeData) {
if (!nodeData.id || nodeData.id === '</join></fork>' || nodeData.id === '</choice>') {
return;
}
//Populate node style attributes if nodeData has classes defined
if (nodeData.cssClasses) {
nodeData.cssClasses.split(' ').forEach((cssClass) => {
if (cssClasses[cssClass]) {
cssClasses[cssClass].styles.forEach((style) => {
// Populate nodeData with style attributes specifically to be used by rough.js
if (style && style.startsWith('fill:')) {
nodeData.backgroundColor = style.replace('fill:', '');
}
if (style && style.startsWith('stroke:')) {
nodeData.borderColor = style.replace('stroke:', '');
}
if (style && style.startsWith('stroke-width:')) {
nodeData.borderWidth = style.replace('stroke-width:', '');
}
nodeData.cssStyles += style + ';';
});
cssClasses[cssClass].textStyles.forEach((style) => {
nodeData.labelStyle += style + ';';
if (style && style.startsWith('fill:')) {
nodeData.labelTextColor = style.replace('fill:', '');
}
});
}
});
}
const existingNodeData = nodes.find((node) => node.id === nodeData.id);
if (existingNodeData) {
//update the existing nodeData
Object.assign(existingNodeData, nodeData);
} else {
nodes.push(nodeData);
}
}
/**
* Get classes from the db for the info item.
* 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 (dbInfoItem === undefined || dbInfoItem === null) {
return '';
} else {
if (dbInfoItem.cssClasses) {
return dbInfoItem.cssClasses.join(' ');
} else {
return '';
}
}
}
export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, useRough) => {
const itemId = parsedItem.id;
const classStr = getClassesFromDbInfo(diagramStates[itemId]);
if (itemId !== 'root') {
let shape = SHAPE_STATE;
if (parsedItem.start === true) {
shape = SHAPE_START;
}
if (parsedItem.start === false) {
shape = SHAPE_END;
}
if (parsedItem.type !== DEFAULT_STATE_TYPE) {
shape = parsedItem.type;
}
// Add the node to our list (nodeDb)
if (!nodeDb[itemId]) {
nodeDb[itemId] = {
id: itemId,
shape,
description: common.sanitizeText(itemId, getConfig()),
cssClasses: `${classStr} ${CSS_DIAGRAM_STATE}`,
};
}
const newNode = nodeDb[itemId];
console.log('New Node:', newNode);
// Save data for description and group so that for instance a statement without description overwrites
// one with description @todo TODO What does this mean? If important, add a test for it
// 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
newNode.shape = SHAPE_STATE_WITH_DESC;
newNode.description.push(parsedItem.description);
} else {
if (newNode.description?.length > 0) {
// if there is a description already transform it to an array
newNode.shape = SHAPE_STATE_WITH_DESC;
if (newNode.description === itemId) {
// If the previous description was this, remove it
newNode.description = [parsedItem.description];
} else {
newNode.description = [newNode.description, parsedItem.description];
}
} else {
newNode.shape = SHAPE_STATE;
newNode.description = parsedItem.description;
}
}
newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig());
}
// If there's only 1 description entry, just use a regular state shape
if (newNode.description?.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) {
newNode.shape = SHAPE_STATE;
}
// group
if (!newNode.type && parsedItem.doc) {
log.info('Setting cluster for ', itemId, getDir(parsedItem));
newNode.type = 'group';
newNode.dir = getDir(parsedItem);
newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP;
newNode.cssClasses =
newNode.cssClasses +
' ' +
CSS_DIAGRAM_CLUSTER +
' ' +
(altFlag ? CSS_DIAGRAM_CLUSTER_ALT : '');
}
// This is what will be added to the graph
const nodeData = {
labelStyle: '',
shape: newNode.shape,
label: newNode.description,
cssClasses: newNode.cssClasses,
cssStyles: '',
id: itemId,
dir: newNode.dir,
domId: stateDomId(itemId, graphItemCount),
type: newNode.type,
padding: 15,
rx: 10,
ry: 10,
useRough,
};
if (parent && parent.id !== 'root') {
log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
nodeData.parentId = parent.id;
}
nodeData.centerLabel = true;
if (parsedItem.note) {
// Todo: set random id
const noteData = {
labelStyle: '',
shape: SHAPE_NOTE,
label: parsedItem.note.text,
cssClasses: CSS_DIAGRAM_NOTE,
// useHtmlLabels: false,
cssStyles: '', // styles.style,
id: itemId + NOTE_ID + '-' + graphItemCount,
domId: stateDomId(itemId, graphItemCount, NOTE),
type: newNode.type,
padding: 15, //getConfig().flowchart.padding
useRough,
};
const groupData = {
labelStyle: '',
shape: SHAPE_NOTEGROUP,
label: parsedItem.note.text,
cssClasses: newNode.cssClasses,
cssStyles: '', // styles.style,
id: itemId + PARENT_ID,
domId: stateDomId(itemId, graphItemCount, PARENT),
type: 'group',
padding: 0, //getConfig().flowchart.padding
useRough,
};
graphItemCount++;
const parentNodeId = itemId + PARENT_ID;
//add parent id to groupData
groupData.id = parentNodeId;
//add parent id to noteData
noteData.parentId = parentNodeId;
//insert groupData
insertOrUpdateNode(nodes, groupData);
//insert noteData
insertOrUpdateNode(nodes, noteData);
//insert nodeData
insertOrUpdateNode(nodes, nodeData);
let from = itemId;
let to = noteData.id;
if (parsedItem.note.position === 'left of') {
from = noteData.id;
to = itemId;
}
edges.push({
id: from + '-' + to,
start: from,
end: to,
arrowhead: 'none',
arrowTypeEnd: '',
style: G_EDGE_STYLE,
labelStyle: '',
classes: CSS_EDGE_NOTE_EDGE,
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
useRough,
});
} else {
insertOrUpdateNode(nodes, nodeData);
}
console.log('Nodes:', nodes);
}
if (parsedItem.doc) {
log.trace('Adding nodes children ');
setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, useRough);
}
};

View File

@ -11,6 +11,7 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import { dataFetcher } from './dataFetcher.js';
import {
DEFAULT_DIAGRAM_DIRECTION,
@ -75,10 +76,6 @@ let rootDoc = [];
let classes = newClassesList(); // style classes defined by a classDef
// --------------------------------------
// List of nodes created from the parsed diagram statement items
let nodeDb = {};
let graphItemCount = 0; // used to construct ids, etc.
const newDoc = () => {
return {
@ -574,300 +571,6 @@ const setDirection = (dir) => {
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, useRough) => {
const itemId = parsedItem.id;
const classStr = getClassesFromDbInfo(diagramStates[itemId]);
if (itemId !== 'root') {
let shape = SHAPE_STATE;
if (parsedItem.start === true) {
shape = SHAPE_START;
}
if (parsedItem.start === false) {
shape = SHAPE_END;
}
if (parsedItem.type !== DEFAULT_STATE_TYPE) {
shape = parsedItem.type;
}
// Add the node to our list (nodeDb)
if (!nodeDb[itemId]) {
nodeDb[itemId] = {
id: itemId,
shape,
description: common.sanitizeText(itemId, getConfig()),
classes: `${classStr} ${CSS_DIAGRAM_STATE}`,
};
}
const newNode = nodeDb[itemId];
console.log('New Node:', newNode);
// Save data for description and group so that for instance a statement without description overwrites
// one with description @todo TODO What does this mean? If important, add a test for it
// 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
newNode.shape = SHAPE_STATE_WITH_DESC;
newNode.description.push(parsedItem.description);
} else {
if (newNode.description?.length > 0) {
// if there is a description already transform it to an array
newNode.shape = SHAPE_STATE_WITH_DESC;
if (newNode.description === itemId) {
// If the previous description was this, remove it
newNode.description = [parsedItem.description];
} else {
newNode.description = [newNode.description, parsedItem.description];
}
} else {
newNode.shape = SHAPE_STATE;
newNode.description = parsedItem.description;
}
}
newNode.description = common.sanitizeTextOrArray(newNode.description, getConfig());
}
// If there's only 1 description entry, just use a regular state shape
if (newNode.description?.length === 1 && newNode.shape === SHAPE_STATE_WITH_DESC) {
newNode.shape = SHAPE_STATE;
}
// group
if (!newNode.type && parsedItem.doc) {
log.info('Setting cluster for ', itemId, getDir(parsedItem));
newNode.type = 'group';
newNode.dir = getDir(parsedItem);
newNode.shape = parsedItem.type === DIVIDER_TYPE ? SHAPE_DIVIDER : SHAPE_GROUP;
newNode.classes =
newNode.classes +
' ' +
CSS_DIAGRAM_CLUSTER +
' ' +
(altFlag ? CSS_DIAGRAM_CLUSTER_ALT : '');
}
// This is what will be added to the graph
const nodeData = {
labelStyle: '',
shape: newNode.shape,
labelText: newNode.description,
classes: newNode.classes,
style: '',
id: itemId,
dir: newNode.dir,
domId: stateDomId(itemId, graphItemCount),
type: newNode.type,
padding: 15,
rx: 10,
ry: 10,
useRough,
};
if (parent && parent.id !== 'root') {
log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id);
nodeData.parentId = parent.id;
}
nodeData.centerLabel = true;
if (parsedItem.note) {
// Todo: set random id
const noteData = {
labelStyle: '',
shape: SHAPE_NOTE,
labelText: parsedItem.note.text,
classes: CSS_DIAGRAM_NOTE,
// useHtmlLabels: false,
style: '', // styles.style,
id: itemId + NOTE_ID + '-' + graphItemCount,
domId: stateDomId(itemId, graphItemCount, NOTE),
type: newNode.type,
padding: 15, //getConfig().flowchart.padding
useRough,
};
const groupData = {
labelStyle: '',
shape: SHAPE_NOTEGROUP,
labelText: parsedItem.note.text,
classes: newNode.classes,
style: '', // styles.style,
id: itemId + PARENT_ID,
domId: stateDomId(itemId, graphItemCount, PARENT),
type: 'group',
padding: 0, //getConfig().flowchart.padding
useRough,
};
graphItemCount++;
const parentNodeId = itemId + PARENT_ID;
//add parent id to groupData
groupData.id = parentNodeId;
//add parent id to noteData
noteData.parentId = parentNodeId;
//insert groupData
insertOrUpdateNode(nodes, groupData);
//insert noteData
insertOrUpdateNode(nodes, noteData);
//insert nodeData
insertOrUpdateNode(nodes, nodeData);
let from = itemId;
let to = noteData.id;
if (parsedItem.note.position === 'left of') {
from = noteData.id;
to = itemId;
}
edges.push({
id: from + '-' + to,
start: from,
end: to,
arrowhead: 'none',
arrowTypeEnd: '',
style: G_EDGE_STYLE,
labelStyle: '',
classes: CSS_EDGE_NOTE_EDGE,
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
useRough,
});
} else {
insertOrUpdateNode(nodes, nodeData);
}
console.log('Nodes:', nodes);
}
if (parsedItem.doc) {
log.trace('Adding nodes children ');
setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, useRough);
}
};
/**
*
* @param nodes
* @param nodeData
*/
function insertOrUpdateNode(nodes, nodeData) {
if (!nodeData.id || nodeData.id === '</join></fork>' || nodeData.id === '</choice>') {
return;
}
//Populate node style attributes if nodeData has classes defined
if (nodeData.classes) {
nodeData.classes.split(' ').forEach((cssClass) => {
if (classes[cssClass]) {
classes[cssClass].styles.forEach((style) => {
// Populate nodeData with style attributes specifically to be used by rough.js
if (style && style.startsWith('fill:')) {
nodeData.backgroundColor = style.replace('fill:', '');
}
if (style && style.startsWith('stroke:')) {
nodeData.borderColor = style.replace('stroke:', '');
}
if (style && style.startsWith('stroke-width:')) {
nodeData.borderWidth = style.replace('stroke-width:', '');
}
nodeData.style += style + ';';
});
classes[cssClass].textStyles.forEach((style) => {
nodeData.labelStyle += style + ';';
if (style && style.startsWith('fill:')) {
nodeData.labelTextColor = style.replace('fill:', '');
}
});
}
});
}
const existingNodeData = nodes.find((node) => node.id === nodeData.id);
if (existingNodeData) {
//update the existing nodeData
Object.assign(existingNodeData, nodeData);
} else {
nodes.push(nodeData);
}
}
/**
* Create a standard string for the dom ID of an item.
* If a type is given, insert that before the counter, preceded by the type spacer
*
* @param itemId
* @param counter
* @param {string | null} type
* @param typeSpacer
* @returns {string}
*/
export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOMID_TYPE_SPACER) {
const typeStr = type !== null && type.length > 0 ? `${typeSpacer}${type}` : '';
return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
}
const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, useRough) => {
// graphItemCount = 0;
log.trace('items', doc);
doc.forEach((item) => {
switch (item.stmt) {
case STMT_STATE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case DEFAULT_STATE_TYPE:
dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, useRough);
break;
case STMT_RELATION:
{
dataFetcher(
parentParsedItem,
item.state1,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
dataFetcher(
parentParsedItem,
item.state2,
diagramStates,
nodes,
edges,
altFlag,
useRough
);
const edgeData = {
id: 'edge' + graphItemCount,
start: item.state1.id,
end: item.state2.id,
arrowhead: 'normal',
arrowTypeEnd: 'arrow_barb',
style: G_EDGE_STYLE,
labelStyle: '',
label: common.sanitizeText(item.description, getConfig()),
arrowheadStyle: G_EDGE_ARROWHEADSTYLE,
labelpos: G_EDGE_LABELPOS,
labelType: G_EDGE_LABELTYPE,
thickness: G_EDGE_THICKNESS,
classes: CSS_EDGE,
useRough,
};
edges.push(edgeData);
//g.setEdge(item.state1.id, item.state2.id, edgeData, graphItemCount);
graphItemCount++;
}
break;
}
});
};
export const getData = () => {
const nodes = [];
const edges = [];
@ -886,47 +589,6 @@ export const getData = () => {
return { nodes, edges, other: {} };
};
/**
* Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param {object[]} parsedItem - the parsed statement item to look through
* @param [defaultDir] - the direction to use if none is found
* @returns {string}
*/
const getDir = (parsedItem, defaultDir = DEFAULT_NESTED_DOC_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;
};
/**
* Get classes from the db for the info item.
* 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 (dbInfoItem === undefined || dbInfoItem === null) {
return '';
} else {
if (dbInfoItem.classes) {
return dbInfoItem.classes.join(' ');
} else {
return '';
}
}
}
export default {
getConfig: () => getConfig().state,
getData,

View File

@ -288,7 +288,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
domId: specialId,
id: specialId,
labelStyle: '',
labelText: edge.label,
label: edge.label,
padding: 0,
shape: 'labelRect',
style: '',
@ -416,7 +416,7 @@ export const extractor = (graph, depth) => {
clusterNode: true,
id: node,
clusterData: clusterDb[node].clusterData,
labelText: clusterDb[node].labelText,
label: clusterDb[node].label,
graph: clusterGraph,
});
log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph));

View File

@ -386,7 +386,7 @@ flowchart TB
*/
const exportedGraph = JSON.parse(
'{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"class":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","classes":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","classes":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}'
'{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"cssClass":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","cssClasses":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","cssClasses":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}'
);
const gr = graphlibJson.read(exportedGraph);

View File

@ -14,10 +14,7 @@ const rect = (parent, node) => {
const siteConfig = getConfig();
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', 'cluster' + (node.class ? ' ' + node.class : ''))
.attr('id', node.id);
const shapeSvg = parent.insert('g').attr('class', 'cluster').attr('id', node.id);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
@ -25,15 +22,15 @@ const rect = (parent, node) => {
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
const labelEl = shapeSvg.insert('g').attr('class', 'cluster-label');
// const text = label
// .node()
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
// .appendChild(createLabel(node.label, node.labelStyle, undefined, true));
const text =
node.labelType === 'markdown'
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
? createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels })
: labelEl.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@ -59,7 +56,7 @@ const rect = (parent, node) => {
log.trace('Data ', node, JSON.stringify(node));
// center the rect around its coordinate
rect
.attr('style', node.style)
.attr('style', node.cssStyles)
.attr('rx', node.rx)
.attr('ry', node.ry)
.attr('x', node.x - width / 2)
@ -69,13 +66,13 @@ const rect = (parent, node) => {
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
if (useHtmlLabels) {
label.attr(
labelEl.attr(
'transform',
// This puts the label on top of the box instead of inside it
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
} else {
label.attr(
labelEl.attr(
'transform',
// This puts the label on top of the box instead of inside it
`translate(${node.x}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
@ -154,7 +151,7 @@ const roundedWithTitle = (parent, node) => {
themeVariables;
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', node.classes).attr('id', node.id);
const shapeSvg = parent.insert('g').attr('class', node.cssClasses).attr('id', node.id);
// add the rect
const outerRectG = shapeSvg.insert('g', ':first-child');
@ -163,9 +160,7 @@ const roundedWithTitle = (parent, node) => {
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
let innerRect = shapeSvg.append('rect');
const text = label
.node()
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
const text = label.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label
let bbox = text.getBBox();
@ -197,7 +192,7 @@ const roundedWithTitle = (parent, node) => {
// add the rect
let rect;
if (node.useRough) {
const isAlt = node.classes.includes('statediagram-cluster-alt');
const isAlt = node.cssClasses.includes('statediagram-cluster-alt');
const rc = rough.svg(shapeSvg);
const roughOuterNode =
node.rx || node.ry
@ -263,7 +258,7 @@ const roundedWithTitle = (parent, node) => {
const divider = (parent, node) => {
const { handdrawnSeed } = getConfig();
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', node.classes).attr('id', node.id);
const shapeSvg = parent.insert('g').attr('class', node.cssClasses).attr('id', node.id);
// add the rect
let rect;
@ -318,7 +313,7 @@ export const insertCluster = (elem, node) => {
return cluster;
};
export const getClusterTitleWidth = (elem, node) => {
const label = createLabel(node.labelText, node.labelStyle, undefined, true);
const label = createLabel(node.label, node.labelStyle, undefined, true);
elem.node().appendChild(label);
const width = label.getBBox().width;
elem.node().removeChild(label);

View File

@ -48,9 +48,9 @@ export const insertNode = async (elem, node, dir) => {
if (node.tooltip) {
el.attr('title', node.tooltip);
}
if (node.class) {
el.attr('class', 'node default ' + node.class);
}
// if (node.class) {
// el.attr('class', 'node default ' + node.class);
// }
nodeElems[node.id] = newEl;

View File

@ -16,12 +16,12 @@ export const note = async (parent: SVGAElement, node: Node) => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
'node ' + node.classes,
'node ' + node.cssClasses,
true
);
log.info('Classes = ', node.classes);
const { style, useRough } = node;
log.info('Classes = ', node.cssClasses);
const { cssStyles, useRough } = node;
let rect;
const totalWidth = bbox.width + node.padding;
const totalHeight = bbox.height + node.padding;
@ -41,7 +41,7 @@ export const note = async (parent: SVGAElement, node: Node) => {
});
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', style);
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect

View File

@ -65,7 +65,7 @@ export const rect = async (parent: SVGAElement, node: Node) => {
const { shapeSvg, bbox, halfPadding } = await labelHelper(
parent,
node,
'node ' + node.classes + ' ' + node.class,
'node ' + node.cssClasses, // + ' ' + node.class,
true
);
@ -75,7 +75,7 @@ export const rect = async (parent: SVGAElement, node: Node) => {
const y = -bbox.height / 2 - halfPadding;
let rect;
const { rx, ry, style, useRough } = node;
const { rx, ry, style: cssStyles, useRough } = node;
if (useRough) {
const rc = rough.svg(shapeSvg);
const options = userNodeOverrides(node, {
@ -93,13 +93,13 @@ export const rect = async (parent: SVGAElement, node: Node) => {
: rc.rectangle(x, y, totalWidth, totalHeight, options);
rect = shapeSvg.insert(() => roughNode, ':first-child');
rect.attr('class', 'basic label-container').attr('style', style);
rect.attr('class', 'basic label-container').attr('style', cssStyles);
} else {
rect = shapeSvg.insert('rect', ':first-child');
rect
.attr('class', 'basic label-container')
.attr('style', style)
.attr('style', cssStyles)
.attr('rx', rx)
.attr('ry', ry)
.attr('x', x)
@ -108,16 +108,16 @@ export const rect = async (parent: SVGAElement, node: Node) => {
.attr('height', totalHeight);
}
if (node.props) {
const propKeys = new Set(Object.keys(node.props));
if (node.props.borders) {
applyNodePropertyBorders(rect, node.props.borders + '', totalWidth, totalHeight);
propKeys.delete('borders');
}
propKeys.forEach((propKey) => {
log.warn(`Unknown node property ${propKey}`);
});
}
// if (node.props) {
// const propKeys = new Set(Object.keys(node.props));
// if (node.props.borders) {
// applyNodePropertyBorders(rect, node.props.borders + '', totalWidth, totalHeight);
// propKeys.delete('borders');
// }
// propKeys.forEach((propKey) => {
// log.warn(`Unknown node property ${propKey}`);
// });
// }
updateNodeBounds(node, rect);
@ -131,7 +131,7 @@ export const rect = async (parent: SVGAElement, node: Node) => {
export const labelRect = async (parent: SVGElement, node: Node) => {
const { shapeSvg } = await labelHelper(parent, node, 'label', true);
log.trace('Classes = ', node.class);
// log.trace('Classes = ', node.class);
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
@ -141,16 +141,16 @@ export const labelRect = async (parent: SVGElement, node: Node) => {
rect.attr('width', totalWidth).attr('height', totalHeight);
shapeSvg.attr('class', 'label edgeLabel');
if (node.props) {
const propKeys = new Set(Object.keys(node.props));
if (node.props.borders) {
applyNodePropertyBorders(rect, node.borders, totalWidth, totalHeight);
propKeys.delete('borders');
}
propKeys.forEach((propKey) => {
log.warn(`Unknown node property ${propKey}`);
});
}
// if (node.props) {
// const propKeys = new Set(Object.keys(node.props));
// if (node.props.borders) {
// applyNodePropertyBorders(rect, node.borders, totalWidth, totalHeight);
// propKeys.delete('borders');
// }
// propKeys.forEach((propKey) => {
// log.warn(`Unknown node property ${propKey}`);
// });
// }
updateNodeBounds(node, rect);

View File

@ -6,48 +6,43 @@ import { evaluate, sanitizeText } from '$root/diagrams/common/common.js';
import { decodeEntities } from '$root/utils.js';
export const labelHelper = async (parent, node, _classes, isNode) => {
let classes;
let cssClasses;
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
if (!_classes) {
classes = 'node default';
cssClasses = 'node default';
} else {
classes = _classes;
cssClasses = _classes;
}
// Add outer g element
const shapeSvg = parent
.insert('g')
.attr('class', classes)
.attr('class', cssClasses)
.attr('id', node.domId || node.id);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle);
const labelEl = shapeSvg.insert('g').attr('class', 'label').attr('style', node.labelStyle);
// Replace labelText with default value if undefined
let labelText;
if (node.labelText === undefined) {
labelText = '';
// Replace label with default value if undefined
let label;
if (node.label === undefined) {
label = '';
} else {
labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0];
label = typeof node.label === 'string' ? node.label : node.label[0];
}
const textNode = label.node();
const textNode = labelEl.node();
let text;
if (node.labelType === 'markdown') {
// text = textNode;
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
text = createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
useHtmlLabels,
width: node.width || getConfig().flowchart.wrappingWidth,
classes: 'markdown-node-label',
cssClasses: 'markdown-node-label',
});
} else {
text = textNode.appendChild(
createLabel(
sanitizeText(decodeEntities(labelText), getConfig()),
node.labelStyle,
false,
isNode
)
createLabel(sanitizeText(decodeEntities(label), getConfig()), node.labelStyle, false, isNode)
);
}
// Get the size of the label
@ -61,7 +56,7 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
// if there are images, need to wait for them to load before getting the bounding box
const images = div.getElementsByTagName('img');
if (images) {
const noImgText = labelText.replace(/<img[^>]*>/g, '').trim() === '';
const noImgText = label.replace(/<img[^>]*>/g, '').trim() === '';
await Promise.all(
[...images].map(
@ -107,15 +102,15 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
// Center the label
if (useHtmlLabels) {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
} else {
label.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
labelEl.attr('transform', 'translate(' + 0 + ', ' + -bbox.height / 2 + ')');
}
if (node.centerLabel) {
label.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
labelEl.attr('transform', 'translate(' + -bbox.width / 2 + ', ' + -bbox.height / 2 + ')');
}
label.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label };
labelEl.insert('rect', ':first-child');
return { shapeSvg, bbox, halfPadding, label: labelEl };
};
export const updateNodeBounds = (node, element) => {

View File

@ -14,25 +14,24 @@ interface Node {
id: string;
label?: string;
parentId?: string;
position?: string; //REMOVE
position?: string; // Keep, this is for notes 'left of', 'right of', etc.
cssStyles?: string; // Renamed from `styles` to `cssStyles`
style?: string; //REMOVE
cssClasses?: string; // Renamed from `classes` to `cssClasses`
classes?: string; //REMOVE
class?: string; //REMOVE
// style?: string; //REMOVE
// class?: string; //REMOVE
// labelText?: string; //REMOVE, use `label` instead
// props?: Record<string, unknown>; //REMOVE
labelStyle?: string;
labelText?: string; //REMOVE, use `label` instead
// Flowchart specific properties
labelType?: string; // REMOVE? Always use markdown string, need to check for KaTeX
labelType?: string; // REMOVE? Always use markdown string, need to check for KaTeX - wait with this one
domId: string;
// Rendering specific properties for both Flowchart and State Diagram nodes
dir?: string; // Only relevant for isGroup true, i.e. a sub-graph or composite state.
haveCallback?: boolean;
link?: string;
linkTarget?: string;
padding?: number; //REMOVE, use from LayoutData.config
props?: Record<string, unknown>; //REMOVE
padding?: number; //REMOVE?, use from LayoutData.config - Keep, this could be shape specific
shape?: string;
tooltip?: string;
type: string; // REMOVE, replace with isGroup: boolean, default false
@ -137,8 +136,8 @@ export function createDomElement(node: Node): Node {
element.id = node.domId;
// Optional: Apply styles and classes to the element
if (node.styles) {
element.style.cssText = node.styles;
if (node.cssStyles) {
element.style.cssText = node.cssStyles;
}
if (node.classes) {
element.className = node.classes;

945
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff