#2315 Adding the possibility to add actor figures as participants

This commit is contained in:
Knut Sveidqvist 2021-09-16 20:43:10 +02:00
parent 0d91eee5e0
commit 6ce1c80a47
7 changed files with 206 additions and 21 deletions

View File

@ -10,7 +10,7 @@
<style>
body {
/* background: rgb(221, 208, 208); */
background:#333;
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
@ -53,13 +53,20 @@ stateDiagram
</div>
<div class="mermaid" style="width: 100%; height: 20%;">
flowchart LR
one --> two
three -.-> four[whoa, big arrowhead nine o'clock]
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
</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
@ -112,8 +119,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,

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');

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-conf.boxMargin);
};
export const setConf = function (cnf) {
@ -688,6 +689,7 @@ export const draw = function (text, id) {
// Draw actors below diagram
bounds.bumpVerticalPos(conf.boxMargin * 2);
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos());
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,23 @@ export const drawLabel = function (elem, txtObject) {
};
let actorCnt = -1;
export const fixLifeLineHeights = (diagram, bounds) => {
console.log('fixLifeLineHeights', diagram, bounds);
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');
@ -225,6 +235,99 @@ export const drawActor = function (elem, actor, conf) {
{ class: 'actor' },
conf
);
return 75;
};
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);
// circle.attr('rx', rectData.rx);
// circle.attr('ry', rectData.ry);
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 100;
};
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 +679,5 @@ export default {
insertArrowCrossHead,
getTextObj,
getNoteRect,
fixLifeLineHeights,
};