diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..952020fc8 --- /dev/null +++ b/.github/pull_request_template.md @@ -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 # diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 3247a4541..3ca5d7f51 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -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 diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js index a27f3b50c..4729537cc 100644 --- a/cypress/integration/rendering/flowchart.spec.js +++ b/cypress/integration/rendering/flowchart.spec.js @@ -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...
Do I want something for work,
something to spend every free second with,
or something to get around?]) + C -->|One| D([Laptop]) + C -->|Two| E([iPhone]) + C -->|Three| F([Car
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
Line] -->|Multi
Line| B1(Multi
Line) + C1[Multi
Line] -->|Multi
Line| D1(Multi
Line) + E1[Multi
Line] -->|Multi
Line| F1(Multi
Line) + A2[Multi
Line] -->|Multi
Line| B2(Multi
Line) + C2[Multi
Line] -->|Multi
Line| D2(Multi
Line) + E2[Multi
Line] -->|Multi
Line| F2(Multi
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 } } + ); + }); }); diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index c1bdee040..027c893a7 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -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 + `, + {} + ); + }); }); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index e1912a307..2625d19c1 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -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
using #lt;br#gt; + participant 2 as multiline
using #lt;br/#gt; + participant 3 as multiline
using #lt;br /#gt; + 1->>2: multiline
using #lt;br#gt; + note right of 2: multiline
using #lt;br#gt; + 2->>3: multiline
using #lt;br/#gt; + note right of 3: multiline
using #lt;br/#gt; + 3->>1: multiline
using #lt;br /#gt; + note right of 1: multiline
using #lt;br /#gt; + `, + {} + ); + }); context('background rects', () => { it('should render a single and nested rects', () => { imgSnapshotTest( diff --git a/cypress/platform/current.html b/cypress/platform/current.html index 283deff93..d2e48875c 100644 --- a/cypress/platform/current.html +++ b/cypress/platform/current.html @@ -16,11 +16,16 @@

info below

-
-
graph TD - A ==> B - A --> C - A -.-> D +
+
+ graph TB + A --> B + A ==> C + A .-> D + A === E + A -.- F + D -- Hello --> a + D-- text including R TD space --xb
@@ -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 }); diff --git a/dist/index.html b/dist/index.html index 3da07dd72..0b3db789f 100644 --- a/dist/index.html +++ b/dist/index.html @@ -300,6 +300,31 @@ click B testClick "click test" classDef someclass fill:#f96; class A someclass;
+
+ graph TD + A([stadium shape test]) + A -->|Get money| B([Go shopping]) + B --> C([Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?]) + C -->|One| D([Laptop]) + C -->|Two| E([iPhone]) + C -->|Three| F([Car
wroom wroom]) + click A "index.html#link-clicked" "link test" + click B testClick "click test" + classDef someclass fill:#f96; + class A someclass; +
+
+ graph LR + A1[Multi
Line] -->|Multi
Line| B1(Multi
Line) + C1[Multi
Line] -->|Multi
Line| D1(Multi
Line) + E1[Multi
Line] -->|Multi
Line| F1(Multi
Line) + A2[Multi
Line] -->|Multi
Line| B2(Multi
Line) + C2[Multi
Line] -->|Multi
Line| D2(Multi
Line) + E2[Multi
Line] -->|Multi
Line| F2(Multi
Line) + linkStyle 0 stroke:DarkGray,stroke-width:2px + linkStyle 1 stroke:DarkGray,stroke-width:2px + linkStyle 2 stroke:DarkGray,stroke-width:2px +

@@ -331,6 +356,18 @@ and Alice -->> John: Parallel message 2 end
+
+ sequenceDiagram + participant 1 as multiline
using #lt;br#gt; + participant 2 as multiline
using #lt;br/#gt; + participant 3 as multiline
using #lt;br /#gt; + 1->>2: multiline
using #lt;br#gt; + note right of 2: multiline
using #lt;br#gt; + 2->>3: multiline
using #lt;br/#gt; + note right of 3: multiline
using #lt;br/#gt; + 3->>1: multiline
using #lt;br /#gt; + note right of 1: multiline
using #lt;br /#gt; +

diff --git a/docs/README.md b/docs/README.md index a28e1f21a..59eb159e3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/classDiagram.md b/docs/classDiagram.md index 6e838c6cd..dc61f79cf 100644 --- a/docs/classDiagram.md +++ b/docs/classDiagram.md @@ -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. diff --git a/docs/flowchart.md b/docs/flowchart.md index a8e40a145..7f0603008 100644 --- a/docs/flowchart.md +++ b/docs/flowchart.md @@ -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 ``` diff --git a/docs/gantt.md b/docs/gantt.md index 80421bb35..521e0b27a 100755 --- a/docs/gantt.md +++ b/docs/gantt.md @@ -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 diff --git a/docs/index.html b/docs/index.html index 6a9ac5a27..7dc93708f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -7,7 +7,7 @@ - + + ``` diff --git a/package.json b/package.json index ac7e38630..5106b9cdb 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 6b6f256a5..113da1dee 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -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); diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index 47e983dd6..3140a41a7 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -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()$'); + }); }); }); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 5287c5ee9..58f78ac34 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -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); } diff --git a/src/diagrams/flowchart/flowChartShapes.js b/src/diagrams/flowchart/flowChartShapes.js index ed69c4801..1da63ebe3 100644 --- a/src/diagrams/flowchart/flowChartShapes.js +++ b/src/diagrams/flowchart/flowChartShapes.js @@ -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; diff --git a/src/diagrams/flowchart/flowChartShapes.spec.js b/src/diagrams/flowchart/flowChartShapes.spec.js index de3f05a1d..415a4f026 100644 --- a/src/diagrams/flowchart/flowChartShapes.spec.js +++ b/src/diagrams/flowchart/flowChartShapes.spec.js @@ -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', diff --git a/src/diagrams/flowchart/flowDb.js b/src/diagrams/flowchart/flowDb.js index a0162b3c9..11e7ebf6c 100644 --- a/src/diagrams/flowchart/flowDb.js +++ b/src/diagrams/flowchart/flowDb.js @@ -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 } diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js index 99eda4b3f..524742b39 100644 --- a/src/diagrams/flowchart/flowRenderer.js +++ b/src/diagrams/flowchart/flowRenderer.js @@ -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 = '' + edge.text + ''; - } else { - edgeData.labelType = 'text'; - edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; - edgeData.label = edge.text.replace(/
/g, '\n'); - } + edgeData.labelpos = 'c'; + + if (getConfig().flowchart.htmlLabels) { + edgeData.labelType = 'html'; + edgeData.label = '' + edge.text + ''; } else { - edgeData.label = edge.text.replace(/
/g, '\n'); + edgeData.labelType = 'text'; + edgeData.label = edge.text.replace(/
/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 diff --git a/src/diagrams/flowchart/flowRenderer.spec.js b/src/diagrams/flowchart/flowRenderer.spec.js index e1c1931a6..fe31292cc 100644 --- a/src/diagrams/flowchart/flowRenderer.spec.js +++ b/src/diagrams/flowchart/flowRenderer.spec.js @@ -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
Line' }, + { text: 'Multi
Line' }, + { text: 'Multi
Line' }, + { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi
Line' }, + { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi
Line' }, + { style: ['stroke:DarkGray', 'stroke-width:2px'], text: 'Multi
Line' } + ], + mockG, + 'svg-id' + ); + + addedEdges.forEach(function(edge) { + expect(edge).toHaveProperty('label', 'Multi\nLine'); + expect(edge).toHaveProperty('labelpos', 'c'); + }); + }); + }); }); diff --git a/src/diagrams/flowchart/parser/flow-edges.spec.js b/src/diagrams/flowchart/parser/flow-edges.spec.js index 87bf7730d..111dee9e0 100644 --- a/src/diagrams/flowchart/parser/flow-edges.spec.js +++ b/src/diagrams/flowchart/parser/flow-edges.spec.js @@ -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(); diff --git a/src/diagrams/flowchart/parser/flow-singlenode.spec.js b/src/diagrams/flowchart/parser/flow-singlenode.spec.js index ba9f23925..15f7cc169 100644 --- a/src/diagrams/flowchart/parser/flow-singlenode.spec.js +++ b/src/diagrams/flowchart/parser/flow-singlenode.spec.js @@ -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 diff --git a/src/diagrams/flowchart/parser/flow-text.spec.js b/src/diagrams/flowchart/parser/flow-text.spec.js index 518aa3710..29979ce54 100644 --- a/src/diagrams/flowchart/parser/flow-text.spec.js +++ b/src/diagrams/flowchart/parser/flow-text.spec.js @@ -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(); diff --git a/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js b/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js index db8cfadfe..4436cb4d6 100644 --- a/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js +++ b/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js @@ -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(''); + }); }); diff --git a/src/diagrams/flowchart/parser/flow.jison b/src/diagrams/flowchart/parser/flow.jison index 80a53b01a..eebea9628 100644 --- a/src/diagrams/flowchart/parser/flow.jison +++ b/src/diagrams/flowchart/parser/flow.jison @@ -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; %% diff --git a/src/diagrams/flowchart/parser/subgraph.spec.js b/src/diagrams/flowchart/parser/subgraph.spec.js index f08c6459e..afe6c6d43 100644 --- a/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/src/diagrams/flowchart/parser/subgraph.spec.js @@ -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'); }); }); diff --git a/src/diagrams/gantt/ganttDb.js b/src/diagrams/gantt/ganttDb.js index 90f5066a0..c5b266414 100644 --- a/src/diagrams/gantt/ganttDb.js +++ b/src/diagrams/gantt/ganttDb.js @@ -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 diff --git a/src/diagrams/git/gitGraphRenderer.js b/src/diagrams/git/gitGraphRenderer.js index 267b01c5b..c412538d3 100644 --- a/src/diagrams/git/gitGraphRenderer.js +++ b/src/diagrams/git/gitGraphRenderer.js @@ -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 diff --git a/src/diagrams/pie/parser/pie.jison b/src/diagrams/pie/parser/pie.jison index be6fcf214..2cc4045ff 100644 --- a/src/diagrams/pie/parser/pie.jison +++ b/src/diagrams/pie/parser/pie.jison @@ -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);} ; diff --git a/src/diagrams/sequence/sequenceDiagram.spec.js b/src/diagrams/sequence/sequenceDiagram.spec.js index 398db8e6e..90875948b 100644 --- a/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/src/diagrams/sequence/sequenceDiagram.spec.js @@ -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
text\n' + + 'participant 2 as multiline
text\n' + + 'participant 3 as multiline
text\n' + + '1->>2: multiline
text\n' + + 'note right of 2: multiline
text\n' + + '2->>3: multiline
text\n' + + 'note right of 3: multiline
text\n' + + '3->>1: multiline
text\n' + + 'note right of 1: multiline
text\n'; + + parser.parse(str); + + const actors = parser.yy.getActors(); + expect(actors['1'].description).toBe('multiline
text'); + expect(actors['2'].description).toBe('multiline
text'); + expect(actors['3'].description).toBe('multiline
text'); + + const messages = parser.yy.getMessages(); + expect(messages[0].message).toBe('multiline
text'); + expect(messages[1].message).toBe('multiline
text'); + expect(messages[2].message).toBe('multiline
text'); + expect(messages[3].message).toBe('multiline
text'); + expect(messages[4].message).toBe('multiline
text'); + expect(messages[5].message).toBe('multiline
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'; diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index 86bb6010c..b00ad38f0 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -168,7 +168,7 @@ export const bounds = { const _drawLongText = (text, x, y, g, width) => { let textHeight = 0; - const lines = text.split(//gi); + const lines = text.split(/
/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(//gi); + const breaklines = msg.message.split(/
/gi); for (const breakline of breaklines) { textElem = g .append('text') // text label for the x axis diff --git a/src/diagrams/sequence/svgDraw.js b/src/diagrams/sequence/svgDraw.js index b7c29bd80..523c6cafd 100644 --- a/src/diagrams/sequence/svgDraw.js +++ b/src/diagrams/sequence/svgDraw.js @@ -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(//gi, ' '); + const nText = textData.text.replace(/
/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(//gi); + const lines = content.split(/
/gi); for (let i = 0; i < lines.length; i++) { const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2; const text = g diff --git a/src/logger.js b/src/logger.js index 5de9e320a..e69af084c 100644 --- a/src/logger.js +++ b/src/logger.js @@ -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} : `; }; diff --git a/src/mermaid.js b/src/mermaid.js index 76b644ef2..08bf9af19 100644 --- a/src/mermaid.js +++ b/src/mermaid.js @@ -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 '); }; /** diff --git a/src/themes/gantt.scss b/src/themes/gantt.scss index 06d533ec4..5fb4bb991 100644 --- a/src/themes/gantt.scss +++ b/src/themes/gantt.scss @@ -54,7 +54,7 @@ .grid .tick { stroke: $gridColor; - opacity: 0.3; + opacity: 0.8; shape-rendering: crispEdges; text { font-family: 'trebuchet ms', verdana, arial;