Merge pull request #1365 from dany74q/feature/sequence-diagrams-improvements

Feature/sequence diagrams improvements
This commit is contained in:
Knut Sveidqvist
2020-04-26 17:21:55 +02:00
committed by GitHub
7 changed files with 594 additions and 125 deletions

View File

@@ -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', () => { context('background rects', () => {
it('should render a single and nested rects', () => { it('should render a single and nested rects', () => {
imgSnapshotTest( imgSnapshotTest(

View File

@@ -9,6 +9,7 @@ sequenceDiagram
Alice->>John: Hello John, how are you? Alice->>John: Hello John, how are you?
John-->>Alice: Great! John-->>Alice: Great!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->>John: Hello John, how are you? Alice->>John: Hello John, how are you?
@@ -31,6 +32,7 @@ sequenceDiagram
Alice->>John: Hello John, how are you? Alice->>John: Hello John, how are you?
John-->>Alice: Great! John-->>Alice: Great!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant John participant John
@@ -50,6 +52,7 @@ sequenceDiagram
A->>J: Hello John, how are you? A->>J: Hello John, how are you?
J->>A: Great! J->>A: Great!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant A as Alice 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: There are six types of arrows currently supported:
Type | Description | Type | Description |
--- | --- | ---- | ------------------------------------------- |
-> | Solid line without arrow | -> | Solid line without arrow |
--> | Dotted line without arrow | --> | Dotted line without arrow |
->> | Solid line with arrowhead | ->> | Solid line with arrowhead |
-->> | Dotted line with arrowhead | -->> | Dotted line with arrowhead |
-x | Solid line with a cross at the end (async) | -x | Solid line with a cross at the end (async) |
--x | Dotted line with a cross at the end (async) | --x | Dotted line with a cross at the end (async) |
## Activations ## Activations
@@ -88,6 +91,7 @@ sequenceDiagram
John-->>Alice: Great! John-->>Alice: Great!
deactivate John deactivate John
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->>John: Hello John, how are you? Alice->>John: Hello John, how are you?
@@ -103,6 +107,7 @@ sequenceDiagram
Alice->>+John: Hello John, how are you? Alice->>+John: Hello John, how are you?
John-->>-Alice: Great! John-->>-Alice: Great!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->>+John: Hello John, how are you? Alice->>+John: Hello John, how are you?
@@ -118,6 +123,7 @@ sequenceDiagram
John-->>-Alice: Hi Alice, I can hear you! John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great! John-->>-Alice: I feel great!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->>+John: Hello John, how are you? Alice->>+John: Hello John, how are you?
@@ -138,6 +144,7 @@ sequenceDiagram
participant John participant John
Note right of John: Text in note Note right of John: Text in note
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant John participant John
@@ -151,6 +158,7 @@ sequenceDiagram
Alice->John: Hello John, how are you? Alice->John: Hello John, how are you?
Note over Alice,John: A typical interaction Note over Alice,John: A typical interaction
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->John: Hello John, how are you? Alice->John: Hello John, how are you?
@@ -176,6 +184,7 @@ sequenceDiagram
John-->Alice: Great! John-->Alice: Great!
end end
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->John: Hello John, how are you? Alice->John: Hello John, how are you?
@@ -218,6 +227,7 @@ sequenceDiagram
Bob->>Alice: Thanks for asking Bob->>Alice: Thanks for asking
end end
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
Alice->>Bob: Hello Bob, how are you? 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 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. The colors are defined using rgb and rgba syntax.
``` ```
rect rgb(0, 255, 0) rect rgb(0, 255, 0)
... content ... ... content ...
end end
``` ```
``` ```
rect rgba(0, 0, 255, .1) rect rgba(0, 0, 255, .1)
... content ... ... content ...
@@ -327,6 +339,7 @@ sequenceDiagram
## sequenceNumbers ## 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: 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> <script>
mermaid.initialize({ 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: It can also be be turned on via the diagram code as in the diagram:
``` ```
sequenceDiagram sequenceDiagram
autonumber autonumber
@@ -348,6 +362,7 @@ sequenceDiagram
John->>Bob: How about you? John->>Bob: How about you?
Bob-->>John: Jolly good! Bob-->>John: Jolly good!
``` ```
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
autonumber autonumber
@@ -367,20 +382,20 @@ Styling of a sequence diagram is done by defining a number of css classes. Durin
### Classes used ### Classes used
Class | Description | Class | Description |
--- | --- | ------------ | ----------------------------------------------------------- |
actor | Style for the actor box at the top of the diagram. | 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. | text.actor | Styles for text in the actor box at the top of the diagram. |
actor-line | The vertical line for an actor. | actor-line | The vertical line for an actor. |
messageLine0 | Styles for the solid message line. | messageLine0 | Styles for the solid message line. |
messageLine1 | Styles for the dotted message line. | messageLine1 | Styles for the dotted message line. |
messageText | Defines styles for the text on the message arrows. | messageText | Defines styles for the text on the message arrows. |
labelBox | Defines styles label to left in a loop. | labelBox | Defines styles label to left in a loop. |
labelText | Styles for the text in label for loops. | labelText | Styles for the text in label for loops. |
loopText | Styles for the text in the loop box. | loopText | Styles for the text in the loop box. |
loopLine | Defines styles for the lines in the loop box. | loopLine | Defines styles for the lines in the loop box. |
note | Styles for the note box. | note | Styles for the note box. |
noteText | Styles for the text on in the note boxes. | noteText | Styles for the text on in the note boxes. |
### Sample stylesheet ### Sample stylesheet
@@ -390,8 +405,8 @@ body {
} }
.actor { .actor {
stroke: #CCCCFF; stroke: #ccccff;
fill: #ECECFF; fill: #ececff;
} }
text.actor { text.actor {
fill: black; fill: black;
@@ -405,20 +420,19 @@ text.actor {
.messageLine0 { .messageLine0 {
stroke-width: 1.5; stroke-width: 1.5;
stroke-dasharray: "2 2"; stroke-dasharray: '2 2';
marker-end:"url(#arrowhead)"; marker-end: 'url(#arrowhead)';
stroke: black; stroke: black;
} }
.messageLine1 { .messageLine1 {
stroke-width: 1.5; stroke-width: 1.5;
stroke-dasharray: "2 2"; stroke-dasharray: '2 2';
stroke: black; stroke: black;
} }
#arrowhead { #arrowhead {
fill: black; fill: black;
} }
.messageText { .messageText {
@@ -429,8 +443,8 @@ text.actor {
} }
.labelBox { .labelBox {
stroke: #CCCCFF; stroke: #ccccff;
fill: #ECECFF; fill: #ececff;
} }
.labelText { .labelText {
@@ -447,14 +461,14 @@ text.actor {
.loopLine { .loopLine {
stroke-width: 2; stroke-width: 2;
stroke-dasharray: "2 2"; stroke-dasharray: '2 2';
marker-end:"url(#arrowhead)"; marker-end: 'url(#arrowhead)';
stroke: #CCCCFF; stroke: #ccccff;
} }
.note { .note {
stroke: #decc93; stroke: #decc93;
stroke: #CCCCFF; stroke: #ccccff;
fill: #fff5ad; fill: #fff5ad;
} }
@@ -487,7 +501,14 @@ mermaid.sequenceConfig = {
### Possible configuration params: ### Possible configuration params:
Param | Description | Default value | Param | Description | Default value |
--- | --- | --- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false | 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 | 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 |

View File

@@ -1,10 +1,12 @@
import { logger } from '../../logger'; import { logger } from '../../logger';
let prevActor = undefined;
let actors = {}; let actors = {};
let messages = []; let messages = [];
const notes = []; const notes = [];
let title = ''; let title = '';
let sequenceNumbersEnabled = false; let sequenceNumbersEnabled = false;
export const addActor = function(id, name, description) { export const addActor = function(id, name, description) {
// Don't allow description nulling // Don't allow description nulling
const old = actors[id]; const old = actors[id];
@@ -13,7 +15,12 @@ export const addActor = function(id, name, description) {
// Don't allow null descriptions, either // Don't allow null descriptions, either
if (description == null) description = name; 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 => { const activationCount = part => {

View File

@@ -19,6 +19,13 @@ const conf = {
height: 65, height: 65,
actorFontSize: 14, actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"', 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 // Margin around loop boxes
boxMargin: 10, boxMargin: 10,
boxTextMargin: 5, boxTextMargin: 5,
@@ -171,26 +178,62 @@ export const bounds = {
const _drawLongText = (text, x, y, g, width) => { const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0; 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); const lines = text.split(common.lineBreakRegex);
for (const line of lines) { for (const line of lines) {
const textObj = svgDraw.getTextObj(); 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.y = y + textHeight;
textObj.textMargin = conf.noteMargin;
textObj.dy = '1em'; textObj.dy = '1em';
textObj.text = line; textObj.text = line;
textObj.class = 'noteText'; 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; textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
textElem.attr('y', y + (prevTextHeight + textHeight + 2 * conf.noteMargin) / 2);
prevTextHeight = textHeight;
} }
return textHeight; return textHeight;
}; };
/** /**
* Draws an actor in the diagram with the attaced line * Draws an note in the diagram with the attaced line
* @param center - The center of the the actor * @param elem - The diagram to draw to.
* @param pos The position if the actor in the liost of actors * @param startx - The x axis start position.
* @param description The text in the box * @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 drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
const rect = svgDraw.getNoteRect(); const rect = svgDraw.getNoteRect();
@@ -202,13 +245,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
let g = elem.append('g'); let g = elem.append('g');
const rectElem = svgDraw.drawRect(g, rect); const rectElem = svgDraw.drawRect(g, rect);
const textHeight = _drawLongText( const textHeight = _drawLongText(msg.message, startx, verticalPos, g, rect.width);
msg.message,
startx - 4,
verticalPos + 24,
g,
rect.width - conf.noteMargin
);
bounds.insert( bounds.insert(
startx, startx,
@@ -216,6 +253,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
startx + rect.width, startx + rect.width,
verticalPos + 2 * conf.noteMargin + textHeight verticalPos + 2 * conf.noteMargin + textHeight
); );
rectElem.attr('height', textHeight + 2 * conf.noteMargin); rectElem.attr('height', textHeight + 2 * conf.noteMargin);
bounds.bumpVerticalPos(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 .append('text') // text label for the x axis
.attr('x', txtCenter) .attr('x', txtCenter)
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset) .attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('text-anchor', 'middle') .style('text-anchor', 'middle')
.attr('class', 'messageText') .attr('class', 'messageText')
.text(breakline.trim()) .text(breakline.trim())
@@ -250,7 +290,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
counterBreaklines++; counterBreaklines++;
} }
const offsetLineCounter = counterBreaklines - 1; const offsetLineCounter = counterBreaklines - 1;
const totalOffset = offsetLineCounter * breaklineOffset; let totalOffset = offsetLineCounter * breaklineOffset;
let textWidths = textElems.map(function(textElem) { let textWidths = textElems.map(function(textElem) {
return (textElem._groups || textElem)[0][0].getBBox().width; return (textElem._groups || textElem)[0][0].getBBox().width;
@@ -278,6 +318,8 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
totalOffset} H ${startx}` totalOffset} H ${startx}`
); );
} else { } else {
totalOffset += 5;
line = g line = g
.append('path') .append('path')
.attr( .attr(
@@ -375,18 +417,26 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
export const drawActors = function(diagram, actors, actorKeys, verticalPos) { export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
// Draw the actors // Draw the actors
let prevWidth = 0;
let prevMargin = 0;
for (let i = 0; i < actorKeys.length; i++) { for (let i = 0; i < actorKeys.length; i++) {
const key = actorKeys[i]; const actor = actors[actorKeys[i]];
// Add some rendering data to the object // Add some rendering data to the object
actors[key].x = i * conf.actorMargin + i * conf.width; actor.width = actor.width || calculateActorWidth(actor);
actors[key].y = verticalPos; actor.height = conf.height;
actors[key].width = conf.diagramMarginX; actor.margin = actor.margin || conf.actorMargin;
actors[key].height = conf.diagramMarginY;
actor.x = prevWidth + prevMargin;
actor.y = verticalPos;
// Draw the box with the attached line // Draw the box with the attached line
svgDraw.drawActor(diagram, actors[key].x, verticalPos, actors[key].description, conf); svgDraw.drawActor(diagram, actor, conf);
bounds.insert(actors[key].x, verticalPos, actors[key].x + conf.width, conf.height); 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 // Add a margin between the actor boxes and the first arrow
@@ -399,7 +449,10 @@ export const setConf = function(cnf) {
keys.forEach(function(key) { keys.forEach(function(key) {
conf[key] = cnf[key]; conf[key] = cnf[key];
}); });
conf.actorFontFamily = cnf.fontFamily;
if (cnf.fontFamily) {
conf.actorFontFamily = conf.noteFontFamily = cnf.fontFamily;
}
}; };
const actorActivations = function(actor) { const actorActivations = function(actor) {
@@ -410,18 +463,89 @@ const actorActivations = function(actor) {
const actorFlowVerticaBounds = function(actor) { const actorFlowVerticaBounds = function(actor) {
// handle multiple stacked activations for same actor // handle multiple stacked activations for same actor
const actors = parser.yy.getActors(); const actorObj = parser.yy.getActors()[actor];
const activations = actorActivations(actor); const activations = actorActivations(actor);
const left = activations.reduce(function(acc, activation) { const left = activations.reduce(function(acc, activation) {
return Math.min(acc, activation.startx); return Math.min(acc, activation.startx);
}, actors[actor].x + conf.width / 2); }, actorObj.x + actorObj.width / 2);
const right = activations.reduce(function(acc, activation) { const right = activations.reduce(function(acc, activation) {
return Math.max(acc, activation.stopx); return Math.max(acc, activation.stopx);
}, actors[actor].x + conf.width / 2); }, actorObj.x + actorObj.width / 2);
return [left, right]; 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. * Draws a flowchart in the tag with id: id based on the graph definition in text.
* @param text * @param text
@@ -443,6 +567,10 @@ export const draw = function(text, id) {
const actorKeys = parser.yy.getActorKeys(); const actorKeys = parser.yy.getActorKeys();
const messages = parser.yy.getMessages(); const messages = parser.yy.getMessages();
const title = parser.yy.getTitle(); const title = parser.yy.getTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
calculateActorMargins(actors, maxMessageWidthPerActor);
drawActors(diagram, actors, actorKeys, 0); drawActors(diagram, actors, actorKeys, 0);
// The arrow head definition is attached to the svg once // 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); bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos);
} }
// const lastMsg
// Draw the messages/signals // Draw the messages/signals
let sequenceIndex = 1; let sequenceIndex = 1;
messages.forEach(function(msg) { messages.forEach(function(msg) {
let loopData; let loopData;
const noteWidth = Math.max(
conf.width,
calculateTextWidth(msg.message, conf.noteFontSize, conf.noteFontFamily)
);
switch (msg.type) { switch (msg.type) {
case parser.yy.LINETYPE.NOTE: case parser.yy.LINETYPE.NOTE:
bounds.bumpVerticalPos(conf.boxMargin); bounds.bumpVerticalPos(conf.boxMargin);
@@ -483,26 +614,35 @@ export const draw = function(text, id) {
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) { if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
drawNote( drawNote(
diagram, diagram,
startx + (conf.width + conf.actorMargin) / 2, startx + (actors[msg.from].width + conf.actorMargin) / 2,
bounds.getVerticalPos(), bounds.getVerticalPos(),
msg msg,
noteWidth
); );
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) { } else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
drawNote( drawNote(
diagram, diagram,
startx - (conf.width + conf.actorMargin) / 2, startx - noteWidth + (actors[msg.from].width - conf.actorMargin) / 2,
bounds.getVerticalPos(), bounds.getVerticalPos(),
msg msg,
noteWidth
); );
} else if (msg.to === msg.from) { } else if (msg.to === msg.from) {
// Single-actor over // Single-actor over
drawNote(diagram, startx, bounds.getVerticalPos(), msg); drawNote(
diagram,
startx + (actors[msg.to].width - noteWidth) / 2,
bounds.getVerticalPos(),
msg,
noteWidth
);
} else { } else {
// Multi-actor over // Multi-actor over
forceWidth = Math.abs(startx - stopx) + conf.actorMargin; forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
drawNote( drawNote(
diagram, diagram,
(startx + stopx + conf.width - forceWidth) / 2, (startx + stopx + noteWidth - forceWidth) / 2,
bounds.getVerticalPos(), bounds.getVerticalPos(),
msg, msg,
forceWidth 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 { export default {
bounds, bounds,
drawActors, drawActors,

View File

@@ -75,14 +75,15 @@ export const drawLabel = function(elem, txtObject) {
let actorCnt = -1; let actorCnt = -1;
/** /**
* Draws an actor in the diagram with the attaced line * Draws an actor in the diagram with the attaced line
* @param center - The center of the the actor * @param elem - The diagram we'll draw to.
* @param pos The position if the actor in the liost of actors * @param actor - The actor to draw.
* @param description The text in the box * @param config - The sequence diagram config object.
*/ */
export const drawActor = function(elem, left, verticalPos, description, conf) { export const drawActor = function(elem, actor, conf) {
const center = left + conf.width / 2; const center = actor.x + actor.width / 2;
const g = elem.append('g'); const g = elem.append('g');
if (verticalPos === 0) { if (actor.y === 0) {
actorCnt++; actorCnt++;
g.append('line') g.append('line')
.attr('id', 'actor' + actorCnt) .attr('id', 'actor' + actorCnt)
@@ -96,18 +97,18 @@ export const drawActor = function(elem, left, verticalPos, description, conf) {
} }
const rect = getNoteRect(); const rect = getNoteRect();
rect.x = left; rect.x = actor.x;
rect.y = verticalPos; rect.y = actor.y;
rect.fill = '#eaeaea'; rect.fill = '#eaeaea';
rect.width = conf.width; rect.width = actor.width;
rect.height = conf.height; rect.height = actor.height;
rect.class = 'actor'; rect.class = 'actor';
rect.rx = 3; rect.rx = 3;
rect.ry = 3; rect.ry = 3;
drawRect(g, rect); drawRect(g, rect);
_drawTextCandidateFunc(conf)( _drawTextCandidateFunc(conf)(
description, actor.description,
g, g,
rect.x, rect.x,
rect.y, rect.y,
@@ -122,10 +123,12 @@ export const anchorElement = function(elem) {
return elem.append('g'); return elem.append('g');
}; };
/** /**
* Draws an actor in the diagram with the attaced line * Draws an activation in the diagram
* @param elem - element to append activation rect * @param elem - element to append activation rect.
* @param bounds - activation box bounds * @param bounds - activation box bounds.
* @param verticalPos - precise y cooridnate of bottom activation box edge * @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) { export const drawActivation = function(elem, bounds, verticalPos, conf, actorActivations) {
const rect = getNoteRect(); 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 * Draws a loop in the diagram
* @param center - The center of the the actor * @param elem - elemenet to append the loop to.
* @param pos The position if the actor in the list of actors * @param bounds - bounds of the given loop.
* @param description The text in the box * @param labelText - Text within the loop.
* @param config - sequence diagram config object.
*/ */
export const drawLoop = function(elem, bounds, labelText, conf) { export const drawLoop = function(elem, bounds, labelText, conf) {
const g = elem.append('g'); const g = elem.append('g');

View File

@@ -333,10 +333,9 @@ const _drawLongText = (_text, x, y, g) => {
}; };
/** /**
* Draws an actor in the diagram with the attaced line * Draws a note to the diagram
* @param center - The center of the the actor * @param text - The text of the given note.
* @param pos The position if the actor in the liost of actors * @param g - The element the note is attached to.
* @param description The text in the box
*/ */
export const drawNote = (text, g) => { export const drawNote = (text, g) => {

View File

@@ -273,7 +273,42 @@ const config = {
* This will show the node numbers * This will show the node numbers
* **Default value false**. * **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'
}, },
/** /**