mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-12 18:49:37 +02:00
#3358 Adding support for classDef and class statements
This commit is contained in:
@@ -73,6 +73,22 @@ block-beta
|
|||||||
<pre id="diagram" class="mermaid">
|
<pre id="diagram" class="mermaid">
|
||||||
block-beta
|
block-beta
|
||||||
columns 3
|
columns 3
|
||||||
|
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||||
|
A
|
||||||
|
B
|
||||||
|
class A green
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid">
|
||||||
|
stateDiagram-v2
|
||||||
|
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||||
|
A
|
||||||
|
class A green
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram" class="mermaid2">
|
||||||
|
block-beta
|
||||||
|
columns 3
|
||||||
|
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||||
A
|
A
|
||||||
space
|
space
|
||||||
block
|
block
|
||||||
@@ -80,6 +96,7 @@ block-beta
|
|||||||
F
|
F
|
||||||
end
|
end
|
||||||
E -- "apa" --> A
|
E -- "apa" --> A
|
||||||
|
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram" class="mermaid2">
|
<pre id="diagram" class="mermaid2">
|
||||||
block-beta
|
block-beta
|
||||||
@@ -538,7 +555,7 @@ mindmap
|
|||||||
// console.error('Mermaid error: ', err);
|
// console.error('Mermaid error: ', err);
|
||||||
};
|
};
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
// theme: 'forest',
|
theme: 'forest',
|
||||||
startOnLoad: true,
|
startOnLoad: true,
|
||||||
logLevel: 0,
|
logLevel: 0,
|
||||||
flowchart: {
|
flowchart: {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
// import type { BlockDB } from './blockTypes.js';
|
// import type { BlockDB } from './blockTypes.js';
|
||||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
import type { BlockConfig, BlockType, Block, Link } from './blockTypes.js';
|
import type { BlockConfig, BlockType, Block, Link, ClassDef } from './blockTypes.js';
|
||||||
|
|
||||||
import * as configApi from '../../config.js';
|
import * as configApi from '../../config.js';
|
||||||
import {
|
import {
|
||||||
@@ -20,10 +20,112 @@ let blockDatabase: Record<string, Block> = {};
|
|||||||
let edgeList: Block[] = [];
|
let edgeList: Block[] = [];
|
||||||
let edgeCount: Record<string, number> = {};
|
let edgeCount: Record<string, number> = {};
|
||||||
|
|
||||||
|
const COLOR_KEYWORD = 'color';
|
||||||
|
const FILL_KEYWORD = 'fill';
|
||||||
|
const BG_FILL = 'bgFill';
|
||||||
|
const STYLECLASS_SEP = ',';
|
||||||
|
|
||||||
|
let classes = {} as Record<string, ClassDef>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the parser comes across a (style) class definition
|
||||||
|
* @example classDef my-style fill:#f96;
|
||||||
|
*
|
||||||
|
* @param {string} id - the id of this (style) class
|
||||||
|
* @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma)
|
||||||
|
*/
|
||||||
|
export const addStyleClass = function (id: string, styleAttributes = '') {
|
||||||
|
// create a new style class object with this id
|
||||||
|
if (classes[id] === undefined) {
|
||||||
|
classes[id] = { id: id, styles: [], textStyles: [] }; // This is a classDef
|
||||||
|
}
|
||||||
|
const foundClass = classes[id];
|
||||||
|
if (styleAttributes !== undefined && styleAttributes !== null) {
|
||||||
|
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
|
||||||
|
// remove any trailing ;
|
||||||
|
const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim();
|
||||||
|
|
||||||
|
// replace some style keywords
|
||||||
|
if (attrib.match(COLOR_KEYWORD)) {
|
||||||
|
const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL);
|
||||||
|
const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD);
|
||||||
|
foundClass.textStyles.push(newStyle2);
|
||||||
|
}
|
||||||
|
foundClass.styles.push(fixedAttrib);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a (style) class or css class to a state with the given id.
|
||||||
|
* If the state isn't already in the list of known states, add it.
|
||||||
|
* Might be called by parser when a style class or CSS class should be applied to a state
|
||||||
|
*
|
||||||
|
* @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to
|
||||||
|
* @param {string} cssClassName CSS class name
|
||||||
|
*/
|
||||||
|
export const setCssClass = function (itemIds: string, cssClassName: string) {
|
||||||
|
console.log('abc88 setCssClass enter', itemIds, cssClassName);
|
||||||
|
itemIds.split(',').forEach(function (id: string) {
|
||||||
|
let foundBlock = blockDatabase[id];
|
||||||
|
if (foundBlock === undefined) {
|
||||||
|
const trimmedId = id.trim();
|
||||||
|
blockDatabase[trimmedId] = { id: trimmedId, type: 'na', children: [] } as Block;
|
||||||
|
foundBlock = blockDatabase[trimmedId];
|
||||||
|
}
|
||||||
|
if (!foundBlock.classes) {
|
||||||
|
foundBlock.classes = [];
|
||||||
|
}
|
||||||
|
foundBlock.classes.push(cssClassName);
|
||||||
|
console.log('abc88 setCssClass', foundBlock);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Add a style to a state with the given id.
|
||||||
|
// * @example style stateId fill:#f9f,stroke:#333,stroke-width:4px
|
||||||
|
// * where 'style' is the keyword
|
||||||
|
// * stateId is the id of a state
|
||||||
|
// * the rest of the string is the styleText (all of the attributes to be applied to the state)
|
||||||
|
// *
|
||||||
|
// * @param itemId The id of item to apply the style to
|
||||||
|
// * @param styleText - the text of the attributes for the style
|
||||||
|
// */
|
||||||
|
// export const setStyle = function (itemId, styleText) {
|
||||||
|
// const item = getState(itemId);
|
||||||
|
// if (item !== undefined) {
|
||||||
|
// item.textStyles.push(styleText);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Add a text style to a state with the given id
|
||||||
|
// *
|
||||||
|
// * @param itemId The id of item to apply the css class to
|
||||||
|
// * @param cssClassName CSS class name
|
||||||
|
// */
|
||||||
|
// export const setTextStyle = function (itemId, cssClassName) {
|
||||||
|
// const item = getState(itemId);
|
||||||
|
// if (item !== undefined) {
|
||||||
|
// item.textStyles.push(cssClassName);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const populateBlockDatabase = (_blockList: Block[], parent: Block): void => {
|
const populateBlockDatabase = (_blockList: Block[], parent: Block): void => {
|
||||||
const blockList = _blockList.flat();
|
const blockList = _blockList.flat();
|
||||||
const children = [];
|
const children = [];
|
||||||
for (const block of blockList) {
|
for (const block of blockList) {
|
||||||
|
if (block.type === 'classDef') {
|
||||||
|
console.log('abc88 classDef', block);
|
||||||
|
addStyleClass(block.id, block.css);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (block.type === 'applyClass') {
|
||||||
|
console.log('abc88 applyClass', block);
|
||||||
|
// addStyleClass(block.id, block.css);
|
||||||
|
setCssClass(block.id, block.styleClass);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (block.type === 'column-setting') {
|
if (block.type === 'column-setting') {
|
||||||
parent.columns = block.columns || -1;
|
parent.columns = block.columns || -1;
|
||||||
} else if (block.type === 'edge') {
|
} else if (block.type === 'edge') {
|
||||||
@@ -87,6 +189,7 @@ const clear = (): void => {
|
|||||||
rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
|
rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
|
||||||
blockDatabase = { root: rootBlock };
|
blockDatabase = { root: rootBlock };
|
||||||
blocks = [] as Block[];
|
blocks = [] as Block[];
|
||||||
|
classes = {} as Record<string, ClassDef>;
|
||||||
|
|
||||||
edgeList = [];
|
edgeList = [];
|
||||||
edgeCount = {};
|
edgeCount = {};
|
||||||
@@ -166,7 +269,7 @@ const setHierarchy = (block: Block[]): void => {
|
|||||||
log.debug('The document from parsing', JSON.stringify(block, null, 2));
|
log.debug('The document from parsing', JSON.stringify(block, null, 2));
|
||||||
rootBlock.children = block;
|
rootBlock.children = block;
|
||||||
populateBlockDatabase(block, rootBlock);
|
populateBlockDatabase(block, rootBlock);
|
||||||
log.debug('The document after popuplation', JSON.stringify(rootBlock, null, 2));
|
log.debug('abc88 The document after popuplation', JSON.stringify(rootBlock, null, 2));
|
||||||
blocks = rootBlock.children;
|
blocks = rootBlock.children;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,6 +334,15 @@ const getLinks: IGetLinks = () => links;
|
|||||||
type IGetLogger = () => Console;
|
type IGetLogger = () => Console;
|
||||||
const getLogger: IGetLogger = () => console;
|
const getLogger: IGetLogger = () => console;
|
||||||
|
|
||||||
|
type IGetClasses = () => Record<string, ClassDef>;
|
||||||
|
/**
|
||||||
|
* Return all of the style classes
|
||||||
|
* @returns {{} | any | classes}
|
||||||
|
*/
|
||||||
|
export const getClasses = function () {
|
||||||
|
console.log('abc88 block db getClasses', classes);
|
||||||
|
return classes;
|
||||||
|
};
|
||||||
export interface BlockDB extends DiagramDB {
|
export interface BlockDB extends DiagramDB {
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
getConfig: () => BlockConfig | undefined;
|
getConfig: () => BlockConfig | undefined;
|
||||||
@@ -243,6 +355,7 @@ export interface BlockDB extends DiagramDB {
|
|||||||
setBlock: ISetBlock;
|
setBlock: ISetBlock;
|
||||||
getLinks: IGetLinks;
|
getLinks: IGetLinks;
|
||||||
getColumns: IGetColumns;
|
getColumns: IGetColumns;
|
||||||
|
getClasses: IGetClasses;
|
||||||
typeStr2Type: ITypeStr2Type;
|
typeStr2Type: ITypeStr2Type;
|
||||||
edgeTypeStr2Type: IEdgeTypeStr2Type;
|
edgeTypeStr2Type: IEdgeTypeStr2Type;
|
||||||
edgeStrToEdgeData: IEdgeStrToEdgeDataType;
|
edgeStrToEdgeData: IEdgeStrToEdgeDataType;
|
||||||
@@ -271,6 +384,7 @@ const db: BlockDB = {
|
|||||||
// getDiagramTitle,
|
// getDiagramTitle,
|
||||||
// setDiagramTitle,
|
// setDiagramTitle,
|
||||||
getColumns,
|
getColumns,
|
||||||
|
getClasses,
|
||||||
clear,
|
clear,
|
||||||
generateId,
|
generateId,
|
||||||
};
|
};
|
||||||
|
@@ -17,6 +17,22 @@ import type { Block } from './blockTypes.js';
|
|||||||
// import { diagram as BlockDiagram } from './blockDiagram.js';
|
// import { diagram as BlockDiagram } from './blockDiagram.js';
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: any, diagObj: any) {
|
||||||
|
log.info('abc88 Extracting classes', diagObj.db.getClasses());
|
||||||
|
try {
|
||||||
|
return diagObj.db.getClasses();
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const draw = async function (
|
export const draw = async function (
|
||||||
text: string,
|
text: string,
|
||||||
id: string,
|
id: string,
|
||||||
@@ -99,4 +115,5 @@ export const draw = async function (
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
draw,
|
draw,
|
||||||
|
getClasses,
|
||||||
};
|
};
|
||||||
|
@@ -28,6 +28,8 @@ export type BlockType =
|
|||||||
| 'cylinder'
|
| 'cylinder'
|
||||||
| 'group'
|
| 'group'
|
||||||
| 'doublecircle'
|
| 'doublecircle'
|
||||||
|
| 'classDef'
|
||||||
|
| 'applyClass'
|
||||||
| 'composite';
|
| 'composite';
|
||||||
|
|
||||||
export interface Block {
|
export interface Block {
|
||||||
@@ -53,9 +55,17 @@ export interface Block {
|
|||||||
columns?: number; // | TBlockColumnsDefaultValue;
|
columns?: number; // | TBlockColumnsDefaultValue;
|
||||||
classes?: string[];
|
classes?: string[];
|
||||||
directions?: string[];
|
directions?: string[];
|
||||||
|
css?: string;
|
||||||
|
styleClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Link {
|
export interface Link {
|
||||||
source: Block;
|
source: Block;
|
||||||
target: Block;
|
target: Block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClassDef {
|
||||||
|
id: string;
|
||||||
|
textStyles: string[];
|
||||||
|
styles: string[];
|
||||||
|
}
|
||||||
|
@@ -19,6 +19,10 @@
|
|||||||
%x BLOCK_ARROW
|
%x BLOCK_ARROW
|
||||||
%x ARROW_DIR
|
%x ARROW_DIR
|
||||||
%x LLABEL
|
%x LLABEL
|
||||||
|
%x CLASS
|
||||||
|
%x CLASS_STYLE
|
||||||
|
%x CLASSDEF
|
||||||
|
%x CLASSDEFID
|
||||||
|
|
||||||
|
|
||||||
// as per section 6.1 of RFC 2234 [2]
|
// as per section 6.1 of RFC 2234 [2]
|
||||||
@@ -53,8 +57,16 @@ space { yytext = '1'; yy.getLogger().info('COLUMNS (LEX)', yyte
|
|||||||
"default" return 'DEFAULT';
|
"default" return 'DEFAULT';
|
||||||
"linkStyle" return 'LINKSTYLE';
|
"linkStyle" return 'LINKSTYLE';
|
||||||
"interpolate" return 'INTERPOLATE';
|
"interpolate" return 'INTERPOLATE';
|
||||||
"classDef" return 'CLASSDEF';
|
|
||||||
"class" return 'CLASS';
|
"classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; }
|
||||||
|
<CLASSDEF>DEFAULT\s+ { this.popState(); this.pushState('CLASSDEFID'); return 'DEFAULT_CLASSDEF_ID' }
|
||||||
|
<CLASSDEF>\w+\s+ { this.popState(); this.pushState('CLASSDEFID'); return 'CLASSDEF_ID' }
|
||||||
|
<CLASSDEFID>[^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' }
|
||||||
|
|
||||||
|
"class"\s+ { this.pushState('CLASS'); return 'class'; }
|
||||||
|
<CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
|
||||||
|
<CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' }
|
||||||
|
|
||||||
accTitle\s*":"\s* { this.pushState("acc_title");return 'acc_title'; }
|
accTitle\s*":"\s* { this.pushState("acc_title");return 'acc_title'; }
|
||||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||||
accDescr\s*":"\s* { this.pushState("acc_descr");return 'acc_descr'; }
|
accDescr\s*":"\s* { this.pushState("acc_descr");return 'acc_descr'; }
|
||||||
@@ -194,6 +206,8 @@ statement
|
|||||||
| SPACE_BLOCK
|
| SPACE_BLOCK
|
||||||
{ const num=parseInt($1); const spaceId = yy.generateId(); $$ = { id: spaceId, type:'space', label:'', width: num, children: [] }}
|
{ const num=parseInt($1); const spaceId = yy.generateId(); $$ = { id: spaceId, type:'space', label:'', width: num, children: [] }}
|
||||||
| blockStatement
|
| blockStatement
|
||||||
|
| classDefStatement
|
||||||
|
| cssClassStatement
|
||||||
;
|
;
|
||||||
|
|
||||||
nodeStatement
|
nodeStatement
|
||||||
@@ -240,4 +254,21 @@ nodeShapeNLabel
|
|||||||
{ yy.getLogger().info("Rule: BLOCK_ARROW nodeShapeNLabel: ", $1, $2, " #3:",$3, $4); $$ = { typeStr: $1 + $4, label: $2, directions: $3}; }
|
{ yy.getLogger().info("Rule: BLOCK_ARROW nodeShapeNLabel: ", $1, $2, " #3:",$3, $4); $$ = { typeStr: $1 + $4, label: $2, directions: $3}; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
classDefStatement
|
||||||
|
: classDef CLASSDEF_ID CLASSDEF_STYLEOPTS {
|
||||||
|
$$ = { type: 'classDef', id: $2.trim(), css: $3.trim() };
|
||||||
|
}
|
||||||
|
| classDef DEFAULT CLASSDEF_STYLEOPTS {
|
||||||
|
$$ = { type: 'classDef', id: $2.trim(), css: $3.trim() };
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
cssClassStatement
|
||||||
|
: class CLASSENTITY_IDS STYLECLASS {
|
||||||
|
//console.log('apply class: id(s): ',$2, ' style class: ', $3);
|
||||||
|
$$={ type: 'applyClass', id: $2.trim(), styleClass: $3.trim() };
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@@ -16,8 +16,10 @@ function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
|
|||||||
|
|
||||||
let classStr = 'default';
|
let classStr = 'default';
|
||||||
if ((vertex?.classes?.length || 0) > 0) {
|
if ((vertex?.classes?.length || 0) > 0) {
|
||||||
|
console.log('abc88 vertex.classes', block.id, vertex?.classes);
|
||||||
classStr = (vertex?.classes || []).join(' ');
|
classStr = (vertex?.classes || []).join(' ');
|
||||||
}
|
}
|
||||||
|
console.log('abc88 vertex.classes done');
|
||||||
classStr = classStr + ' flowchart-label';
|
classStr = classStr + ' flowchart-label';
|
||||||
|
|
||||||
// We create a SVG label, either by delegating to addHtmlLabel or manually
|
// We create a SVG label, either by delegating to addHtmlLabel or manually
|
||||||
|
@@ -363,7 +363,7 @@ export const getClasses = function (text, diagObj) {
|
|||||||
* @param _version
|
* @param _version
|
||||||
* @param diagObj
|
* @param diagObj
|
||||||
*/
|
*/
|
||||||
// [MermaidChart: 33a97b35-1f95-4ce9-81b5-3038669bc170]
|
|
||||||
export const draw = async function (text, id, _version, diagObj) {
|
export const draw = async function (text, id, _version, diagObj) {
|
||||||
log.info('Drawing flowchart');
|
log.info('Drawing flowchart');
|
||||||
diagObj.db.clear();
|
diagObj.db.clear();
|
||||||
|
@@ -39,6 +39,7 @@ const CLASSDEF_DIAGRAMS = [
|
|||||||
'flowchart-elk',
|
'flowchart-elk',
|
||||||
'stateDiagram',
|
'stateDiagram',
|
||||||
'stateDiagram-v2',
|
'stateDiagram-v2',
|
||||||
|
'block',
|
||||||
];
|
];
|
||||||
const MAX_TEXTLENGTH = 50_000;
|
const MAX_TEXTLENGTH = 50_000;
|
||||||
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
||||||
@@ -203,6 +204,8 @@ export const createCssStyles = (
|
|||||||
cssStyles += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
|
cssStyles += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('abc88 expr check', !isEmpty(classDefs), classDefs);
|
||||||
|
|
||||||
// classDefs defined in the diagram text
|
// classDefs defined in the diagram text
|
||||||
if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) {
|
if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) {
|
||||||
const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config?
|
const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config?
|
||||||
|
Reference in New Issue
Block a user