Merge branch 'develop' of github.com:mermaid-js/mermaid into develop

This commit is contained in:
Knut Sveidqvist 2020-06-14 18:41:31 +02:00
commit 3589bb9597
27 changed files with 1527 additions and 704 deletions

View File

@ -20,223 +20,40 @@
</head>
<body>
<h1>info below</h1>
<div class="mermaid" style="width: 100%; height: 20%;">
flowchart LR
user1[fa:fa-user User 1] -- edit -> folder
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
flowchart LR
A{{A}}-- apa -->B{{B}};
click A callback "Tooltip"
click B "http://www.github.com" "This is a link"
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
graph LR
A{{A}}--apa-->B{{B}};
</div>
<div class="mermaid2" style="width: 50%; height: 20%;">
stateDiagram-v2
[*]-->TV
state TV {
state fork_state &lt;&lt;fork&gt;&gt;
[*] --> fork_state
fork_state --> State2
fork_state --> State3
state join_state &lt;&lt;join&gt;&gt;
State2 --> join_state
State3 --> join_state
join_state --> State4
State4 --> [*]
}
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2
[*] --> First
First --> Third
First --> sec
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
thi --> sec
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
flowchart TD
subgraph A
a
end
subgraph B
b
end
subgraph C
subgraph D
d
end
end
A -- oAo --o B
A --> C
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
flowchart TD
subgraph A
a
end
subgraph B
b
end
c-->A
c-->B
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2
[*] --> First
First --> Second
First --> Third
state First {
[*] --> fir
fir --> [*]
}
state Second {
[*] --> sec
sec --> [*]
}
state Third {
[*] --> thi
thi --> [*]
}
</div>
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2
[*]-->TV
state TV {
[*] --> Off: Off to start with
On --> Off : Turn off
Off --> On : Turn on
}
TV--> Console
state Console {
[*] --> Off2: Off to start with
On2--> Off2 : Turn off
Off2 --> On2 : Turn on
On2-->Playing
state Playing {
Alive --> Dead
Dead-->Alive
}
}
<div class="mermaid" style="width: 50%; height: 20%;">
flowchart TB
subgraph 1
A --> B;
A -.-> C;
A ==> D;
A ==> E;
B <--> F
C <--> F
D <--> F
E <--> F
end
subgraph 2
A2 --x B2;
A2 -.-x C2;
A2 ==x D2;
A2 ==x E2;
B2 x--x F2
C2 x--x F2
D2 x--x F2
E2 x--x F2
end
subgraph 3
A3 --o B3;
A3 -.-o C3;
A3 ==o D3;
A3 ==o E3;
B3 o--o F3
C3 o--o F3
D3 o--o F3
E3 o--o F3
end
</div>
<div style="display: flex;flex-direction:column;width: 100%; height: 100%">
<div class="mermaid2" style="width: 100%; height: 100%;">
stateDiagram-v2
state apa {
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
}
</div>
<div class="mermaid2" style="width: 100%; height: 100%">
flowchart TB
a --> b
subgraph id1 [Test]
a --apa--> c
b
c-->b
b-->H
end
G-->H
G-->id1
id1 --> I
I --> G
</div>
<div class="mermaid2" style="width: 100%; height: 100%">
flowchart RL
a --> b
subgraph id1 [Test]
a --apa--> c
b
c-->b
b-->H
end
G-->H
G-->id1
id1 --> I
I --> G
</div>
<div class="mermaid2" style="width: 100%; height: 100%">
flowchart RL
subgraph id1 [Test]
a
end
b-->id1
</div>
<div class="mermaid2" style="width: 100%; height: 100%">
flowchart RL
subgraph id1 [Test1]
a
end
subgraph id2 [Test2]
b
end
a --> id2
a --> b
b-->id1
id1 --> id2
</div>
new:
<div class="mermaid2" style="width: 100%; height: 100%">
flowchart LR
a <--> b
b o--o c
c x--x d
a21([In the box]) --> b2
b2((b2)) --o c2
c2(c2) --x d2 --> id1{{This is the text in the box}} --> A[(cylindrical<br />shape<br />test)]
</div>
old:
<div class="mermaid2" style="width: 100%; height: 100%">
graph LR
a((a)) --> b --> id1{{This is the text in the box}}
A[(cylindrical<br />shape<br />test)]
</div>
</div>
<script src="./mermaid.js"></script>
<script>
mermaid.parseError = function (err, hash) {

12
dist/mermaid.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,15 +3,15 @@
> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project.
## A note to users
Gannt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed.
Gantt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed.
It is important to remember that when a date, day or collection of dates specific to a task are "excluded", the Gannt Chart will accomodate those changes by extending an equal number of day, towards the right, not by creating a gap inside the task.
As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-excluded-days-within.png)
It is important to remember that when a date, day, or collection of dates specific to a task are "excluded", the Gantt Chart will accomodate those changes by extending an equal number of day, towards the right, not by creating a gap inside the task.
As shown here ![](https://raw.githubusercontent.com/NeilCuzon/mermaid/develop/docs/img/Gantt-excluded-days-within.png)
However, if the excluded date/s is between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded date/s.
As shown here ![](https://github.com/NeilCuzon/mermaid/blob/develop/docs/img/Gantt-long-weekend-look.png)
However, if the excluded dates are between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded dates.
As shown here ![](https://raw.githubusercontent.com/NeilCuzon/mermaid/develop/docs/img/Gantt-long-weekend-look.png)
A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days, with a few tweaks.
A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days", with a few tweaks.
Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs.
@ -117,12 +117,14 @@ It is possible to set multiple depenendenies separated by space:
### Title
Tbd
The `title` is an *optional* string to be displayed at the top of the Gantt chart to describe the chart as a whole.
## Sections statements
### Section statements
Tbd
You can divide the chart into various sections, for example to separate different parts of a project like development and documentation.
To do so, start a line with the `section` keyword and give it a name. (Note that unlike with the [title for the entire chart](#title), this name is *required*.
## Setting dates
@ -293,6 +295,19 @@ noteText | Styles for the text on in the note boxes.
}
```
## Today marker
You can style or hide the marker for the current date. To style it, add a value for the `todayMarker` key.
```
todayMarker stroke-width:5px,stroke:#0f0,opacity:0.5
```
To hide the marker, set `todayMarker` to `off`.
```
todayMarker off
```
## Configuration

View File

@ -219,6 +219,11 @@ This sets the font size of the actor's description
This sets the font family of the actor's description
**Default value "Open-Sans", "sans-serif"**.
### actorFontWeight
This sets the font weight of the actor's description
\*\*Default value 400.
### noteFontSize
This sets the font size of actor-attached notes.
@ -229,6 +234,11 @@ This sets the font size of actor-attached notes.
This sets the font family of actor-attached notes.
**Default value "trebuchet ms", verdana, arial**.
### noteFontWeight
This sets the font weight of the note's description
\*\*Default value 400.
### noteAlign
This sets the text alignment of actor-attached notes.
@ -244,6 +254,21 @@ This sets the font size of actor messages.
This sets the font family of actor messages.
**Default value "trebuchet ms", verdana, arial**.
### messageFontWeight
This sets the font weight of the message's description
\*\*Default value 400.
### wrapEnabled
This sets the auto-wrap state for the diagram
\*\*Default value false.
### wrapPadding
This sets the auto-wrap padding for the diagram (sides only)
\*\*Default value 15.
## gantt
The object containing configurations specific for gantt diagrams\*

View File

@ -506,8 +506,11 @@ mermaid.sequenceConfig = {
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1 |
| actorFontSize | Sets the font size for the actor's description | 14 |
| actorFontFamily | Sets the font family for the actor's description | "Open-Sans", "sans-serif" |
| actorFontWeight | Sets the font weight for the actor's description | "Open-Sans", "sans-serif" |
| noteFontSize | Sets the font size for actor-attached notes | 14 |
| noteFontFamily | Sets the font family for actor-attached notes | "trebuchet ms", verdana, arial |
| noteFontWeight | Sets the font weight for actor-attached notes | "trebuchet ms", verdana, arial |
| noteAlign | Sets the text alignment for text in actor-attached notes | center |
| messageFontSize | Sets the font size for actor<->actor messages | 16 |
| messageFontFamily | Sets the font family for actor<->actor messages | "trebuchet ms", verdana, arial |
| messageFontWeight | Sets the font weight for actor<->actor messages | "trebuchet ms", verdana, arial |

View File

@ -112,6 +112,8 @@ Required edgeData for proper rendering:
| label | overlap between label and labelText? |
| labelPos | |
| labelType | overlap between label and labelText? |
| thickness | Sets the thinkess of the edge. Can be ['normal', 'thick'] |
| pattern | Sets the pattern of the edge. Can be ['solid', 'dotted', 'dashed'] |
# Markers

View File

@ -191,11 +191,35 @@ export const insertEdge = function(elem, e, edge, clusterDb, diagramType, graph)
})
.curve(curveBasis);
// Contruct stroke classes based on properties
let strokeClasses;
switch (edge.thickness) {
case 'normal':
strokeClasses = 'edge-thickness-normal';
break;
case 'thick':
strokeClasses = 'edge-thickness-thick';
break;
default:
strokeClasses = '';
}
switch (edge.pattern) {
case 'solid':
strokeClasses += ' edge-pattern-solid';
break;
case 'dotted':
strokeClasses += ' edge-pattern-dotted';
break;
case 'dashed':
strokeClasses += ' edge-pattern-dashed';
break;
}
const svgPath = elem
.append('path')
.attr('d', lineFunction(lineData))
.attr('id', edge.id)
.attr('class', 'transition' + (edge.classes ? ' ' + edge.classes : ''));
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''));
// DEBUG code, adds a red circle at each edge coordinate
// edge.points.forEach(point => {

View File

@ -17,7 +17,7 @@ const extension = (elem, type, id) => {
.append('defs')
.append('marker')
.attr('id', type + '-extensionStart')
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@ -30,7 +30,7 @@ const extension = (elem, type, id) => {
.append('defs')
.append('marker')
.attr('id', type + '-extensionEnd ' + type)
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@ -45,7 +45,7 @@ const composition = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-compositionStart')
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@ -58,7 +58,7 @@ const composition = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-compositionEnd')
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@ -72,7 +72,7 @@ const aggregation = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-aggregationStart')
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@ -85,7 +85,7 @@ const aggregation = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-aggregationEnd')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@ -99,7 +99,7 @@ const dependency = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-dependencyStart')
.attr('class', 'extension ' + type)
.attr('class', 'marker extension ' + type)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
@ -112,7 +112,7 @@ const dependency = (elem, type) => {
.append('defs')
.append('marker')
.attr('id', type + '-dependencyEnd')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
@ -125,13 +125,13 @@ const point = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-pointEnd')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 10)
.attr('refX', 9)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
@ -141,13 +141,13 @@ const point = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-pointStart')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 0)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
.attr('markerHeight', 12)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M 0 5 L 10 10 L 10 0 z')
@ -159,13 +159,13 @@ const circle = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-circleEnd')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 11)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('circle')
.attr('cx', '5')
@ -178,13 +178,13 @@ const circle = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-circleStart')
.attr('class', type)
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', -1)
.attr('refY', 5)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('circle')
.attr('cx', '5')
@ -198,16 +198,16 @@ const cross = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-crossEnd')
.attr('class', type)
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', 12)
.attr('refY', 5.2)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('path')
.attr('stroke', 'black')
// .attr('stroke', 'black')
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 2)
@ -216,16 +216,16 @@ const cross = (elem, type) => {
elem
.append('marker')
.attr('id', type + '-crossStart')
.attr('class', type)
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', -1)
.attr('refY', 5.2)
.attr('markerUnits', 'strokeWidth')
.attr('markerWidth', 7)
.attr('markerHeight', 7)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 11)
.attr('markerHeight', 11)
.attr('orient', 'auto')
.append('path')
.attr('stroke', 'black')
// .attr('stroke', 'black')
.attr('d', 'M 1,1 l 9,9 M 10,1 l -9,9')
.attr('class', 'arrowMarkerPath')
.style('stroke-width', 2)

View File

@ -217,7 +217,7 @@ const setClickFunc = function(domId, functionName, tooltip) {
elem.addEventListener(
'click',
function() {
window[functionName](elemId);
utils.runFunc(functionName, elemId);
},
false
);

View File

@ -232,7 +232,7 @@ const setClickFun = function(_id, functionName) {
elem.addEventListener(
'click',
function() {
window[functionName](id);
utils.runFunc(functionName, id);
},
false
);

View File

@ -217,12 +217,16 @@ export const addEdges = function(edges, g) {
if (typeof defaultLabelStyle !== 'undefined') {
labelStyle = defaultLabelStyle;
}
edgeData.thickness = 'normal';
edgeData.pattern = 'solid';
break;
case 'dotted':
style = 'fill:none;stroke-width:2px;stroke-dasharray:3;';
edgeData.thickness = 'normal';
edgeData.pattern = 'dotted';
break;
case 'thick':
style = ' stroke-width: 3.5px;fill:none';
edgeData.thickness = 'thick';
edgeData.pattern = 'solid';
break;
}
}
@ -262,7 +266,7 @@ export const addEdges = function(edges, g) {
}
edgeData.id = linkId;
edgeData.class = linkNameStart + ' ' + linkNameEnd;
edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd;
// Add the edge to the graph
g.setEdge(edge.start, edge.end, edgeData, cnt);

View File

@ -2,6 +2,7 @@ import moment from 'moment-mini';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger';
import { getConfig } from '../../config';
import utils from '../../utils';
const config = getConfig();
let dateFormat = '';
@ -520,7 +521,7 @@ const setClickFun = function(id, functionName, functionArgs) {
let rawTask = findTaskById(id);
if (typeof rawTask !== 'undefined') {
pushFun(id, () => {
window[functionName](...argList);
utils.runFunc(functionName, ...argList);
});
}
};

View File

@ -16,52 +16,63 @@
%x ID
%x ALIAS
// Directive states
%x OPEN_DIRECTIVE
%x TYPE_DIRECTIVE
%x ARG_DIRECTIVE
// A special state for grabbing text up to the first comment/newline
%x LINE
%%
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE>\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; }
"loop" { this.begin('LINE'); return 'loop'; }
"rect" { this.begin('LINE'); return 'rect'; }
"opt" { this.begin('LINE'); return 'opt'; }
"alt" { this.begin('LINE'); return 'alt'; }
"else" { this.begin('LINE'); return 'else'; }
"par" { this.begin('LINE'); return 'par'; }
"and" { this.begin('LINE'); return 'and'; }
<LINE>[^#\n;]* { this.popState(); return 'restOfLine'; }
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
"over" return 'over';
"note" return 'note';
"activate" { this.begin('ID'); return 'activate'; }
"deactivate" { this.begin('ID'); return 'deactivate'; }
"title" return 'title';
"sequenceDiagram" return 'SD';
"autonumber" return 'autonumber';
"," return ',';
";" return 'NL';
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
"-->" return 'DOTTED_OPEN_ARROW';
\-[x] return 'SOLID_CROSS';
\-\-[x] return 'DOTTED_CROSS';
":"[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NL';
. return 'INVALID';
\%\%\{ { this.begin('OPEN_DIRECTIVE'); return 'open_directive'; }
<OPEN_DIRECTIVE>((?:(?!\}\%\%)[^:.])*) { this.begin('TYPE_DIRECTIVE'); return 'type_directive'; }
<TYPE_DIRECTIVE>":" { this.popState(); this.begin('ARG_DIRECTIVE'); return ':'; }
<TYPE_DIRECTIVE,ARG_DIRECTIVE>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<ARG_DIRECTIVE>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE,ARG_DIRECTIVE,TYPE_DIRECTIVE,OPEN_DIRECTIVE>\#[^\n]* /* skip comments */
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; }
"loop" { this.begin('LINE'); return 'loop'; }
"rect" { this.begin('LINE'); return 'rect'; }
"opt" { this.begin('LINE'); return 'opt'; }
"alt" { this.begin('LINE'); return 'alt'; }
"else" { this.begin('LINE'); return 'else'; }
"par" { this.begin('LINE'); return 'par'; }
"and" { this.begin('LINE'); return 'and'; }
<LINE>(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; }
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
"over" return 'over';
"note" return 'note';
"activate" { this.begin('ID'); return 'activate'; }
"deactivate" { this.begin('ID'); return 'deactivate'; }
"title" return 'title';
"sequenceDiagram" return 'SD';
"autonumber" return 'autonumber';
"," return ',';
";" return 'NL';
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
"-->" return 'DOTTED_OPEN_ARROW';
\-[x] return 'SOLID_CROSS';
\-\-[x] return 'DOTTED_CROSS';
":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NL';
. return 'INVALID';
/lex
@ -74,6 +85,7 @@
start
: SPACE start
| NL start
| directive start
| SD document { yy.apply($2);return $2; }
;
@ -85,11 +97,16 @@ document
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$=[];}
| NL { $$=[]; }
;
directive
: openDirective typeDirective closeDirective 'NL'
| openDirective typeDirective ':' argDirective closeDirective 'NL'
;
statement
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;}
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NL' {$$=$2;}
| signal 'NL'
| autonumber {yy.enableSequenceNumbers()}
@ -99,23 +116,23 @@ statement
| title text2 'NL' {$$=[{type:'setTitle', text:$2}]}
| 'loop' restOfLine document end
{
$3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START});
$3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START});
$3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END});
$$=$3;}
| 'rect' restOfLine document end
{
$3.unshift({type: 'rectStart', color:$2, signalType: yy.LINETYPE.RECT_START });
$3.push({type: 'rectEnd', color:$2, signalType: yy.LINETYPE.RECT_END });
$3.unshift({type: 'rectStart', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_START });
$3.push({type: 'rectEnd', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_END });
$$=$3;}
| opt restOfLine document end
{
$3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START});
$3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END});
$3.unshift({type: 'optStart', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_START});
$3.push({type: 'optEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_END});
$$=$3;}
| alt restOfLine else_sections end
{
// Alt start
$3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START});
$3.unshift({type: 'altStart', altText:yy.parseMessage($2), signalType: yy.LINETYPE.ALT_START});
// Content in alt is already in $3
// End
$3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
@ -123,23 +140,24 @@ statement
| par restOfLine par_sections end
{
// Parallel start
$3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START});
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_START});
// Content in par is already in $3
// End
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
$$=$3;}
| directive
;
par_sections
: document
| document and restOfLine par_sections
{ $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); }
{ $$ = $1.concat([{type: 'and', parText:yy.parseMessage($3), signalType: yy.LINETYPE.PAR_AND}, $4]); }
;
else_sections
: document
| document else restOfLine else_sections
{ $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
{ $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
;
note_statement
@ -195,6 +213,24 @@ signaltype
| DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; }
;
text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
text2
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive'); }
;
%%

View File

@ -1,11 +1,68 @@
import { logger } from '../../logger';
import { getConfig, setConfig } from '../../config';
import mermaidAPI from '../../mermaidAPI';
let prevActor = undefined;
let actors = {};
let messages = [];
const notes = [];
let title = '';
let titleWrapped = false;
let sequenceNumbersEnabled = false;
let wrapEnabled = false;
let configUpdated = false;
let currentDirective = {};
export const parseDirective = function(statement, context) {
try {
if (statement !== undefined) {
statement = statement.trim();
switch (context) {
case 'open_directive':
currentDirective = {};
break;
case 'type_directive':
currentDirective.type = statement.toLowerCase();
break;
case 'arg_directive':
currentDirective.args = JSON.parse(statement);
break;
case 'close_directive':
handleDirective(currentDirective);
currentDirective = null;
break;
}
}
} catch (error) {
logger.error(
`Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}`
);
logger.error(error.message);
}
};
const handleDirective = function(directive) {
logger.debug(`Directive type=${directive.type} with args:`, directive.args);
switch (directive.type) {
case 'init':
case 'initialize':
mermaidAPI.initialize(directive.args);
break;
case 'config':
updateConfig(directive.args);
break;
case 'wrap':
case 'nowrap':
wrapEnabled = directive.type === 'wrap';
break;
default:
logger.warn(
`Unrecognized directive: source: '%%{${directive.type}: ${directive.args}}%%`,
directive
);
break;
}
};
export const addActor = function(id, name, description) {
// Don't allow description nulling
@ -13,9 +70,16 @@ export const addActor = function(id, name, description) {
if (old && name === old.name && description == null) return;
// Don't allow null descriptions, either
if (description == null) description = name;
if (description == null || description.text == null) {
description = { text: name, wrap: null };
}
actors[id] = { name: name, description: description, prevActor: prevActor };
actors[id] = {
name: name,
description: description.text,
wrap: (description.wrap === null && autoWrap()) || !!description.wrap,
prevActor: prevActor
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
}
@ -24,7 +88,7 @@ export const addActor = function(id, name, description) {
};
const activationCount = part => {
let i = 0;
let i;
let count = 0;
for (i = 0; i < messages.length; i++) {
// console.warn(i, messages[i]);
@ -43,12 +107,27 @@ const activationCount = part => {
};
export const addMessage = function(idFrom, idTo, message, answer) {
messages.push({ from: idFrom, to: idTo, message: message, answer: answer });
messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
answer: answer
});
};
export const addSignal = function(idFrom, idTo, message, messageType) {
export const addSignal = function(idFrom, idTo, message = { text: null, wrap: null }, messageType) {
logger.debug(
'Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType
'Adding message from=' +
idFrom +
' to=' +
idTo +
' message=' +
message.text +
' wrap=' +
message.wrap +
' type=' +
messageType
);
if (messageType === LINETYPE.ACTIVE_END) {
@ -56,7 +135,7 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
logger.debug('Adding message from=', messages, cnt);
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
var error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
error.hash = {
text: '->>-',
token: '->>-',
@ -67,7 +146,13 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
throw error;
}
}
messages.push({ from: idFrom, to: idTo, message: message, type: messageType });
messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
type: messageType
});
return true;
};
@ -87,14 +172,43 @@ export const getActorKeys = function() {
export const getTitle = function() {
return title;
};
export const getTitleWrapped = function() {
return titleWrapped;
};
export const enableSequenceNumbers = function() {
sequenceNumbersEnabled = true;
};
export const showSequenceNumbers = () => sequenceNumbersEnabled;
export const enableWrap = function() {
wrapEnabled = true;
};
export const disableWrap = function() {
wrapEnabled = false;
};
export const autoWrap = () => wrapEnabled;
export const clear = function() {
actors = {};
messages = [];
configUpdated = false;
};
export const parseMessage = function(str) {
const _str = str.trim();
return {
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^[:]?(?:no)?wrap:/) === null
? autoWrap()
: _str.match(/^[:]?wrap:/) !== null
? true
: _str.match(/^[:]?nowrap:/) !== null
? false
: autoWrap()
};
};
export const LINETYPE = {
@ -133,7 +247,12 @@ export const PLACEMENT = {
};
export const addNote = function(actor, placement, message) {
const note = { actor: actor, placement: placement, message: message };
const note = {
actor: actor,
placement: placement,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap
};
// Coerce actor into a [to, from, ...] array
const actors = [].concat(actor, actor);
@ -142,14 +261,29 @@ export const addNote = function(actor, placement, message) {
messages.push({
from: actors[0],
to: actors[1],
message: message,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
type: LINETYPE.NOTE,
placement: placement
});
};
export const setTitle = function(titleText) {
title = titleText;
export const setTitle = function(titleWrap) {
title = titleWrap.text;
titleWrapped = (titleWrap.wrap === null && autoWrap()) || !!titleWrap.wrap;
};
export const updateConfig = function(config = getConfig()) {
try {
setConfig(config);
configUpdated = true;
} catch (error) {
logger.error('Error: unable to parse config');
}
};
export const hasConfigChange = function() {
return configUpdated;
};
export const apply = function(param) {
@ -221,14 +355,23 @@ export default {
addActor,
addMessage,
addSignal,
enableWrap,
disableWrap,
enableSequenceNumbers,
showSequenceNumbers,
autoWrap,
getMessages,
getActors,
getActor,
getActorKeys,
getTitle,
parseDirective,
hasConfigChange,
getConfig,
updateConfig,
getTitleWrapped,
clear,
parseMessage,
LINETYPE,
ARROWTYPE,
PLACEMENT,

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import { logger } from '../../logger';
import { parser } from './parser/sequenceDiagram';
import common from '../common/common';
import sequenceDb from './sequenceDb';
import { getConfig } from '../../config';
parser.yy = sequenceDb;
@ -18,13 +19,17 @@ const conf = {
height: 65,
actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"',
// 400 = normal
actorFontWeight: 400,
// Note font settings
noteFontSize: 14,
noteFontFamily: '"trebuchet ms", verdana, arial',
noteFontWeight: 400,
noteAlign: 'center',
// Message font settings
messageFontSize: 16,
messageFontFamily: '"trebuchet ms", verdana, arial',
messageFontWeight: 400,
// Margin around loop boxes
boxMargin: 10,
boxTextMargin: 5,
@ -45,7 +50,12 @@ const conf = {
// text placement as: tspan | fo | old only text as before
textPlacement: 'tspan',
showSequenceNumbers: false
showSequenceNumbers: false,
// wrap text
wrapEnabled: false,
// padding for wrapped text
wrapPadding: 15
};
export const bounds = {
@ -69,6 +79,9 @@ export const bounds = {
stopy: undefined
};
this.verticalPos = 0;
if (parser.yy.hasConfigChange()) {
setConf(getConfig());
}
},
updateVal: function(obj, key, val, fun) {
if (typeof obj[key] === 'undefined') {
@ -138,8 +151,7 @@ export const bounds = {
return activation.actor;
})
.lastIndexOf(message.from.actor);
const activation = this.activations.splice(lastActorActivationIdx, 1)[0];
return activation;
return this.activations.splice(lastActorActivationIdx, 1)[0];
},
newLoop: function(title, fill) {
this.sequenceItems.push({
@ -152,8 +164,7 @@ export const bounds = {
});
},
endLoop: function() {
const loop = this.sequenceItems.pop();
return loop;
return this.sequenceItems.pop();
},
addSectionToLoop: function(message) {
const loop = this.sequenceItems.pop();
@ -175,6 +186,55 @@ export const bounds = {
}
};
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
if (common.lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `);
const nextLineLength = calculateTextWidth(nextLine);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter(line => line !== '').join(joinWith);
};
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
};
const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0;
let prevTextHeight = 0;
@ -187,7 +247,6 @@ const _drawLongText = (text, x, y, g, width) => {
right: 'end',
end: 'end'
};
const lines = text.split(common.lineBreakRegex);
for (const line of lines) {
const textObj = svgDraw.getTextObj();
@ -215,6 +274,7 @@ const _drawLongText = (text, x, y, g, width) => {
.style('text-anchor', alignment)
.style('font-size', conf.noteFontSize)
.style('font-family', conf.noteFontFamily)
.style('font-weight', conf.noteFontWeight)
.attr('dominant-baseline', 'central')
.attr('alignment-baseline', 'central');
@ -263,25 +323,48 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
* @param startx
* @param stopx
* @param verticalPos
* @param txtCenter
* @param msg
* @param sequenceIndex
*/
const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceIndex) {
const g = elem.append('g');
const txtCenter = startx + (stopx - startx) / 2;
let textElems = [];
/*
let textHeight = 0;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
let textElem = g
.append('text') // text label for the x axis
.attr('x', txtCenter)
.attr('y', verticalPos + textHeight)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('font-weight', conf.messageFontWeight)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim());
textElems.push(textElem);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
}
let totalOffset = textHeight;
*/
let counterBreaklines = 0;
let breaklineOffset = 17;
let breaklineOffset = conf.messageFontSize + 4;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
textElems.push(
g
.append('text') // text label for the x axis
.attr('x', txtCenter)
// .attr('y', verticalPos - breaklineVerticalOffset + counterBreaklines * breaklineOffset)
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('font-weight', conf.messageFontWeight)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim())
@ -290,7 +373,6 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
}
const offsetLineCounter = counterBreaklines - 1;
let totalOffset = offsetLineCounter * breaklineOffset;
let textWidths = textElems.map(function(textElem) {
return (textElem._groups || textElem)[0][0].getBBox().width;
});
@ -418,28 +500,42 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
// Draw the actors
let prevWidth = 0;
let prevMargin = 0;
let maxActorHeight = conf.height;
for (let i = 0; i < actorKeys.length; i++) {
const actor = actors[actorKeys[i]];
// Add some rendering data to the object
actor.width = actor.width || calculateActorWidth(actor);
actor.height = conf.height;
actor.height = actor.wrap
? calculateTextHeight(
actor.message,
conf.height,
actor.width,
conf.wrapPadding,
actor.wrap,
conf.actorFontSize
)
: conf.height;
maxActorHeight = Math.max(maxActorHeight, actor.height);
actor.margin = actor.margin || conf.actorMargin;
actor.x = prevWidth + prevMargin;
actor.y = verticalPos;
if (actor.wrap) {
actor.description = wrapLabel(actor.description, actor.width);
}
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, conf.height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width;
prevMargin += actor.margin;
}
// Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(conf.height);
bounds.bumpVerticalPos(maxActorHeight);
};
export const setConf = function(cnf) {
@ -450,7 +546,14 @@ export const setConf = function(cnf) {
});
if (cnf.fontFamily) {
conf.actorFontFamily = conf.noteFontFamily = cnf.fontFamily;
conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily;
}
if (cnf.fontSize) {
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
// conf.height = cnf.fontSize * (65 / 14);
}
if (cnf.fontWeight) {
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
}
};
@ -489,10 +592,50 @@ const calculateActorWidth = function(actor) {
return conf.width;
}
return Math.max(
conf.width,
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
);
return actor.wrap
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
};
/**
* This calculates the text's height, taking into account the wrap value and
* both the statically configured height, width, and the length of the text (in pixels).
*
* If the wrapped text text has greater height, we extend the height, so it's
* value won't overflow.
*
* @return - The height for the given actor
* @param message the text to measure
* @param elementHeight the height of the default bounding box containing the text
* @param elementWidth the width of the default bounding box containing the text
* @param margin space above and below
* @param wrap wrap the text based on: elementWidth - 2 * margin
* @param fontSize
*/
export const calculateTextHeight = function(
message,
elementHeight,
elementWidth,
margin,
wrap,
fontSize
) {
if (!message) {
return elementHeight;
}
let lineHeightFactor = wrap
? wrapLabel(message, elementWidth - 2 * margin).split(common.lineBreakRegex).length
: 1;
return wrap ? Math.max(elementHeight, lineHeightFactor * fontSize) : elementHeight;
};
/**
@ -501,14 +644,16 @@ const calculateActorWidth = function(actor) {
* @param text - The text to calculate the width of
* @param fontSize - The font size of the given text
* @param fontFamily - The font family (one, or more fonts) to render
* @param fontWeight - The font weight (normal, bold, italics)
*/
export const calculateTextWidth = function(text, fontSize, fontFamily) {
export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeight) {
if (!text) {
return 0;
}
fontSize = fontSize ? fontSize : conf.actorFontSize;
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
fontWeight = fontWeight ? fontWeight : conf.actorFontWeight;
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
@ -518,7 +663,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
let maxWidth = 0;
const body = select('body');
// We don'y want to leak DOM elements - if a removal operation isn't available
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return 0;
@ -533,6 +678,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
const textElem = svgDraw
.drawText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
@ -542,7 +688,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders
return maxWidth + 35;
return maxWidth + conf.wrapPadding * 2;
};
/**
@ -568,10 +714,16 @@ export const draw = function(text, id) {
const title = parser.yy.getTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
calculateActorMargins(actors, maxMessageWidthPerActor);
const maxActorHeight = calculateActorMargins(actors, maxMessageWidthPerActor);
drawActors(diagram, actors, actorKeys, 0);
bounds.bumpVerticalPos(
maxActorHeight > conf.height
? Math.min(conf.boxMargin, Math.abs(maxActorHeight - conf.height))
: 0
);
// The arrow head definition is attached to the svg once
svgDraw.insertArrowHead(diagram);
svgDraw.insertArrowCrossHead(diagram);
@ -597,11 +749,10 @@ export const draw = function(text, id) {
// Draw the messages/signals
let sequenceIndex = 1;
messages.forEach(function(msg) {
let loopData;
const noteWidth = Math.max(
conf.width,
calculateTextWidth(msg.message, conf.noteFontSize, conf.noteFontFamily)
);
let loopData,
noteWidth,
textWidth,
shouldWrap = msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message);
switch (msg.type) {
case parser.yy.LINETYPE.NOTE:
@ -609,8 +760,18 @@ export const draw = function(text, id) {
startx = actors[msg.from].x;
stopx = actors[msg.to].x;
textWidth = calculateTextWidth(
msg.message,
conf.noteFontSize,
conf.noteFontFamily,
conf.noteFontWeight
);
noteWidth = shouldWrap ? conf.width : Math.max(conf.width, textWidth);
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
startx + (actors[msg.from].width + conf.actorMargin) / 2,
@ -619,6 +780,9 @@ export const draw = function(text, id) {
noteWidth
);
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
startx - noteWidth + (actors[msg.from].width - conf.actorMargin) / 2,
@ -628,6 +792,9 @@ export const draw = function(text, id) {
);
} else if (msg.to === msg.from) {
// Single-actor over
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
startx + (actors[msg.to].width - noteWidth) / 2,
@ -637,11 +804,17 @@ export const draw = function(text, id) {
);
} else {
// Multi-actor over
forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
forceWidth = Math.abs(startx - stopx) + conf.actorMargin / 2;
if (shouldWrap) {
noteWidth = forceWidth;
msg.message = wrapLabel(msg.message, noteWidth);
} else {
noteWidth = Math.max(forceWidth, textWidth - 2 * conf.noteMargin);
}
let x =
startx < stopx
? startx + (actors[msg.from].width - conf.actorMargin) / 2
: stopx + (actors[msg.to].width - conf.actorMargin) / 2;
? startx + (actors[msg.from].width - conf.actorMargin / 2) / 2
: stopx + (actors[msg.to].width - conf.actorMargin / 2) / 2;
drawNote(diagram, x, bounds.getVerticalPos(), msg, forceWidth);
}
@ -703,6 +876,9 @@ export const draw = function(text, id) {
break;
case parser.yy.LINETYPE.PAR_START:
bounds.bumpVerticalPos(conf.boxMargin);
if (shouldWrap) {
msg.message = wrapLabel(msg.message, conf.boxMargin);
}
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
break;
@ -726,6 +902,15 @@ export const draw = function(text, id) {
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
startx = fromBounds[fromIdx];
stopx = toBounds[toIdx];
if (shouldWrap) {
msg.message = wrapLabel(
msg.message,
Math.max(
Math.abs(stopx - startx) + conf.messageMargin * 2,
conf.width + conf.messageMargin * 2
)
);
}
const verticalPos = bounds.getVerticalPos();
drawMessage(diagram, startx, stopx, verticalPos, msg, sequenceIndex);
@ -823,12 +1008,12 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
const actor = actors[msg.to];
// If this is the first actor, and the message is left of it, no need to calculate the margin
if (msg.placement == parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
if (msg.placement === parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
return;
}
// If this is the last actor, and the message is right of it, no need to calculate the margin
if (msg.placement == parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
return;
}
@ -837,7 +1022,13 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
const messageWidth = calculateTextWidth(msg.message, fontSize, fontFamily);
const fontWeight = isNote ? conf.noteFontWeight : conf.messageFontWeight;
const messageWidth = calculateTextWidth(
msg.wrap ? wrapLabel(msg.message, conf.width - conf.noteMargin) : msg.message,
fontSize,
fontFamily,
fontWeight
);
/*
* The following scenarios should be supported:
@ -855,25 +1046,25 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
* - If the note is on the right of the actor, we should define the current actor
* margin
*/
if (isMessage && msg.from == actor.nextActor) {
if (isMessage && msg.from === actor.nextActor) {
maxMessageWidthPerActor[msg.to] = Math.max(
maxMessageWidthPerActor[msg.to] || 0,
messageWidth
);
} else if (
(isMessage && msg.from == actor.prevActor) ||
msg.placement == parser.yy.PLACEMENT.RIGHTOF
(isMessage && msg.from === actor.prevActor) ||
msg.placement === parser.yy.PLACEMENT.RIGHTOF
) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth
);
} else if (msg.placement == parser.yy.PLACEMENT.LEFTOF) {
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] || 0,
messageWidth
);
} else if (msg.placement == parser.yy.PLACEMENT.OVER) {
} else if (msg.placement === parser.yy.PLACEMENT.OVER) {
if (actor.prevActor) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] || 0,
@ -905,6 +1096,7 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
* @param actorToMessageWidth - A map of actor key -> max message width it holds
*/
const calculateActorMargins = function(actors, actorToMessageWidth) {
let maxHeight = 0;
for (let actorKey in actorToMessageWidth) {
const actor = actors[actorKey];
@ -919,21 +1111,42 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
continue;
}
actor.width = Math.max(
conf.width,
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
);
[actor, nextActor].forEach(function(act) {
act.width = act.wrap
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
act.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
nextActor.width = Math.max(
conf.width,
calculateTextWidth(nextActor.description, conf.actorFontSize, conf.actorFontFamily)
);
act.height = act.wrap
? calculateTextHeight(
act.description,
conf.height,
actor.width,
conf.actorMargin,
act.wrap,
conf.actorFontSize
)
: conf.height;
maxHeight = Math.max(maxHeight, act.height);
});
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
actor.margin = Math.max(actorWidth, conf.actorMargin);
}
Object.keys(actors).forEach(function(key) {
actors[key].height = maxHeight;
});
return maxHeight;
};
export default {

View File

@ -26,6 +26,9 @@ export const drawText = function(elem, textData) {
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
@ -77,7 +80,7 @@ let actorCnt = -1;
* Draws an actor in the diagram with the attaced line
* @param elem - The diagram we'll draw to.
* @param actor - The actor to draw.
* @param config - The sequence diagram config object.
* @param conf - drawText implementation discriminator object
*/
export const drawActor = function(elem, actor, conf) {
const center = actor.x + actor.width / 2;
@ -146,7 +149,7 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
* @param elem - elemenet to append the loop to.
* @param bounds - bounds of the given loop.
* @param labelText - Text within the loop.
* @param config - sequence diagram config object.
* @param conf
*/
export const drawLoop = function(elem, bounds, labelText, conf) {
const g = elem.append('g');
@ -169,11 +172,17 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
});
}
let minSize =
Math.round((3 * conf.fontSize) / 4) < 10 ? conf.fontSize : Math.round((3 * conf.fontSize) / 4);
let txt = getTextObj();
txt.text = labelText;
txt.x = bounds.startx;
txt.y = bounds.starty;
txt.labelMargin = 1.5 * 10; // This is the small box that says "loop"
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
txt.class = 'labelText'; // Its size & position are fixed.
drawLabel(g, txt);
@ -184,23 +193,31 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
txt.y = bounds.starty + 1.5 * conf.boxMargin;
txt.anchor = 'middle';
txt.class = 'loopText';
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
drawText(g, txt);
let textElem = drawText(g, txt);
let textHeight = (textElem._groups || textElem)[0][0].getBBox().height;
if (typeof bounds.sectionTitles !== 'undefined') {
bounds.sectionTitles.forEach(function(item, idx) {
if (item !== '') {
txt.text = '[ ' + item + ' ]';
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
drawText(g, txt);
textElem = drawText(g, txt);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
}
});
}
return textHeight + 4;
};
/**
* Draws a background rectangle
* @param color - The fill color for the background
* @param elem diagram (reference for bounds)
* @param bounds shape of the rectangle
*/
export const drawBackgroundRect = function(elem, bounds) {
const rectElem = drawRect(elem, {
@ -325,7 +342,7 @@ const _drawTextCandidateFunc = (function() {
}
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
const { actorFontSize, actorFontFamily } = conf;
const { actorFontSize, actorFontFamily, actorFontWeight } = conf;
const lines = content.split(common.lineBreakRegex);
for (let i = 0; i < lines.length; i++) {
@ -336,6 +353,7 @@ const _drawTextCandidateFunc = (function() {
.attr('y', y)
.style('text-anchor', 'middle')
.style('font-size', actorFontSize)
.style('font-weight', actorFontWeight)
.style('font-family', actorFontFamily);
text
.append('tspan')

View File

@ -145,10 +145,11 @@ const setupNode = (g, parent, node, altFlag) => {
arrowType: '',
style: 'fill:none',
labelStyle: '',
classes: 'note-edge',
classes: 'transition note-edge',
arrowheadStyle: 'fill: #333',
labelpos: 'c',
labelType: 'text'
labelType: 'text',
thickness: 'normal'
});
} else {
g.setNode(node.id, nodeData);
@ -184,7 +185,9 @@ const setupDoc = (g, parent, doc, altFlag) => {
label: item.description,
arrowheadStyle: 'fill: #333',
labelpos: 'c',
labelType: 'text'
labelType: 'text',
thickness: 'normal',
classes: 'transition'
};
let startId = item.state1.id;
let endId = item.state2.id;

View File

@ -16,7 +16,13 @@ export const logger = {
fatal: () => {}
};
export const setLogLevel = function(level) {
export const setLogLevel = function(level = 'fatal') {
if (isNaN(level)) {
level = level.toLowerCase();
if (LEVELS[level] !== undefined) {
level = LEVELS[level];
}
}
logger.trace = () => {};
logger.debug = () => {};
logger.info = () => {};

View File

@ -285,6 +285,11 @@ const config = {
* **Default value "Open-Sans", "sans-serif"**.
*/
actorFontFamily: '"Open-Sans", "sans-serif"',
/**
* This sets the font weight of the actor's description
* **Default value 400.
*/
actorFontWeight: 400,
/**
* This sets the font size of actor-attached notes.
* **Default value 14**.
@ -295,6 +300,11 @@ const config = {
* **Default value "trebuchet ms", verdana, arial**.
*/
noteFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the note's description
* **Default value 400.
*/
noteFontWeight: 400,
/**
* This sets the text alignment of actor-attached notes.
* **Default value center**.
@ -309,7 +319,22 @@ const config = {
* This sets the font family of actor messages.
* **Default value "trebuchet ms", verdana, arial**.
*/
messageFontFamily: '"trebuchet ms", verdana, arial'
messageFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the message's description
* **Default value 400.
*/
messageFontWeight: 400,
/**
* This sets the auto-wrap state for the diagram
* **Default value false.
*/
wrapEnabled: false,
/**
* This sets the auto-wrap padding for the diagram (sides only)
* **Default value 15.
*/
wrapPadding: 15
},
/**
@ -536,6 +561,11 @@ setLogLevel(config.logLevel);
setConfig(config);
function parse(text) {
const graphInit = utils.detectInit(text);
if (graphInit) {
reinitialize(graphInit);
logger.debug('Init ', graphInit);
}
const graphType = utils.detectType(text);
let parser;
@ -711,6 +741,10 @@ const render = function(id, _txt, cb, container) {
txt = encodeEntities(txt);
const element = select('#d' + id).node();
const graphInit = utils.detectInit(txt);
if (graphInit) {
reinitialize(graphInit);
}
const graphType = utils.detectType(txt);
// insert inline style into svg
@ -919,15 +953,25 @@ const setConf = function(cnf) {
}
};
function initialize(options) {
logger.debug('Initializing mermaidAPI ', pkg.version);
// Update default config with options supplied at initialization
function reinitialize(options) {
if (typeof options === 'object') {
setConf(options);
}
setConfig(config);
setLogLevel(config.logLevel);
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, config });
}
function initialize(options) {
let _config = config;
logger.debug('Initializing mermaidAPI ', { version: pkg.version, options, _config });
// Update default config with options supplied at initialization
if (typeof options === 'object') {
_config = Object.assign(_config, options);
setConf(_config);
}
setConfig(_config);
setLogLevel(_config.logLevel);
}
// function getConfig () {
@ -939,6 +983,7 @@ const mermaidAPI = {
render,
parse,
initialize,
reinitialize,
getConfig
};

View File

@ -1,5 +1,5 @@
$mainBkg: #BDD5EA;
$secondBkg: #6D6D65;
$mainBkg: #1f2020;
$secondBkg: lighten(#1f2020, 16);
$mainContrastColor: lightgrey;
$darkTextColor: #323D47;
$lineColor: $mainContrastColor;
@ -21,18 +21,18 @@ $edgeLabelBackground: #e8e8e8;
$actorBorder: $border1;
$actorBkg: $mainBkg;
$actorTextColor: black;
$actorTextColor: $mainContrastColor;
$actorLineColor: $mainContrastColor;
$signalColor: $mainContrastColor;
$signalTextColor: $mainContrastColor;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $darkTextColor;
$labelTextColor: $mainContrastColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
$activationBorderColor: $border1;
$activationBkgColor: $secondBkg;
$sequenceNumberColor: white;
/* Gantt chart variables */

View File

@ -34,6 +34,11 @@
stroke-width: 1.5px;
}
.flowchart-link {
stroke: $lineColor;
fill: none;
}
.edgeLabel {
background-color: $edgeLabelBackground;
rect {

View File

@ -17,10 +17,38 @@
// --mermaid-alt-font-family: '"Lucida Console", Monaco, monospace';
}
/* Classes common for multiple diagrams */
.error-icon {
fill: $errorBkgColor;
}
.error-text {
fill: $errorTextColor;
stroke: $errorTextColor;
}
}
.edge-thickness-normal {
// stroke: $lineColor;
stroke-width: 2px;
}
.edge-thickness-thick {
// stroke: $lineColor;
stroke-width: 3.5px
}
.edge-pattern-solid {
stroke-dasharray: 0;
}
.edge-pattern-dashed{
stroke-dasharray: 3;
}
.edge-pattern-dotted {
stroke-dasharray: 2;
}
.marker {
fill: $lineColor;
}
.marker.cross {
stroke: $lineColor;
}

View File

@ -3,7 +3,7 @@
fill: $actorBkg;
}
text.actor {
text.actor > tspan {
fill: $actorTextColor;
stroke: none;
}
@ -14,18 +14,19 @@ text.actor {
.messageLine0 {
stroke-width: 1.5;
stroke-dasharray: '2 2';
stroke-dasharray: none;
stroke: $signalColor;
}
.messageLine1 {
stroke-width: 1.5;
stroke-dasharray: '2 2';
stroke-dasharray: 2, 2;
stroke: $signalColor;
}
#arrowhead {
#arrowhead path {
fill: $signalColor;
stroke: $signalColor;
}
.sequenceNumber {
@ -37,13 +38,13 @@ text.actor {
}
#crosshead path {
fill: $signalColor !important;
stroke: $signalColor !important;
fill: $signalColor;
stroke: $signalColor;
}
.messageText {
fill: $signalTextColor;
stroke: none;
stroke: $signalTextColor;
}
.labelBox {
@ -51,20 +52,21 @@ text.actor {
fill: $labelBoxBkgColor;
}
.labelText {
.labelText, .labelText > tspan {
fill: $labelTextColor;
stroke: none;
}
.loopText {
.loopText, .loopText > tspan {
fill: $loopTextColor;
stroke: none;
}
.loopLine {
stroke-width: 2;
stroke-dasharray: '2 2';
stroke-width: 2px;
stroke-dasharray: 2, 2;
stroke: $labelBoxBorderColor;
fill: $labelBoxBorderColor;
}
.note {
@ -74,11 +76,8 @@ text.actor {
}
.noteText {
fill: black;
fill: $actorBkg;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 14px;
}
.activation0 {
@ -95,3 +94,14 @@ text.actor {
fill: $activationBkgColor;
stroke: $activationBorderColor;
}
g > rect[class="note"] {
stroke: $noteBorderColor;
fill: $noteBkgColor;
}
g > text[class="noteText"] > tspan {
stroke: $actorBkg;
fill: $actorBkg;
stroke-width: 1px;
}

View File

@ -28,11 +28,124 @@ const d3CurveTypes = {
curveStepAfter: curveStepAfter,
curveStepBefore: curveStepBefore
};
const directive = /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
const directiveWithoutOpen = /\s*(?:(?:(\w+)(?=:):|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
const anyComment = /\s*%%.*\n/gm;
/**
* @function detectInit
* Detects the init config object from the text
* ```mermaid
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
* graph LR
* a-->b
* b-->c
* c-->d
* d-->e
* e-->f
* f-->g
* g-->h
* ```
* or
* ```mermaid
* %%{initialize: {"theme": "dark", logLevel: "debug" }}%%
* graph LR
* a-->b
* b-->c
* c-->d
* d-->e
* e-->f
* f-->g
* g-->h
* ```
*
* @param {string} text The text defining the graph
* @returns {object} the json object representing the init to pass to mermaid.initialize()
*/
export const detectInit = function(text) {
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
let results = {};
if (Array.isArray(inits)) {
let args = inits.map(init => init.args);
results = Object.assign(results, ...args);
} else {
results = inits.args;
}
return results;
};
/**
* @function detectDirective
* Detects the directive from the text. Text can be single line or multiline. If type is null or omitted
* the first directive encountered in text will be returned
* ```mermaid
* graph LR
* %%{somedirective}%%
* a-->b
* b-->c
* c-->d
* d-->e
* e-->f
* f-->g
* g-->h
* ```
*
* @param {string} text The text defining the graph
* @param {string|RegExp} type The directive to return (default: null
* @returns {object | Array} An object or Array representing the directive(s): { type: string, args: object|null } matchd by the input type
* if a single directive was found, that directive object will be returned.
*/
export const detectDirective = function(text, type = null) {
try {
const commentWithoutDirectives = new RegExp(
`[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`,
'ig'
);
text = text
.trim()
.replace(commentWithoutDirectives, '')
.replace(/'/gm, '"');
logger.debug(
`Detecting diagram directive${type !== null ? ' type:' + type : ''} based on the text:${text}`
);
let match,
result = [];
while ((match = directive.exec(text)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (match.index === directive.lastIndex) {
directive.lastIndex++;
}
if (
(match && !type) ||
(type && match[1] && match[1].match(type)) ||
(type && match[2] && match[2].match(type))
) {
let type = match[1] ? match[1] : match[2];
let args = match[3] ? match[3].trim() : match[4] ? JSON.parse(match[4].trim()) : null;
result.push({ type, args });
}
}
if (result.length === 0) {
result.push({ type: text, args: null });
}
return result.length === 1 ? result[0] : result;
} catch (error) {
logger.error(
`ERROR: ${error.message} - Unable to parse directive${
type !== null ? ' type:' + type : ''
} based on the text:${text}`
);
return { type: null, args: null };
}
};
/**
* @function detectType
* Detects the type of the graph text.
* Detects the type of the graph text. Takes into consideration the possible existence of an %%init
* directive
* ```mermaid
* %%{initialize: {"startOnLoad": true, logLevel: "fatal" }}%%
* graph LR
* a-->b
* b-->c
@ -47,7 +160,7 @@ const d3CurveTypes = {
* @returns {string} A graph definition key
*/
export const detectType = function(text) {
text = text.replace(/^\s*%%.*\n/g, '\n');
text = text.replace(directive, '').replace(anyComment, '\n');
logger.debug('Detecting diagram type based on the text ' + text);
if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence';
@ -127,6 +240,21 @@ export const formatUrl = (linkStr, config) => {
}
};
export const runFunc = (functionName, ...params) => {
var arrPaths = functionName.split('.');
var len = arrPaths.length - 1;
var fnName = arrPaths[len];
var obj = window;
for (var i = 0; i < len; i++) {
obj = obj[arrPaths[i]];
if (!obj) return;
}
obj[fnName](...params);
};
const distance = (p1, p2) =>
p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
@ -255,6 +383,8 @@ export const generateId = () => {
};
export default {
detectInit,
detectDirective,
detectType,
isSubstringInArray,
interpolateToCurve,
@ -262,5 +392,6 @@ export default {
calcCardinalityPosition,
formatUrl,
getStylesFromArray,
generateId
generateId,
runFunc
};

View File

@ -2,23 +2,73 @@
import utils from './utils';
describe('when detecting chart type ', function() {
it('should handle a graph defintion', function() {
it('should handle a graph definition', function() {
const str = 'graph TB\nbfs1:queue';
const type = utils.detectType(str);
expect(type).toBe('flowchart');
});
it('should handle a graph defintion with leading spaces', function() {
it('should handle an initialize definition', function() {
const str = `
%%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%%
sequenceDiagram
Alice->Bob: hi`;
const type = utils.detectType(str);
const init = utils.detectInit(str);
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark"});
});
it('should handle an init definition', function() {
const str = `
%%{init: { 'logLevel': 0, 'theme': 'dark' }}%%
sequenceDiagram
Alice->Bob: hi`;
const type = utils.detectType(str);
const init = utils.detectInit(str);
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark"});
});
it('should handle a multiline init definition', function() {
const str = `
%%{
init: {
'logLevel': 0,
'theme': 'dark'
}
}%%
sequenceDiagram
Alice->Bob: hi`;
const type = utils.detectType(str);
const init = utils.detectInit(str);
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark"});
});
it('should handle multiple init directives', function() {
const str = `
%%{ init: { 'logLevel': 0, 'theme': 'forest' } }%%
%%{
init: {
'theme': 'dark'
}
}%%
sequenceDiagram
Alice->Bob: hi`;
const type = utils.detectType(str);
const init = utils.detectInit(str);
expect(type).toBe('sequence');
expect(init).toEqual({logLevel:0,theme:"dark"});
});
it('should handle a graph definition with leading spaces', function() {
const str = ' graph TB\nbfs1:queue';
const type = utils.detectType(str);
expect(type).toBe('flowchart');
});
it('should handle a graph defintion with leading spaces and newline', function() {
it('should handle a graph definition with leading spaces and newline', function() {
const str = ' \n graph TB\nbfs1:queue';
const type = utils.detectType(str);
expect(type).toBe('flowchart');
});
it('should handle a graph defintion for gitGraph', function() {
it('should handle a graph definition for gitGraph', function() {
const str = ' \n gitGraph TB:\nbfs1:queue';
const type = utils.detectType(str);
expect(type).toBe('git');