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'); 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( imgSnapshotTest(
` `
classDiagram classDiagram
@@ -184,7 +208,7 @@ describe('Class diagram', () => {
cy.get('svg'); 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( imgSnapshotTest(
` `
classDiagram classDiagram

View File

@@ -396,4 +396,61 @@ describe('Flowcart', () => {
{ flowchart: { htmlLabels: false } } { 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'; import { imgSnapshotTest } from '../../helpers/util';
context('Aliasing', () => { context('Sequence diagram', () => {
it('should render a simple sequence diagrams', () => { it('should render a simple sequence diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
sequenceDiagram 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', () => { context('background rects', () => {
it('should render a single and nested rects', () => { it('should render a single and nested rects', () => {
imgSnapshotTest( imgSnapshotTest(

View File

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

37
dist/index.html vendored
View File

@@ -300,6 +300,31 @@ click B testClick "click test"
classDef someclass fill:#f96; classDef someclass fill:#f96;
class A someclass; class A someclass;
</div> </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/> <hr/>
@@ -331,6 +356,18 @@ and
Alice -->> John: Parallel message 2 Alice -->> John: Parallel message 2
end end
</div> </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/> <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) [![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) [![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. UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.
#### Visibility 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.
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:
- `+` 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 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 the 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: - 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 : +deposit(amount)
BankAccount : +withdrawl(amount) BankAccount : +withdrawl(amount)
``` ```
```mermaid ``` mermaid
classDiagram classDiagram
class BankAccount class BankAccount
BankAccount : +String owner BankAccount : +String owner
@@ -150,7 +143,22 @@ class BankAccount{
+BigDecimal balance +BigDecimal balance
+deposit(amount) +deposit(amount)
+withdrawl(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 ## Defining Relationship
A relationship is a general term covering the specific types of logical connections found on class and object diagrams. 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) 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 ### A node in the form of a circle
``` ```

View File

@@ -87,6 +87,20 @@ gantt
Add another diagram to demo page :48h 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 ### Title
Tbd 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="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"> <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"> <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> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (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), (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 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. 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 The example below show an outline of how this could be used. The example just logs the resulting svg to the javascript console.
could be used. The example just logs the resulting svg to the javascript console.
```html ```html
<script src="mermaidAPI.js"></script> <script src="mermaid.js"></script>
<script> <script>
mermaidAPI.initialize({ mermaid.mermaidAPI.initialize({
startOnLoad:false startOnLoad:false
}); });
$(function(){ $(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 graphDefinition = 'graph TB\na-->b';
var graph = mermaidAPI.render('graphDiv', graphDefinition, insertSvg); var graph = mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);
}); });
</script> </script>
``` ```

View File

@@ -1,6 +1,6 @@
{ {
"name": "mermaid", "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.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js", "main": "dist/mermaid.core.js",
"keywords": [ "keywords": [

View File

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

View File

@@ -442,5 +442,27 @@ describe('class diagram, ', function () {
expect(testClass.methods[0]).toBe('test()'); expect(testClass.methods[0]).toBe('test()');
expect(testClass.methods[1]).toBe('foo()'); 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); logger.info('Rendering class ' + classDef);
const addTspan = function(textEl, txt, isFirst) { 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 const tSpan = textEl
.append('tspan') .append('tspan')
.attr('x', conf.padding) .attr('x', conf.padding)
.text(txt); .text(displayText);
if (cssStyle !== '') {
tSpan.attr('style', cssStyle);
}
if (!isFirst) { if (!isFirst) {
tSpan.attr('dy', conf.textHeight); tSpan.attr('dy', conf.textHeight);
} }

View File

@@ -135,9 +135,29 @@ function rect_right_inv_arrow(parent, bbox, node) {
return shapeSvg; 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) { export function addToRender(render) {
render.shapes().question = question; render.shapes().question = question;
render.shapes().hexagon = hexagon; render.shapes().hexagon = hexagon;
render.shapes().stadium = stadium;
// Add custom shape for box with inverted arrow on left side // Add custom shape for box with inverted arrow on left side
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow; render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;

View File

@@ -1,6 +1,29 @@
import { addToRender } from './flowChartShapes'; import { addToRender } from './flowChartShapes';
describe('flowchart shapes', function() { 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', 'question',

View File

@@ -102,7 +102,7 @@ export const addVertex = function(_id, text, type, style, classes) {
* @param type * @param type
* @param linktext * @param linktext
*/ */
export const addLink = function(_start, _end, type, linktext) { export const addSingleLink = function(_start, _end, type, linktext) {
let start = _start; let start = _start;
let end = _end; let end = _end;
if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start; 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); 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 * Updates a link's line interpolation algorithm
@@ -501,6 +509,130 @@ export const firstGraph = () => {
return false; 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 { export default {
addVertex, addVertex,
addLink, addLink,
@@ -523,6 +655,7 @@ export default {
getDepthFirstPos, getDepthFirstPos,
indexNodes, indexNodes,
getSubGraphs, getSubGraphs,
destructLink,
lex: { lex: {
firstGraph firstGraph
} }

View File

@@ -154,6 +154,9 @@ export const addVertices = function(vert, g, svgId) {
case 'ellipse': case 'ellipse':
_shape = 'ellipse'; _shape = 'ellipse';
break; break;
case 'stadium':
_shape = 'stadium';
break;
case 'group': case 'group':
_shape = 'rect'; _shape = 'rect';
break; break;
@@ -236,18 +239,18 @@ export const addEdges = function(edges, g) {
} }
} else { } else {
edgeData.arrowheadStyle = 'fill: #333'; edgeData.arrowheadStyle = 'fill: #333';
if (typeof edge.style === 'undefined') { edgeData.labelpos = 'c';
edgeData.labelpos = 'c';
if (getConfig().flowchart.htmlLabels) { if (getConfig().flowchart.htmlLabels) {
edgeData.labelType = 'html'; edgeData.labelType = 'html';
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>'; 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');
}
} else { } 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 // 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'; import { setConfig } from '../../config';
setConfig({ setConfig({
@@ -22,6 +22,7 @@ describe('the flowchart renderer', function() {
['odd_right', 'rect_left_inv_arrow'], ['odd_right', 'rect_left_inv_arrow'],
['circle', 'circle'], ['circle', 'circle'],
['ellipse', 'ellipse'], ['ellipse', 'ellipse'],
['stadium', 'stadium'],
['group', 'rect'] ['group', 'rect']
].forEach(function([type, expectedShape, expectedRadios = 0]) { ].forEach(function([type, expectedShape, expectedRadios = 0]) {
it(`should add the correct shaped node to the graph for vertex type ${type}`, function() { 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); 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(''); 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 res = flow.parser.parse('graph TD;\nA x== text ==x B;');
const vert = flow.parser.yy.getVertices(); const vert = flow.parser.yy.getVertices();

View File

@@ -22,6 +22,16 @@ describe('[Singlenodes] when parsing', () => {
expect(edges.length).toBe(0); expect(edges.length).toBe(0);
expect(vert['A'].styles.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() { it('should handle a single square node', function() {
// Silly but syntactically correct // Silly but syntactically correct

View File

@@ -182,7 +182,7 @@ describe('[Text] when parsing', () => {
expect(edges[0].stroke).toBe('normal'); 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 res = flow.parser.parse('graph TD;A-. test text with == .->B;');
const vert = flow.parser.yy.getVertices(); const vert = flow.parser.yy.getVertices();
@@ -265,7 +265,7 @@ describe('[Text] when parsing', () => {
expect(edges[0].text).toBe('text including URL space'); 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 res = flow.parser.parse('graph TD;A-- text including R TD space --xB;');
const vert = flow.parser.yy.getVertices(); 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].type).toBe('arrow');
expect(edges[1].text).toBe(''); 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 %lex
%x string %x string
%x dir %x dir
%x vertex
%% %%
\%\%[^\n]*\n* /* do nothing */ \%\%[^\n]*\n* /* do nothing */
["] this.begin("string"); ["] this.begin("string");
@@ -40,48 +41,50 @@
";" return 'SEMI'; ";" return 'SEMI';
"," return 'COMMA'; "," return 'COMMA';
"*" return 'MULT'; "*" return 'MULT';
\s*\-\-[x]\s* return 'ARROW_CROSS'; \s*\-\-[x]\s* return 'LINK';
\s*\-\-\>\s* return 'ARROW_POINT'; \s*\-\-\>\s* return 'LINK';
\s*\<\-\-\>\s* return 'DOUBLE_ARROW_POINT'; \s*\<\-\-\>\s* return 'LINK';
\s*[x]\-\-[x]\s* return 'DOUBLE_ARROW_CROSS'; \s*[x]\-\-[x]\s* return 'LINK';
\s*[o]\-\-[o]\s* return 'DOUBLE_ARROW_CIRCLE'; \s*[o]\-\-[o]\s* return 'LINK';
\s*[o]\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE'; \s*[o]\.\-[o]\s* return 'LINK';
\s*\<\=\=\>\s* return 'DOUBLE_THICK_ARROW_POINT'; \s*\<\=\=\>\s* return 'LINK';
\s*[o]\=\=[o]\s* return 'DOUBLE_THICK_ARROW_CIRCLE'; \s*[o]\=\=[o]\s* return 'LINK';
\s*[x]\=\=[x]\s* return 'DOUBLE_THICK_ARROW_CROSS'; \s*[x]\=\=[x]\s* return 'LINK';
\s*[x].\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS'; \s*[x].\-[x]\s* return 'LINK';
\s*[x]\-\.\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS'; \s*[x]\-\.\-[x]\s* return 'LINK';
\s*\<\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT'; \s*\<\.\-\>\s* return 'LINK';
\s*\<\-\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT'; \s*\<\-\.\-\>\s* return 'LINK';
\s*[o]\-\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE'; \s*[o]\-\.\-[o]\s* return 'LINK';
\s*\-\-[o]\s* return 'ARROW_CIRCLE'; \s*\-\-[o]\s* return 'LINK';
\s*\-\-\-\s* return 'ARROW_OPEN'; \s*\-\-\-\s* return 'LINK';
\s*\-\.\-[x]\s* return 'DOTTED_ARROW_CROSS'; \s*\-\.\-[x]\s* return 'LINK';
\s*\-\.\-\>\s* return 'DOTTED_ARROW_POINT'; \s*\-\.\-\>\s* return 'LINK';
\s*\-\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE'; \s*\-\.\-[o]\s* return 'LINK';
\s*\-\.\-\s* return 'DOTTED_ARROW_OPEN'; \s*\-\.\-\s* return 'LINK';
\s*.\-[x]\s* return 'DOTTED_ARROW_CROSS'; \s*.\-[x]\s* return 'LINK';
\s*\.\-\>\s* return 'DOTTED_ARROW_POINT'; \s*\.\-\>\s* return 'LINK';
\s*\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE'; \s*\.\-[o]\s* return 'LINK';
\s*\.\-\s* return 'DOTTED_ARROW_OPEN'; \s*\.\-\s* return 'LINK';
\s*\=\=[x]\s* return 'THICK_ARROW_CROSS'; \s*\=\=[x]\s* return 'LINK';
\s*\=\=\>\s* return 'THICK_ARROW_POINT'; \s*\=\=\>\s* return 'LINK';
\s*\=\=[o]\s* return 'THICK_ARROW_CIRCLE'; \s*\=\=[o]\s* return 'LINK';
\s*\=\=[\=]\s* return 'THICK_ARROW_OPEN'; \s*\=\=[\=]\s* return 'LINK';
\s*\<\-\-\s* return 'START_DOUBLE_ARROW_POINT'; \s*\<\-\-\s* return 'START_LINK';
\s*[x]\-\-\s* return 'START_DOUBLE_ARROW_CROSS'; \s*[x]\-\-\s* return 'START_LINK';
\s*[o]\-\-\s* return 'START_DOUBLE_ARROW_CIRCLE'; \s*[o]\-\-\s* return 'START_LINK';
\s*\<\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_POINT'; \s*\<\-\.\s* return 'START_LINK';
\s*[x]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CROSS'; \s*[x]\-\.\s* return 'START_LINK';
\s*[o]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CIRCLE'; \s*[o]\-\.\s* return 'START_LINK';
\s*\<\=\=\s* return 'START_DOUBLE_THICK_ARROW_POINT'; \s*\<\=\=\s* return 'START_LINK';
\s*[x]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CROSS'; \s*[x]\=\=\s* return 'START_LINK';
\s*[o]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CIRCLE'; \s*[o]\=\=\s* return 'START_LINK';
\s*\-\-\s* return '--'; \s*\-\-\s* return 'START_LINK';
\s*\-\.\s* return '-.'; \s*\-\.\s* return 'START_LINK';
\s*\=\=\s* return '=='; \s*\=\=\s* return 'START_LINK';
"(-" return '(-'; "(-" return '(-';
"-)" return '-)'; "-)" return '-)';
"([" return 'STADIUMSTART';
"])" return 'STADIUMEND';
\- return 'MINUS'; \- return 'MINUS';
"." return 'DOT'; "." return 'DOT';
[\_] return 'UNDERSCORE'; [\_] return 'UNDERSCORE';
@@ -92,12 +95,13 @@
"<" return 'TAGSTART'; "<" return 'TAGSTART';
">" return 'TAGEND'; ">" return 'TAGEND';
"^" return 'UP'; "^" return 'UP';
"\|" return 'SEP';
"v" return 'DOWN'; "v" return 'DOWN';
[A-Za-z]+ return 'ALPHA'; [A-Za-z]+ return 'ALPHA';
"\\]" return 'TRAPEND'; "\\]" return 'TRAPEND';
"[/" return 'TRAPSTART'; "[/" return 'TRAPSTART';
"/]" return 'INVTRAPEND'; "/]" return 'INVTRAPEND';
"[\\" return 'INVTRAPSTART'; "[\\" return 'INVTRAPSTART';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION'; [!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]| [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@@ -245,7 +249,7 @@ spaceList
statement statement
: verticeStatement separator : verticeStatement separator
{ $$=$1} { /* console.warn('finat vs', $1.nodes); */ $$=$1.nodes}
| styleStatement separator | styleStatement separator
{$$=[];} {$$=[];}
| linkStyleStatement separator | linkStyleStatement separator
@@ -283,68 +287,49 @@ separator: NEWLINE | SEMI | EOF ;
// {$$ = [$1];yy.setClass($1,$3)} // {$$ = [$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 node: vertex
{ $$ = [$1];} { /* console.warn('nod', $1); */ $$ = [$1];}
| node spaceList vertex
{ $$ = [$1[0], $3]; /*console.warn('pip', $1, $3, $$);*/ }
| vertex STYLE_SEPARATOR idString | vertex STYLE_SEPARATOR idString
{$$ = [$1];yy.setClass($1,$3)} {$$ = [$1];yy.setClass($1,$3)}
; ;
vertex: idString SQS text SQE vertex: idString SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'square');} {$$ = $1;yy.addVertex($1,$3,'square');}
| idString SQS text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'square');}
| idString PS PS text PE PE | idString PS PS text PE PE
{$$ = $1;yy.addVertex($1,$4,'circle');} {$$ = $1;yy.addVertex($1,$4,'circle');}
| idString PS PS text PE PE spaceList
{$$ = $1;yy.addVertex($1,$4,'circle');}
| idString '(-' text '-)' | idString '(-' text '-)'
{$$ = $1;yy.addVertex($1,$3,'ellipse');} {$$ = $1;yy.addVertex($1,$3,'ellipse');}
| idString '(-' text '-)' spaceList | idString STADIUMSTART text STADIUMEND
{$$ = $1;yy.addVertex($1,$3,'ellipse');} {$$ = $1;yy.addVertex($1,$3,'stadium');}
| idString PS text PE | idString PS text PE
{$$ = $1;yy.addVertex($1,$3,'round');} {$$ = $1;yy.addVertex($1,$3,'round');}
| idString PS text PE spaceList
{$$ = $1;yy.addVertex($1,$3,'round');}
| idString DIAMOND_START text DIAMOND_STOP | idString DIAMOND_START text DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$3,'diamond');} {$$ = $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 | idString DIAMOND_START DIAMOND_START text DIAMOND_STOP DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$4,'hexagon');} {$$ = $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 | idString TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');} {$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TAGEND text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'odd');}
| idString TRAPSTART text TRAPEND | idString TRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'trapezoid');} {$$ = $1;yy.addVertex($1,$3,'trapezoid');}
| idString TRAPSTART text TRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'trapezoid');}
| idString INVTRAPSTART text INVTRAPEND | idString INVTRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');} {$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');}
| idString INVTRAPSTART text INVTRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'inv_trapezoid');}
| idString TRAPSTART text INVTRAPEND | idString TRAPSTART text INVTRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_right');} {$$ = $1;yy.addVertex($1,$3,'lean_right');}
| idString TRAPSTART text INVTRAPEND spaceList
{$$ = $1;yy.addVertex($1,$3,'lean_right');}
| idString INVTRAPSTART text TRAPEND | idString INVTRAPSTART text TRAPEND
{$$ = $1;yy.addVertex($1,$3,'lean_left');} {$$ = $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 | idString
{$$ = $1;yy.addVertex($1);} { /*console.warn('h: ', $1);*/$$ = $1;yy.addVertex($1);}
| idString spaceList
{$$ = $1;yy.addVertex($1);}
; ;
@@ -357,92 +342,12 @@ link: linkStatement arrowText
{$1.text = $2;$$ = $1;} {$1.text = $2;$$ = $1;}
| linkStatement | linkStatement
{$$ = $1;} {$$ = $1;}
| '--' text ARROW_POINT | START_LINK text LINK
{$$ = {"type":"arrow","stroke":"normal","text":$2};} {var inf = yy.destructLink($3, $1); $$ = {"type":inf.type,"stroke":inf.stroke,"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};}
; ;
linkStatement: ARROW_POINT linkStatement: LINK
{$$ = {"type":"arrow","stroke":"normal"};} {var inf = yy.destructLink($1);$$ = {"type":inf.type,"stroke":inf.stroke};}
| 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"};}
; ;
arrowText: arrowText:
@@ -530,7 +435,7 @@ styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT |
/* Token lists */ /* Token lists */
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT; textToken : textNoTagsToken | TAGSTART | TAGEND | START_LINK | PCT | DEFAULT;
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ; 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; 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(); const subgraphs = flow.parser.yy.getSubGraphs();
expect(subgraphs.length).toBe(1); expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0]; const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2); expect(subgraph.nodes.length).toBe(2);
expect(subgraph.nodes[0]).toBe('a2'); expect(subgraph.nodes[0]).toBe('a2');
expect(subgraph.nodes[1]).toBe('a1'); expect(subgraph.nodes[1]).toBe('a1');
@@ -191,6 +192,15 @@ describe('when parsing subgraphs', function() {
expect(edges[0].type).toBe('arrow'); 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() { it('should handle nested subgraphs', function() {
const str = const str =
'graph TD\n' + 'graph TD\n' +
@@ -218,6 +228,14 @@ describe('when parsing subgraphs', function() {
const vert = flow.parser.yy.getVertices(); const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges(); 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'); expect(edges[0].type).toBe('arrow');
}); });
}); });

View File

@@ -134,18 +134,32 @@ const getStartDate = function(prevTime, dateFormat, str) {
str = str.trim(); str = str.trim();
// Test for after // Test for after
const re = /^after\s+([\d\w-]+)/; const re = /^after\s+([\d\w- ]+)/;
const afterStatement = re.exec(str.trim()); const afterStatement = re.exec(str.trim());
if (afterStatement !== null) { 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(); const dt = new Date();
dt.setHours(0, 0, 0, 0); dt.setHours(0, 0, 0, 0);
return dt; return dt;
} else {
return latestEndingTask.endTime;
} }
return task.endTime;
} }
// Check for actual date set // Check for actual date set

View File

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

View File

@@ -49,7 +49,7 @@ line
statement statement
: STR VALUE { : STR VALUE {
console.log('str:'+$1+' value: '+$2) /*console.log('str:'+$1+' value: '+$2)*/
yy.addSection($1,yy.cleanupValue($2)); } yy.addSection($1,yy.cleanupValue($2)); }
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);} | 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[0].from).toBe('Alice');
expect(messages[2].from).toBe('John'); 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() { it('it should handle notes over a single actor', function() {
const str = const str =
'sequenceDiagram\n' + 'Alice->Bob: Hello Bob, how are you?\n' + 'Note over Bob: Bob thinks\n'; '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) => { const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0; let textHeight = 0;
const lines = text.split(/<br\/?>/gi); const lines = text.split(/<br ?\/?>/gi);
for (const line of lines) { for (const line of lines) {
const textObj = svgDraw.getTextObj(); const textObj = svgDraw.getTextObj();
textObj.x = x; textObj.x = x;
@@ -233,7 +233,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
let textElem; let textElem;
let counterBreaklines = 0; let counterBreaklines = 0;
let breaklineOffset = 17; let breaklineOffset = 17;
const breaklines = msg.message.split(/<br\/?>/gi); const breaklines = msg.message.split(/<br ?\/?>/gi);
for (const breakline of breaklines) { for (const breakline of breaklines) {
textElem = g textElem = g
.append('text') // text label for the x axis .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) { export const drawText = function(elem, textData) {
// Remove and ignore br:s // Remove and ignore br:s
const nText = textData.text.replace(/<br\/?>/gi, ' '); const nText = textData.text.replace(/<br ?\/?>/gi, ' ');
const textElem = elem.append('text'); const textElem = elem.append('text');
textElem.attr('x', textData.x); textElem.attr('x', textData.x);
@@ -321,7 +321,7 @@ const _drawTextCandidateFunc = (function() {
function byTspan(content, g, x, y, width, height, textAttrs, conf) { function byTspan(content, g, x, y, width, height, textAttrs, conf) {
const { actorFontSize, actorFontFamily } = 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++) { for (let i = 0; i < lines.length; i++) {
const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2; const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2;
const text = g const text = g

View File

@@ -23,23 +23,34 @@ export const setLogLevel = function(level) {
logger.error = () => {}; logger.error = () => {};
logger.fatal = () => {}; logger.fatal = () => {};
if (level <= LEVELS.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) { 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) { 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) { 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) { 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 format = level => {
const time = moment().format('HH:mm:ss.SSS'); const time = moment().format('ss.SSS');
return `${time} : ${level} : `; return `%c${time} : ${level} : `;
}; };

View File

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

View File

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