Merge pull request #2318 from mermaid-js/2315_adding_actor_shape

2315 adding actor shape
This commit is contained in:
Knut Sveidqvist 2021-09-18 10:15:10 +02:00 committed by GitHub
commit 549011edf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 2948 additions and 62804 deletions

View File

@ -173,6 +173,18 @@ context('Sequence diagram', () => {
{}
);
});
it('should be possible to use actor symbols instead of boxes', () => {
imgSnapshotTest(
`
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
`,
{}
);
});
it('should render long notes left of actor', () => {
imgSnapshotTest(
`

View File

@ -10,9 +10,10 @@
<style>
body {
/* background: rgb(221, 208, 208); */
background:#333;
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
width: 100%;
}
h1 { color: grey;}
.mermaid2,.mermaid3 {
@ -25,7 +26,7 @@
</head>
<body>
<div>info below</div>
<div class="flex">
<div class="flex flex-wrap">
<div class="mermaid2" style="width: 100%; height: 20%;">
classDiagram
@ -53,28 +54,56 @@ stateDiagram
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
flowchart LR
one --> two
three -.-> four[whoa, big arrowhead nine o'clock]
sequenceDiagram
%%{init: {'config': {'wrap': true }}}%%
actor Alice as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
actor Bob
participant John as John2
participant Mandy
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
Alice->>John: Hi John
John->>Mandy: Hi Mandy
Mandy ->>Joan: Hi Joan
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
%%{init: { "apa":"b", "theme":"forest"}}%%
<div class="mermaid" style="width: 100%; height: 20%;">
%%{int: { "apa":"b", "theme":"forest"}}%%
sequenceDiagram
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
</div>
<div class="mermaid2">
%%{init: { 'theme':'base', '__proto__': {'polluted': 'asdf'}} }%%
%%{init: { 'theme':'base', '__proto__': {'polluted': 'asdf'}} }%%
graph LR
A --> B
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
flowchart TD
Link --> b
click Link href "&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29" "Tooltip for
Amet"
<div class="mermaid">
sequenceDiagram
autonumber
par Action 1
Alice->>John: Hello John, how are you?
and Action 2
Alice->>Bob: Hello Bob, how are you?
end
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
Note right of John: John is perceptive
John-->>-Alice: I feel great!
loop Every minute
John-->Alice: Great!
end
</div>
<div class="mermaid">
sequenceDiagram
%%{init: {'config': {'wrap': true }}}%%
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
%%{init: {'config': {'wrap': true }}}%%
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 !
</div>
<div class="mermaid2" style="width: 100%; height: 20%;">
stateDiagram-v2
@ -112,8 +141,8 @@ YourState
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'dark',
// theme: 'forest',
// theme: 'dark',
theme: 'forest',
arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 2,

666
dist/mermaid.core.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

64715
dist/mermaid.js vendored

File diff suppressed because one or more lines are too long

2
dist/mermaid.js.map vendored

File diff suppressed because one or more lines are too long

12
dist/mermaid.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,8 +7,9 @@
<meta name="description" content="Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<!-- <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css"> -->
<link rel="stylesheet" href="theme.css"> <script src="//cdn.jsdelivr.net/npm/mermaid@8.12.0/dist/mermaid.min.js"></script>
<!-- <script src="http://localhost:9000/mermaid.js"></script> -->
<link rel="stylesheet" href="theme.css">
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@8.12.0/dist/mermaid.min.js"></script> -->
<script src="http://localhost:9000/mermaid.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),

View File

@ -36,18 +36,53 @@ appearance by doing the following:
```
sequenceDiagram
participant John
participant Alice
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
participant Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
participant Alice
participant Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
### Actors
If you specifically want to use the actor symbol instead of a rectangle with text you can do so by using actor statements as per below.
```
sequenceDiagram
participant John
participant Alice
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
```mermaid
sequenceDiagram
actor Alice
actor Bob
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
```
### Aliases

View File

@ -33,6 +33,7 @@
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
<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 'NEWLINE'; }
@ -103,8 +104,10 @@ directive
;
statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$$=$2;}
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
| signal 'NEWLINE'
| autonumber {yy.enableSequenceNumbers()}
| 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
@ -197,9 +200,13 @@ signal
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
actor
: ACTOR {$$={type: 'addActor', actor:$1}}
;
// actor
// : actor_participant
// | actor_actor
// ;
actor: ACTOR {$$={ type: 'addParticipant', actor:$1}};
// actor_actor: ACTOR {$$={type: 'addActor', actor:$1}};
signaltype
: SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; }

View File

@ -15,14 +15,17 @@ export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export const addActor = function (id, name, description) {
export const addActor = function (id, name, description, type) {
// Don't allow description nulling
const old = actors[id];
if (old && name === old.name && description == null) return;
// Don't allow null descriptions, either
if (description == null || description.text == null) {
description = { text: name, wrap: null };
description = { text: name, wrap: null, type };
}
if (type == null || description.text == null) {
description = { text: name, wrap: null, type };
}
actors[id] = {
@ -30,6 +33,7 @@ export const addActor = function (id, name, description) {
description: description.text,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
prevActor: prevActor,
type: type || 'participant',
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
@ -218,8 +222,11 @@ export const apply = function (param) {
});
} else {
switch (param.type) {
case 'addParticipant':
addActor(param.actor, param.actor, param.description, 'participant');
break;
case 'addActor':
addActor(param.actor, param.actor, param.description);
addActor(param.actor, param.actor, param.description, 'actor');
break;
case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType);

View File

@ -121,6 +121,55 @@ B-->A: I am good thanks!`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['A', 'B']);
expect(actors.A.description).toBe('Alice');
expect(actors.B.description).toBe('Bob');
const messages = parser.yy.getMessages();
expect(messages.length).toBe(2);
expect(messages[0].from).toBe('A');
expect(messages[1].from).toBe('B');
});
it('it should alias a mix of actors and participants apa12', function() {
const str = `
sequenceDiagram
actor Alice as Alice2
actor Bob
participant John as John2
participant Mandy
Alice->>Bob: Hi Bob
Bob->>Alice: Hi Alice
Alice->>John: Hi John
John->>Mandy: Hi Mandy
Mandy ->>Joan: Hi Joan`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['Alice', 'Bob', 'John', 'Mandy', 'Joan']);
expect(actors.Alice.description).toBe('Alice2');
expect(actors.Alice.type).toBe('actor');
expect(actors.Bob.description).toBe('Bob');
expect(actors.John.type).toBe('participant');
expect(actors.Joan.type).toBe('participant');
const messages = parser.yy.getMessages();
expect(messages.length).toBe(5);
expect(messages[0].from).toBe('Alice');
expect(messages[4].to).toBe('Joan');
});
it('it should alias actors apa13', function() {
const str = `
sequenceDiagram
actor A as Alice
actor B as Bob
A->B:Hello Bob, how are you?
B-->A: I am good thanks!`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(Object.keys(actors)).toEqual(['A', 'B']);
expect(actors.A.description).toBe('Alice');
@ -1452,7 +1501,7 @@ participant Alice`;
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + conf.boxMargin);
});
});
});
@ -1501,7 +1550,7 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin);
});
it('it should handle one actor, when logLevel is 3', function() {
const str = `
@ -1519,6 +1568,6 @@ participant Alice
expect(bounds.startx).toBe(0);
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height);
expect(bounds.stopy).toBe(models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin);
});
});

View File

@ -1,5 +1,5 @@
import { select, selectAll } from 'd3';
import svgDraw, { drawText } from './svgDraw';
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw';
import { log } from '../../logger';
import { parser } from './parser/sequenceDiagram';
import common from '../common/common';
@ -421,7 +421,7 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
// Draw the actors
let prevWidth = 0;
let prevMargin = 0;
let maxHeight = 0;
for (let i = 0; i < actorKeys.length; i++) {
const actor = actors[actorKeys[i]];
@ -434,7 +434,8 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
actor.y = verticalPos;
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf);
const height = svgDraw.drawActor(diagram, actor, conf);
maxHeight = Math.max(maxHeight, height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width;
@ -443,7 +444,7 @@ export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
}
// Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(conf.height);
bounds.bumpVerticalPos(maxHeight);
};
export const setConf = function (cnf) {
@ -688,6 +689,8 @@ export const draw = function (text, id) {
// Draw actors below diagram
bounds.bumpVerticalPos(conf.boxMargin * 2);
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos());
bounds.bumpVerticalPos(conf.boxMargin);
fixLifeLineHeights(diagram, bounds.getVerticalPos());
}
const { bounds: box } = bounds.getBounds();

View File

@ -95,6 +95,15 @@ const getStyles = (options) =>
fill: ${options.activationBkgColor};
stroke: ${options.activationBorderColor};
}
.actor-man line {
stroke: ${options.actorBorder};
fill: ${options.actorBkg};
}
.actor-man circle, line {
stroke: ${options.actorBorder};
fill: ${options.actorBkg};
stroke-width: 2px;
}
`;
export default getStyles;

View File

@ -181,13 +181,22 @@ export const drawLabel = function (elem, txtObject) {
};
let actorCnt = -1;
export const fixLifeLineHeights = (diagram, bounds) => {
if (!diagram.selectAll) return;
diagram
.selectAll('.actor-line')
.attr('class', '200')
.attr('y2', bounds - 55);
};
/**
* Draws an actor in the diagram with the attached line
* @param elem - The diagram we'll draw to.
* @param actor - The actor to draw.
* @param conf - drawText implementation discriminator object
*/
export const drawActor = function (elem, actor, conf) {
const drawActorTypeParticipant = function (elem, actor, conf) {
const center = actor.x + actor.width / 2;
const g = elem.append('g');
@ -213,7 +222,7 @@ export const drawActor = function (elem, actor, conf) {
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
drawRect(g, rect);
const rectElem = drawRect(g, rect);
_drawTextCandidateFunc(conf)(
actor.description,
@ -225,6 +234,105 @@ export const drawActor = function (elem, actor, conf) {
{ class: 'actor' },
conf
);
let height = actor.height;
if (rectElem.node) {
const bounds = rectElem.node().getBBox();
actor.height = bounds.height;
height = bounds.height;
}
return height;
};
const drawActorTypeActor = function (elem, actor, conf) {
const center = actor.x + actor.width / 2;
if (actor.y === 0) {
actorCnt++;
elem
.append('line')
.attr('id', 'actor' + actorCnt)
.attr('x1', center)
.attr('y1', 80)
.attr('x2', center)
.attr('y2', 2000)
.attr('class', 'actor-line')
.attr('stroke-width', '0.5px')
.attr('stroke', '#999');
}
const actElem = elem.append('g');
actElem.attr('class', 'actor-man');
const rect = getNoteRect();
rect.x = actor.x;
rect.y = actor.y;
rect.fill = '#eaeaea';
rect.width = actor.width;
rect.height = actor.height;
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
// drawRect(actElem, rect);
actElem
.append('line')
.attr('id', 'actor-man-torso' + actorCnt)
.attr('x1', center)
.attr('y1', actor.y + 25)
.attr('x2', center)
.attr('y2', actor.y + 45);
actElem
.append('line')
.attr('id', 'actor-man-arms' + actorCnt)
.attr('x1', center - 18)
.attr('y1', actor.y + 33)
.attr('x2', center + 18)
.attr('y2', actor.y + 33);
actElem
.append('line')
.attr('x1', center - 18)
.attr('y1', actor.y + 60)
.attr('x2', center)
.attr('y2', actor.y + 45);
actElem
.append('line')
.attr('x1', center)
.attr('y1', actor.y + 45)
.attr('x2', center + 16)
.attr('y2', actor.y + 60);
const circle = actElem.append('circle');
circle.attr('cx', actor.x + actor.width / 2);
circle.attr('cy', actor.y + 10);
circle.attr('r', 15);
circle.attr('width', actor.width);
circle.attr('height', actor.height);
const bounds = actElem.node().getBBox();
actor.height = bounds.height;
_drawTextCandidateFunc(conf)(
actor.description,
actElem,
rect.x,
rect.y + 35,
rect.width,
rect.height,
{ class: 'actor' },
conf
);
return actor.height;
};
export const drawActor = function (elem, actor, conf) {
switch (actor.type) {
case 'actor':
return drawActorTypeActor(elem, actor, conf);
case 'participant':
return drawActorTypeParticipant(elem, actor, conf);
}
};
export const anchorElement = function (elem) {
@ -576,4 +684,5 @@ export default {
insertArrowCrossHead,
getTextObj,
getNoteRect,
fixLifeLineHeights,
};