Merge pull request #8 from mermaid-js/develop

merge from base
This commit is contained in:
Justin Greywolf
2019-12-23 12:21:44 -08:00
committed by GitHub
37 changed files with 838 additions and 227 deletions

7
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,7 @@
## Summary
Brief description about the content of your PR.
## Design Decisions
Describe the way your implementation works or what design decisions you made if applicable.
Resolves #<your issue id here>

View File

@@ -164,7 +164,31 @@ describe('Class diagram', () => {
cy.get('svg');
});
it('5: should render a simple class diagram with Generic class', () => {
it('5: should render a simple class diagram with abstract method', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
Class01 : someMethod()*
`,
{}
);
cy.get('svg');
});
it('6: should render a simple class diagram with static method', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
Class01 : someMethod()$
`,
{}
);
cy.get('svg');
});
it('7: should render a simple class diagram with Generic class', () => {
imgSnapshotTest(
`
classDiagram
@@ -184,7 +208,7 @@ describe('Class diagram', () => {
cy.get('svg');
});
it('6: should render a simple class diagram with Generic class and relations', () => {
it('8: should render a simple class diagram with Generic class and relations', () => {
imgSnapshotTest(
`
classDiagram

View File

@@ -396,4 +396,61 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } }
);
});
it('15: Render Stadium shape', () => {
imgSnapshotTest(
` graph TD
A([stadium shape test])
A -->|Get money| B([Go shopping])
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
C -->|One| D([Laptop])
C -->|Two| E([iPhone])
C -->|Three| F([Car<br/>wroom wroom])
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;`,
{ flowchart: { htmlLabels: false } }
);
});
it('16: Render Stadium shape', () => {
imgSnapshotTest(
`graph LR
A1[Multi<br>Line] -->|Multi<br>Line| B1(Multi<br>Line)
C1[Multi<br/>Line] -->|Multi<br/>Line| D1(Multi<br/>Line)
E1[Multi<br />Line] -->|Multi<br />Line| F1(Multi<br />Line)
A2[Multi<br>Line] -->|Multi<br>Line| B2(Multi<br>Line)
C2[Multi<br/>Line] -->|Multi<br/>Line| D2(Multi<br/>Line)
E2[Multi<br />Line] -->|Multi<br />Line| F2(Multi<br />Line)
linkStyle 0 stroke:DarkGray,stroke-width:2px
linkStyle 1 stroke:DarkGray,stroke-width:2px
linkStyle 2 stroke:DarkGray,stroke-width:2px
`,
{ flowchart: { htmlLabels: false } }
);
});
it('17: Chaining of nodes', () => {
imgSnapshotTest(
`graph LR
a --> b --> c
`,
{ flowchart: { htmlLabels: false } }
);
});
it('18: Multiple nodes and chaining in one statement', () => {
imgSnapshotTest(
`graph LR
a --> b c--> d
`,
{ flowchart: { htmlLabels: false } }
);
});
it('19: Multiple nodes and chaining in one statement', () => {
imgSnapshotTest(
`graph TD
A[ h ] -- hello --> B[" test "]:::exClass C --> D;
classDef exClass background:#bbb,border:1px solid red;
`,
{ flowchart: { htmlLabels: false } }
);
});
});

View File

@@ -38,4 +38,20 @@ describe('Sequencediagram', () => {
{}
);
});
it('Multiple dependencies syntax', () => {
imgSnapshotTest(
`
gantt
dateFormat YYYY-MM-DD
axisFormat %d/%m
title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
apple :a, 2017-07-20, 1w
banana :crit, b, 2017-07-23, 1d
cherry :active, c, after b a, 1d
`,
{}
);
});
});

View File

@@ -2,8 +2,8 @@
import { imgSnapshotTest } from '../../helpers/util';
context('Aliasing', () => {
it('should render a simple sequence diagrams', () => {
context('Sequence diagram', () => {
it('should render a simple sequence diagram', () => {
imgSnapshotTest(
`
sequenceDiagram
@@ -32,6 +32,23 @@ context('Aliasing', () => {
{}
);
});
it('should handle different line breaks', () => {
imgSnapshotTest(
`
sequenceDiagram
participant 1 as multiline<br>using #lt;br#gt;
participant 2 as multiline<br/>using #lt;br/#gt;
participant 3 as multiline<br />using #lt;br /#gt;
1->>2: multiline<br>using #lt;br#gt;
note right of 2: multiline<br>using #lt;br#gt;
2->>3: multiline<br/>using #lt;br/#gt;
note right of 3: multiline<br/>using #lt;br/#gt;
3->>1: multiline<br />using #lt;br /#gt;
note right of 1: multiline<br />using #lt;br /#gt;
`,
{}
);
});
context('background rects', () => {
it('should render a single and nested rects', () => {
imgSnapshotTest(

View File

@@ -16,11 +16,16 @@
</head>
<body>
<h1>info below</h1>
<div style="display: flex;">
<div class="mermaid">graph TD
A ==> B
A --> C
A -.-> D
<div style="display: flex;width: 100%; height: 100%">
<div class="mermaid" style="width: 100%; height: 100%">
graph TB
A --> B
A ==> C
A .-> D
A === E
A -.- F
D -- Hello --> a
D-- text including R TD space --xb
</div>
</div>
<script src="./mermaid.js"></script>
@@ -29,9 +34,9 @@
theme: 'dark',
// arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 3,
logLevel: 0,
flowchart: { curve: 'linear', "htmlLabels": false },
gantt: { axisFormat: '%m/%d/%Y' },
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorMargin: 50 },
// sequenceDiagram: { actorMargin: 300 } // deprecated
});

37
dist/index.html vendored
View File

@@ -300,6 +300,31 @@ click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
</div>
<div class="mermaid">
graph TD
A([stadium shape test])
A -->|Get money| B([Go shopping])
B --> C([Let me think...<br />Do I want something for work,<br />something to spend every free second with,<br />or something to get around?])
C -->|One| D([Laptop])
C -->|Two| E([iPhone])
C -->|Three| F([Car<br/>wroom wroom])
click A "index.html#link-clicked" "link test"
click B testClick "click test"
classDef someclass fill:#f96;
class A someclass;
</div>
<div class="mermaid">
graph LR
A1[Multi<br>Line] -->|Multi<br>Line| B1(Multi<br>Line)
C1[Multi<br/>Line] -->|Multi<br/>Line| D1(Multi<br/>Line)
E1[Multi<br />Line] -->|Multi<br />Line| F1(Multi<br />Line)
A2[Multi<br>Line] -->|Multi<br>Line| B2(Multi<br>Line)
C2[Multi<br/>Line] -->|Multi<br/>Line| D2(Multi<br/>Line)
E2[Multi<br />Line] -->|Multi<br />Line| F2(Multi<br />Line)
linkStyle 0 stroke:DarkGray,stroke-width:2px
linkStyle 1 stroke:DarkGray,stroke-width:2px
linkStyle 2 stroke:DarkGray,stroke-width:2px
</div>
<hr/>
@@ -331,6 +356,18 @@ and
Alice -->> John: Parallel message 2
end
</div>
<div class="mermaid">
sequenceDiagram
participant 1 as multiline<br>using #lt;br#gt;
participant 2 as multiline<br/>using #lt;br/#gt;
participant 3 as multiline<br />using #lt;br /#gt;
1->>2: multiline<br>using #lt;br#gt;
note right of 2: multiline<br>using #lt;br#gt;
2->>3: multiline<br/>using #lt;br/#gt;
note right of 3: multiline<br/>using #lt;br/#gt;
3->>1: multiline<br />using #lt;br /#gt;
note right of 1: multiline<br />using #lt;br /#gt;
</div>
<hr/>

View File

@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/knsv/mermaid.svg?branch=master)](https://travis-ci.org/knsv/mermaid)
[![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid)
[![Coverage Status](https://coveralls.io/repos/github/knsv/mermaid/badge.svg?branch=master)](https://coveralls.io/github/knsv/mermaid?branch=master)
[![Join the chat at https://gitter.im/knsv/mermaid](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/knsv/mermaid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

View File

@@ -105,17 +105,10 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo
UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
#### Visibility
To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but is it optional:
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes.
- `+` Public
- `-` Private
- `#` Protected
- `~` Package
Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The one with `()` are treated as functions/methods, and others as attributes.
There are two ways to define the members of a class, and regardless of the whichever syntax is used to define the members, the output will still be same. The two different ways are :
There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are :
- Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example:
```
@@ -125,7 +118,7 @@ There are two ways to define the members of a class, and regardless of the which
BankAccount : +deposit(amount)
BankAccount : +withdrawl(amount)
```
```mermaid
``` mermaid
classDiagram
class BankAccount
BankAccount : +String owner
@@ -150,7 +143,22 @@ class BankAccount{
+BigDecimal balance
+deposit(amount)
+withdrawl(amount)
}```
}
```
#### Visibility
To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but it is optional:
- `+` Public
- `-` Private
- `#` Protected
- `~` Package
>_note_ you can also include additional _classifers_ to a method definition by adding the following notations to the end of the method, i.e.: after the `()`:
> - `*` Abstract e.g.: `someAbstractMethod()*`
> - `$` Static e.g.: `someStaticMethod()$`
## Defining Relationship
A relationship is a general term covering the specific types of logical connections found on class and object diagrams.

View File

@@ -78,6 +78,17 @@ graph LR
id1(This is the text in the box)
```
### A stadium-shaped node
```
graph LR
id1([This is the text in the box])
```
```mermaid
graph LR
id1([This is the text in the box])
```
### A node in the form of a circle
```

View File

@@ -87,6 +87,20 @@ gantt
Add another diagram to demo page :48h
```
It is possible to set multiple depenendenies separated by space:
```
gantt
apple :a, 2017-07-20, 1w
banana :crit, b, 2017-07-23, 1d
cherry :active, c, after b a, 1d
```
```
gantt
apple :a, 2017-07-20, 1w
banana :crit, b, 2017-07-23, 1d
cherry :active, c, after b a, 1d
```
### Title
Tbd

View File

@@ -7,7 +7,7 @@
<meta name="description" content="Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
<script src="//cdn.jsdelivr.net/npm/mermaid@8.4.3/dist/mermaid.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/mermaid@8.4.4/dist/mermaid.min.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),

View File

@@ -159,14 +159,13 @@ The main idea of the API is to be able to call a render function with the graph
will render the graph and call a callback with the resulting svg code. With this approach it is up to the site creator to
fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
To do this, include mermaidAPI on your web website instead of mermaid.js. The example below show an outline of how this
could be used. The example just logs the resulting svg to the javascript console.
The example below show an outline of how this could be used. The example just logs the resulting svg to the javascript console.
```html
<script src="mermaidAPI.js"></script>
<script src="mermaid.js"></script>
<script>
mermaidAPI.initialize({
mermaid.mermaidAPI.initialize({
startOnLoad:false
});
$(function(){
@@ -178,7 +177,7 @@ could be used. The example just logs the resulting svg to the javascript console
};
var graphDefinition = 'graph TB\na-->b';
var graph = mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
var graph = mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
});
</script>
```

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "8.4.3",
"version": "8.4.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js",
"keywords": [

View File

@@ -94,7 +94,7 @@ export const addMember = function(className, member) {
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
// Remove leading and trailing brackets
theClass.annotations.push(memberString.substring(2, memberString.length - 2));
} else if (memberString.endsWith(')')) {
} else if (memberString.indexOf(')') > 0) {
theClass.methods.push(memberString);
} else if (memberString) {
theClass.members.push(memberString);

View File

@@ -442,5 +442,27 @@ describe('class diagram, ', function () {
expect(testClass.methods[0]).toBe('test()');
expect(testClass.methods[1]).toBe('foo()');
});
it('should handle abstract methods', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1);
expect(testClass.methods[0]).toBe('someMethod()*');
});
it('should handle static methods', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$';
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1);
expect(testClass.methods[0]).toBe('someMethod()$');
});
});
});

View File

@@ -281,10 +281,34 @@ const drawClass = function(elem, classDef) {
logger.info('Rendering class ' + classDef);
const addTspan = function(textEl, txt, isFirst) {
let displayText = txt;
let cssStyle = '';
let methodEnd = txt.indexOf(')') + 1;
if (methodEnd > 1 && methodEnd <= txt.length) {
let classifier = txt.substring(methodEnd);
switch (classifier) {
case '*':
cssStyle = 'font-style:italic;';
break;
case '$':
cssStyle = 'text-decoration:underline;';
break;
}
displayText = txt.substring(0, methodEnd);
}
const tSpan = textEl
.append('tspan')
.attr('x', conf.padding)
.text(txt);
.text(displayText);
if (cssStyle !== '') {
tSpan.attr('style', cssStyle);
}
if (!isFirst) {
tSpan.attr('dy', conf.textHeight);
}

View File

@@ -135,9 +135,29 @@ function rect_right_inv_arrow(parent, bbox, node) {
return shapeSvg;
}
function stadium(parent, bbox, node) {
const h = bbox.height;
const w = bbox.width + h / 4;
const shapeSvg = parent
.insert('rect', ':first-child')
.attr('rx', h / 2)
.attr('ry', h / 2)
.attr('x', -w / 2)
.attr('y', -h / 2)
.attr('width', w)
.attr('height', h);
node.intersect = function(point) {
return dagreD3.intersect.rect(node, point);
};
return shapeSvg;
}
export function addToRender(render) {
render.shapes().question = question;
render.shapes().hexagon = hexagon;
render.shapes().stadium = stadium;
// Add custom shape for box with inverted arrow on left side
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;

View File

@@ -1,6 +1,29 @@
import { addToRender } from './flowChartShapes';
describe('flowchart shapes', function() {
// rect-based shapes
[
['stadium', useWidth, useHeight]
].forEach(function([shapeType, getW, getH]) {
it(`should add a ${shapeType} shape that renders a properly positioned rect element`, function() {
const mockRender = MockRender();
const mockSvg = MockSvg();
addToRender(mockRender);
[[100, 100], [123, 45], [71, 300]].forEach(function([width, height]) {
const shape = mockRender.shapes()[shapeType](mockSvg, { width, height }, {});
const w = width + height / 4;
const h = height;
const dx = -getW(w, h) / 2;
const dy = -getH(w, h) / 2;
expect(shape.__tag).toEqual('rect');
expect(shape.__attrs).toHaveProperty('x', dx);
expect(shape.__attrs).toHaveProperty('y', dy);
});
});
});
// polygon-based shapes
[
[
'question',

View File

@@ -102,7 +102,7 @@ export const addVertex = function(_id, text, type, style, classes) {
* @param type
* @param linktext
*/
export const addLink = function(_start, _end, type, linktext) {
export const addSingleLink = function(_start, _end, type, linktext) {
let start = _start;
let end = _end;
if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
@@ -127,6 +127,14 @@ export const addLink = function(_start, _end, type, linktext) {
}
edges.push(edge);
};
export const addLink = function(_start, _end, type, linktext) {
let i, j;
for (i = 0; i < _start.length; i++) {
for (j = 0; j < _end.length; j++) {
addSingleLink(_start[i], _end[j], type, linktext);
}
}
};
/**
* Updates a link's line interpolation algorithm
@@ -501,6 +509,130 @@ export const firstGraph = () => {
return false;
};
const destructStartLink = _str => {
const str = _str.trim();
switch (str) {
case '<--':
return { type: 'arrow', stroke: 'normal' };
case 'x--':
return { type: 'arrow_cross', stroke: 'normal' };
case 'o--':
return { type: 'arrow_circle', stroke: 'normal' };
case '<-.':
return { type: 'arrow', stroke: 'dotted' };
case 'x-.':
return { type: 'arrow_cross', stroke: 'dotted' };
case 'o-.':
return { type: 'arrow_circle', stroke: 'dotted' };
case '<==':
return { type: 'arrow', stroke: 'thick' };
case 'x==':
return { type: 'arrow_cross', stroke: 'thick' };
case 'o==':
return { type: 'arrow_circle', stroke: 'thick' };
case '--':
return { type: 'arrow_open', stroke: 'normal' };
case '==':
return { type: 'arrow_open', stroke: 'thick' };
case '-.':
return { type: 'arrow_open', stroke: 'dotted' };
}
};
const destructEndLink = _str => {
const str = _str.trim();
switch (str) {
case '--x':
return { type: 'arrow_cross', stroke: 'normal' };
case '-->':
return { type: 'arrow', stroke: 'normal' };
case '<-->':
return { type: 'double_arrow_point', stroke: 'normal' };
case 'x--x':
return { type: 'double_arrow_cross', stroke: 'normal' };
case 'o--o':
return { type: 'double_arrow_circle', stroke: 'normal' };
case 'o.-o':
return { type: 'double_arrow_circle', stroke: 'dotted' };
case '<==>':
return { type: 'double_arrow_point', stroke: 'thick' };
case 'o==o':
return { type: 'double_arrow_circle', stroke: 'thick' };
case 'x==x':
return { type: 'double_arrow_cross', stroke: 'thick' };
case 'x.-x':
return { type: 'double_arrow_cross', stroke: 'dotted' };
case 'x-.-x':
return { type: 'double_arrow_cross', stroke: 'dotted' };
case '<.->':
return { type: 'double_arrow_point', stroke: 'dotted' };
case '<-.->':
return { type: 'double_arrow_point', stroke: 'dotted' };
case 'o-.-o':
return { type: 'double_arrow_circle', stroke: 'dotted' };
case '--o':
return { type: 'arrow_circle', stroke: 'normal' };
case '---':
return { type: 'arrow_open', stroke: 'normal' };
case '-.-x':
return { type: 'arrow_cross', stroke: 'dotted' };
case '-.->':
return { type: 'arrow', stroke: 'dotted' };
case '-.-o':
return { type: 'arrow_circle', stroke: 'dotted' };
case '-.-':
return { type: 'arrow_open', stroke: 'dotted' };
case '.-x':
return { type: 'arrow_cross', stroke: 'dotted' };
case '.->':
return { type: 'arrow', stroke: 'dotted' };
case '.-o':
return { type: 'arrow_circle', stroke: 'dotted' };
case '.-':
return { type: 'arrow_open', stroke: 'dotted' };
case '==x':
return { type: 'arrow_cross', stroke: 'thick' };
case '==>':
return { type: 'arrow', stroke: 'thick' };
case '==o':
return { type: 'arrow_circle', stroke: 'thick' };
case '===':
return { type: 'arrow_open', stroke: 'thick' };
}
};
const destructLink = (_str, _startStr) => {
const info = destructEndLink(_str);
let startInfo;
if (_startStr) {
startInfo = destructStartLink(_startStr);
console.log(startInfo, info);
if (startInfo.stroke !== info.stroke) {
return { type: 'INVALID', stroke: 'INVALID' };
}
if (startInfo.type === 'arrow_open') {
// -- xyz --> - take arrow type form ending
startInfo.type = info.type;
} else {
// x-- xyz --> - not supported
if (startInfo.type !== info.type) return { type: 'INVALID', stroke: 'INVALID' };
startInfo.type = 'double_' + startInfo.type;
}
if (startInfo.type === 'double_arrow') {
startInfo.type = 'double_arrow_point';
}
return startInfo;
}
return info;
};
export default {
addVertex,
addLink,
@@ -523,6 +655,7 @@ export default {
getDepthFirstPos,
indexNodes,
getSubGraphs,
destructLink,
lex: {
firstGraph
}

View File

@@ -154,6 +154,9 @@ export const addVertices = function(vert, g, svgId) {
case 'ellipse':
_shape = 'ellipse';
break;
case 'stadium':
_shape = 'stadium';
break;
case 'group':
_shape = 'rect';
break;
@@ -236,18 +239,18 @@ export const addEdges = function(edges, g) {
}
} else {
edgeData.arrowheadStyle = 'fill: #333';
if (typeof edge.style === 'undefined') {
edgeData.labelpos = 'c';
if (getConfig().flowchart.htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else {
edgeData.labelType = 'text';
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
edgeData.label = edge.text.replace(/<br>/g, '\n');
}
edgeData.labelpos = 'c';
if (getConfig().flowchart.htmlLabels) {
edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
} else {
edgeData.label = edge.text.replace(/<br>/g, '\n');
edgeData.labelType = 'text';
edgeData.label = edge.text.replace(/<br ?\/?>/g, '\n');
if (typeof edge.style === 'undefined') {
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
}
}
}
// Add the edge to the graph

View File

@@ -1,4 +1,4 @@
import { addVertices } from './flowRenderer';
import { addVertices, addEdges } from './flowRenderer';
import { setConfig } from '../../config';
setConfig({
@@ -22,6 +22,7 @@ describe('the flowchart renderer', function() {
['odd_right', 'rect_left_inv_arrow'],
['circle', 'circle'],
['ellipse', 'ellipse'],
['stadium', 'stadium'],
['group', 'rect']
].forEach(function([type, expectedShape, expectedRadios = 0]) {
it(`should add the correct shaped node to the graph for vertex type ${type}`, function() {
@@ -93,4 +94,32 @@ describe('the flowchart renderer', function() {
expect(addedNodes[0][1]).toHaveProperty('labelStyle', expectedLabelStyle);
});
});
describe('when adding edges to a graph', function() {
it('should handle multiline texts and set centered label position', function() {
const addedEdges = [];
const mockG = {
setEdge: function(s, e, data, c) {
addedEdges.push(data);
}
};
addEdges(
[
{ text: 'Multi<br>Line' },
{ text: 'Multi<br/>Line' },
{ text: 'Multi<br />Line' },
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br>Line' },
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br/>Line' },
{ style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi<br />Line' }
],
mockG,
'svg-id'
);
addedEdges.forEach(function(edge) {
expect(edge).toHaveProperty('label', 'Multi\nLine');
expect(edge).toHaveProperty('labelpos', 'c');
});
});
});
});

View File

@@ -87,7 +87,7 @@ describe('[Edges] when parsing', () => {
expect(edges[0].text).toBe('');
});
it('should handle double edged nodes with text on thick arrows', function() {
it('should handle double edged nodes with text on thick arrows XYZ1', function() {
const res = flow.parser.parse('graph TD;\nA x== text ==x B;');
const vert = flow.parser.yy.getVertices();

View File

@@ -22,6 +22,16 @@ describe('[Singlenodes] when parsing', () => {
expect(edges.length).toBe(0);
expect(vert['A'].styles.length).toBe(0);
});
it('should handle a single node with white space after it (SN1)', function() {
// Silly but syntactically correct
const res = flow.parser.parse('graph TD;A ;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert['A'].styles.length).toBe(0);
});
it('should handle a single square node', function() {
// Silly but syntactically correct

View File

@@ -182,7 +182,7 @@ describe('[Text] when parsing', () => {
expect(edges[0].stroke).toBe('normal');
});
it('it should handle dotted text on lines', function() {
it('it should handle dotted text on lines (TD3)', function() {
const res = flow.parser.parse('graph TD;A-. test text with == .->B;');
const vert = flow.parser.yy.getVertices();
@@ -265,7 +265,7 @@ describe('[Text] when parsing', () => {
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and dir (TD)', function() {
it('should handle space and dir (TD2)', function() {
const res = flow.parser.parse('graph TD;A-- text including R TD space --xB;');
const vert = flow.parser.yy.getVertices();

View File

@@ -34,4 +34,179 @@ describe('when parsing flowcharts', function() {
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
});
it('should handle chaining of vertices', function() {
const res = flow.parser.parse(`
graph TD
A B --> C;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('C');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
});
it('should multiple vertices in link statement in the begining', function() {
const res = flow.parser.parse(`
graph TD
A-->B C;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(edges.length).toBe(2);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
});
it('should multiple vertices in link statement at the end', function() {
const res = flow.parser.parse(`
graph TD
A B--> C D;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(vert['D'].id).toBe('D');
expect(edges.length).toBe(4);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('C');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('D');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
expect(edges[2].start).toBe('B');
expect(edges[2].end).toBe('C');
expect(edges[2].type).toBe('arrow');
expect(edges[2].text).toBe('');
expect(edges[3].start).toBe('B');
expect(edges[3].end).toBe('D');
expect(edges[3].type).toBe('arrow');
expect(edges[3].text).toBe('');
});
it('should handle chaining of vertices at both ends at once', function() {
const res = flow.parser.parse(`
graph TD
A B--> C D;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(vert['D'].id).toBe('D');
expect(edges.length).toBe(4);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('C');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('D');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
expect(edges[2].start).toBe('B');
expect(edges[2].end).toBe('C');
expect(edges[2].type).toBe('arrow');
expect(edges[2].text).toBe('');
expect(edges[3].start).toBe('B');
expect(edges[3].end).toBe('D');
expect(edges[3].type).toBe('arrow');
expect(edges[3].text).toBe('');
});
it('should handle chaining and multiple nodes in in link statement', function() {
const res = flow.parser.parse(`
graph TD
A --> B C --> D;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['C'].id).toBe('C');
expect(vert['D'].id).toBe('D');
expect(edges.length).toBe(4);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('');
expect(edges[2].start).toBe('B');
expect(edges[2].end).toBe('D');
expect(edges[2].type).toBe('arrow');
expect(edges[2].text).toBe('');
expect(edges[3].start).toBe('C');
expect(edges[3].end).toBe('D');
expect(edges[3].type).toBe('arrow');
expect(edges[3].text).toBe('');
});
it('should handle chaining and multiple nodes in in link statement with extra info in statements', function() {
const res = flow.parser.parse(`
graph TD
A[ h ] -- hello --> B[" test "]:::exClass C --> D;
classDef exClass background:#bbb,border:1px solid red;
`);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
const classes = flow.parser.yy.getClasses();
expect(classes['exClass'].styles.length).toBe(2);
expect(classes['exClass'].styles[0]).toBe('background:#bbb');
expect(classes['exClass'].styles[1]).toBe('border:1px solid red');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(vert['B'].classes[0]).toBe('exClass');
expect(vert['C'].id).toBe('C');
expect(vert['D'].id).toBe('D');
expect(edges.length).toBe(4);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('hello');
expect(edges[1].start).toBe('A');
expect(edges[1].end).toBe('C');
expect(edges[1].type).toBe('arrow');
expect(edges[1].text).toBe('hello');
expect(edges[2].start).toBe('B');
expect(edges[2].end).toBe('D');
expect(edges[2].type).toBe('arrow');
expect(edges[2].text).toBe('');
expect(edges[3].start).toBe('C');
expect(edges[3].end).toBe('D');
expect(edges[3].type).toBe('arrow');
expect(edges[3].text).toBe('');
});
});

View File

@@ -8,6 +8,7 @@
%lex
%x string
%x dir
%x vertex
%%
\%\%[^\n]*\n* /* do nothing */
["] this.begin("string");
@@ -40,48 +41,50 @@
";" return 'SEMI';
"," return 'COMMA';
"*" return 'MULT';
\s*\-\-[x]\s* return 'ARROW_CROSS';
\s*\-\-\>\s* return 'ARROW_POINT';
\s*\<\-\-\>\s* return 'DOUBLE_ARROW_POINT';
\s*[x]\-\-[x]\s* return 'DOUBLE_ARROW_CROSS';
\s*[o]\-\-[o]\s* return 'DOUBLE_ARROW_CIRCLE';
\s*[o]\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE';
\s*\<\=\=\>\s* return 'DOUBLE_THICK_ARROW_POINT';
\s*[o]\=\=[o]\s* return 'DOUBLE_THICK_ARROW_CIRCLE';
\s*[x]\=\=[x]\s* return 'DOUBLE_THICK_ARROW_CROSS';
\s*[x].\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS';
\s*[x]\-\.\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS';
\s*\<\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT';
\s*\<\-\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT';
\s*[o]\-\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE';
\s*\-\-[o]\s* return 'ARROW_CIRCLE';
\s*\-\-\-\s* return 'ARROW_OPEN';
\s*\-\.\-[x]\s* return 'DOTTED_ARROW_CROSS';
\s*\-\.\-\>\s* return 'DOTTED_ARROW_POINT';
\s*\-\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
\s*\-\.\-\s* return 'DOTTED_ARROW_OPEN';
\s*.\-[x]\s* return 'DOTTED_ARROW_CROSS';
\s*\.\-\>\s* return 'DOTTED_ARROW_POINT';
\s*\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
\s*\.\-\s* return 'DOTTED_ARROW_OPEN';
\s*\=\=[x]\s* return 'THICK_ARROW_CROSS';
\s*\=\=\>\s* return 'THICK_ARROW_POINT';
\s*\=\=[o]\s* return 'THICK_ARROW_CIRCLE';
\s*\=\=[\=]\s* return 'THICK_ARROW_OPEN';
\s*\<\-\-\s* return 'START_DOUBLE_ARROW_POINT';
\s*[x]\-\-\s* return 'START_DOUBLE_ARROW_CROSS';
\s*[o]\-\-\s* return 'START_DOUBLE_ARROW_CIRCLE';
\s*\<\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_POINT';
\s*[x]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CROSS';
\s*[o]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CIRCLE';
\s*\<\=\=\s* return 'START_DOUBLE_THICK_ARROW_POINT';
\s*[x]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CROSS';
\s*[o]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CIRCLE';
\s*\-\-\s* return '--';
\s*\-\.\s* return '-.';
\s*\=\=\s* return '==';
\s*\-\-[x]\s* return 'LINK';
\s*\-\-\>\s* return 'LINK';
\s*\<\-\-\>\s* return 'LINK';
\s*[x]\-\-[x]\s* return 'LINK';
\s*[o]\-\-[o]\s* return 'LINK';
\s*[o]\.\-[o]\s* return 'LINK';
\s*\<\=\=\>\s* return 'LINK';
\s*[o]\=\=[o]\s* return 'LINK';
\s*[x]\=\=[x]\s* return 'LINK';
\s*[x].\-[x]\s* return 'LINK';
\s*[x]\-\.\-[x]\s* return 'LINK';
\s*\<\.\-\>\s* return 'LINK';
\s*\<\-\.\-\>\s* return 'LINK';
\s*[o]\-\.\-[o]\s* return 'LINK';
\s*\-\-[o]\s* return 'LINK';
\s*\-\-\-\s* return 'LINK';
\s*\-\.\-[x]\s* return 'LINK';
\s*\-\.\-\>\s* return 'LINK';
\s*\-\.\-[o]\s* return 'LINK';
\s*\-\.\-\s* return 'LINK';
\s*.\-[x]\s* return 'LINK';
\s*\.\-\>\s* return 'LINK';
\s*\.\-[o]\s* return 'LINK';
\s*\.\-\s* return 'LINK';
\s*\=\=[x]\s* return 'LINK';
\s*\=\=\>\s* return 'LINK';
\s*\=\=[o]\s* return 'LINK';
\s*\=\=[\=]\s* return 'LINK';
\s*\<\-\-\s* return 'START_LINK';
\s*[x]\-\-\s* return 'START_LINK';
\s*[o]\-\-\s* return 'START_LINK';
\s*\<\-\.\s* return 'START_LINK';
\s*[x]\-\.\s* return 'START_LINK';
\s*[o]\-\.\s* return 'START_LINK';
\s*\<\=\=\s* return 'START_LINK';
\s*[x]\=\=\s* return 'START_LINK';
\s*[o]\=\=\s* return 'START_LINK';
\s*\-\-\s* return 'START_LINK';
\s*\-\.\s* return 'START_LINK';
\s*\=\=\s* return 'START_LINK';
"(-" return '(-';
"-)" return '-)';
"([" return 'STADIUMSTART';
"])" return 'STADIUMEND';
\- return 'MINUS';
"." return 'DOT';
[\_] return 'UNDERSCORE';
@@ -92,12 +95,13 @@
"<" return 'TAGSTART';
">" return 'TAGEND';
"^" return 'UP';
"\|" return 'SEP';
"v" return 'DOWN';
[A-Za-z]+ return 'ALPHA';
"\\]" return 'TRAPEND';
"[/" return 'TRAPSTART';
"/]" return 'INVTRAPEND';
"[\\" return 'INVTRAPSTART';
"/]" return 'INVTRAPEND';
"[\\" return 'INVTRAPSTART';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@@ -245,7 +249,7 @@ spaceList
statement
: verticeStatement separator
{ $$=$1}
{ /* console.warn('finat vs', $1.nodes); */ $$=$1.nodes}
| styleStatement separator
{$$=[];}
| linkStyleStatement separator
@@ -283,68 +287,49 @@ separator: NEWLINE | SEMI | EOF ;
// {$$ = [$1];yy.setClass($1,$3)}
// ;
verticeStatement: verticeStatement link node { yy.addLink($1[0],$3[0],$2); $$ = $3.concat($1) }
|node { $$ = $1 }
verticeStatement: verticeStatement link node
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
| verticeStatement link node spaceList
{ /* console.warn('vs',$1.stmt,$3); */ yy.addLink($1.stmt,$3,$2); $$ = { stmt: $3, nodes: $3.concat($1.nodes) } }
|node spaceList {/*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
|node { /*console.warn('noda', $1);*/ $$ = {stmt: $1, nodes:$1 }}
;
node: vertex
{ $$ = [$1];}
{ /* console.warn('nod', $1); */ $$ = [$1];}
| node spaceList vertex
{ $$ = [$1[0], $3]; /*console.warn('pip', $1, $3, $$);*/ }
| vertex STYLE_SEPARATOR idString
{$$ = [$1];yy.setClass($1,$3)}
;
vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'square');}
| idString SQS text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'square');}
| idString PS PS text PE PE
{$$ = $1;yy.addVertex($1,$4,'circle');}
| idString PS PS text PE PE spaceList
{$$ = $1;yy.addVertex($1,$4,'circle');}
| idString '(-' text '-)'
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
| idString '(-' text '-)' spaceList
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
| idString STADIUMSTART text STADIUMEND
{$$ = $1;yy.addVertex($1,$3,'stadium');}
| idString PS text PE
{$$ = $1;yy.addVertex($1,$3,'round');}
| idString PS text PE spaceList
{$$ = $1;yy.addVertex($1,$3,'round');}
| idString DIAMOND_START text DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| idString DIAMOND_START text DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$4,'hexagon');}
| idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$4,'hexagon');}
| idString TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TAGEND text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'trapezoid');}
| idString TRAPSTART text TRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'trapezoid');}
| idString INVTRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');}
| idString INVTRAPSTART text INVTRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');}
| idString TRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_right');}
| idString TRAPSTART text INVTRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'lean_right');}
| idString INVTRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_left');}
| idString INVTRAPSTART text TRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'lean_left');}
/* | idString SQS text TAGSTART
{$$ = $1;yy.addVertex($1,$3,'odd_right');}
| idString SQS text TAGSTART spaceList
{$$ = $1;yy.addVertex($1,$3,'odd_right');} */
| idString
{$$ = $1;yy.addVertex($1);}
| idString spaceList
{$$ = $1;yy.addVertex($1);}
{ /*console.warn('h: ', $1);*/$$ = $1;yy.addVertex($1);}
;
@@ -357,92 +342,12 @@ link: linkStatement arrowText
{$1.text = $2;$$ = $1;}
| linkStatement
{$$ = $1;}
| '--' text ARROW_POINT
{$$ = {"type":"arrow","stroke":"normal","text":$2};}
| 'START_DOUBLE_ARROW_POINT' text ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"normal","text":$2};}
| '--' text ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"normal","text":$2};}
| 'START_DOUBLE_ARROW_CIRCLE' text ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"normal","text":$2};}
| '--' text ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"normal","text":$2};}
| 'START_DOUBLE_ARROW_CROSS' text ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"normal","text":$2};}
| '--' text ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"normal","text":$2};}
| '-.' text DOTTED_ARROW_POINT
{$$ = {"type":"arrow","stroke":"dotted","text":$2};}
| 'START_DOUBLE_DOTTED_ARROW_POINT' text DOTTED_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"dotted","text":$2};}
| 'START_DOUBLE_DOTTED_ARROW_CIRCLE' text DOTTED_ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"dotted","text":$2};}
| 'START_DOUBLE_DOTTED_ARROW_CROSS' text DOTTED_ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"dotted","text":$2};}
| '==' text THICK_ARROW_POINT
{$$ = {"type":"arrow","stroke":"thick","text":$2};}
| 'START_DOUBLE_THICK_ARROW_POINT' text THICK_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"thick","text":$2};}
| 'START_DOUBLE_THICK_ARROW_CIRCLE' text THICK_ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"thick","text":$2};}
| 'START_DOUBLE_THICK_ARROW_CROSS' text THICK_ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"thick","text":$2};}
| START_LINK text LINK
{var inf = yy.destructLink($3, $1); $$ = {"type":inf.type,"stroke":inf.stroke,"text":$2};}
;
linkStatement: ARROW_POINT
{$$ = {"type":"arrow","stroke":"normal"};}
| DOUBLE_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"normal"};}
| ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"normal"};}
| DOUBLE_ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"normal"};}
| ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"normal"};}
| DOUBLE_ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"normal"};}
| ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"normal"};}
| DOTTED_ARROW_POINT
{$$ = {"type":"arrow","stroke":"dotted"};}
| DOUBLE_DOTTED_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"dotted"};}
| DOTTED_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"dotted"};}
| DOUBLE_DOTTED_ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"dotted"};}
| DOTTED_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"dotted"};}
| DOUBLE_DOTTED_ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"dotted"};}
| DOTTED_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"dotted"};}
| THICK_ARROW_POINT
{$$ = {"type":"arrow","stroke":"thick"};}
| DOUBLE_THICK_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"thick"};}
| THICK_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"thick"};}
| DOUBLE_THICK_ARROW_CIRCLE
{$$ = {"type":"double_arrow_circle","stroke":"thick"};}
| THICK_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"thick"};}
| DOUBLE_THICK_ARROW_CROSS
{$$ = {"type":"double_arrow_cross","stroke":"thick"};}
| THICK_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"thick"};}
linkStatement: LINK
{var inf = yy.destructLink($1);$$ = {"type":inf.type,"stroke":inf.stroke};}
;
arrowText:
@@ -530,7 +435,7 @@ styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT |
/* Token lists */
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
textToken : textNoTagsToken | TAGSTART | TAGEND | START_LINK | PCT | DEFAULT;
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
@@ -563,5 +468,5 @@ alphaNumToken : PUNCTUATION | UNICODE_TEXT | NUM| ALPHA | COLON | COMMA | PLUS
idStringToken : ALPHA|UNDERSCORE |UNICODE_TEXT | NUM| COLON | COMMA | PLUS | MINUS | DOWN |EQUALS | MULT | BRKT | DOT | PUNCTUATION;
graphCodeTokens: TRAPSTART | TRAPEND | INVTRAPSTART | INVTRAPEND | PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAGSTART | TAGEND | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI ;
graphCodeTokens: STADIUMSTART | STADIUMEND | TRAPSTART | TRAPEND | INVTRAPSTART | INVTRAPEND | PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAGSTART | TAGEND | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI;
%%

View File

@@ -16,6 +16,7 @@ describe('when parsing subgraphs', function() {
const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a2');
expect(subgraph.nodes[1]).toBe('a1');
@@ -191,6 +192,15 @@ describe('when parsing subgraphs', function() {
expect(edges[0].type).toBe('arrow');
});
it('should handle subgraphs3', function() {
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle \n\n c-->d \nend\n');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
it('should handle nested subgraphs', function() {
const str =
'graph TD\n' +
@@ -218,6 +228,14 @@ describe('when parsing subgraphs', function() {
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
it('should handle subgraphs with multi node statements in it', function() {
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\na b --> c e\n end;');
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow');
});
});

View File

@@ -134,18 +134,32 @@ const getStartDate = function(prevTime, dateFormat, str) {
str = str.trim();
// Test for after
const re = /^after\s+([\d\w-]+)/;
const re = /^after\s+([\d\w- ]+)/;
const afterStatement = re.exec(str.trim());
if (afterStatement !== null) {
const task = findTaskById(afterStatement[1]);
// check all after ids and take the latest
let latestEndingTask = null;
afterStatement[1].split(' ').forEach(function(id) {
let task = findTaskById(id);
if (typeof task !== 'undefined') {
if (!latestEndingTask) {
latestEndingTask = task;
} else {
if (task.endTime > latestEndingTask.endTime) {
latestEndingTask = task;
}
}
}
});
if (typeof task === 'undefined') {
if (!latestEndingTask) {
const dt = new Date();
dt.setHours(0, 0, 0, 0);
return dt;
} else {
return latestEndingTask.endTime;
}
return task.endTime;
}
// Check for actual date set

View File

@@ -302,6 +302,7 @@ export const draw = function(txt, id, ver) {
try {
const parser = gitGraphParser.parser;
parser.yy = db;
parser.yy.clear();
logger.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
// Parse the graph definition

View File

@@ -49,7 +49,7 @@ line
statement
: STR VALUE {
console.log('str:'+$1+' value: '+$2)
/*console.log('str:'+$1+' value: '+$2)*/
yy.addSection($1,yy.cleanupValue($2)); }
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
;

View File

@@ -333,6 +333,34 @@ describe('when parsing a sequenceDiagram', function() {
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('John');
});
it('it should handle different line breaks', function() {
const str =
'sequenceDiagram\n' +
'participant 1 as multiline<br>text\n' +
'participant 2 as multiline<br/>text\n' +
'participant 3 as multiline<br />text\n' +
'1->>2: multiline<br>text\n' +
'note right of 2: multiline<br>text\n' +
'2->>3: multiline<br/>text\n' +
'note right of 3: multiline<br/>text\n' +
'3->>1: multiline<br />text\n' +
'note right of 1: multiline<br />text\n';
parser.parse(str);
const actors = parser.yy.getActors();
expect(actors['1'].description).toBe('multiline<br>text');
expect(actors['2'].description).toBe('multiline<br/>text');
expect(actors['3'].description).toBe('multiline<br />text');
const messages = parser.yy.getMessages();
expect(messages[0].message).toBe('multiline<br>text');
expect(messages[1].message).toBe('multiline<br>text');
expect(messages[2].message).toBe('multiline<br/>text');
expect(messages[3].message).toBe('multiline<br/>text');
expect(messages[4].message).toBe('multiline<br />text');
expect(messages[5].message).toBe('multiline<br />text');
});
it('it should handle notes over a single actor', function() {
const str =
'sequenceDiagram\n' + 'Alice->Bob: Hello Bob, how are you?\n' + 'Note over Bob: Bob thinks\n';

View File

@@ -168,7 +168,7 @@ export const bounds = {
const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0;
const lines = text.split(/<br\/?>/gi);
const lines = text.split(/<br ?\/?>/gi);
for (const line of lines) {
const textObj = svgDraw.getTextObj();
textObj.x = x;
@@ -233,7 +233,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
let textElem;
let counterBreaklines = 0;
let breaklineOffset = 17;
const breaklines = msg.message.split(/<br\/?>/gi);
const breaklines = msg.message.split(/<br ?\/?>/gi);
for (const breakline of breaklines) {
textElem = g
.append('text') // text label for the x axis

View File

@@ -18,7 +18,7 @@ export const drawRect = function(elem, rectData) {
export const drawText = function(elem, textData) {
// Remove and ignore br:s
const nText = textData.text.replace(/<br\/?>/gi, ' ');
const nText = textData.text.replace(/<br ?\/?>/gi, ' ');
const textElem = elem.append('text');
textElem.attr('x', textData.x);
@@ -321,7 +321,7 @@ const _drawTextCandidateFunc = (function() {
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
const { actorFontSize, actorFontFamily } = conf;
const lines = content.split(/<br\/?>/gi);
const lines = content.split(/<br ?\/?>/gi);
for (let i = 0; i < lines.length; i++) {
const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2;
const text = g

View File

@@ -23,23 +23,34 @@ export const setLogLevel = function(level) {
logger.error = () => {};
logger.fatal = () => {};
if (level <= LEVELS.fatal) {
logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL'));
logger.fatal = console.error
? console.error.bind(console, format('FATAL'), 'color: orange')
: console.log.bind(console, '\x1b[35m', format('FATAL'));
}
if (level <= LEVELS.error) {
logger.error = console.log.bind(console, '\x1b[31m', format('ERROR'));
logger.error = console.error
? console.error.bind(console, format('ERROR'), 'color: orange')
: console.log.bind(console, '\x1b[31m', format('ERROR'));
}
if (level <= LEVELS.warn) {
logger.warn = console.log.bind(console, `\x1b[33m`, format('WARN'));
logger.warn = console.warn
? console.warn.bind(console, format('WARN'), 'color: orange')
: console.log.bind(console, `\x1b[33m`, format('WARN'));
}
if (level <= LEVELS.info) {
logger.info = console.log.bind(console, '\x1b[34m', format('INFO'));
logger.info = console.info
? // ? console.info.bind(console, '\x1b[34m', format('INFO'), 'color: blue')
console.info.bind(console, format('INFO'), 'color: lightblue')
: console.log.bind(console, '\x1b[34m', format('INFO'));
}
if (level <= LEVELS.debug) {
logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG'));
logger.debug = console.debug
? console.debug.bind(console, format('DEBUG'), 'color: lightgreen')
: console.log.bind(console, '\x1b[32m', format('DEBUG'));
}
};
const format = level => {
const time = moment().format('HH:mm:ss.SSS');
return `${time} : ${level} : `;
const time = moment().format('ss.SSS');
return `%c${time} : ${level} : `;
};

View File

@@ -116,7 +116,6 @@ const init = function() {
};
const initialize = function(config) {
logger.debug('Initializing mermaid ');
if (typeof config.mermaid !== 'undefined') {
if (typeof config.mermaid.startOnLoad !== 'undefined') {
mermaid.startOnLoad = config.mermaid.startOnLoad;
@@ -126,6 +125,7 @@ const initialize = function(config) {
}
}
mermaidAPI.initialize(config);
logger.debug('Initializing mermaid ');
};
/**

View File

@@ -54,7 +54,7 @@
.grid .tick {
stroke: $gridColor;
opacity: 0.3;
opacity: 0.8;
shape-rendering: crispEdges;
text {
font-family: 'trebuchet ms', verdana, arial;