mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-18 23:09:49 +02:00
Merge branch 'release/8.13.0'
This commit is contained in:
@@ -274,7 +274,7 @@ export const drawClass = function (elem, classDef, conf) {
|
||||
};
|
||||
|
||||
export const parseMember = function (text) {
|
||||
const fieldRegEx = /(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+)/;
|
||||
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+) *(\*|\$)?$/;
|
||||
const methodRegEx = /^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/;
|
||||
|
||||
let fieldMatch = text.match(fieldRegEx);
|
||||
@@ -290,6 +290,7 @@ export const parseMember = function (text) {
|
||||
};
|
||||
|
||||
const buildFieldDisplay = function (parsedText) {
|
||||
let cssStyle = '';
|
||||
let displayText = '';
|
||||
|
||||
try {
|
||||
@@ -297,15 +298,17 @@ const buildFieldDisplay = function (parsedText) {
|
||||
let fieldType = parsedText[2] ? parsedText[2].trim() : '';
|
||||
let genericType = parsedText[3] ? parseGenericTypes(parsedText[3].trim()) : '';
|
||||
let fieldName = parsedText[4] ? parsedText[4].trim() : '';
|
||||
let classifier = parsedText[5] ? parsedText[5].trim() : '';
|
||||
|
||||
displayText = visibility + fieldType + genericType + ' ' + fieldName;
|
||||
cssStyle = parseClassifier(classifier);
|
||||
} catch (err) {
|
||||
displayText = parsedText;
|
||||
}
|
||||
|
||||
return {
|
||||
displayText: displayText,
|
||||
cssStyle: '',
|
||||
cssStyle: cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -321,7 +324,6 @@ const buildMethodDisplay = function (parsedText) {
|
||||
let returnType = parsedText[5] ? ' : ' + parseGenericTypes(parsedText[5]).trim() : '';
|
||||
|
||||
displayText = visibility + methodName + '(' + parameters + ')' + returnType;
|
||||
|
||||
cssStyle = parseClassifier(classifier);
|
||||
} catch (err) {
|
||||
displayText = parsedText;
|
||||
|
@@ -51,7 +51,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle abstract classifier', function () {
|
||||
it('should handle abstract method classifier', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static classifier', function () {
|
||||
it('should handle static method classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -156,5 +156,13 @@ describe('class member Renderer, ', function () {
|
||||
expect(actual.displayText).toBe('List<int> ids');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle static field classifier', function () {
|
||||
const str = 'String foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('String foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -35,13 +35,20 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||
const attrFontSize = conf.fontSize * 0.85;
|
||||
const labelBBox = entityTextNode.node().getBBox();
|
||||
const attributeNodes = []; // Intermediate storage for attribute nodes created so that we can do a second pass
|
||||
let hasKeyType = false;
|
||||
let hasComment = false;
|
||||
let maxWidth = 0;
|
||||
let maxTypeWidth = 0;
|
||||
let maxNameWidth = 0;
|
||||
let maxKeyWidth = 0;
|
||||
let maxCommentWidth = 0;
|
||||
let cumulativeHeight = labelBBox.height + heightPadding * 2;
|
||||
let attrNum = 1;
|
||||
|
||||
attributes.forEach((item) => {
|
||||
const attrPrefix = `${entityTextNode.node().id}-attr-${attrNum}`;
|
||||
let nodeWidth = 0;
|
||||
let nodeHeight = 0;
|
||||
|
||||
// Add a text node for the attribute type
|
||||
const typeNode = groupNode
|
||||
@@ -73,16 +80,70 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||
)
|
||||
.text(item.attributeName);
|
||||
|
||||
// Keep a reference to the nodes so that we can iterate through them later
|
||||
attributeNodes.push({ tn: typeNode, nn: nameNode });
|
||||
const attributeNode = {};
|
||||
attributeNode.tn = typeNode;
|
||||
attributeNode.nn = nameNode;
|
||||
|
||||
const typeBBox = typeNode.node().getBBox();
|
||||
const nameBBox = nameNode.node().getBBox();
|
||||
|
||||
maxTypeWidth = Math.max(maxTypeWidth, typeBBox.width);
|
||||
maxNameWidth = Math.max(maxNameWidth, nameBBox.width);
|
||||
nodeWidth += typeBBox.width;
|
||||
nodeWidth += nameBBox.width;
|
||||
|
||||
cumulativeHeight += Math.max(typeBBox.height, nameBBox.height) + heightPadding * 2;
|
||||
nodeHeight = Math.max(typeBBox.height, nameBBox.height);
|
||||
|
||||
if (hasKeyType || item.attributeKeyType !== undefined) {
|
||||
const keyTypeNode = groupNode
|
||||
.append('text')
|
||||
.attr('class', 'er entityLabel')
|
||||
.attr('id', `${attrPrefix}-name`)
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'left')
|
||||
.attr(
|
||||
'style',
|
||||
'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
|
||||
)
|
||||
.text(item.attributeKeyType || '');
|
||||
|
||||
attributeNode.kn = keyTypeNode;
|
||||
const keyTypeBBox = keyTypeNode.node().getBBox();
|
||||
nodeWidth += keyTypeBBox.width;
|
||||
maxKeyWidth = Math.max(maxKeyWidth, nodeWidth);
|
||||
nodeHeight = Math.max(nodeHeight, keyTypeBBox.height);
|
||||
hasKeyType = true;
|
||||
}
|
||||
|
||||
if (hasComment || item.attributeComment !== undefined) {
|
||||
const commentNode = groupNode
|
||||
.append('text')
|
||||
.attr('class', 'er entityLabel')
|
||||
.attr('id', `${attrPrefix}-name`)
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('dominant-baseline', 'middle')
|
||||
.attr('text-anchor', 'left')
|
||||
.attr(
|
||||
'style',
|
||||
'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
|
||||
)
|
||||
.text(item.attributeComment || '');
|
||||
|
||||
attributeNode.cn = commentNode;
|
||||
const commentNodeBBox = commentNode.node().getBBox();
|
||||
nodeWidth += commentNodeBBox.width;
|
||||
maxCommentWidth = Math.max(nodeWidth, nameBBox.width);
|
||||
nodeHeight = Math.max(nodeHeight, commentNodeBBox.height);
|
||||
hasComment = true;
|
||||
}
|
||||
|
||||
attributeNode.height = nodeHeight;
|
||||
// Keep a reference to the nodes so that we can iterate through them later
|
||||
attributeNodes.push(attributeNode);
|
||||
maxWidth = Math.max(maxWidth, nodeWidth);
|
||||
cumulativeHeight += nodeHeight + heightPadding * 2;
|
||||
attrNum += 1;
|
||||
});
|
||||
|
||||
@@ -90,10 +151,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||
const bBox = {
|
||||
width: Math.max(
|
||||
conf.minEntityWidth,
|
||||
Math.max(
|
||||
labelBBox.width + conf.entityPadding * 2,
|
||||
maxTypeWidth + maxNameWidth + widthPadding * 4
|
||||
)
|
||||
Math.max(labelBBox.width + conf.entityPadding * 2, maxWidth + widthPadding * 4)
|
||||
),
|
||||
height:
|
||||
attributes.length > 0
|
||||
@@ -102,7 +160,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||
};
|
||||
|
||||
// There might be some spare width for padding out attributes if the entity name is very long
|
||||
const spareWidth = Math.max(0, bBox.width - (maxTypeWidth + maxNameWidth) - widthPadding * 4);
|
||||
const spareWidth = Math.max(0, bBox.width - maxWidth - widthPadding * 4);
|
||||
|
||||
if (attributes.length > 0) {
|
||||
// Position the entity label near the top of the entity bounding box
|
||||
@@ -115,51 +173,85 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
|
||||
let heightOffset = labelBBox.height + heightPadding * 2; // Start at the bottom of the entity label
|
||||
let attribStyle = 'attributeBoxOdd'; // We will flip the style on alternate rows to achieve a banded effect
|
||||
|
||||
attributeNodes.forEach((nodePair) => {
|
||||
attributeNodes.forEach((attributeNode) => {
|
||||
// Calculate the alignment y co-ordinate for the type/name of the attribute
|
||||
const alignY =
|
||||
heightOffset +
|
||||
heightPadding +
|
||||
Math.max(nodePair.tn.node().getBBox().height, nodePair.nn.node().getBBox().height) / 2;
|
||||
const alignY = heightOffset + heightPadding + attributeNode.height / 2;
|
||||
|
||||
// Position the type of the attribute
|
||||
nodePair.tn.attr('transform', 'translate(' + widthPadding + ',' + alignY + ')');
|
||||
attributeNode.tn.attr('transform', 'translate(' + widthPadding + ',' + alignY + ')');
|
||||
|
||||
// Insert a rectangle for the type
|
||||
const typeRect = groupNode
|
||||
.insert('rect', '#' + nodePair.tn.node().id)
|
||||
.insert('rect', '#' + attributeNode.tn.node().id)
|
||||
.attr('class', `er ${attribStyle}`)
|
||||
.attr('fill', conf.fill)
|
||||
.attr('fill-opacity', '100%')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('x', 0)
|
||||
.attr('y', heightOffset)
|
||||
.attr('width', maxTypeWidth + widthPadding * 2 + spareWidth / 2)
|
||||
.attr('height', nodePair.tn.node().getBBox().height + heightPadding * 2);
|
||||
.attr('width', maxTypeWidth * 2 + spareWidth / 2)
|
||||
.attr('height', attributeNode.tn.node().getBBox().height + heightPadding * 2);
|
||||
|
||||
// Position the name of the attribute
|
||||
nodePair.nn.attr(
|
||||
attributeNode.nn.attr(
|
||||
'transform',
|
||||
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
|
||||
);
|
||||
|
||||
// Insert a rectangle for the name
|
||||
groupNode
|
||||
.insert('rect', '#' + nodePair.nn.node().id)
|
||||
.insert('rect', '#' + attributeNode.nn.node().id)
|
||||
.attr('class', `er ${attribStyle}`)
|
||||
.attr('fill', conf.fill)
|
||||
.attr('fill-opacity', '100%')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
|
||||
//.attr('x', maxTypeWidth + (widthPadding * 2))
|
||||
.attr('y', heightOffset)
|
||||
.attr('width', maxNameWidth + widthPadding * 2 + spareWidth / 2)
|
||||
.attr('height', nodePair.nn.node().getBBox().height + heightPadding * 2);
|
||||
.attr('height', attributeNode.nn.node().getBBox().height + heightPadding * 2);
|
||||
|
||||
if (hasKeyType) {
|
||||
// Position the name of the attribute
|
||||
attributeNode.kn.attr(
|
||||
'transform',
|
||||
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
|
||||
);
|
||||
|
||||
// Insert a rectangle for the name
|
||||
groupNode
|
||||
.insert('rect', '#' + attributeNode.kn.node().id)
|
||||
.attr('class', `er ${attribStyle}`)
|
||||
.attr('fill', conf.fill)
|
||||
.attr('fill-opacity', '100%')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
|
||||
.attr('y', heightOffset)
|
||||
.attr('width', maxKeyWidth + widthPadding * 2 + spareWidth / 2)
|
||||
.attr('height', attributeNode.kn.node().getBBox().height + heightPadding * 2);
|
||||
}
|
||||
|
||||
if (hasComment) {
|
||||
// Position the name of the attribute
|
||||
attributeNode.cn.attr(
|
||||
'transform',
|
||||
'translate(' + (parseFloat(typeRect.attr('width')) + widthPadding) + ',' + alignY + ')'
|
||||
);
|
||||
|
||||
// Insert a rectangle for the name
|
||||
groupNode
|
||||
.insert('rect', '#' + attributeNode.cn.node().id)
|
||||
.attr('class', `er ${attribStyle}`)
|
||||
.attr('fill', conf.fill)
|
||||
.attr('fill-opacity', '100%')
|
||||
.attr('stroke', conf.stroke)
|
||||
.attr('x', `${typeRect.attr('x') + typeRect.attr('width')}`)
|
||||
.attr('y', heightOffset)
|
||||
.attr('width', maxCommentWidth + widthPadding * 2 + spareWidth / 2)
|
||||
.attr('height', attributeNode.cn.node().getBBox().height + heightPadding * 2);
|
||||
}
|
||||
|
||||
// Increment the height offset to move to the next row
|
||||
heightOffset +=
|
||||
Math.max(nodePair.tn.node().getBBox().height, nodePair.nn.node().getBBox().height) +
|
||||
heightPadding * 2;
|
||||
heightOffset += attributeNode.height + heightPadding * 2;
|
||||
|
||||
// Flip the attribute style for row banding
|
||||
attribStyle = attribStyle == 'attributeBoxOdd' ? 'attributeBoxEven' : 'attributeBoxOdd';
|
||||
|
@@ -18,7 +18,9 @@
|
||||
"erDiagram" return 'ER_DIAGRAM';
|
||||
"{" { this.begin("block"); return 'BLOCK_START'; }
|
||||
<block>\s+ /* skip whitespace in block */
|
||||
<block>[A-Za-z][A-Za-z0-9\-_]* { return 'ATTRIBUTE_WORD'; }
|
||||
<block>(?:PK)|(?:FK) return 'ATTRIBUTE_KEY'
|
||||
<block>[A-Za-z][A-Za-z0-9\-_]* return 'ATTRIBUTE_WORD'
|
||||
<block>\"[^"]*\" return 'COMMENT';
|
||||
<block>[\n]+ /* nothing */
|
||||
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
|
||||
<block>. return yytext[0];
|
||||
@@ -95,6 +97,9 @@ attributes
|
||||
|
||||
attribute
|
||||
: attributeType attributeName { $$ = { attributeType: $1, attributeName: $2 }; }
|
||||
| attributeType attributeName attributeKeyType { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3 }; }
|
||||
| attributeType attributeName COMMENT { $$ = { attributeType: $1, attributeName: $2, attributeComment: $3 }; }
|
||||
| attributeType attributeName attributeKeyType COMMENT { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3, attributeComment: $4 }; }
|
||||
;
|
||||
|
||||
attributeType
|
||||
@@ -105,6 +110,10 @@ attributeName
|
||||
: ATTRIBUTE_WORD { $$=$1; }
|
||||
;
|
||||
|
||||
attributeKeyType
|
||||
: ATTRIBUTE_KEY { $$=$1; }
|
||||
;
|
||||
|
||||
relSpec
|
||||
: cardinality relType cardinality
|
||||
{
|
||||
|
@@ -42,6 +42,36 @@ describe('when parsing ER diagram it...', function () {
|
||||
expect(entities[entity].attributes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow an entity with a single attribute to be defined with a key', function () {
|
||||
const entity = 'BOOK';
|
||||
const attribute = 'string title PK';
|
||||
|
||||
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
|
||||
const entities = erDb.getEntities();
|
||||
expect(Object.keys(entities).length).toBe(1);
|
||||
expect(entities[entity].attributes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow an entity with a single attribute to be defined with a comment', function () {
|
||||
const entity = 'BOOK';
|
||||
const attribute = `string title "comment"`;
|
||||
|
||||
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
|
||||
const entities = erDb.getEntities();
|
||||
expect(Object.keys(entities).length).toBe(1);
|
||||
expect(entities[entity].attributes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow an entity with a single attribute to be defined with a key and a comment', function () {
|
||||
const entity = 'BOOK';
|
||||
const attribute = `string title PK "comment"`;
|
||||
|
||||
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
|
||||
const entities = erDb.getEntities();
|
||||
expect(Object.keys(entities).length).toBe(1);
|
||||
expect(entities[entity].attributes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow an entity with multiple attributes to be defined', function () {
|
||||
const entity = 'BOOK';
|
||||
const attribute1 = 'string title';
|
||||
|
@@ -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; }
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
};
|
||||
|
Reference in New Issue
Block a user