mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge pull request #1365 from dany74q/feature/sequence-diagrams-improvements
Feature/sequence diagrams improvements
This commit is contained in:
commit
3aa5fc0cc2
@ -52,6 +52,138 @@ context('Sequence diagram', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render loops with a slight margin', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
loop Loopy
|
||||
Bob->>Alice: Pasten
|
||||
end `,
|
||||
{}
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
it('should render different note fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I should have bigger fonts
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteFontSize: 18, noteFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render different message fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { messageFontSize: 18, messageFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render different actor fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { actorFontSize: 18, actorFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should render notes aligned to the left when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I am left aligned
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteAlign: 'left' } }
|
||||
);
|
||||
});
|
||||
it('should render notes aligned to the right when configured', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: I am right aligned
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ sequence: { noteAlign: 'right' } }
|
||||
);
|
||||
});
|
||||
});
|
||||
context('auth width scaling', () => {
|
||||
it('should render long actor descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes left of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note left of Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes right of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note right of Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes over actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note over Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the left to one to the right', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the right to one to the left', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
context('background rects', () => {
|
||||
it('should render a single and nested rects', () => {
|
||||
imgSnapshotTest(
|
||||
@ -162,7 +294,7 @@ context('Sequence diagram', () => {
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
`,
|
||||
{sequence: { actorMargin: 50, showSequenceNumbers: true }}
|
||||
{ sequence: { actorMargin: 50, showSequenceNumbers: true } }
|
||||
);
|
||||
});
|
||||
it('should render autonumber when autonumber keyword is used', () => {
|
||||
@ -179,7 +311,7 @@ context('Sequence diagram', () => {
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
`,
|
||||
{}
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render autonumber with different line breaks', () => {
|
||||
@ -192,7 +324,7 @@ context('Sequence diagram', () => {
|
||||
John-->>Alice: Hi Alice,<br />I can hear you!
|
||||
John-->>Alice: I feel great!
|
||||
`,
|
||||
{}
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
John-->>Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
@ -31,6 +32,7 @@ sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
John-->>Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant John
|
||||
@ -50,6 +52,7 @@ sequenceDiagram
|
||||
A->>J: Hello John, how are you?
|
||||
J->>A: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Alice
|
||||
@ -68,14 +71,14 @@ Messages can be of two displayed either solid or with a dotted line.
|
||||
|
||||
There are six types of arrows currently supported:
|
||||
|
||||
Type | Description
|
||||
--- | ---
|
||||
-> | Solid line without arrow
|
||||
--> | Dotted line without arrow
|
||||
->> | Solid line with arrowhead
|
||||
-->> | Dotted line with arrowhead
|
||||
-x | Solid line with a cross at the end (async)
|
||||
--x | Dotted line with a cross at the end (async)
|
||||
| Type | Description |
|
||||
| ---- | ------------------------------------------- |
|
||||
| -> | Solid line without arrow |
|
||||
| --> | Dotted line without arrow |
|
||||
| ->> | Solid line with arrowhead |
|
||||
| -->> | Dotted line with arrowhead |
|
||||
| -x | Solid line with a cross at the end (async) |
|
||||
| --x | Dotted line with a cross at the end (async) |
|
||||
|
||||
## Activations
|
||||
|
||||
@ -88,6 +91,7 @@ sequenceDiagram
|
||||
John-->>Alice: Great!
|
||||
deactivate John
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
@ -103,6 +107,7 @@ sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
John-->>-Alice: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
@ -118,6 +123,7 @@ sequenceDiagram
|
||||
John-->>-Alice: Hi Alice, I can hear you!
|
||||
John-->>-Alice: I feel great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
@ -138,6 +144,7 @@ sequenceDiagram
|
||||
participant John
|
||||
Note right of John: Text in note
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant John
|
||||
@ -151,6 +158,7 @@ sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
Note over Alice,John: A typical interaction
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
@ -176,6 +184,7 @@ sequenceDiagram
|
||||
John-->Alice: Great!
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->John: Hello John, how are you?
|
||||
@ -218,6 +227,7 @@ sequenceDiagram
|
||||
Bob->>Alice: Thanks for asking
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob, how are you?
|
||||
@ -281,11 +291,13 @@ sequenceDiagram
|
||||
It is possible to highlight flows by providing colored background rects. This is done by the notation
|
||||
|
||||
The colors are defined using rgb and rgba syntax.
|
||||
|
||||
```
|
||||
rect rgb(0, 255, 0)
|
||||
... content ...
|
||||
end
|
||||
```
|
||||
|
||||
```
|
||||
rect rgba(0, 0, 255, .1)
|
||||
... content ...
|
||||
@ -327,6 +339,7 @@ sequenceDiagram
|
||||
## sequenceNumbers
|
||||
|
||||
It is possible to get a sequence number attached to each arrow in a sequence diagram. This can be configured when adding mermaid to the website as shown below:
|
||||
|
||||
```
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
@ -336,6 +349,7 @@ It is possible to get a sequence number attached to each arrow in a sequence dia
|
||||
```
|
||||
|
||||
It can also be be turned on via the diagram code as in the diagram:
|
||||
|
||||
```
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
@ -348,6 +362,7 @@ sequenceDiagram
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
@ -367,20 +382,20 @@ Styling of a sequence diagram is done by defining a number of css classes. Durin
|
||||
|
||||
### Classes used
|
||||
|
||||
Class | Description
|
||||
--- | ---
|
||||
actor | Style for the actor box at the top of the diagram.
|
||||
text.actor | Styles for text in the actor box at the top of the diagram.
|
||||
actor-line | The vertical line for an actor.
|
||||
messageLine0 | Styles for the solid message line.
|
||||
messageLine1 | Styles for the dotted message line.
|
||||
messageText | Defines styles for the text on the message arrows.
|
||||
labelBox | Defines styles label to left in a loop.
|
||||
labelText | Styles for the text in label for loops.
|
||||
loopText | Styles for the text in the loop box.
|
||||
loopLine | Defines styles for the lines in the loop box.
|
||||
note | Styles for the note box.
|
||||
noteText | Styles for the text on in the note boxes.
|
||||
| Class | Description |
|
||||
| ------------ | ----------------------------------------------------------- |
|
||||
| actor | Style for the actor box at the top of the diagram. |
|
||||
| text.actor | Styles for text in the actor box at the top of the diagram. |
|
||||
| actor-line | The vertical line for an actor. |
|
||||
| messageLine0 | Styles for the solid message line. |
|
||||
| messageLine1 | Styles for the dotted message line. |
|
||||
| messageText | Defines styles for the text on the message arrows. |
|
||||
| labelBox | Defines styles label to left in a loop. |
|
||||
| labelText | Styles for the text in label for loops. |
|
||||
| loopText | Styles for the text in the loop box. |
|
||||
| loopLine | Defines styles for the lines in the loop box. |
|
||||
| note | Styles for the note box. |
|
||||
| noteText | Styles for the text on in the note boxes. |
|
||||
|
||||
### Sample stylesheet
|
||||
|
||||
@ -390,79 +405,78 @@ body {
|
||||
}
|
||||
|
||||
.actor {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF;
|
||||
stroke: #ccccff;
|
||||
fill: #ececff;
|
||||
}
|
||||
text.actor {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: Helvetica;
|
||||
}
|
||||
|
||||
.actor-line {
|
||||
stroke:grey;
|
||||
stroke: grey;
|
||||
}
|
||||
|
||||
.messageLine0 {
|
||||
stroke-width:1.5;
|
||||
stroke-dasharray: "2 2";
|
||||
marker-end:"url(#arrowhead)";
|
||||
stroke:black;
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
marker-end: 'url(#arrowhead)';
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
.messageLine1 {
|
||||
stroke-width:1.5;
|
||||
stroke-dasharray: "2 2";
|
||||
stroke:black;
|
||||
stroke-width: 1.5;
|
||||
stroke-dasharray: '2 2';
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
#arrowhead {
|
||||
fill:black;
|
||||
|
||||
fill: black;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-size:14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.labelBox {
|
||||
stroke: #CCCCFF;
|
||||
fill: #ECECFF;
|
||||
stroke: #ccccff;
|
||||
fill: #ececff;
|
||||
}
|
||||
|
||||
.labelText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
|
||||
.loopText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
}
|
||||
|
||||
.loopLine {
|
||||
stroke-width:2;
|
||||
stroke-dasharray: "2 2";
|
||||
marker-end:"url(#arrowhead)";
|
||||
stroke: #CCCCFF;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: '2 2';
|
||||
marker-end: 'url(#arrowhead)';
|
||||
stroke: #ccccff;
|
||||
}
|
||||
|
||||
.note {
|
||||
stroke: #decc93;
|
||||
stroke: #CCCCFF;
|
||||
stroke: #ccccff;
|
||||
fill: #fff5ad;
|
||||
}
|
||||
|
||||
.noteText {
|
||||
fill:black;
|
||||
stroke:none;
|
||||
fill: black;
|
||||
stroke: none;
|
||||
font-family: 'trebuchet ms', verdana, arial;
|
||||
font-size:14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
@ -476,18 +490,25 @@ How to use the CLI is described in the [mermaidCLI](mermaidCLI.html) page.
|
||||
|
||||
```javascript
|
||||
mermaid.sequenceConfig = {
|
||||
diagramMarginX:50,
|
||||
diagramMarginY:10,
|
||||
boxTextMargin:5,
|
||||
noteMargin:10,
|
||||
messageMargin:35,
|
||||
mirrorActors:true
|
||||
diagramMarginX: 50,
|
||||
diagramMarginY: 10,
|
||||
boxTextMargin: 5,
|
||||
noteMargin: 10,
|
||||
messageMargin: 35,
|
||||
mirrorActors: true
|
||||
};
|
||||
```
|
||||
|
||||
### Possible configuration params:
|
||||
|
||||
Param | Description | Default value
|
||||
--- | --- | ---
|
||||
mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false
|
||||
bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1
|
||||
| Param | Description | Default value |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
|
||||
| mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false |
|
||||
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate 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" |
|
||||
| noteFontSize | Sets the font size for actor-attached notes | 14 |
|
||||
| noteFontFamily | Sets the font family 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 |
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let prevActor = undefined;
|
||||
let actors = {};
|
||||
let messages = [];
|
||||
const notes = [];
|
||||
let title = '';
|
||||
let sequenceNumbersEnabled = false;
|
||||
|
||||
export const addActor = function(id, name, description) {
|
||||
// Don't allow description nulling
|
||||
const old = actors[id];
|
||||
@ -13,7 +15,12 @@ export const addActor = function(id, name, description) {
|
||||
// Don't allow null descriptions, either
|
||||
if (description == null) description = name;
|
||||
|
||||
actors[id] = { name: name, description: description };
|
||||
actors[id] = { name: name, description: description, prevActor: prevActor };
|
||||
if (prevActor && actors[prevActor]) {
|
||||
actors[prevActor].nextActor = id;
|
||||
}
|
||||
|
||||
prevActor = id;
|
||||
};
|
||||
|
||||
const activationCount = part => {
|
||||
|
@ -19,6 +19,13 @@ const conf = {
|
||||
height: 65,
|
||||
actorFontSize: 14,
|
||||
actorFontFamily: '"Open-Sans", "sans-serif"',
|
||||
// Note font settings
|
||||
noteFontSize: 14,
|
||||
noteFontFamily: '"trebuchet ms", verdana, arial',
|
||||
noteAlign: 'center',
|
||||
// Message font settings
|
||||
messageFontSize: 16,
|
||||
messageFontFamily: '"trebuchet ms", verdana, arial',
|
||||
// Margin around loop boxes
|
||||
boxMargin: 10,
|
||||
boxTextMargin: 5,
|
||||
@ -171,26 +178,62 @@ export const bounds = {
|
||||
|
||||
const _drawLongText = (text, x, y, g, width) => {
|
||||
let textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
|
||||
const alignmentToAnchor = {
|
||||
left: 'start',
|
||||
start: 'start',
|
||||
center: 'middle',
|
||||
middle: 'middle',
|
||||
right: 'end',
|
||||
end: 'end'
|
||||
};
|
||||
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
for (const line of lines) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.x = x;
|
||||
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
|
||||
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
textObj.x = x + conf.noteMargin;
|
||||
break;
|
||||
case 'middle':
|
||||
textObj.x = x + width / 2;
|
||||
break;
|
||||
case 'end':
|
||||
textObj.x = x + width - conf.noteMargin;
|
||||
break;
|
||||
}
|
||||
|
||||
textObj.y = y + textHeight;
|
||||
textObj.textMargin = conf.noteMargin;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = line;
|
||||
textObj.class = 'noteText';
|
||||
const textElem = svgDraw.drawText(g, textObj, width);
|
||||
|
||||
const textElem = svgDraw
|
||||
.drawText(g, textObj)
|
||||
.style('text-anchor', alignment)
|
||||
.style('font-size', conf.noteFontSize)
|
||||
.style('font-family', conf.noteFontFamily)
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
textElem.attr('y', y + (prevTextHeight + textHeight + 2 * conf.noteMargin) / 2);
|
||||
prevTextHeight = textHeight;
|
||||
}
|
||||
|
||||
return textHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param center - The center of the the actor
|
||||
* @param pos The position if the actor in the liost of actors
|
||||
* @param description The text in the box
|
||||
* Draws an note in the diagram with the attaced line
|
||||
* @param elem - The diagram to draw to.
|
||||
* @param startx - The x axis start position.
|
||||
* @param verticalPos - The y axis position.
|
||||
* @param msg - The message to be drawn.
|
||||
* @param forceWidth - Set this with a custom width to override the default configured width.
|
||||
*/
|
||||
const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
||||
const rect = svgDraw.getNoteRect();
|
||||
@ -202,13 +245,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
||||
let g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
|
||||
const textHeight = _drawLongText(
|
||||
msg.message,
|
||||
startx - 4,
|
||||
verticalPos + 24,
|
||||
g,
|
||||
rect.width - conf.noteMargin
|
||||
);
|
||||
const textHeight = _drawLongText(msg.message, startx, verticalPos, g, rect.width);
|
||||
|
||||
bounds.insert(
|
||||
startx,
|
||||
@ -216,6 +253,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
||||
startx + rect.width,
|
||||
verticalPos + 2 * conf.noteMargin + textHeight
|
||||
);
|
||||
|
||||
rectElem.attr('height', textHeight + 2 * conf.noteMargin);
|
||||
bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin);
|
||||
};
|
||||
@ -243,6 +281,8 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
|
||||
.style('font-size', conf.messageFontSize)
|
||||
.style('font-family', conf.messageFontFamily)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'messageText')
|
||||
.text(breakline.trim())
|
||||
@ -250,7 +290,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
counterBreaklines++;
|
||||
}
|
||||
const offsetLineCounter = counterBreaklines - 1;
|
||||
const totalOffset = offsetLineCounter * breaklineOffset;
|
||||
let totalOffset = offsetLineCounter * breaklineOffset;
|
||||
|
||||
let textWidths = textElems.map(function(textElem) {
|
||||
return (textElem._groups || textElem)[0][0].getBBox().width;
|
||||
@ -278,6 +318,8 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
totalOffset} H ${startx}`
|
||||
);
|
||||
} else {
|
||||
totalOffset += 5;
|
||||
|
||||
line = g
|
||||
.append('path')
|
||||
.attr(
|
||||
@ -375,18 +417,26 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
|
||||
export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
||||
// Draw the actors
|
||||
let prevWidth = 0;
|
||||
let prevMargin = 0;
|
||||
|
||||
for (let i = 0; i < actorKeys.length; i++) {
|
||||
const key = actorKeys[i];
|
||||
const actor = actors[actorKeys[i]];
|
||||
|
||||
// Add some rendering data to the object
|
||||
actors[key].x = i * conf.actorMargin + i * conf.width;
|
||||
actors[key].y = verticalPos;
|
||||
actors[key].width = conf.diagramMarginX;
|
||||
actors[key].height = conf.diagramMarginY;
|
||||
actor.width = actor.width || calculateActorWidth(actor);
|
||||
actor.height = conf.height;
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
actor.y = verticalPos;
|
||||
|
||||
// Draw the box with the attached line
|
||||
svgDraw.drawActor(diagram, actors[key].x, verticalPos, actors[key].description, conf);
|
||||
bounds.insert(actors[key].x, verticalPos, actors[key].x + conf.width, conf.height);
|
||||
svgDraw.drawActor(diagram, actor, conf);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, conf.height);
|
||||
|
||||
prevWidth += actor.width;
|
||||
prevMargin += actor.margin;
|
||||
}
|
||||
|
||||
// Add a margin between the actor boxes and the first arrow
|
||||
@ -399,7 +449,10 @@ export const setConf = function(cnf) {
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
conf.actorFontFamily = cnf.fontFamily;
|
||||
|
||||
if (cnf.fontFamily) {
|
||||
conf.actorFontFamily = conf.noteFontFamily = cnf.fontFamily;
|
||||
}
|
||||
};
|
||||
|
||||
const actorActivations = function(actor) {
|
||||
@ -410,18 +463,89 @@ const actorActivations = function(actor) {
|
||||
|
||||
const actorFlowVerticaBounds = function(actor) {
|
||||
// handle multiple stacked activations for same actor
|
||||
const actors = parser.yy.getActors();
|
||||
const actorObj = parser.yy.getActors()[actor];
|
||||
const activations = actorActivations(actor);
|
||||
|
||||
const left = activations.reduce(function(acc, activation) {
|
||||
return Math.min(acc, activation.startx);
|
||||
}, actors[actor].x + conf.width / 2);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
const right = activations.reduce(function(acc, activation) {
|
||||
return Math.max(acc, activation.stopx);
|
||||
}, actors[actor].x + conf.width / 2);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
return [left, right];
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the actor's width, taking into account both the statically configured width,
|
||||
* and the actor's description.
|
||||
*
|
||||
* If the description text has greater length, we extend the width of the actor, so it's description
|
||||
* won't overflow.
|
||||
*
|
||||
* @param actor - An actor object
|
||||
* @return - The width for the given actor
|
||||
*/
|
||||
const calculateActorWidth = function(actor) {
|
||||
if (!actor.description) {
|
||||
return conf.width;
|
||||
}
|
||||
|
||||
return Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the width of the given text, font size and family.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
export const calculateTextWidth = function(text, fontSize, fontFamily) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fontSize = fontSize ? fontSize : conf.actorFontSize;
|
||||
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
|
||||
|
||||
// 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
|
||||
// of sans-serif.
|
||||
const fontFamilies = ['sans-serif', fontFamily];
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
let maxWidth = 0;
|
||||
|
||||
const body = d3.select('body');
|
||||
// We don'y want to leak DOM elements - if a removal operation isn't available
|
||||
// for any reason, do not continue.
|
||||
if (!body.remove) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const g = body.append('svg');
|
||||
|
||||
for (let line of lines) {
|
||||
for (let fontFamily of fontFamilies) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.text = line;
|
||||
const textElem = svgDraw
|
||||
.drawText(g, textObj)
|
||||
.style('font-size', fontSize)
|
||||
.style('font-family', fontFamily);
|
||||
|
||||
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
|
||||
}
|
||||
}
|
||||
|
||||
g.remove();
|
||||
|
||||
// Adds some padding, so the text won't sit exactly within the actor's borders
|
||||
return maxWidth + 35;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
@ -443,6 +567,10 @@ export const draw = function(text, id) {
|
||||
const actorKeys = parser.yy.getActorKeys();
|
||||
const messages = parser.yy.getMessages();
|
||||
const title = parser.yy.getTitle();
|
||||
|
||||
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
|
||||
calculateActorMargins(actors, maxMessageWidthPerActor);
|
||||
|
||||
drawActors(diagram, actors, actorKeys, 0);
|
||||
|
||||
// The arrow head definition is attached to the svg once
|
||||
@ -467,12 +595,15 @@ export const draw = function(text, id) {
|
||||
bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos);
|
||||
}
|
||||
|
||||
// const lastMsg
|
||||
|
||||
// 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)
|
||||
);
|
||||
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.NOTE:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
@ -483,26 +614,35 @@ export const draw = function(text, id) {
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
|
||||
drawNote(
|
||||
diagram,
|
||||
startx + (conf.width + conf.actorMargin) / 2,
|
||||
startx + (actors[msg.from].width + conf.actorMargin) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg
|
||||
msg,
|
||||
noteWidth
|
||||
);
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
drawNote(
|
||||
diagram,
|
||||
startx - (conf.width + conf.actorMargin) / 2,
|
||||
startx - noteWidth + (actors[msg.from].width - conf.actorMargin) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg
|
||||
msg,
|
||||
noteWidth
|
||||
);
|
||||
} else if (msg.to === msg.from) {
|
||||
// Single-actor over
|
||||
drawNote(diagram, startx, bounds.getVerticalPos(), msg);
|
||||
drawNote(
|
||||
diagram,
|
||||
startx + (actors[msg.to].width - noteWidth) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg,
|
||||
noteWidth
|
||||
);
|
||||
} else {
|
||||
// Multi-actor over
|
||||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
|
||||
|
||||
drawNote(
|
||||
diagram,
|
||||
(startx + stopx + conf.width - forceWidth) / 2,
|
||||
(startx + stopx + noteWidth - forceWidth) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg,
|
||||
forceWidth
|
||||
@ -668,6 +808,137 @@ export const draw = function(text, id) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the max message width of each actor, supports signals (messages, loops)
|
||||
* and notes.
|
||||
*
|
||||
* It will enumerate each given message, and will determine its text width, in relation
|
||||
* to the actor it originates from, and destined to.
|
||||
*
|
||||
* @param actors - The actors map
|
||||
* @param messages - A list of message objects to iterate
|
||||
*/
|
||||
const getMaxMessageWidthPerActor = function(actors, messages) {
|
||||
const maxMessageWidthPerActor = {};
|
||||
|
||||
messages.forEach(function(msg) {
|
||||
if (actors[msg.to] && actors[msg.from]) {
|
||||
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) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isNote = msg.placement !== undefined;
|
||||
const isMessage = !isNote;
|
||||
|
||||
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
|
||||
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
|
||||
const messageWidth = calculateTextWidth(msg.message, fontSize, fontFamily);
|
||||
|
||||
/*
|
||||
* The following scenarios should be supported:
|
||||
*
|
||||
* - There's a message (non-note) between fromActor and toActor
|
||||
* - If fromActor is on the right and toActor is on the left, we should
|
||||
* define the toActor's margin
|
||||
* - If fromActor is on the left and toActor is on the right, we should
|
||||
* define the fromActor's margin
|
||||
* - There's a note, in which case fromActor == toActor
|
||||
* - If the note is to the left of the actor, we should define the previous actor
|
||||
* margin
|
||||
* - If the note is on the actor, we should define both the previous and next actor
|
||||
* margins, each being the half of the note size
|
||||
* - If the note is on the right of the actor, we should define the current actor
|
||||
* margin
|
||||
*/
|
||||
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
|
||||
) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} 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) {
|
||||
if (actor.prevActor) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
}
|
||||
|
||||
if (actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return maxMessageWidthPerActor;
|
||||
};
|
||||
|
||||
/**
|
||||
* This will calculate the optimal margin for each given actor, for a given
|
||||
* actor->messageWidth map.
|
||||
*
|
||||
* An actor's margin is determined by the width of the actor, the width of the
|
||||
* largest message that originates from it, and the configured conf.actorMargin.
|
||||
*
|
||||
* @param actors - The actors map to calculate margins for
|
||||
* @param actorToMessageWidth - A map of actor key -> max message width it holds
|
||||
*/
|
||||
const calculateActorMargins = function(actors, actorToMessageWidth) {
|
||||
for (let actorKey in actorToMessageWidth) {
|
||||
const actor = actors[actorKey];
|
||||
|
||||
if (!actor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextActor = actors[actor.nextActor];
|
||||
|
||||
// No need to space out an actor that doesn't have a next link
|
||||
if (!nextActor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
actor.width = Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
|
||||
nextActor.width = Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(nextActor.description, conf.actorFontSize, conf.actorFontFamily)
|
||||
);
|
||||
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
|
||||
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
bounds,
|
||||
drawActors,
|
||||
|
@ -75,14 +75,15 @@ export const drawLabel = function(elem, txtObject) {
|
||||
let actorCnt = -1;
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param center - The center of the the actor
|
||||
* @param pos The position if the actor in the liost of actors
|
||||
* @param description The text in the box
|
||||
* @param elem - The diagram we'll draw to.
|
||||
* @param actor - The actor to draw.
|
||||
* @param config - The sequence diagram config object.
|
||||
*/
|
||||
export const drawActor = function(elem, left, verticalPos, description, conf) {
|
||||
const center = left + conf.width / 2;
|
||||
export const drawActor = function(elem, actor, conf) {
|
||||
const center = actor.x + actor.width / 2;
|
||||
|
||||
const g = elem.append('g');
|
||||
if (verticalPos === 0) {
|
||||
if (actor.y === 0) {
|
||||
actorCnt++;
|
||||
g.append('line')
|
||||
.attr('id', 'actor' + actorCnt)
|
||||
@ -96,18 +97,18 @@ export const drawActor = function(elem, left, verticalPos, description, conf) {
|
||||
}
|
||||
|
||||
const rect = getNoteRect();
|
||||
rect.x = left;
|
||||
rect.y = verticalPos;
|
||||
rect.x = actor.x;
|
||||
rect.y = actor.y;
|
||||
rect.fill = '#eaeaea';
|
||||
rect.width = conf.width;
|
||||
rect.height = conf.height;
|
||||
rect.width = actor.width;
|
||||
rect.height = actor.height;
|
||||
rect.class = 'actor';
|
||||
rect.rx = 3;
|
||||
rect.ry = 3;
|
||||
drawRect(g, rect);
|
||||
|
||||
_drawTextCandidateFunc(conf)(
|
||||
description,
|
||||
actor.description,
|
||||
g,
|
||||
rect.x,
|
||||
rect.y,
|
||||
@ -122,10 +123,12 @@ export const anchorElement = function(elem) {
|
||||
return elem.append('g');
|
||||
};
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param elem - element to append activation rect
|
||||
* @param bounds - activation box bounds
|
||||
* @param verticalPos - precise y cooridnate of bottom activation box edge
|
||||
* Draws an activation in the diagram
|
||||
* @param elem - element to append activation rect.
|
||||
* @param bounds - activation box bounds.
|
||||
* @param verticalPos - precise y cooridnate of bottom activation box edge.
|
||||
* @param conf - sequence diagram config object.
|
||||
* @param actorActivations - number of activations on the actor.
|
||||
*/
|
||||
export const drawActivation = function(elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect();
|
||||
@ -139,10 +142,11 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param center - The center of the the actor
|
||||
* @param pos The position if the actor in the list of actors
|
||||
* @param description The text in the box
|
||||
* Draws a loop in the diagram
|
||||
* @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.
|
||||
*/
|
||||
export const drawLoop = function(elem, bounds, labelText, conf) {
|
||||
const g = elem.append('g');
|
||||
|
@ -333,10 +333,9 @@ const _drawLongText = (_text, x, y, g) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param center - The center of the the actor
|
||||
* @param pos The position if the actor in the liost of actors
|
||||
* @param description The text in the box
|
||||
* Draws a note to the diagram
|
||||
* @param text - The text of the given note.
|
||||
* @param g - The element the note is attached to.
|
||||
*/
|
||||
|
||||
export const drawNote = (text, g) => {
|
||||
|
@ -273,7 +273,42 @@ const config = {
|
||||
* This will show the node numbers
|
||||
* **Default value false**.
|
||||
*/
|
||||
showSequenceNumbers: false
|
||||
showSequenceNumbers: false,
|
||||
/**
|
||||
* This sets the font size of the actor's description
|
||||
* **Default value 14**.
|
||||
*/
|
||||
actorFontSize: 14,
|
||||
/**
|
||||
* This sets the font family of the actor's description
|
||||
* **Default value "Open-Sans", "sans-serif"**.
|
||||
*/
|
||||
actorFontFamily: '"Open-Sans", "sans-serif"',
|
||||
/**
|
||||
* This sets the font size of actor-attached notes.
|
||||
* **Default value 14**.
|
||||
*/
|
||||
noteFontSize: 14,
|
||||
/**
|
||||
* This sets the font family of actor-attached notes.
|
||||
* **Default value "trebuchet ms", verdana, arial**.
|
||||
*/
|
||||
noteFontFamily: '"trebuchet ms", verdana, arial',
|
||||
/**
|
||||
* This sets the text alignment of actor-attached notes.
|
||||
* **Default value center**.
|
||||
*/
|
||||
noteAlign: 'center',
|
||||
/**
|
||||
* This sets the font size of actor messages.
|
||||
* **Default value 16**.
|
||||
*/
|
||||
messageFontSize: 16,
|
||||
/**
|
||||
* This sets the font family of actor messages.
|
||||
* **Default value "trebuchet ms", verdana, arial**.
|
||||
*/
|
||||
messageFontFamily: '"trebuchet ms", verdana, arial'
|
||||
},
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user