mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-19 15:30:03 +02:00
Updated syntax and fixed comments from review
This commit is contained in:
@@ -1,39 +1,58 @@
|
|||||||
/** mermaid */
|
/** mermaid */
|
||||||
%lex
|
%lex
|
||||||
|
TOKEN \w+
|
||||||
|
NUM \d+(.\d+)?
|
||||||
|
|
||||||
%options case-insensitive
|
%options case-insensitive
|
||||||
%options easy_keyword_rules
|
%options easy_keword_rules
|
||||||
|
|
||||||
|
%s link_value
|
||||||
|
|
||||||
// when we are inside [] section we are defining attrubutes
|
|
||||||
%x attributes
|
%x attributes
|
||||||
// or if we use "" we are expecting a string containing value
|
%x attr_value
|
||||||
%x string
|
%x string
|
||||||
%x value
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
//--------------------------------------------------------------
|
||||||
// skip all whitespace EXCEPT newlines, but not within a string
|
// skip all whitespace EXCEPT newlines, but not within a string
|
||||||
<INITIAL,attributes,value>[^\S\r\n]+ {}
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
// main
|
<INITIAL,link_value,attributes,attr_value>[^\S\r\n]+ {}
|
||||||
"sankey" { return 'SANKEY'; }
|
|
||||||
\d+(.\d+)? { return 'AMOUNT'; }
|
//--------------
|
||||||
"->" { return 'ARROW'; }
|
// basic tokens
|
||||||
\w+ { return 'NODE'; }
|
//--------------
|
||||||
(?:<<EOF>>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file
|
|
||||||
|
(<<EOF>>|[\n;])+ { return 'EOS'; } // end of statement is semicolon ; new line \n or end of file
|
||||||
|
"sankey" { return 'SANKEY'; }
|
||||||
|
<INITIAL>{TOKEN} { return 'NODE_ID'; }
|
||||||
|
<link_value>{NUM} { return 'AMOUNT'; }
|
||||||
|
"->" {
|
||||||
|
if(this.topState()!=='link_value') this.pushState('link_value');
|
||||||
|
else this.popState();
|
||||||
|
return 'ARROW';
|
||||||
|
}
|
||||||
|
//------------
|
||||||
// attributes
|
// attributes
|
||||||
"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; }
|
//------------
|
||||||
<attributes>"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; }
|
|
||||||
<attributes>\w+ { return 'ATTRIBUTE'; }
|
"[" { this.pushState('attributes'); return 'OPEN_ATTRIBUTES'; }
|
||||||
<attributes>\= { this.pushState('value'); return 'EQUAL'; }
|
<attributes>"]" { this.popState(); return 'CLOSE_ATTRIBUTES'; }
|
||||||
<value>\w+ { this.popState(); return 'VALUE'; }
|
<attributes>{TOKEN} { return 'ATTRIBUTE'; }
|
||||||
|
<attributes>\= { this.pushState('attr_value'); return 'EQUAL'; }
|
||||||
|
<attr_value>{TOKEN} { this.popState(); return 'VALUE'; }
|
||||||
|
|
||||||
|
//------------
|
||||||
// strings
|
// strings
|
||||||
<INITIAL,attributes,value>\" { this.pushState('string'); return 'OPEN_STRING'; }
|
//------------
|
||||||
<string>(?!\\)\" {
|
|
||||||
if(this.topState()==='string') this.popState();
|
<INITIAL,attributes,attr_value>\" { this.pushState('string'); return 'OPEN_STRING'; }
|
||||||
if(this.topState()==='value') this.popState();
|
<string>(?!\\)\" {
|
||||||
return 'CLOSE_STRING';
|
if(this.topState()==='string') this.popState();
|
||||||
}
|
if(this.topState()==='attr_value') this.popState();
|
||||||
<string>([^"\\]|\\\"|\\\\)+ { return 'STRING'; }
|
return 'CLOSE_STRING';
|
||||||
|
}
|
||||||
|
<string>([^"\\]|\\\"|\\\\)+ { return 'STRING'; }
|
||||||
|
|
||||||
/lex
|
/lex
|
||||||
|
|
||||||
@@ -43,20 +62,20 @@
|
|||||||
%% // language grammar
|
%% // language grammar
|
||||||
|
|
||||||
start
|
start
|
||||||
: EOS SANKEY document
|
: EOS SANKEY document
|
||||||
| SANKEY document
|
| SANKEY document
|
||||||
;
|
;
|
||||||
|
|
||||||
document
|
document
|
||||||
: line document
|
: line document
|
||||||
|
|
|
|
||||||
;
|
;
|
||||||
|
|
||||||
line
|
line
|
||||||
: stream optional_attributes EOS
|
: node optional_attributes EOS
|
||||||
| node optional_attributes EOS
|
| stream optional_attributes EOS
|
||||||
| EOS
|
| EOS
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ;
|
optional_attributes: OPEN_ATTRIBUTES attributes CLOSE_ATTRIBUTES | ;
|
||||||
|
|
||||||
@@ -65,20 +84,22 @@ attribute: ATTRIBUTE EQUAL value | ATTRIBUTE;
|
|||||||
|
|
||||||
value: VALUE | OPEN_STRING STRING CLOSE_STRING;
|
value: VALUE | OPEN_STRING STRING CLOSE_STRING;
|
||||||
|
|
||||||
stream: node[source] ARROW amount ARROW tail[target] {
|
stream
|
||||||
$$=$source;
|
: node\[source] ARROW amount ARROW tail\[target] {
|
||||||
yy.addLink($source, $target, $amount);
|
$$=$source;
|
||||||
};
|
yy.addLink($source, $target, $amount);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
tail
|
||||||
|
: stream { $$ = $stream }
|
||||||
|
| node { $$ = $node; }
|
||||||
|
;
|
||||||
|
|
||||||
amount: AMOUNT { $$=parseFloat($AMOUNT); };
|
amount: AMOUNT { $$=parseFloat($AMOUNT); };
|
||||||
|
|
||||||
tail
|
|
||||||
: stream { $$ = $stream }
|
|
||||||
| node { $$ = $node; }
|
|
||||||
;
|
|
||||||
|
|
||||||
node
|
node
|
||||||
: NODE { $$ = yy.addNode($NODE); }
|
: NODE_ID { $$ = yy.findOrCreateNode($NODE_ID); }
|
||||||
| OPEN_STRING STRING[title] CLOSE_STRING { $$ = yy.addNode($title); /* TODO: add title and id separately?*/ }
|
| OPEN_STRING STRING\[node_label] CLOSE_STRING { $$ = yy.findOrCreateNode($node_label); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@@ -50,6 +50,15 @@ describe('Sankey diagram', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('while attributes parsing', () => {
|
describe('while attributes parsing', () => {
|
||||||
|
it('recognized node and attribute ids starting with numbers', () => {
|
||||||
|
const str = `
|
||||||
|
sankey
|
||||||
|
1st -> 200 -> 2nd -> 180 -> 3rd;
|
||||||
|
`;
|
||||||
|
|
||||||
|
parser.parse(str);
|
||||||
|
});
|
||||||
|
|
||||||
it('parses different quotless variations', () => {
|
it('parses different quotless variations', () => {
|
||||||
const str = `
|
const str = `
|
||||||
sankey
|
sankey
|
||||||
@@ -149,6 +158,7 @@ describe('Sankey diagram', function () {
|
|||||||
"Wave" -> 19.013 -> "Electricity grid"
|
"Wave" -> 19.013 -> "Electricity grid"
|
||||||
"Wind" -> 289.366 -> "Electricity grid"
|
"Wind" -> 289.366 -> "Electricity grid"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
parser.parse(str);
|
parser.parse(str);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -12,16 +12,13 @@ import {
|
|||||||
clear as commonClear,
|
clear as commonClear,
|
||||||
} from '../../commonDb.js';
|
} from '../../commonDb.js';
|
||||||
|
|
||||||
// export const parseDirective = function (statement, context, type) {
|
// Variables where graph data is stored
|
||||||
// mermaidAPI.parseDirective(this, statement, context, type);
|
// Sankey diagram represented by nodes and links between those nodes
|
||||||
// };
|
// We have to track nodes uniqueness (by ID), thats why we need hash also
|
||||||
|
//
|
||||||
// export const cleanupComments = (text: string): string => {
|
let links: Array<SankeyLink> = [];
|
||||||
// return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
|
let nodes: Array<SankeyNode> = [];
|
||||||
// };
|
let nodesHash: Record<string, SankeyNode> = {};
|
||||||
let links: Array<Link> = [];
|
|
||||||
let nodes: Array<Node> = [];
|
|
||||||
let nodesHash: Record<string, Node> = {};
|
|
||||||
|
|
||||||
const clear = function () {
|
const clear = function () {
|
||||||
links = [];
|
links = [];
|
||||||
@@ -30,71 +27,35 @@ const clear = function () {
|
|||||||
commonClear();
|
commonClear();
|
||||||
};
|
};
|
||||||
|
|
||||||
type Nullable<T> = T | null;
|
class SankeyLink {
|
||||||
|
constructor(public source: SankeyNode, public target: SankeyNode, public value: number = 0) {}
|
||||||
// interface ILink {
|
|
||||||
// source?: Node;
|
|
||||||
// target?: Node;
|
|
||||||
// value?: number;
|
|
||||||
// }
|
|
||||||
|
|
||||||
class Link {
|
|
||||||
source: Nullable<Node>;
|
|
||||||
target: Nullable<Node>;
|
|
||||||
value: Nullable<number>;
|
|
||||||
constructor() {
|
|
||||||
this.source = null;
|
|
||||||
this.target = null;
|
|
||||||
this.value = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a link between two elements on the diagram
|
|
||||||
*
|
|
||||||
* @param source - Node where the link starts
|
* @param source - Node where the link starts
|
||||||
* @param target - Node where the link ends
|
* @param target - Node where the link ends
|
||||||
* @param value - number, float or integer, describes the amount to be passed
|
* @param value - number, float or integer, describes the amount to be passed
|
||||||
*/
|
*/
|
||||||
// const addLink = ({ source, target, amount }: ILink = {}): Link => {
|
const addLink = function (source: SankeyNode, target: SankeyNode, value: number): SankeyLink {
|
||||||
const addLink = function (source?: Node, target?: Node, value?: number): Link {
|
const link: SankeyLink = new SankeyLink(source, target, value);
|
||||||
const link: Link = new Link();
|
|
||||||
|
|
||||||
// TODO: make attribute setters
|
|
||||||
if (source !== undefined) {
|
|
||||||
link.source = source;
|
|
||||||
}
|
|
||||||
if (target !== undefined) {
|
|
||||||
link.target = target;
|
|
||||||
}
|
|
||||||
if (value !== undefined) {
|
|
||||||
link.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
links.push(link);
|
links.push(link);
|
||||||
|
|
||||||
return link;
|
return link;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Node {
|
class SankeyNode {
|
||||||
ID: string;
|
constructor(public ID: string, public label: string = ID) {}
|
||||||
title: string;
|
|
||||||
constructor(ID: string, title: string = ID) {
|
|
||||||
this.ID = ID;
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds or creates a new Node by ID
|
* @param ID - The id of the node
|
||||||
*
|
|
||||||
* @param id - The id Node
|
|
||||||
*/
|
*/
|
||||||
const addNode = function (ID: string): Node {
|
const findOrCreateNode = function (ID: string): SankeyNode {
|
||||||
ID = common.sanitizeText(ID, configApi.getConfig());
|
ID = common.sanitizeText(ID, configApi.getConfig());
|
||||||
let node: Node;
|
let node: SankeyNode;
|
||||||
if (nodesHash[ID] === undefined) {
|
if (nodesHash[ID] === undefined) {
|
||||||
node = new Node(ID);
|
node = new SankeyNode(ID);
|
||||||
nodesHash[ID] = node;
|
nodesHash[ID] = node;
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,16 +64,27 @@ const addNode = function (ID: string): Node {
|
|||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: this will be better using getters in typescript
|
||||||
const getNodes = () => nodes;
|
const getNodes = () => nodes;
|
||||||
const getLinks = () => links;
|
const getLinks = () => links;
|
||||||
|
|
||||||
|
const getGraph = () => ({
|
||||||
|
nodes: nodes.map((node) => ({ id: node.ID, label: node.label })),
|
||||||
|
links: links.map((link) => ({
|
||||||
|
source: link.source.ID,
|
||||||
|
target: link.target.ID,
|
||||||
|
value: link.value,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
nodesHash,
|
nodesHash,
|
||||||
getConfig: () => configApi.getConfig().sankey,
|
getConfig: () => configApi.getConfig().sankey,
|
||||||
getNodes,
|
getNodes,
|
||||||
getLinks,
|
getLinks,
|
||||||
|
getGraph,
|
||||||
addLink,
|
addLink,
|
||||||
addNode,
|
findOrCreateNode,
|
||||||
// TODO: If this is a must this probably should be an interface
|
// TODO: If this is a must this probably should be an interface
|
||||||
getAccTitle,
|
getAccTitle,
|
||||||
setAccTitle,
|
setAccTitle,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
// @ts-ignore: TODO Fix ts errors
|
// @ts-ignore: jison doesn't export types
|
||||||
import parser from './parser/sankey.jison';
|
import parser from './parser/sankey.jison';
|
||||||
import db from './sankeyDB.js';
|
import db from './sankeyDB.js';
|
||||||
import styles from './styles.js';
|
import styles from './styles.js';
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import { Diagram } from '../../Diagram.js';
|
import { Diagram } from '../../Diagram.js';
|
||||||
// import { log } from '../../logger.js';
|
// import { log } from '../../logger.js';
|
||||||
import * as configApi from '../../config.js';
|
import * as configApi from '../../config.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
select as d3select,
|
select as d3select,
|
||||||
scaleOrdinal as d3scaleOrdinal,
|
scaleOrdinal as d3scaleOrdinal,
|
||||||
@@ -19,9 +20,7 @@ import {
|
|||||||
sankeyJustify as d3SankeyJustify,
|
sankeyJustify as d3SankeyJustify,
|
||||||
} from 'd3-sankey';
|
} from 'd3-sankey';
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
import sankeyDB from './sankeyDB.js';
|
// import { debug } from 'console';
|
||||||
import { db } from '../info/infoDb.js';
|
|
||||||
import { debug } from 'console';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
||||||
@@ -51,7 +50,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||||||
const { securityLevel, sequence: conf } = configApi.getConfig();
|
const { securityLevel, sequence: conf } = configApi.getConfig();
|
||||||
let sandboxElement;
|
let sandboxElement;
|
||||||
if (securityLevel === 'sandbox') {
|
if (securityLevel === 'sandbox') {
|
||||||
sandboxElement = select('#i' + id);
|
sandboxElement = d3select('#i' + id);
|
||||||
}
|
}
|
||||||
const root =
|
const root =
|
||||||
securityLevel === 'sandbox'
|
securityLevel === 'sandbox'
|
||||||
@@ -86,35 +85,8 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||||||
// ]
|
// ]
|
||||||
// };
|
// };
|
||||||
//
|
//
|
||||||
const graph = {
|
|
||||||
nodes: [],
|
|
||||||
links: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
diagObj.db.getNodes().forEach((node) => {
|
const graph = diagObj.db.getGraph();
|
||||||
graph.nodes.push({ id: node.ID, title: node.title });
|
|
||||||
});
|
|
||||||
|
|
||||||
diagObj.db.getLinks().forEach((link) => {
|
|
||||||
graph.links.push({ source: link.source.ID, target: link.target.ID, value: link.value });
|
|
||||||
});
|
|
||||||
|
|
||||||
// debugger;
|
|
||||||
// const graph = {
|
|
||||||
// nodes: [
|
|
||||||
// { id: 'Alice' },
|
|
||||||
// { id: 'Bob' },
|
|
||||||
// { id: 'Carol' },
|
|
||||||
// { id: 'Andrew' },
|
|
||||||
// { id: 'Peter' }
|
|
||||||
// ],
|
|
||||||
// links: [
|
|
||||||
// { source: 'Alice', target: 'Andrew', value: 11 },
|
|
||||||
// { source: 'Alice', target: 'Bob', value: 23 },
|
|
||||||
// { source: 'Bob', target: 'Carol', value: 43 },
|
|
||||||
// { source: 'Peter', target: 'Carol', value: 15 },
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Construct and configure a Sankey generator
|
// Construct and configure a Sankey generator
|
||||||
// That will be a function that calculates nodes and links dimensions
|
// That will be a function that calculates nodes and links dimensions
|
||||||
@@ -145,11 +117,10 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||||||
// Get color scheme for the graph
|
// Get color scheme for the graph
|
||||||
const color = d3scaleOrdinal(d3schemeTableau10);
|
const color = d3scaleOrdinal(d3schemeTableau10);
|
||||||
|
|
||||||
// Creates the groups for nodes
|
// Create groups for nodes
|
||||||
svg
|
svg
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('class', 'nodes')
|
.attr('class', 'nodes')
|
||||||
.attr('stroke', '#000')
|
|
||||||
.selectAll('.node')
|
.selectAll('.node')
|
||||||
.data(graph.nodes)
|
.data(graph.nodes)
|
||||||
.join('g')
|
.join('g')
|
||||||
@@ -166,7 +137,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||||||
.attr('width', (d) => d.x1 - d.x0)
|
.attr('width', (d) => d.x1 - d.x0)
|
||||||
.attr('fill', (d) => color(d.id));
|
.attr('fill', (d) => color(d.id));
|
||||||
|
|
||||||
// Create text for nodes
|
// Create labels for nodes
|
||||||
svg
|
svg
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('class', 'node-labels')
|
.attr('class', 'node-labels')
|
||||||
@@ -179,7 +150,7 @@ export const draw = function (text: string, id: string, _version: string, diagOb
|
|||||||
.attr('y', (d) => (d.y1 + d.y0) / 2)
|
.attr('y', (d) => (d.y1 + d.y0) / 2)
|
||||||
.attr('dy', '0.35em')
|
.attr('dy', '0.35em')
|
||||||
.attr('text-anchor', (d) => (d.x0 < width / 2 ? 'start' : 'end'))
|
.attr('text-anchor', (d) => (d.x0 < width / 2 ? 'start' : 'end'))
|
||||||
.text((d) => d.title);
|
.text((d) => d.label);
|
||||||
|
|
||||||
// Creates the paths that represent the links.
|
// Creates the paths that represent the links.
|
||||||
const link_g = svg
|
const link_g = svg
|
||||||
|
Reference in New Issue
Block a user