mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge branch 'develop' of github.com:mermaid-js/mermaid into develop
This commit is contained in:
commit
3589bb9597
@ -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 <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
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
12
dist/mermaid.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/mermaid.min.js.map
vendored
2
dist/mermaid.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
||||
|
@ -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\*
|
||||
|
@ -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 |
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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)
|
||||
|
@ -217,7 +217,7 @@ const setClickFunc = function(domId, functionName, tooltip) {
|
||||
elem.addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
window[functionName](elemId);
|
||||
utils.runFunc(functionName, elemId);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
@ -232,7 +232,7 @@ const setClickFun = function(_id, functionName) {
|
||||
elem.addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
window[functionName](id);
|
||||
utils.runFunc(functionName, id);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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
@ -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 {
|
||||
|
@ -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')
|
||||
|
@ -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;
|
||||
|
@ -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 = () => {};
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -34,6 +34,11 @@
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.flowchart-link {
|
||||
stroke: $lineColor;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.edgeLabel {
|
||||
background-color: $edgeLabelBackground;
|
||||
rect {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
137
src/utils.js
137
src/utils.js
@ -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
|
||||
};
|
||||
|
@ -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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user