mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-07 08:09:39 +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">
|
||||
block-beta
|
||||
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
|
||||
space
|
||||
block
|
||||
@@ -80,6 +96,7 @@ block-beta
|
||||
F
|
||||
end
|
||||
E -- "apa" --> A
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
block-beta
|
||||
@@ -538,7 +555,7 @@ mindmap
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
mermaid.initialize({
|
||||
// theme: 'forest',
|
||||
theme: 'forest',
|
||||
startOnLoad: true,
|
||||
logLevel: 0,
|
||||
flowchart: {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// import type { BlockDB } from './blockTypes.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 {
|
||||
@@ -20,10 +20,112 @@ let blockDatabase: Record<string, Block> = {};
|
||||
let edgeList: Block[] = [];
|
||||
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 blockList = _blockList.flat();
|
||||
const children = [];
|
||||
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') {
|
||||
parent.columns = block.columns || -1;
|
||||
} else if (block.type === 'edge') {
|
||||
@@ -87,6 +189,7 @@ const clear = (): void => {
|
||||
rootBlock = { id: 'root', type: 'composite', children: [], columns: -1 } as Block;
|
||||
blockDatabase = { root: rootBlock };
|
||||
blocks = [] as Block[];
|
||||
classes = {} as Record<string, ClassDef>;
|
||||
|
||||
edgeList = [];
|
||||
edgeCount = {};
|
||||
@@ -166,7 +269,7 @@ const setHierarchy = (block: Block[]): void => {
|
||||
log.debug('The document from parsing', JSON.stringify(block, null, 2));
|
||||
rootBlock.children = block;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -231,6 +334,15 @@ const getLinks: IGetLinks = () => links;
|
||||
type 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 {
|
||||
clear: () => void;
|
||||
getConfig: () => BlockConfig | undefined;
|
||||
@@ -243,6 +355,7 @@ export interface BlockDB extends DiagramDB {
|
||||
setBlock: ISetBlock;
|
||||
getLinks: IGetLinks;
|
||||
getColumns: IGetColumns;
|
||||
getClasses: IGetClasses;
|
||||
typeStr2Type: ITypeStr2Type;
|
||||
edgeTypeStr2Type: IEdgeTypeStr2Type;
|
||||
edgeStrToEdgeData: IEdgeStrToEdgeDataType;
|
||||
@@ -271,6 +384,7 @@ const db: BlockDB = {
|
||||
// getDiagramTitle,
|
||||
// setDiagramTitle,
|
||||
getColumns,
|
||||
getClasses,
|
||||
clear,
|
||||
generateId,
|
||||
};
|
||||
|
@@ -17,6 +17,22 @@ import type { Block } from './blockTypes.js';
|
||||
// import { diagram as BlockDiagram } from './blockDiagram.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 (
|
||||
text: string,
|
||||
id: string,
|
||||
@@ -99,4 +115,5 @@ export const draw = async function (
|
||||
|
||||
export default {
|
||||
draw,
|
||||
getClasses,
|
||||
};
|
||||
|
@@ -28,6 +28,8 @@ export type BlockType =
|
||||
| 'cylinder'
|
||||
| 'group'
|
||||
| 'doublecircle'
|
||||
| 'classDef'
|
||||
| 'applyClass'
|
||||
| 'composite';
|
||||
|
||||
export interface Block {
|
||||
@@ -53,9 +55,17 @@ export interface Block {
|
||||
columns?: number; // | TBlockColumnsDefaultValue;
|
||||
classes?: string[];
|
||||
directions?: string[];
|
||||
css?: string;
|
||||
styleClass?: string;
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
source: Block;
|
||||
target: Block;
|
||||
}
|
||||
|
||||
export interface ClassDef {
|
||||
id: string;
|
||||
textStyles: string[];
|
||||
styles: string[];
|
||||
}
|
||||
|
@@ -19,6 +19,10 @@
|
||||
%x BLOCK_ARROW
|
||||
%x ARROW_DIR
|
||||
%x LLABEL
|
||||
%x CLASS
|
||||
%x CLASS_STYLE
|
||||
%x CLASSDEF
|
||||
%x CLASSDEFID
|
||||
|
||||
|
||||
// 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';
|
||||
"linkStyle" return 'LINKSTYLE';
|
||||
"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'; }
|
||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||
accDescr\s*":"\s* { this.pushState("acc_descr");return 'acc_descr'; }
|
||||
@@ -194,6 +206,8 @@ statement
|
||||
| SPACE_BLOCK
|
||||
{ const num=parseInt($1); const spaceId = yy.generateId(); $$ = { id: spaceId, type:'space', label:'', width: num, children: [] }}
|
||||
| blockStatement
|
||||
| classDefStatement
|
||||
| cssClassStatement
|
||||
;
|
||||
|
||||
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}; }
|
||||
;
|
||||
|
||||
|
||||
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';
|
||||
if ((vertex?.classes?.length || 0) > 0) {
|
||||
console.log('abc88 vertex.classes', block.id, vertex?.classes);
|
||||
classStr = (vertex?.classes || []).join(' ');
|
||||
}
|
||||
console.log('abc88 vertex.classes done');
|
||||
classStr = classStr + ' flowchart-label';
|
||||
|
||||
// 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 diagObj
|
||||
*/
|
||||
// [MermaidChart: 33a97b35-1f95-4ce9-81b5-3038669bc170]
|
||||
|
||||
export const draw = async function (text, id, _version, diagObj) {
|
||||
log.info('Drawing flowchart');
|
||||
diagObj.db.clear();
|
||||
|
@@ -39,6 +39,7 @@ const CLASSDEF_DIAGRAMS = [
|
||||
'flowchart-elk',
|
||||
'stateDiagram',
|
||||
'stateDiagram-v2',
|
||||
'block',
|
||||
];
|
||||
const MAX_TEXTLENGTH = 50_000;
|
||||
const MAX_TEXTLENGTH_EXCEEDED_MSG =
|
||||
@@ -203,6 +204,8 @@ export const createCssStyles = (
|
||||
cssStyles += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
|
||||
}
|
||||
|
||||
console.log('abc88 expr check', !isEmpty(classDefs), classDefs);
|
||||
|
||||
// classDefs defined in the diagram text
|
||||
if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) {
|
||||
const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config?
|
||||
|
Reference in New Issue
Block a user