mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-22 23:49:38 +02:00
@@ -70,7 +70,7 @@ describe('Interaction', () => {
|
||||
expect(location.href).to.eq('http://localhost:9000/webpackUsage.html');
|
||||
});
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
it('should handle a click on a task with a bound function without args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
@@ -78,9 +78,20 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
it('should handle a click on a task with a bound function with args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('rect#cl3')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3');
|
||||
});
|
||||
|
||||
it('should handle a click on a task with a bound function without args', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
@@ -88,8 +99,19 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function with args ', () => {
|
||||
const url = 'http://localhost:9000/click_security_loose.html';
|
||||
cy.viewport(1440, 1024);
|
||||
cy.visit(url);
|
||||
cy.get('body')
|
||||
.find('text#cl3-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Interaction - security level tight', () => {
|
||||
@@ -170,7 +192,7 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
const url = 'http://localhost:9000/click_security_strict.html';
|
||||
@@ -180,7 +202,7 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -226,7 +248,7 @@ describe('Interaction', () => {
|
||||
.find('rect#cl2')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
it('should handle a click on a task with a bound function', () => {
|
||||
const url = 'http://localhost:9000/click_security_strict.html';
|
||||
@@ -236,7 +258,7 @@ describe('Interaction', () => {
|
||||
.find('text#cl2-text')
|
||||
.click({ force: true });
|
||||
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant');
|
||||
cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -228,4 +228,48 @@ describe('Class diagram', () => {
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('9: should render a simple class diagram with clickable link', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
Class03~T~ *-- Class04~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
link class01 "google.com" "A Tooltip"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('10: should render a simple class diagram with clickable callback', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
Class01~T~ <|-- AveryLongClass : Cool
|
||||
Class03~T~ *-- Class04~T~
|
||||
Class01 : size()
|
||||
Class01 : int chimp
|
||||
Class01 : int gorilla
|
||||
Class08 <--> C2: Cool label
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
test()
|
||||
}
|
||||
callback class01 "functionCall" "A Tooltip"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-env jest */
|
||||
import { imgSnapshotTest } from '../../helpers/util';
|
||||
|
||||
describe('Flowcart', () => {
|
||||
describe('Flowchart', () => {
|
||||
it('1: should render a simple flowchart no htmlLabels', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
@@ -57,7 +57,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('4: should style nodes via a class.', () => {
|
||||
it('5: should style nodes via a class.', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -73,7 +73,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('5: should render a flowchart full of circles', () => {
|
||||
it('6: should render a flowchart full of circles', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph LR
|
||||
@@ -102,7 +102,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('6: should render a flowchart full of icons', () => {
|
||||
it('7: should render a flowchart full of icons', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -173,7 +173,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('7: should render labels with numbers at the start', () => {
|
||||
it('8: should render labels with numbers at the start', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB;subgraph "number as labels";1;end;
|
||||
@@ -182,7 +182,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('8: should render subgraphs', () => {
|
||||
it('9: should render subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -194,7 +194,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('9: should render subgraphs with a title starting with a digit', () => {
|
||||
it('10: should render subgraphs with a title starting with a digit', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -206,7 +206,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('10: should render styled subgraphs', () => {
|
||||
it('11: should render styled subgraphs', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TB
|
||||
@@ -241,7 +241,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('11: should render a flowchart with long names and class definitions', () => {
|
||||
it('12: should render a flowchart with long names and class definitions', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
|
||||
@@ -343,7 +343,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('12: should render color of styled nodes', () => {
|
||||
it('13: should render color of styled nodes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph LR
|
||||
@@ -361,7 +361,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('13: should render hexagons', () => {
|
||||
it('14: should render hexagons', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
@@ -383,7 +383,7 @@ describe('Flowcart', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('14: should render a simple flowchart with comments', () => {
|
||||
it('15: should render a simple flowchart with comments', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
@@ -396,7 +396,7 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('15: Render Stadium shape', () => {
|
||||
it('16: Render Stadium shape', () => {
|
||||
imgSnapshotTest(
|
||||
` graph TD
|
||||
A([stadium shape test])
|
||||
@@ -412,7 +412,7 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('16: Render Stadium shape', () => {
|
||||
it('17: Render multiline texts', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A1[Multi<br>Line] -->|Multi<br>Line| B1(Multi<br>Line)
|
||||
@@ -428,7 +428,7 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('17: Chaining of nodes', () => {
|
||||
it('18: Chaining of nodes', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
a --> b --> c
|
||||
@@ -436,21 +436,42 @@ describe('Flowcart', () => {
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('18: Multiple nodes and chaining in one statement', () => {
|
||||
it('19: Multiple nodes and chaining in one statement', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
a --> b c--> d
|
||||
a --> b & c--> d
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('19: Multiple nodes and chaining in one statement', () => {
|
||||
it('20: Multiple nodes and chaining in one statement', () => {
|
||||
imgSnapshotTest(
|
||||
`graph TD
|
||||
A[ h ] -- hello --> B[" test "]:::exClass C --> D;
|
||||
A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
|
||||
classDef exClass background:#bbb,border:1px solid red;
|
||||
`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('21: Render cylindrical shape', () => {
|
||||
imgSnapshotTest(
|
||||
`graph LR
|
||||
A[(cylindrical<br />shape<br />test)]
|
||||
A -->|Get money| B1[(Go shopping 1)]
|
||||
A -->|Get money| B2[(Go shopping 2)]
|
||||
A -->|Get money| B3[(Go shopping 3)]
|
||||
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?)]
|
||||
B1 --> C
|
||||
B2 --> C
|
||||
B3 --> C
|
||||
C -->|One| D[(Laptop)]
|
||||
C -->|Two| E[(iPhone)]
|
||||
C -->|Three| F[(Car)]
|
||||
click A "index.html#link-clicked" "link test"
|
||||
click B testClick "click test"
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;`,
|
||||
{ flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -54,4 +54,44 @@ describe('Sequencediagram', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a gantt chart for issue #1060', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
excludes weekdays 2017-01-10
|
||||
title Projects Timeline
|
||||
|
||||
section asdf
|
||||
specs :done, :ps, 2019-05-10, 50d
|
||||
Plasma :pc, 2019-06-20, 60d
|
||||
Rollup :or, 2019-08-20, 50d
|
||||
|
||||
section CEL
|
||||
|
||||
plasma-chamber :done, :pc, 2019-05-20, 60d
|
||||
Plasma Implementation (Rust) :por, 2019-06-20, 120d
|
||||
Predicates (Atomic Swap) :pred, 2019-07-20, 60d
|
||||
|
||||
section DEX 💰
|
||||
|
||||
History zkSNARK :hs, 2019-08-10, 40d
|
||||
Exit :vs, after hs, 60d
|
||||
PredicateSpec :ps, 2019-09-1, 20d
|
||||
PlasmaIntegration :pi, after ps,40d
|
||||
|
||||
|
||||
section Events 🏁
|
||||
|
||||
ETHBoston :done, :eb, 2019-09-08, 3d
|
||||
DevCon :active, :dc, 2019-10-08, 3d
|
||||
|
||||
section Plasma Calls & updates ✨
|
||||
OVM :ovm, 2019-07-12, 120d
|
||||
Plasma call 26 :pc26, 2019-08-21, 1d
|
||||
Plasma call 27 :pc27, 2019-09-03, 1d
|
||||
Plasma call 28 :pc28, 2019-09-17, 1d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -39,12 +39,15 @@ context('Sequence diagram', () => {
|
||||
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;
|
||||
participant 4 as multiline<br \t/>using #lt;br \t/#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;
|
||||
3->>4: multiline<br />using #lt;br /#gt;
|
||||
note right of 4: multiline<br />using #lt;br /#gt;
|
||||
4->>1: multiline<br />using #lt;br /#gt;
|
||||
note right of 1: multiline<br \t/>using #lt;br \t/#gt;
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
@@ -49,9 +49,11 @@
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
|
||||
Calling a Callback (look at the console log) :cl2, after cl1, 3d
|
||||
Calling a Callback with args :cl3, after cl1, 3d
|
||||
|
||||
click cl1 href "http://localhost:9000/webpackUsage.html"
|
||||
click cl2 call clickByGantt("test", test, test)
|
||||
click cl2 call clickByGantt()
|
||||
click cl3 call clickByGantt("test1", test2, test3)
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
@@ -69,11 +71,14 @@
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
function clickByGantt(elemName) {
|
||||
function clickByGantt(arg1, arg2, arg3) {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'created-by-gant-click'
|
||||
div.style = 'padding: 20px; background: green; color: white;'
|
||||
div.innerText = 'Clicked By Gant'
|
||||
if (arg1) div.innerText += ' ' + arg1;
|
||||
if (arg2) div.innerText += ' ' + arg2;
|
||||
if (arg3) div.innerText += ' ' + arg3;
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
|
@@ -49,9 +49,11 @@
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07,2014-01-10
|
||||
Calling a Callback (look at the console log) :cl2, after cl1, 3d
|
||||
Calling a Callback with args :cl3, after cl1, 3d
|
||||
|
||||
click cl1 href "http://localhost:9000/webpackUsage.html"
|
||||
click cl2 call clickByGantt("test", test, test)
|
||||
click cl2 call clickByGantt()
|
||||
click cl3 call clickByGantt("test1", test2, test3)
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
@@ -69,11 +71,14 @@
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
function clickByGantt(elemName) {
|
||||
function clickByGantt(arg1, arg2, arg3) {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'created-by-gant-click'
|
||||
div.style = 'padding: 20px; background: green; color: white;'
|
||||
div.innerText = 'Clicked By Gant'
|
||||
if (arg1) div.innerText += ' ' + arg1;
|
||||
if (arg2) div.innerText += ' ' + arg2;
|
||||
if (arg3) div.innerText += ' ' + arg3;
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div)
|
||||
}
|
||||
|
25
dist/index.html
vendored
25
dist/index.html
vendored
@@ -313,6 +313,24 @@ class A someclass;
|
||||
classDef someclass fill:#f96;
|
||||
class A someclass;
|
||||
</div>
|
||||
<div class="mermaid">
|
||||
graph LR
|
||||
A[(cylindrical<br />shape<br />test)]
|
||||
A -->|Get money| B1[(Go shopping 1)]
|
||||
A -->|Get money| B2[(Go shopping 2)]
|
||||
A -->|Get money| B3[(Go shopping 3)]
|
||||
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?)]
|
||||
B1 --> C
|
||||
B2 --> C
|
||||
B3 --> C
|
||||
C -->|One| D[(Laptop)]
|
||||
C -->|Two| E[(iPhone)]
|
||||
C -->|Three| F[(Car)]
|
||||
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)
|
||||
@@ -361,12 +379,15 @@ end
|
||||
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;
|
||||
participant 4 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;
|
||||
3->>4: multiline<br />using #lt;br /#gt;
|
||||
note right of 4: multiline<br />using #lt;br /#gt;
|
||||
4->>1: multiline<br />using #lt;br /#gt;
|
||||
note right of 1: multiline<br />using #lt;br /#gt;
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
@@ -89,6 +89,17 @@ graph LR
|
||||
id1([This is the text in the box])
|
||||
```
|
||||
|
||||
### A node in a cylindrical shape
|
||||
|
||||
```
|
||||
graph LR
|
||||
id1[(Database)]
|
||||
```
|
||||
```mermaid
|
||||
graph LR
|
||||
id1[(Database)]
|
||||
```
|
||||
|
||||
### A node in the form of a circle
|
||||
|
||||
```
|
||||
|
@@ -302,3 +302,48 @@ Param | Descriotion | Default value
|
||||
--- | --- | ---
|
||||
mirrorActor|Turns on/off the rendering of actors below the diagram as well as above it|false
|
||||
bottomMarginAdj|Adjusts how far down the graph ended. Wide borders styles with css could generate unwantewd clipping which is why this config param exists.|1
|
||||
|
||||
## Interaction
|
||||
|
||||
It is possible to bind a click event to a task, the click can lead to either a javascript callback or to a link which will be opened in the current browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||
|
||||
```
|
||||
click taskId call callback(arguments)
|
||||
click taskId href URL
|
||||
```
|
||||
|
||||
* taskId is the id of the task
|
||||
* callback is the name of a javascript function defined on the page displaying the graph, the function will be called with the taskId as the parameter if no other arguments are specified..
|
||||
|
||||
Beginners tip, a full example using interactive links in an html context:
|
||||
```
|
||||
<body>
|
||||
<div class="mermaid">
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07, 3d
|
||||
Print arguments :cl2, after cl1, 3d
|
||||
Print task :cl3, after cl2, 3d
|
||||
|
||||
click cl1 href "https://mermaidjs.github.io/"
|
||||
click cl2 call printArguments("test1", "test2", test3)
|
||||
click cl3 call printTask()
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var printArguments = function(arg1, arg2, arg3) {
|
||||
alert('printArguments called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3);
|
||||
}
|
||||
var printTask = function(taskId) {
|
||||
alert('taskId: ' + taskId);
|
||||
}
|
||||
var config = {
|
||||
startOnLoad:true,
|
||||
securityLevel:'loose',
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
@@ -1,8 +1,17 @@
|
||||
import * as d3 from 'd3';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import { logger } from '../../logger';
|
||||
import { getConfig } from '../../config';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = '';
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
let relations = [];
|
||||
let classes = {};
|
||||
|
||||
let funs = [];
|
||||
|
||||
const splitClassNameAndType = function(id) {
|
||||
let genericType = '';
|
||||
let className = id;
|
||||
@@ -29,6 +38,7 @@ export const addClass = function(id) {
|
||||
classes[classId.className] = {
|
||||
id: classId.className,
|
||||
type: classId.type,
|
||||
cssClasses: [],
|
||||
methods: [],
|
||||
members: [],
|
||||
annotations: []
|
||||
@@ -38,6 +48,8 @@ export const addClass = function(id) {
|
||||
export const clear = function() {
|
||||
relations = [];
|
||||
classes = {};
|
||||
funs = [];
|
||||
funs.push(setupToolTips);
|
||||
};
|
||||
|
||||
export const getClass = function(id) {
|
||||
@@ -117,6 +129,91 @@ export const cleanupLabel = function(label) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a special node is found, e.g. a clickable element.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param className Class to add
|
||||
*/
|
||||
export const setCssClass = function(ids, className) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
classes[id].cssClasses.push(className);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param linkStr URL to create a link for
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setLink = function(ids, linkStr, tooltip) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
if (config.securityLevel !== 'loose') {
|
||||
classes[id].link = sanitizeUrl(linkStr);
|
||||
} else {
|
||||
classes[id].link = linkStr;
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
classes[id].tooltip = tooltip;
|
||||
}
|
||||
}
|
||||
});
|
||||
setCssClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a click definition is found. Registers an event handler.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param functionName Function to be called on click
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setClickEvent = function(ids, functionName, tooltip) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
setClickFunc(id, functionName, tooltip);
|
||||
});
|
||||
setCssClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
const setClickFunc = function(_id, functionName) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||
if (config.securityLevel !== 'loose') {
|
||||
return;
|
||||
}
|
||||
if (typeof functionName === 'undefined') {
|
||||
return;
|
||||
}
|
||||
if (typeof classes[id] !== 'undefined') {
|
||||
funs.push(function() {
|
||||
const elem = document.querySelector(`[id="${id}"]`);
|
||||
if (elem !== null) {
|
||||
elem.setAttribute('title', classes[id].tooltip);
|
||||
elem.addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
window[functionName](id);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const bindFunctions = function(element) {
|
||||
funs.forEach(function(fun) {
|
||||
fun(element);
|
||||
});
|
||||
};
|
||||
|
||||
export const lineType = {
|
||||
LINE: 0,
|
||||
DOTTED_LINE: 1
|
||||
@@ -129,8 +226,53 @@ export const relationType = {
|
||||
DEPENDENCY: 3
|
||||
};
|
||||
|
||||
const setupToolTips = function(element) {
|
||||
let tooltipElem = d3.select('.mermaidTooltip');
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = d3
|
||||
.select('body')
|
||||
.append('div')
|
||||
.attr('class', 'mermaidTooltip')
|
||||
.style('opacity', 0);
|
||||
}
|
||||
|
||||
const svg = d3.select(element).select('svg');
|
||||
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function() {
|
||||
const el = d3.select(this);
|
||||
const title = el.attr('title');
|
||||
// Dont try to draw a tooltip if no data is provided
|
||||
if (title === null) {
|
||||
return;
|
||||
}
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', '.9');
|
||||
tooltipElem
|
||||
.html(el.attr('title'))
|
||||
.style('left', rect.left + (rect.right - rect.left) / 2 + 'px')
|
||||
.style('top', rect.top - 14 + document.body.scrollTop + 'px');
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function() {
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0);
|
||||
const el = d3.select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
};
|
||||
funs.push(setupToolTips);
|
||||
|
||||
export default {
|
||||
addClass,
|
||||
bindFunctions,
|
||||
clear,
|
||||
getClass,
|
||||
getClasses,
|
||||
@@ -141,5 +283,8 @@ export default {
|
||||
addMembers,
|
||||
cleanupLabel,
|
||||
lineType,
|
||||
relationType
|
||||
relationType,
|
||||
setClickEvent,
|
||||
setCssClass,
|
||||
setLink
|
||||
};
|
||||
|
@@ -250,6 +250,66 @@ describe('class diagram, ', function () {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with link', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'link Class01 "google.com" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with link and tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'link Class01 "google.com" "A Tooltip" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with callback', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'callback Class01 "functionCall" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle click statement with callback and tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'%% Comment Class01 <|-- Class02\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}\n' +
|
||||
'callback Class01 "functionCall" "A Tooltip" ';
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fetching data from a classDiagram graph it', function () {
|
||||
@@ -464,5 +524,41 @@ describe('class diagram, ', function () {
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()$');
|
||||
});
|
||||
|
||||
it('should associate link and css appropriately', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
it('should associate link with tooltip', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('about:blank');//('google.com'); security needs to be set to 'loose' for this to work right
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', undefined);
|
||||
});
|
||||
|
||||
it('should associate callback with tooltip', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'callback Class1 "functionCall" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall', 'A tooltip');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -280,6 +280,11 @@ const drawEdge = function(elem, path, relation) {
|
||||
const drawClass = function(elem, classDef) {
|
||||
logger.info('Rendering class ' + classDef);
|
||||
|
||||
let cssClassStr = 'classGroup ';
|
||||
if (classDef.cssClasses.length > 0) {
|
||||
cssClassStr = cssClassStr + classDef.cssClasses.join(' ');
|
||||
}
|
||||
|
||||
const addTspan = function(textEl, txt, isFirst) {
|
||||
let displayText = txt;
|
||||
let cssStyle = '';
|
||||
@@ -326,13 +331,25 @@ const drawClass = function(elem, classDef) {
|
||||
const g = elem
|
||||
.append('g')
|
||||
.attr('id', id)
|
||||
.attr('class', 'classGroup');
|
||||
.attr('class', cssClassStr);
|
||||
|
||||
// add title
|
||||
const title = g
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
let title;
|
||||
if (classDef.link) {
|
||||
title = g
|
||||
.append('svg:a')
|
||||
.attr('xlink:href', classDef.link)
|
||||
.attr('xlink:target', '_blank')
|
||||
.attr('xlink:title', classDef.tooltip)
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
} else {
|
||||
title = g
|
||||
.append('text')
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.attr('x', 0);
|
||||
}
|
||||
|
||||
// add annotations
|
||||
let isFirst = true;
|
||||
@@ -348,7 +365,6 @@ const drawClass = function(elem, classDef) {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
|
||||
// add class title
|
||||
const classTitle = title
|
||||
.append('tspan')
|
||||
.text(classTitleString)
|
||||
@@ -434,6 +450,7 @@ export const setConf = function(cnf) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
@@ -470,10 +487,12 @@ export const draw = function(text, id) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const classDef = classes[keys[i]];
|
||||
const node = drawClass(diagram, classDef);
|
||||
|
||||
// Add nodes to the graph. The first argument is the node id. The second is
|
||||
// metadata about the node. In this case we're going to add labels to each of
|
||||
// our nodes.
|
||||
g.setNode(node.id, node);
|
||||
|
||||
logger.info('Org height: ' + node.height);
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,9 @@
|
||||
|
||||
|
||||
"class" return 'CLASS';
|
||||
//"click" return 'CLICK';
|
||||
"callback" return 'CALLBACK';
|
||||
"link" return 'LINK';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
[~] this.begin("generic");
|
||||
@@ -149,6 +152,7 @@ statement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
| clickStatement
|
||||
;
|
||||
|
||||
classStatement
|
||||
@@ -198,6 +202,13 @@ lineType
|
||||
| DOTTED_LINE {$$=yy.lineType.DOTTED_LINE;}
|
||||
;
|
||||
|
||||
clickStatement
|
||||
: CALLBACK className STR {$$ = $1;yy.setClickEvent($2, $3, undefined);}
|
||||
| CALLBACK className STR STR {$$ = $1;yy.setClickEvent($2, $3, $4);}
|
||||
| LINK className STR {$$ = $1;yy.setLink($2, $3, undefined);}
|
||||
| LINK className STR STR {$$ = $1;yy.setLink($2, $3, $4);}
|
||||
;
|
||||
|
||||
commentToken : textToken | graphCodeTokens ;
|
||||
|
||||
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
|
||||
|
@@ -154,10 +154,74 @@ function stadium(parent, bbox, node) {
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
function cylinder(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const rx = w / 2;
|
||||
const ry = rx / (2.5 + w / 50);
|
||||
const h = bbox.height + ry;
|
||||
|
||||
const shape =
|
||||
'M 0,' +
|
||||
ry +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
-w +
|
||||
' 0 l 0,' +
|
||||
h +
|
||||
' a ' +
|
||||
rx +
|
||||
',' +
|
||||
ry +
|
||||
' 0,0,0 ' +
|
||||
w +
|
||||
' 0 l 0,' +
|
||||
-h;
|
||||
|
||||
const shapeSvg = parent
|
||||
.attr('label-offset-y', ry)
|
||||
.insert('path', ':first-child')
|
||||
.attr('d', shape)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + -(h / 2 + ry) + ')');
|
||||
|
||||
node.intersect = function(point) {
|
||||
const pos = dagreD3.intersect.rect(node, point);
|
||||
const x = pos.x - node.x;
|
||||
|
||||
if (
|
||||
rx != 0 &&
|
||||
(Math.abs(x) < node.width / 2 ||
|
||||
(Math.abs(x) == node.width / 2 && Math.abs(pos.y - node.y) > node.height / 2 - ry))
|
||||
) {
|
||||
// ellipsis equation: x*x / a*a + y*y / b*b = 1
|
||||
// solve for y to get adjustion value for pos.y
|
||||
let y = ry * ry * (1 - (x * x) / (rx * rx));
|
||||
if (y != 0) y = Math.sqrt(y);
|
||||
y = ry - y;
|
||||
if (point.y - node.y > 0) y = -y;
|
||||
|
||||
pos.y += y;
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
return shapeSvg;
|
||||
}
|
||||
|
||||
export function addToRender(render) {
|
||||
render.shapes().question = question;
|
||||
render.shapes().hexagon = hexagon;
|
||||
render.shapes().stadium = stadium;
|
||||
render.shapes().cylinder = cylinder;
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().rect_left_inv_arrow = rect_left_inv_arrow;
|
||||
|
@@ -23,6 +23,23 @@ describe('flowchart shapes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// path-based shapes
|
||||
[
|
||||
['cylinder', useWidth, useHeight]
|
||||
].forEach(function([shapeType, getW, getH]) {
|
||||
it(`should add a ${shapeType} shape that renders a properly positioned path 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 }, {});
|
||||
expect(shape.__tag).toEqual('path');
|
||||
expect(shape.__attrs).toHaveProperty('d');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// polygon-based shapes
|
||||
[
|
||||
[
|
||||
|
@@ -608,7 +608,7 @@ const destructLink = (_str, _startStr) => {
|
||||
let startInfo;
|
||||
if (_startStr) {
|
||||
startInfo = destructStartLink(_startStr);
|
||||
console.log(startInfo, info);
|
||||
|
||||
if (startInfo.stroke !== info.stroke) {
|
||||
return { type: 'INVALID', stroke: 'INVALID' };
|
||||
}
|
||||
|
@@ -104,15 +104,6 @@ export const addVertices = function(vert, g, svgId) {
|
||||
vertexNode = svgLabel;
|
||||
}
|
||||
|
||||
// If the node has a link, we wrap it in a SVG link
|
||||
if (vertex.link) {
|
||||
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
|
||||
link.appendChild(vertexNode);
|
||||
vertexNode = link;
|
||||
}
|
||||
|
||||
let radious = 0;
|
||||
let _shape = '';
|
||||
// Set the shape based parameters
|
||||
@@ -157,6 +148,9 @@ export const addVertices = function(vert, g, svgId) {
|
||||
case 'stadium':
|
||||
_shape = 'stadium';
|
||||
break;
|
||||
case 'cylinder':
|
||||
_shape = 'cylinder';
|
||||
break;
|
||||
case 'group':
|
||||
_shape = 'rect';
|
||||
break;
|
||||
@@ -246,7 +240,7 @@ export const addEdges = function(edges, g) {
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
|
||||
} else {
|
||||
edgeData.labelType = 'text';
|
||||
edgeData.label = edge.text.replace(/<br ?\/?>/g, '\n');
|
||||
edgeData.label = edge.text.replace(/<br\s*\/?>/g, '\n');
|
||||
|
||||
if (typeof edge.style === 'undefined') {
|
||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
|
||||
@@ -494,6 +488,39 @@ export const draw = function(text, id) {
|
||||
label.insertBefore(rect, label.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// If node has a link, wrap it in an anchor SVG object.
|
||||
const keys = Object.keys(vert);
|
||||
keys.forEach(function(key) {
|
||||
const vertex = vert[key];
|
||||
|
||||
if (vertex.link) {
|
||||
const node = d3.select('#' + id + ' [id="' + key + '"]');
|
||||
if (node) {
|
||||
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
|
||||
|
||||
const linkNode = node.insert(function() {
|
||||
return link;
|
||||
}, ':first-child');
|
||||
|
||||
const shape = node.select('.label-container');
|
||||
if (shape) {
|
||||
linkNode.append(function() {
|
||||
return shape.node();
|
||||
});
|
||||
}
|
||||
|
||||
const label = node.select('.label');
|
||||
if (label) {
|
||||
linkNode.append(function() {
|
||||
return label.node();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@@ -23,6 +23,7 @@ describe('the flowchart renderer', function() {
|
||||
['circle', 'circle'],
|
||||
['ellipse', 'ellipse'],
|
||||
['stadium', 'stadium'],
|
||||
['cylinder', 'cylinder'],
|
||||
['group', 'rect']
|
||||
].forEach(function([type, expectedShape, expectedRadios = 0]) {
|
||||
it(`should add the correct shaped node to the graph for vertex type ${type}`, function() {
|
||||
|
@@ -37,7 +37,7 @@ describe('when parsing flowcharts', function() {
|
||||
it('should handle chaining of vertices', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A B --> C;
|
||||
A & B --> C;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
@@ -59,7 +59,7 @@ describe('when parsing flowcharts', function() {
|
||||
it('should multiple vertices in link statement in the begining', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A-->B C;
|
||||
A-->B & C;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
@@ -81,7 +81,7 @@ describe('when parsing flowcharts', function() {
|
||||
it('should multiple vertices in link statement at the end', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A B--> C D;
|
||||
A & B--> C & D;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
@@ -112,7 +112,7 @@ describe('when parsing flowcharts', function() {
|
||||
it('should handle chaining of vertices at both ends at once', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A B--> C D;
|
||||
A & B--> C & D;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
@@ -140,10 +140,10 @@ describe('when parsing flowcharts', function() {
|
||||
expect(edges[3].type).toBe('arrow');
|
||||
expect(edges[3].text).toBe('');
|
||||
});
|
||||
it('should handle chaining and multiple nodes in in link statement', function() {
|
||||
it('should handle chaining and multiple nodes in in link statement FVC ', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A --> B C --> D;
|
||||
A --> B & B2 & C --> D2;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
@@ -151,30 +151,39 @@ describe('when parsing flowcharts', function() {
|
||||
|
||||
expect(vert['A'].id).toBe('A');
|
||||
expect(vert['B'].id).toBe('B');
|
||||
expect(vert['B2'].id).toBe('B2');
|
||||
expect(vert['C'].id).toBe('C');
|
||||
expect(vert['D'].id).toBe('D');
|
||||
expect(edges.length).toBe(4);
|
||||
expect(vert['D2'].id).toBe('D2');
|
||||
expect(edges.length).toBe(6);
|
||||
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].end).toBe('B2');
|
||||
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].start).toBe('A');
|
||||
expect(edges[2].end).toBe('C');
|
||||
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].start).toBe('B');
|
||||
expect(edges[3].end).toBe('D2');
|
||||
expect(edges[3].type).toBe('arrow');
|
||||
expect(edges[3].text).toBe('');
|
||||
expect(edges[4].start).toBe('B2');
|
||||
expect(edges[4].end).toBe('D2');
|
||||
expect(edges[4].type).toBe('arrow');
|
||||
expect(edges[4].text).toBe('');
|
||||
expect(edges[5].start).toBe('C');
|
||||
expect(edges[5].end).toBe('D2');
|
||||
expect(edges[5].type).toBe('arrow');
|
||||
expect(edges[5].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;
|
||||
A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
|
||||
classDef exClass background:#bbb,border:1px solid red;
|
||||
`);
|
||||
|
||||
|
@@ -38,6 +38,7 @@
|
||||
\# return 'BRKT';
|
||||
":::" return 'STYLE_SEPARATOR';
|
||||
":" return 'COLON';
|
||||
"&" return 'AMP';
|
||||
";" return 'SEMI';
|
||||
"," return 'COMMA';
|
||||
"*" return 'MULT';
|
||||
@@ -85,6 +86,8 @@
|
||||
"-)" return '-)';
|
||||
"([" return 'STADIUMSTART';
|
||||
"])" return 'STADIUMEND';
|
||||
"[(" return 'CYLINDERSTART';
|
||||
")]" return 'CYLINDEREND';
|
||||
\- return 'MINUS';
|
||||
"." return 'DOT';
|
||||
[\_] return 'UNDERSCORE';
|
||||
@@ -298,8 +301,8 @@ verticeStatement: verticeStatement link node
|
||||
|
||||
node: vertex
|
||||
{ /* console.warn('nod', $1); */ $$ = [$1];}
|
||||
| node spaceList vertex
|
||||
{ $$ = [$1[0], $3]; /*console.warn('pip', $1, $3, $$);*/ }
|
||||
| node spaceList AMP spaceList vertex
|
||||
{ $$ = $1.concat($5); /* console.warn('pip', $1[0], $5, $$); */ }
|
||||
| vertex STYLE_SEPARATOR idString
|
||||
{$$ = [$1];yy.setClass($1,$3)}
|
||||
;
|
||||
@@ -312,6 +315,8 @@ vertex: idString SQS text SQE
|
||||
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
|
||||
| idString STADIUMSTART text STADIUMEND
|
||||
{$$ = $1;yy.addVertex($1,$3,'stadium');}
|
||||
| idString CYLINDERSTART text CYLINDEREND
|
||||
{$$ = $1;yy.addVertex($1,$3,'cylinder');}
|
||||
| idString PS text PE
|
||||
{$$ = $1;yy.addVertex($1,$3,'round');}
|
||||
| idString DIAMOND_START text DIAMOND_STOP
|
||||
@@ -464,9 +469,9 @@ alphaNumStatement
|
||||
{$$='-';}
|
||||
;
|
||||
|
||||
alphaNumToken : PUNCTUATION | UNICODE_TEXT | NUM| ALPHA | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
|
||||
alphaNumToken : PUNCTUATION | AMP | UNICODE_TEXT | NUM| ALPHA | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
|
||||
|
||||
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 | AMP;
|
||||
|
||||
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;
|
||||
graphCodeTokens: STADIUMSTART | STADIUMEND | CYLINDERSTART | CYLINDEREND | 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;
|
||||
%%
|
||||
|
@@ -231,7 +231,7 @@ describe('when parsing subgraphs', function() {
|
||||
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 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();
|
||||
|
@@ -117,7 +117,7 @@ const checkTaskDates = function(task, dateFormat, excludes) {
|
||||
const fixTaskDates = function(startTime, endTime, dateFormat, excludes) {
|
||||
let invalid = false;
|
||||
let renderEndTime = null;
|
||||
while (startTime.date() <= endTime.date()) {
|
||||
while (startTime <= endTime) {
|
||||
if (!invalid) {
|
||||
renderEndTime = endTime.toDate();
|
||||
}
|
||||
@@ -502,6 +502,11 @@ const setClickFun = function(id, functionName, functionArgs) {
|
||||
}
|
||||
}
|
||||
|
||||
/* if no arguments passed into callback, default to passing in id */
|
||||
if (argList.length === 0) {
|
||||
argList.push(id);
|
||||
}
|
||||
|
||||
let rawTask = findTaskById(id);
|
||||
if (typeof rawTask !== 'undefined') {
|
||||
pushFun(id, () => {
|
||||
|
@@ -174,6 +174,26 @@ describe('when using the ganttDb', function() {
|
||||
expect(tasks[6].task).toEqual('test7');
|
||||
});
|
||||
|
||||
it('should work when end date is the 31st', function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.addSection('Task endTime is on the 31st day of the month');
|
||||
ganttDb.addTask('test1', 'id1,2019-09-30,11d');
|
||||
ganttDb.addTask('test2', 'id2,after id1,20d');
|
||||
const tasks = ganttDb.getTasks();
|
||||
|
||||
expect(tasks[0].startTime).toEqual(moment('2019-09-30', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].endTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
|
||||
expect(tasks[0].id).toEqual('id1');
|
||||
expect(tasks[0].task).toEqual('test1');
|
||||
|
||||
expect(tasks[1].startTime).toEqual(moment('2019-10-11', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].endTime).toEqual(moment('2019-10-31', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
|
||||
expect(tasks[1].id).toEqual('id2');
|
||||
expect(tasks[1].task).toEqual('test2');
|
||||
});
|
||||
|
||||
describe('when setting inclusive end dates', function() {
|
||||
beforeEach(function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
|
@@ -106,4 +106,34 @@ describe('when parsing a gantt diagram it', function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
it('should parse callback specifier with no args', function() {
|
||||
spyOn(ganttDb, 'setClickEvent');
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
'section Clickable\n' +
|
||||
'Visit mermaidjs :active, cl1, 2014-01-07, 3d\n' +
|
||||
'Calling a callback :cl2, after cl1, 3d\n\n' +
|
||||
'click cl1 href "https://mermaidjs.github.io/"\n' +
|
||||
'click cl2 call ganttTestClick()\n';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(ganttDb.setClickEvent).toHaveBeenCalledWith('cl2', 'ganttTestClick', null);
|
||||
});
|
||||
it('should parse callback specifier with arbitrary number of args', function() {
|
||||
spyOn(ganttDb, 'setClickEvent');
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
'section Clickable\n' +
|
||||
'Visit mermaidjs :active, cl1, 2014-01-07, 3d\n' +
|
||||
'Calling a callback :cl2, after cl1, 3d\n\n' +
|
||||
'click cl1 href "https://mermaidjs.github.io/"\n' +
|
||||
'click cl2 call ganttTestClick("test0", test1, test2)\n';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
const args = '"test1", "test2", "test3"';
|
||||
expect(ganttDb.setClickEvent).toHaveBeenCalledWith('cl2', 'ganttTestClick', '"test0", test1, test2');
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -339,12 +339,15 @@ describe('when parsing a sequenceDiagram', function() {
|
||||
'participant 1 as multiline<br>text\n' +
|
||||
'participant 2 as multiline<br/>text\n' +
|
||||
'participant 3 as multiline<br />text\n' +
|
||||
'participant 4 as multiline<br \t/>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';
|
||||
'3->>4: multiline<br />text\n' +
|
||||
'note right of 4: multiline<br />text\n' +
|
||||
'4->>1: multiline<br \t/>text\n' +
|
||||
'note right of 1: multiline<br \t/>text\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
@@ -352,6 +355,7 @@ describe('when parsing a sequenceDiagram', function() {
|
||||
expect(actors['1'].description).toBe('multiline<br>text');
|
||||
expect(actors['2'].description).toBe('multiline<br/>text');
|
||||
expect(actors['3'].description).toBe('multiline<br />text');
|
||||
expect(actors['4'].description).toBe('multiline<br \t/>text');
|
||||
|
||||
const messages = parser.yy.getMessages();
|
||||
expect(messages[0].message).toBe('multiline<br>text');
|
||||
@@ -360,6 +364,8 @@ describe('when parsing a sequenceDiagram', function() {
|
||||
expect(messages[3].message).toBe('multiline<br/>text');
|
||||
expect(messages[4].message).toBe('multiline<br />text');
|
||||
expect(messages[5].message).toBe('multiline<br />text');
|
||||
expect(messages[6].message).toBe('multiline<br \t/>text');
|
||||
expect(messages[7].message).toBe('multiline<br \t/>text');
|
||||
});
|
||||
it('it should handle notes over a single actor', function() {
|
||||
const str =
|
||||
|
@@ -168,7 +168,7 @@ export const bounds = {
|
||||
|
||||
const _drawLongText = (text, x, y, g, width) => {
|
||||
let textHeight = 0;
|
||||
const lines = text.split(/<br ?\/?>/gi);
|
||||
const lines = text.split(/<br\s*\/?>/gi);
|
||||
for (const line of lines) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.x = x;
|
||||
@@ -233,7 +233,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
let textElem;
|
||||
let counterBreaklines = 0;
|
||||
let breaklineOffset = 17;
|
||||
const breaklines = msg.message.split(/<br ?\/?>/gi);
|
||||
const breaklines = msg.message.split(/<br\s*\/?>/gi);
|
||||
for (const breakline of breaklines) {
|
||||
textElem = g
|
||||
.append('text') // text label for the x axis
|
||||
|
@@ -18,7 +18,7 @@ export const drawRect = function(elem, rectData) {
|
||||
|
||||
export const drawText = function(elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br ?\/?>/gi, ' ');
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
@@ -321,7 +321,7 @@ const _drawTextCandidateFunc = (function() {
|
||||
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
|
||||
const { actorFontSize, actorFontFamily } = conf;
|
||||
|
||||
const lines = content.split(/<br ?\/?>/gi);
|
||||
const lines = content.split(/<br\s*\/?>/gi);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2;
|
||||
const text = g
|
||||
|
@@ -11,7 +11,8 @@
|
||||
.node rect,
|
||||
.node circle,
|
||||
.node ellipse,
|
||||
.node polygon {
|
||||
.node polygon,
|
||||
.node path {
|
||||
fill: $mainBkg;
|
||||
stroke: $nodeBorder;
|
||||
stroke-width: 1px;
|
||||
|
Reference in New Issue
Block a user