mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-21 06:53:17 +08:00
#3358 First commit with basic grammar and 1st test
This commit is contained in:
parent
f431bae0ba
commit
86e1bb38ee
@ -33,6 +33,7 @@ export interface MermaidConfig {
|
||||
gitGraph?: GitGraphDiagramConfig;
|
||||
c4?: C4DiagramConfig;
|
||||
sankey?: SankeyDiagramConfig;
|
||||
blockDiagram?: BlockDiagramConfig;
|
||||
dompurifyConfig?: DOMPurify.Config;
|
||||
wrap?: boolean;
|
||||
fontSize?: number;
|
||||
@ -421,6 +422,9 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
|
||||
linkColor?: SankeyLinkColor | string;
|
||||
nodeAlignment?: SankeyNodeAlignment;
|
||||
}
|
||||
export interface BlockDiagramConfig extends BaseDiagramConfig {
|
||||
padding?: number;
|
||||
}
|
||||
|
||||
export interface FontConfig {
|
||||
fontSize?: string | number;
|
||||
|
35
packages/mermaid/src/diagrams/blockDiagram/blockDB.ts
Normal file
35
packages/mermaid/src/diagrams/blockDiagram/blockDB.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as configApi from '../../config.js';
|
||||
import common from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
clear as commonClear,
|
||||
} from '../../commonDb.js';
|
||||
|
||||
type Block = {
|
||||
ID: string;
|
||||
};
|
||||
|
||||
// Array of nodes guarantees their order
|
||||
let blocks: Block[] = [];
|
||||
|
||||
const clear = (): void => {
|
||||
blocks = [];
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export default {
|
||||
getConfig: () => configApi.getConfig().blockDiagram,
|
||||
|
||||
getAccTitle,
|
||||
setAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
getDiagramTitle,
|
||||
setDiagramTitle,
|
||||
clear,
|
||||
};
|
15
packages/mermaid/src/diagrams/blockDiagram/blockDiagram.ts
Normal file
15
packages/mermaid/src/diagrams/blockDiagram/blockDiagram.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: jison doesn't export types
|
||||
import parser from './parser/sankey.jison';
|
||||
import db from './blockDB.js';
|
||||
import renderer from './blockDiagramRenderer.js';
|
||||
import { prepareTextForParsing } from './blockDiagramUtils.js';
|
||||
|
||||
const originalParse = parser.parse.bind(parser);
|
||||
parser.parse = (text: string) => originalParse(prepareTextForParsing(text));
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
db,
|
||||
renderer,
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
const id = 'sankey';
|
||||
|
||||
const detector: DiagramDetector = (txt) => {
|
||||
return /^\s*blockDiagram-beta/.test(txt);
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./blockDiagram.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
@ -0,0 +1,63 @@
|
||||
import { Diagram } from '../../Diagram.js';
|
||||
import * as configApi from '../../config.js';
|
||||
|
||||
import {
|
||||
select as d3select,
|
||||
scaleOrdinal as d3scaleOrdinal,
|
||||
schemeTableau10 as d3schemeTableau10,
|
||||
} from 'd3';
|
||||
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { Uid } from '../../rendering-util/uid.js';
|
||||
import type { SankeyLinkColor, SankeyNodeAlignment } from '../../config.type.js';
|
||||
|
||||
export const draw = function (text: string, id: string, _version: string, diagObj: Diagram): void {
|
||||
// Get the config
|
||||
const { securityLevel, sankey: conf } = configApi.getConfig();
|
||||
const defaultSankeyConfig = configApi!.defaultConfig!.blockDiagram!;
|
||||
|
||||
// TODO:
|
||||
// This code repeats for every diagram
|
||||
// Figure out what is happening there, probably it should be separated
|
||||
// The main thing is svg object that is a d3 wrapper for svg operations
|
||||
//
|
||||
let sandboxElement: any;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = d3select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? d3select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: d3select('body');
|
||||
// @ts-ignore TODO root.select is not callable
|
||||
const svg = securityLevel === 'sandbox' ? root.select(`[id="${id}"]`) : d3select(`[id="${id}"]`);
|
||||
|
||||
// Establish svg dimensions and get width and height
|
||||
//
|
||||
|
||||
// FIX: using max width prevents height from being set, is it intended?
|
||||
// to add height directly one can use `svg.attr('height', height)`
|
||||
//
|
||||
// @ts-ignore TODO: svg type vs selection mismatch
|
||||
configureSvgSize(svg, height, width, useMaxWidth);
|
||||
|
||||
// Prepare data for construction based on diagObj.db
|
||||
// This must be a mutable object with `nodes` and `links` properties:
|
||||
//
|
||||
// {
|
||||
// "nodes": [ { "id": "Alice" }, { "id": "Bob" }, { "id": "Carol" } ],
|
||||
// "links": [ { "source": "Alice", "target": "Bob", "value": 23 }, { "source": "Bob", "target": "Carol", "value": 43 } ]
|
||||
// }
|
||||
//
|
||||
// @ts-ignore TODO: db type
|
||||
const graph = diagObj.db.getGraph();
|
||||
|
||||
const nodeWidth = 10;
|
||||
|
||||
// Get color scheme for the graph
|
||||
const colorScheme = d3scaleOrdinal(d3schemeTableau10);
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
export const prepareTextForParsing = (text: string): string => {
|
||||
const textToParse = text
|
||||
.replaceAll(/^[^\S\n\r]+|[^\S\n\r]+$/g, '') // remove all trailing spaces for each row
|
||||
.replaceAll(/([\n\r])+/g, '\n') // remove empty lines duplicated
|
||||
.trim();
|
||||
|
||||
return textToParse;
|
||||
};
|
@ -0,0 +1,195 @@
|
||||
/** mermaid */
|
||||
|
||||
//---------------------------------------------------------
|
||||
// We support csv format as defined here:
|
||||
// https://www.ietf.org/rfc/rfc4180.txt
|
||||
// There are some minor changes for compliance with jison
|
||||
// We also parse only 3 columns: source,target,value
|
||||
// And allow blank lines for visual purposes
|
||||
//---------------------------------------------------------
|
||||
|
||||
%lex
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%x string
|
||||
%x md_string
|
||||
%x NODE
|
||||
%options easy_keword_rules
|
||||
|
||||
|
||||
// as per section 6.1 of RFC 2234 [2]
|
||||
COMMA \u002C
|
||||
CR \u000D
|
||||
LF \u000A
|
||||
CRLF \u000D\u000A
|
||||
|
||||
|
||||
%%
|
||||
|
||||
"blockDiagram-beta" { return 'BLOCK_DIAGRAM_KEY'; }
|
||||
// \s*\%\%.* { yy.getLogger().info('Found comment',yytext); }
|
||||
[\s]+ { yy.getLogger().info('.', yytext); /* skip all whitespace */ }
|
||||
[\n]+ {yy.getLogger().info('_', yytext); /* skip all whitespace */ }
|
||||
// [\n] return 'NL';
|
||||
<INITIAL>({CRLF}|{LF}) { return 'NL' }
|
||||
["][`] { this.begin("md_string");}
|
||||
<md_string>[^`"]+ { return "MD_STR";}
|
||||
<md_string>[`]["] { this.popState();}
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
"style" return 'STYLE';
|
||||
"default" return 'DEFAULT';
|
||||
"linkStyle" return 'LINKSTYLE';
|
||||
"interpolate" return 'INTERPOLATE';
|
||||
"classDef" return 'CLASSDEF';
|
||||
"class" return 'CLASS';
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
|
||||
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
|
||||
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
<acc_descr_multiline>[\}] { this.popState(); }
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
"subgraph" return 'subgraph';
|
||||
"end"\b\s* return 'end';
|
||||
.*direction\s+TB[^\n]* return 'direction_tb';
|
||||
.*direction\s+BT[^\n]* return 'direction_bt';
|
||||
.*direction\s+RL[^\n]* return 'direction_rl';
|
||||
.*direction\s+LR[^\n]* return 'direction_lr';
|
||||
|
||||
// Start of nodes with shapes and description
|
||||
"-)" { yy.getLogger().info('Lex: -)'); this.begin('NODE');return 'NODE_D START'; }
|
||||
"(-" { yy.getLogger().info('Lex: (-'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"))" { yy.getLogger().info('Lex: ))'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
")" { yy.getLogger().info('Lex: )'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"((" { yy.getLogger().info('Lex: )'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"{{" { yy.getLogger().info('Lex: )'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"(" { yy.getLogger().info('Lex: )'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[" { yy.getLogger().info('Lex: ['); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"([" { yy.getLogger().info('Lex: )'); this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[[" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[|" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[(" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"(((" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
")))" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[/" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
"[\\" { this.begin('NODE');return 'NODE_DSTART'; }
|
||||
|
||||
|
||||
[^\(\[\n\-\)\{\}]+ { yy.getLogger().info('Lex: NODE_ID', yytext);return 'NODE_ID'; }
|
||||
<<EOF>> { yy.getLogger().info('Lex: EOF', yytext);return 'EOF'; }
|
||||
|
||||
// Handling of strings in node
|
||||
<NODE>["][`] { this.begin("md_string");}
|
||||
<md_string>[^`"]+ { return "NODE_DESCR";}
|
||||
<md_string>[`]["] { this.popState();}
|
||||
<NODE>["] { yy.getLogger().info('Lex: Starting string');this.begin("string");}
|
||||
<string>[^"]+ { yy.getLogger().info('Lex: NODE_DESCR:', yytext); return "NODE_DESCR";}
|
||||
<string>["] {this.popState();}
|
||||
|
||||
// Node end of shape
|
||||
<NODE>[\)]\) { this.popState();yy.getLogger().info('Lex: ))'); return "NODE_DEND"; }
|
||||
<NODE>[\)] { this.popState();yy.getLogger().info('Lex: )'); return "NODE_DEND"; }
|
||||
<NODE>[\]] { this.popState();yy.getLogger().info('Lex: ]'); return "NODE_DEND"; }
|
||||
<NODE>"}}" { this.popState();yy.getLogger().info('Lex: (('); return "NODE_DEND"; }
|
||||
<NODE>"(-" { this.popState();yy.getLogger().info('Lex: (-'); return "NODE_DEND"; }
|
||||
<NODE>"-)" { this.popState();yy.getLogger().info('Lex: -)'); return "NODE_DEND"; }
|
||||
<NODE>"((" { this.popState();yy.getLogger().info('Lex: (('); return "NODE_DEND"; }
|
||||
<NODE>"(" { this.popState();yy.getLogger().info('Lex: ('); return "NODE_DEND"; }
|
||||
<NODE>"])" { this.popState();yy.getLogger().info('Lex: ])'); return "NODE_DEND"; }
|
||||
<NODE>"]]" { this.popState();yy.getLogger().info('Lex: ]]'); return "NODE_DEND"; }
|
||||
<NODE>"/]" { this.popState();yy.getLogger().info('Lex: /]'); return "NODE_DEND"; }
|
||||
<NODE>")]" { this.popState();yy.getLogger().info('Lex: )]'); return "NODE_DEND"; }
|
||||
|
||||
// Edges
|
||||
\s*[xo<]?\-\-+[-xo>]\s* { yy.getLogger().info('Lex: LINK', '#'+yytext+'#'); return 'LINK'; }
|
||||
\s*[xo<]?\=\=+[=xo>]\s* { yy.getLogger().info('Lex: LINK', yytext); return 'LINK'; }
|
||||
\s*[xo<]?\-?\.+\-[xo>]?\s* { yy.getLogger().info('Lex: LINK', yytext); return 'LINK'; }
|
||||
\s*\~\~[\~]+\s* { yy.getLogger().info('Lex: LINK', yytext); return 'LINK'; }
|
||||
\s*[xo<]?\-\-\s* { yy.getLogger().info('Lex: START_LINK', yytext); return 'START_LINK'; }
|
||||
\s*[xo<]?\=\=\s* { yy.getLogger().info('Lex: START_LINK', yytext); return 'START_LINK'; }
|
||||
\s*[xo<]?\-\.\s* { yy.getLogger().info('Lex: START_LINK', yytext); return 'START_LINK'; }
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
|
||||
%% // language grammar
|
||||
|
||||
spaceLines
|
||||
: SPACELINE
|
||||
| spaceLines SPACELINE
|
||||
| spaceLines NL
|
||||
;
|
||||
|
||||
seperator
|
||||
: NL
|
||||
{yy.getLogger().info('Rule: seperator (NL) ');}
|
||||
| SPACE
|
||||
{yy.getLogger().info('Rule: seperator (Space) ');}
|
||||
| EOF
|
||||
{yy.getLogger().info('Rule: seperator (EOF) ');}
|
||||
;
|
||||
|
||||
start: BLOCK_DIAGRAM_KEY document;
|
||||
|
||||
blockDiagram
|
||||
: blockDiagram document { return yy; }
|
||||
| blockDiagram NL document { return yy; }
|
||||
;
|
||||
|
||||
stop
|
||||
: NL {yy.getLogger().info('Stop NL ');}
|
||||
| EOF {yy.getLogger().info('Stop EOF ');}
|
||||
// | SPACELINE
|
||||
| stop NL {yy.getLogger().info('Stop NL2 ');}
|
||||
| stop EOF {yy.getLogger().info('Stop EOF2 ');}
|
||||
;
|
||||
|
||||
document
|
||||
: document statement
|
||||
| statement
|
||||
;
|
||||
|
||||
link
|
||||
: LINK
|
||||
{ yy.getLogger().info("Rule: link: ", $1); }
|
||||
| START_LINK
|
||||
{ yy.getLogger().info("Rule: link: ", $1); }
|
||||
;
|
||||
|
||||
statement
|
||||
: nodeStatement
|
||||
// SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
|
||||
// | SPACELIST ICON { yy.getLogger().info('Icon: ',$2);yy.decorateNode({icon: $2}); }
|
||||
// | SPACELIST CLASS { yy.decorateNode({class: $2}); }
|
||||
// | SPACELINE { yy.getLogger().info('SPACELIST');}
|
||||
// |
|
||||
// node { yy.getLogger().info('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
|
||||
// | ICON { yy.decorateNode({icon: $1}); }
|
||||
// | CLASS { yy.decorateNode({class: $1}); }
|
||||
// // | SPACELIST
|
||||
| EOF
|
||||
;
|
||||
|
||||
nodeStatement: nodeStatement link node { yy.getLogger().info('Rule: nodeStatement (nodeStatement link node) ');}
|
||||
|node { yy.getLogger().info('Rule: nodeStatement (node) ');}
|
||||
;
|
||||
|
||||
node
|
||||
: NODE_ID
|
||||
{ yy.getLogger().info("Rule: node (NODE_ID seperator): ", $1); }
|
||||
|NODE_ID nodeShapeNLabel
|
||||
{ yy.getLogger().info("Rule: node (NODE_ID nodeShapeNLabel seperator): ", $1, $2); }
|
||||
// |nodeShapeNLabel seperator
|
||||
// { yy.getLogger().info("Rule: node (nodeShapeNLabel seperator): ", $1, $2, $3); }
|
||||
;
|
||||
|
||||
nodeShapeNLabel
|
||||
: NODE_DSTART STR NODE_DEND
|
||||
{ yy.getLogger().info("Rule: nodeShapeNLabel: ", $1, $2, $3); $$ = { type: $1 + $3, descr: $2 }; }
|
||||
;
|
||||
|
||||
%%
|
@ -0,0 +1,85 @@
|
||||
// @ts-ignore: jison doesn't export types
|
||||
import blockDiagram from './blockDiagram.jison';
|
||||
import db from '../blockDB.js';
|
||||
import { cleanupComments } from '../../../diagram-api/comments.js';
|
||||
import { prepareTextForParsing } from '../blockDiagramUtils.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('Sankey diagram', function () {
|
||||
describe('when parsing an block diagram graph it should handle > ', function () {
|
||||
beforeEach(function () {
|
||||
blockDiagram.parser.yy = db;
|
||||
blockDiagram.parser.yy.clear();
|
||||
blockDiagram.parser.yy.getLogger = () => console;
|
||||
});
|
||||
|
||||
it('a diagram with a node', async () => {
|
||||
const str = `blockDiagram-beta
|
||||
id
|
||||
`;
|
||||
|
||||
blockDiagram.parse(str);
|
||||
});
|
||||
it('a diagram with multiple nodes', async () => {
|
||||
const str = `blockDiagram-beta
|
||||
id1
|
||||
id2
|
||||
`;
|
||||
|
||||
blockDiagram.parse(str);
|
||||
});
|
||||
it('a node with a square shape and a label', async () => {
|
||||
const str = `blockDiagram-beta
|
||||
id["A label"]
|
||||
id2`;
|
||||
|
||||
blockDiagram.parse(str);
|
||||
});
|
||||
it('a diagram with multiple nodes with edges', async () => {
|
||||
const str = `blockDiagram-beta
|
||||
id1["first"] --> id2["second"]
|
||||
`;
|
||||
|
||||
blockDiagram.parse(str);
|
||||
});
|
||||
// it('a diagram with column statements', async () => {
|
||||
// const str = `blockDiagram-beta
|
||||
// columns 1
|
||||
// block1["Block 1"]
|
||||
// `;
|
||||
|
||||
// blockDiagram.parse(str);
|
||||
// });
|
||||
// it('a diagram with block hierarchies', async () => {
|
||||
// const str = `blockDiagram-beta
|
||||
// columns 1
|
||||
// block1[Block 1]
|
||||
|
||||
// block
|
||||
// columns 2
|
||||
// block2[Block 2]
|
||||
// block3[Block 3]
|
||||
// end %% End the compound block
|
||||
// `;
|
||||
|
||||
// blockDiagram.parse(str);
|
||||
// });
|
||||
// it('a diagram with differernt column values in different blocks', async () => {
|
||||
// const str = `blockDiagram-beta
|
||||
// columns 1
|
||||
// block1[Block 1]
|
||||
|
||||
// block
|
||||
// columns 2
|
||||
// block2[Block 2]
|
||||
// block3[Block 3]
|
||||
// end %% End the compound block
|
||||
// `;
|
||||
|
||||
// blockDiagram.parse(str);
|
||||
|
||||
// // Todo check that the different blocks have different column values
|
||||
// });
|
||||
});
|
||||
});
|
33
packages/mermaid/src/docs/.vitepress/block.mmd
Normal file
33
packages/mermaid/src/docs/.vitepress/block.mmd
Normal file
@ -0,0 +1,33 @@
|
||||
block
|
||||
columns 3
|
||||
Block1
|
||||
Block2["Block 2"]
|
||||
block
|
||||
columns 2
|
||||
Block2.1
|
||||
Block2.2
|
||||
end
|
||||
Block3
|
||||
|
||||
|
||||
----
|
||||
|
||||
block
|
||||
columns 2
|
||||
Block[Frontend]:vertical
|
||||
|
||||
block "Document management System"
|
||||
columns 3
|
||||
MO[Manager Operation]:vertical
|
||||
block
|
||||
columns 2
|
||||
block "Security and User Manager"
|
||||
end
|
||||
|
||||
|
||||
----
|
||||
block frontend:vertical
|
||||
move right
|
||||
block "Document Management System"
|
||||
move down
|
||||
|
Loading…
x
Reference in New Issue
Block a user