## Work in progress.

Implemented directives per PR review comment:

%%{directive: json-encoded-arg-string}%%

example:

%%{init: { 'logLevel': 0, 'theme': 'dark' }}%%

Also changed wrap and config to directives:

%%{wrap}%%
%%{config: { 'fontSize': 18 }}%%
This commit is contained in:
Chris Moran 2020-06-11 15:31:59 -04:00
parent dba617a7fe
commit 04b4da0c75
No known key found for this signature in database
GPG Key ID: FBD13F2A0E1B9152
18 changed files with 1619 additions and 845 deletions

543
dist/mermaid.core.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

543
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

10
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

@ -506,8 +506,11 @@ mermaid.sequenceConfig = {
| 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" |
| actorFontWeight | Sets the font weight 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 |
| noteFontWeight | Sets the font weight 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 |
| messageFontWeight | Sets the font weight for actor<->actor messages | "trebuchet ms", verdana, arial |

View File

@ -16,60 +16,62 @@
%x ID
%x ALIAS
// Directive states
%x OPEN_DIRECTIVE
%x IN_DIRECTIVE
// A special state for grabbing text up to the first comment/newline
%x LINE
%%
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE>\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
<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 'NL'; }
"loop" { this.begin('LINE'); return 'loop'; }
"rect" { this.begin('LINE'); return 'rect'; }
"opt" { this.begin('LINE'); return 'opt'; }
"alt" { this.begin('LINE'); return 'alt'; }
"else" { this.begin('LINE'); return 'else'; }
"par" { this.begin('LINE'); return 'par'; }
"and" { this.begin('LINE'); return 'and'; }
<LINE>[^#\n;]* { this.popState(); return 'restOfLine'; }
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
"over" return 'over';
"note" return 'note';
"activate" { this.begin('ID'); return 'activate'; }
"deactivate" { this.begin('ID'); return 'deactivate'; }
"title" return 'title';
"sequenceDiagram" return 'SD';
"autonumber" return 'autonumber';
"init" return 'INIT';
"initialize" return 'INIT';
"conf" return 'conf';
"config" return 'conf';
"configure" return 'conf';
"configuration" return 'conf';
"wrap" return 'wrap';
"nowrap" return 'nowrap';
"," return ',';
";" return 'NL';
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
"-->" return 'DOTTED_OPEN_ARROW';
\-[x] return 'SOLID_CROSS';
\-\-[x] return 'DOTTED_CROSS';
":"[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NL';
. return 'INVALID';
\%\%\{ { this.begin('OPEN_DIRECTIVE'); return 'open_directive'; }
<OPEN_DIRECTIVE>(?!\}\%\%)(?:\w+)\s*[:]?\s*(?:.*?)?(?=\}\%\%) { this.popState(); return 'IN_DIRECTIVE'; }
\}\%\% { this.popState(); return 'close_directive'; }
"close_directive" return 'NL';
"open_directive" return 'NL';
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE,IN_DIRECTIVE,OPEN_DIRECTIVE>\#[^\n]* /* skip comments */
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
<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 'NL'; }
"loop" { this.begin('LINE'); return 'loop'; }
"rect" { this.begin('LINE'); return 'rect'; }
"opt" { this.begin('LINE'); return 'opt'; }
"alt" { this.begin('LINE'); return 'alt'; }
"else" { this.begin('LINE'); return 'else'; }
"par" { this.begin('LINE'); return 'par'; }
"and" { this.begin('LINE'); return 'and'; }
<LINE>(?:[:]?(?:no)?wrap:)?[^#\n;]* { this.popState(); return 'restOfLine'; }
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
"over" return 'over';
"note" return 'note';
"activate" { this.begin('ID'); return 'activate'; }
"deactivate" { this.begin('ID'); return 'deactivate'; }
"title" return 'title';
"sequenceDiagram" return 'SD';
"autonumber" return 'autonumber';
"," return ',';
";" return 'NL';
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
"-->" return 'DOTTED_OPEN_ARROW';
\-[x] return 'SOLID_CROSS';
\-\-[x] return 'DOTTED_CROSS';
":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NL';
. return 'INVALID';
/lex
@ -82,7 +84,7 @@
start
: SPACE start
| NL start
| INIT text3 start
| directive start
| SD document { yy.apply($2);return $2; }
;
@ -97,37 +99,39 @@ line
| NL { $$=[]; }
;
directive
: open_directive textDirective close_directive { yy.handleDirective($2); }
;
statement
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;}
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NL' {$$=$2;}
| signal 'NL'
| autonumber {yy.enableSequenceNumbers()}
| wrap {yy.enableWrap()}
| nowrap {yy.disableWrap()}
| directive 'NL'
| 'activate' actor 'NL' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
| 'deactivate' actor 'NL' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};}
| note_statement 'NL'
| conf text3 'NL' {$$=[{type:'config', config:$2}]}
| title text2 'NL' {$$=[{type:'setTitle', text:$2}]}
| 'loop' restOfLine document end
{
$3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START});
$3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START});
$3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END});
$$=$3;}
| 'rect' restOfLine document end
{
$3.unshift({type: 'rectStart', color:$2, signalType: yy.LINETYPE.RECT_START });
$3.push({type: 'rectEnd', color:$2, signalType: yy.LINETYPE.RECT_END });
$3.unshift({type: 'rectStart', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_START });
$3.push({type: 'rectEnd', color:yy.parseMessage($2), signalType: yy.LINETYPE.RECT_END });
$$=$3;}
| opt restOfLine document end
{
$3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START});
$3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END});
$3.unshift({type: 'optStart', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_START});
$3.push({type: 'optEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.OPT_END});
$$=$3;}
| alt restOfLine else_sections end
{
// Alt start
$3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START});
$3.unshift({type: 'altStart', altText:yy.parseMessage($2), signalType: yy.LINETYPE.ALT_START});
// Content in alt is already in $3
// End
$3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
@ -135,7 +139,7 @@ statement
| par restOfLine par_sections end
{
// Parallel start
$3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START});
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_START});
// Content in par is already in $3
// End
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
@ -145,13 +149,13 @@ statement
par_sections
: document
| document and restOfLine par_sections
{ $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); }
{ $$ = $1.concat([{type: 'and', parText:yy.parseMessage($3), signalType: yy.LINETYPE.PAR_AND}, $4]); }
;
else_sections
: document
| document else restOfLine else_sections
{ $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
{ $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
;
note_statement
@ -207,8 +211,23 @@ signaltype
| DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; }
;
text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
text2
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) } ;
text3: TXT {$$ = JSON.parse($1.substring(1).trim().replace(/\\n/gm, "\n").replace(/'/gm, "\""));} ;
text3
: TXT {$$ = JSON.parse($1.substring(1).trim().replace(/\\n/gm, "\n").replace(/'/gm, "\""));} ;
textDirective
: IN_DIRECTIVE
{
$1 = $1.trim().replace(/'/gm, '"');
if (/(\w+)[:]?\s*(\{.*}(?!%%))?/.test($1)) {
$1 = $1.match(/(\w+)[:]?\s*(\{.*}(?!%%))?/);
$$ = { type: $1[1], args: $1[2] !== undefined ? JSON.parse($1[2]) : null };
} else {
$$ = { type: $1, args: null };
}
}
;
%%

View File

@ -1,13 +1,31 @@
import { logger } from '../../logger';
import { getConfig, setConfig } from '../../config';
import mermaidAPI from '../../mermaidAPI';
let prevActor = undefined;
let actors = {};
let messages = [];
const notes = [];
let title = '';
let titleWrapped = false;
let sequenceNumbersEnabled = false;
let wrapEnabled = false;
let configUpdated = false;
export const handleDirective = function(directive) {
switch (directive.type) {
case 'init':
case 'initialize':
mermaidAPI.reinitialize(directive.args);
break;
case 'config':
updateConfig(directive.args);
break;
case 'wrap':
wrapEnabled = true;
break;
}
};
export const addActor = function(id, name, description) {
// Don't allow description nulling
@ -15,9 +33,16 @@ export const addActor = function(id, name, description) {
if (old && name === old.name && description == null) return;
// Don't allow null descriptions, either
if (description == null) description = name;
if (description == null || description.text == null) {
description = { text: name, wrap: null };
}
actors[id] = { name: name, description: description, prevActor: prevActor };
actors[id] = {
name: name,
description: description.text,
wrap: (description.wrap === null && autoWrap()) || !!description.wrap,
prevActor: prevActor
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
}
@ -26,7 +51,7 @@ export const addActor = function(id, name, description) {
};
const activationCount = part => {
let i = 0;
let i;
let count = 0;
for (i = 0; i < messages.length; i++) {
// console.warn(i, messages[i]);
@ -45,12 +70,27 @@ const activationCount = part => {
};
export const addMessage = function(idFrom, idTo, message, answer) {
messages.push({ from: idFrom, to: idTo, message: message, answer: answer });
messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
answer: answer
});
};
export const addSignal = function(idFrom, idTo, message, messageType) {
export const addSignal = function(idFrom, idTo, message = { text: null, wrap: null }, messageType) {
logger.debug(
'Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType
'Adding message from=' +
idFrom +
' to=' +
idTo +
' message=' +
message.text +
' wrap=' +
message.wrap +
' type=' +
messageType
);
if (messageType === LINETYPE.ACTIVE_END) {
@ -58,7 +98,7 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
logger.debug('Adding message from=', messages, cnt);
if (cnt < 1) {
// Bail out as there is an activation signal from an inactive participant
var error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
let error = new Error('Trying to inactivate an inactive participant (' + idFrom.actor + ')');
error.hash = {
text: '->>-',
token: '->>-',
@ -69,7 +109,13 @@ export const addSignal = function(idFrom, idTo, message, messageType) {
throw error;
}
}
messages.push({ from: idFrom, to: idTo, message: message, type: messageType });
messages.push({
from: idFrom,
to: idTo,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
type: messageType
});
return true;
};
@ -89,6 +135,9 @@ export const getActorKeys = function() {
export const getTitle = function() {
return title;
};
export const getTitleWrapped = function() {
return titleWrapped;
};
export const enableSequenceNumbers = function() {
sequenceNumbersEnabled = true;
};
@ -107,6 +156,22 @@ export const autoWrap = () => wrapEnabled;
export const clear = function() {
actors = {};
messages = [];
configUpdated = false;
};
export const parseMessage = function(str) {
const _str = str.trim();
return {
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^[:]?(?:no)?wrap:/) === null
? autoWrap()
: _str.match(/^[:]?wrap:/) !== null
? true
: _str.match(/^[:]?nowrap:/) !== null
? false
: autoWrap()
};
};
export const LINETYPE = {
@ -145,7 +210,12 @@ export const PLACEMENT = {
};
export const addNote = function(actor, placement, message) {
const note = { actor: actor, placement: placement, message: message };
const note = {
actor: actor,
placement: placement,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap
};
// Coerce actor into a [to, from, ...] array
const actors = [].concat(actor, actor);
@ -154,14 +224,29 @@ export const addNote = function(actor, placement, message) {
messages.push({
from: actors[0],
to: actors[1],
message: message,
message: message.text,
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
type: LINETYPE.NOTE,
placement: placement
});
};
export const setTitle = function(titleText) {
title = titleText;
export const setTitle = function(titleWrap) {
title = titleWrap.text;
titleWrapped = (titleWrap.wrap === null && autoWrap()) || !!titleWrap.wrap;
};
export const updateConfig = function(config = getConfig()) {
try {
setConfig(config);
configUpdated = true;
} catch (error) {
logger.error('Error: unable to parse config');
}
};
export const hasConfigChange = function() {
return configUpdated;
};
export const apply = function(param) {
@ -225,16 +310,6 @@ export const apply = function(param) {
case 'parEnd':
addSignal(undefined, undefined, undefined, param.signalType);
break;
case 'config':
try {
let cfg = param.config;
let _config = getConfig();
let config = Object.assign(_config, cfg);
setConfig(config);
} catch (error) {
logger.error('Error: unable to parse config');
}
break;
}
}
};
@ -253,7 +328,13 @@ export default {
getActor,
getActorKeys,
getTitle,
handleDirective,
hasConfigChange,
getConfig,
updateConfig,
getTitleWrapped,
clear,
parseMessage,
LINETYPE,
ARROWTYPE,
PLACEMENT,

File diff suppressed because it is too large Load Diff

View File

@ -79,6 +79,9 @@ export const bounds = {
stopy: undefined
};
this.verticalPos = 0;
if (parser.yy.hasConfigChange()) {
setConf(getConfig());
}
},
updateVal: function(obj, key, val, fun) {
if (typeof obj[key] === 'undefined') {
@ -182,7 +185,11 @@ export const bounds = {
return this.data;
}
};
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
if (common.lineBreakRegex.test(label)) {
return label;
}
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
@ -207,6 +214,7 @@ const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
});
return completedLines.filter(line => line !== '').join(joinWith);
};
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
const characters = word.split('');
const lines = [];
@ -266,6 +274,7 @@ const _drawLongText = (text, x, y, g, width) => {
.style('text-anchor', alignment)
.style('font-size', conf.noteFontSize)
.style('font-family', conf.noteFontFamily)
.style('font-weight', conf.noteFontWeight)
.attr('dominant-baseline', 'central')
.attr('alignment-baseline', 'central');
@ -322,17 +331,40 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
const txtCenter = startx + (stopx - startx) / 2;
let textElems = [];
/*
let textHeight = 0;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
let textElem = g
.append('text') // text label for the x axis
.attr('x', txtCenter)
.attr('y', verticalPos + textHeight)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('font-weight', conf.messageFontWeight)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim());
textElems.push(textElem);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
}
let totalOffset = textHeight;
*/
let counterBreaklines = 0;
let breaklineOffset = 17;
let breaklineOffset = conf.messageFontSize + 4;
const breaklines = msg.message.split(common.lineBreakRegex);
for (const breakline of breaklines) {
textElems.push(
g
.append('text') // text label for the x axis
.attr('x', txtCenter)
// .attr('y', verticalPos - breaklineVerticalOffset + counterBreaklines * breaklineOffset)
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
.style('font-size', conf.messageFontSize)
.style('font-family', conf.messageFontFamily)
.style('font-weight', conf.messageFontWeight)
.style('text-anchor', 'middle')
.attr('class', 'messageText')
.text(breakline.trim())
@ -341,7 +373,6 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
}
const offsetLineCounter = counterBreaklines - 1;
let totalOffset = offsetLineCounter * breaklineOffset;
let textWidths = textElems.map(function(textElem) {
return (textElem._groups || textElem)[0][0].getBBox().width;
});
@ -469,28 +500,42 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
// Draw the actors
let prevWidth = 0;
let prevMargin = 0;
let maxActorHeight = conf.height;
for (let i = 0; i < actorKeys.length; i++) {
const actor = actors[actorKeys[i]];
// Add some rendering data to the object
actor.width = actor.width || calculateActorWidth(actor);
actor.height = conf.height;
actor.height = actor.wrap
? calculateTextHeight(
actor.message,
conf.height,
actor.width,
conf.wrapPadding,
actor.wrap,
conf.actorFontSize
)
: conf.height;
maxActorHeight = Math.max(maxActorHeight, actor.height);
actor.margin = actor.margin || conf.actorMargin;
actor.x = prevWidth + prevMargin;
actor.y = verticalPos;
if (actor.wrap) {
actor.description = wrapLabel(actor.description, actor.width);
}
// Draw the box with the attached line
svgDraw.drawActor(diagram, actor, conf);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, conf.height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width;
prevMargin += actor.margin;
}
// Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(conf.height);
bounds.bumpVerticalPos(maxActorHeight);
};
export const setConf = function(cnf) {
@ -505,6 +550,7 @@ export const setConf = function(cnf) {
}
if (cnf.fontSize) {
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
// conf.height = cnf.fontSize * (65 / 14);
}
if (cnf.fontWeight) {
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
@ -546,15 +592,50 @@ const calculateActorWidth = function(actor) {
return conf.width;
}
return Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
return actor.wrap
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
};
/**
* This calculates the text's height, taking into account the wrap value and
* both the statically configured height, width, and the length of the text (in pixels).
*
* If the wrapped text text has greater height, we extend the height, so it's
* value won't overflow.
*
* @return - The height for the given actor
* @param message the text to measure
* @param elementHeight the height of the default bounding box containing the text
* @param elementWidth the width of the default bounding box containing the text
* @param margin space above and below
* @param wrap wrap the text based on: elementWidth - 2 * margin
* @param fontSize
*/
export const calculateTextHeight = function(
message,
elementHeight,
elementWidth,
margin,
wrap,
fontSize
) {
if (!message) {
return elementHeight;
}
let lineHeightFactor = wrap
? wrapLabel(message, elementWidth - 2 * margin).split(common.lineBreakRegex).length
: 1;
return wrap ? Math.max(elementHeight, lineHeightFactor * fontSize) : elementHeight;
};
/**
@ -616,11 +697,8 @@ export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeigh
* @param id
*/
export const draw = function(text, id) {
logger.debug(`Config (preclear): ${JSON.stringify(conf)}`, conf);
parser.yy.clear();
parser.parse(text + '\n');
logger.debug(`Config (postclear): ${JSON.stringify(conf)}`, conf);
setConf(getConfig());
bounds.init();
const diagram = select(`[id="${id}"]`);
@ -636,10 +714,16 @@ export const draw = function(text, id) {
const title = parser.yy.getTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
calculateActorMargins(actors, maxMessageWidthPerActor);
const maxActorHeight = calculateActorMargins(actors, maxMessageWidthPerActor);
drawActors(diagram, actors, actorKeys, 0);
bounds.bumpVerticalPos(
maxActorHeight > conf.height
? Math.min(conf.boxMargin, Math.abs(maxActorHeight - conf.height))
: 0
);
// The arrow head definition is attached to the svg once
svgDraw.insertArrowHead(diagram);
svgDraw.insertArrowCrossHead(diagram);
@ -667,16 +751,8 @@ export const draw = function(text, id) {
messages.forEach(function(msg) {
let loopData,
noteWidth,
textWidth = calculateTextWidth(
msg.message,
conf.noteFontSize,
conf.noteFontFamily,
conf.noteFontWeight
),
shouldWrap =
(sequenceDb.autoWrap() || conf.wrapEnabled) &&
msg.message &&
!common.lineBreakRegex.test(msg.message);
textWidth,
shouldWrap = msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message);
switch (msg.type) {
case parser.yy.LINETYPE.NOTE:
@ -684,11 +760,17 @@ export const draw = function(text, id) {
startx = actors[msg.from].x;
stopx = actors[msg.to].x;
textWidth = calculateTextWidth(
msg.message,
conf.noteFontSize,
conf.noteFontFamily,
conf.noteFontWeight
);
noteWidth = shouldWrap ? conf.width : Math.max(conf.width, textWidth);
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth - conf.noteMargin * 2);
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
@ -699,7 +781,7 @@ export const draw = function(text, id) {
);
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth - conf.noteMargin * 2);
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
@ -711,7 +793,7 @@ export const draw = function(text, id) {
} else if (msg.to === msg.from) {
// Single-actor over
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth - conf.noteMargin * 2);
msg.message = wrapLabel(msg.message, noteWidth);
}
drawNote(
diagram,
@ -722,16 +804,17 @@ export const draw = function(text, id) {
);
} else {
// Multi-actor over
forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
noteWidth =
(shouldWrap ? forceWidth : Math.max(forceWidth, textWidth)) - conf.noteMargin * 2;
forceWidth = Math.abs(startx - stopx) + conf.actorMargin / 2;
if (shouldWrap) {
noteWidth = forceWidth;
msg.message = wrapLabel(msg.message, noteWidth);
} else {
noteWidth = Math.max(forceWidth, textWidth - 2 * conf.noteMargin);
}
let x =
startx < stopx
? startx + (actors[msg.from].width - conf.actorMargin) / 2
: stopx + (actors[msg.to].width - conf.actorMargin) / 2;
? startx + (actors[msg.from].width - conf.actorMargin / 2) / 2
: stopx + (actors[msg.to].width - conf.actorMargin / 2) / 2;
drawNote(diagram, x, bounds.getVerticalPos(), msg, forceWidth);
}
@ -793,6 +876,9 @@ export const draw = function(text, id) {
break;
case parser.yy.LINETYPE.PAR_START:
bounds.bumpVerticalPos(conf.boxMargin);
if (shouldWrap) {
msg.message = wrapLabel(msg.message, conf.boxMargin);
}
bounds.newLoop(msg.message);
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
break;
@ -817,7 +903,13 @@ export const draw = function(text, id) {
startx = fromBounds[fromIdx];
stopx = toBounds[toIdx];
if (shouldWrap) {
msg.message = wrapLabel(msg.message, Math.abs(stopx - startx - conf.messageMargin));
msg.message = wrapLabel(
msg.message,
Math.max(
Math.abs(stopx - startx) + conf.messageMargin * 2,
conf.width + conf.messageMargin * 2
)
);
}
const verticalPos = bounds.getVerticalPos();
@ -927,19 +1019,12 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
const isNote = msg.placement !== undefined;
const isMessage = !isNote;
const wrapped = sequenceDb.autoWrap() || conf.wrapEnabled;
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
const fontWeight = isNote ? conf.noteFontWeight : conf.messageFontWeight;
const messageWidth = calculateTextWidth(
wrapped
? wrapLabel(
msg.message,
conf.width -
(isNote ? conf.noteMargin * 2 + conf.messageMargin * 2 : conf.messageMargin * 2)
)
: msg.message,
msg.wrap ? wrapLabel(msg.message, conf.width - conf.noteMargin) : msg.message,
fontSize,
fontFamily,
fontWeight
@ -997,7 +1082,6 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
}
});
logger.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor);
return maxMessageWidthPerActor;
};
@ -1012,6 +1096,7 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
* @param actorToMessageWidth - A map of actor key -> max message width it holds
*/
const calculateActorMargins = function(actors, actorToMessageWidth) {
let maxHeight = 0;
for (let actorKey in actorToMessageWidth) {
const actor = actors[actorKey];
@ -1026,37 +1111,42 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
continue;
}
const wrapped = sequenceDb.autoWrap() || conf.wrapEnabled;
[actor, nextActor].forEach(function(act) {
act.width = act.wrap
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
act.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
actor.width = wrapped
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
act.height = act.wrap
? calculateTextHeight(
act.description,
conf.height,
actor.width,
conf.actorMargin,
act.wrap,
conf.actorFontSize
)
);
nextActor.width = wrapped
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
nextActor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
: conf.height;
maxHeight = Math.max(maxHeight, act.height);
});
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
actor.margin = Math.max(actorWidth, conf.actorMargin);
}
Object.keys(actors).forEach(function(key) {
actors[key].height = maxHeight;
});
return maxHeight;
};
export default {

View File

@ -26,6 +26,9 @@ export const drawText = function(elem, textData) {
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.style('font-family', textData.fontFamily);
textElem.style('font-size', textData.fontSize);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
if (typeof textData.class !== 'undefined') {
textElem.attr('class', textData.class);
@ -77,7 +80,7 @@ let actorCnt = -1;
* Draws an actor in the diagram with the attaced line
* @param elem - The diagram we'll draw to.
* @param actor - The actor to draw.
* @param config - The sequence diagram config object.
* @param conf - drawText implementation discriminator object
*/
export const drawActor = function(elem, actor, conf) {
const center = actor.x + actor.width / 2;
@ -146,7 +149,7 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
* @param elem - elemenet to append the loop to.
* @param bounds - bounds of the given loop.
* @param labelText - Text within the loop.
* @param config - sequence diagram config object.
* @param conf
*/
export const drawLoop = function(elem, bounds, labelText, conf) {
const g = elem.append('g');
@ -169,11 +172,17 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
});
}
let minSize =
Math.round((3 * conf.fontSize) / 4) < 10 ? conf.fontSize : Math.round((3 * conf.fontSize) / 4);
let txt = getTextObj();
txt.text = labelText;
txt.x = bounds.startx;
txt.y = bounds.starty;
txt.labelMargin = 1.5 * 10; // This is the small box that says "loop"
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
txt.class = 'labelText'; // Its size & position are fixed.
drawLabel(g, txt);
@ -184,23 +193,31 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
txt.y = bounds.starty + 1.5 * conf.boxMargin;
txt.anchor = 'middle';
txt.class = 'loopText';
txt.fontFamily = conf.fontFamily;
txt.fontSize = minSize;
txt.fontWeight = conf.fontWeight;
drawText(g, txt);
let textElem = drawText(g, txt);
let textHeight = (textElem._groups || textElem)[0][0].getBBox().height;
if (typeof bounds.sectionTitles !== 'undefined') {
bounds.sectionTitles.forEach(function(item, idx) {
if (item !== '') {
txt.text = '[ ' + item + ' ]';
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
drawText(g, txt);
textElem = drawText(g, txt);
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
}
});
}
return textHeight + 4;
};
/**
* Draws a background rectangle
* @param color - The fill color for the background
* @param elem diagram (reference for bounds)
* @param bounds shape of the rectangle
*/
export const drawBackgroundRect = function(elem, bounds) {
const rectElem = drawRect(elem, {
@ -325,7 +342,7 @@ const _drawTextCandidateFunc = (function() {
}
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
const { actorFontSize, actorFontFamily } = conf;
const { actorFontSize, actorFontFamily, actorFontWeight } = conf;
const lines = content.split(common.lineBreakRegex);
for (let i = 0; i < lines.length; i++) {
@ -336,6 +353,7 @@ const _drawTextCandidateFunc = (function() {
.attr('y', y)
.style('text-anchor', 'middle')
.style('font-size', actorFontSize)
.style('font-weight', actorFontWeight)
.style('font-family', actorFontFamily);
text
.append('tspan')

View File

@ -561,6 +561,11 @@ setLogLevel(config.logLevel);
setConfig(config);
function parse(text) {
const graphInit = utils.detectInit(text);
if (graphInit) {
reinitialize(graphInit);
logger.debug('Init ', graphInit);
}
const graphType = utils.detectType(text);
let parser;
@ -949,14 +954,12 @@ const setConf = function(cnf) {
};
function reinitialize(options) {
let _config = getConfig();
if (typeof options === 'object') {
_config = Object.assign(_config, options);
setConf(_config);
setConf(options);
}
setConfig(_config);
setLogLevel(_config.logLevel);
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, _config });
setConfig(config);
setLogLevel(config.logLevel);
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, config });
}
function initialize(options) {
@ -980,6 +983,7 @@ const mermaidAPI = {
render,
parse,
initialize,
reinitialize,
getConfig
};

View File

@ -1,5 +1,5 @@
$mainBkg: #1f2020;
$secondBkg: lighten(#1f2020, 100);
$secondBkg: lighten(#1f2020, 16);
$mainContrastColor: lightgrey;
$darkTextColor: #323D47;
$lineColor: $mainContrastColor;
@ -31,8 +31,8 @@ $labelTextColor: $mainContrastColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
$activationBkgColor: #f4f4f4;
$activationBorderColor: $border1;
$activationBkgColor: $secondBkg;
$sequenceNumberColor: white;
/* Gantt chart variables */

View File

@ -12,8 +12,7 @@
// }
:root {
--mermaid-font-family: '"trebuchet ms", verdana, arial';
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive;
--mermaid-font-family: '"trebuchet ms", verdana, arial, "Comic Sans MS", "Comic Sans", cursive';
// --mermaid-alt-font-family: '"Lucida Console", Monaco, monospace';
}
@ -23,4 +22,4 @@
.error-text {
fill: $errorTextColor;
stroke: $errorTextColor;
}
}

View File

@ -78,8 +78,6 @@ text.actor > tspan {
.noteText {
fill: $actorBkg;
stroke: none;
font-family: 'trebuchet ms', verdana, arial, var(--mermaid-font-family);
font-size: 14px;
}
.activation0 {

View File

@ -28,14 +28,19 @@ const d3CurveTypes = {
curveStepAfter: curveStepAfter,
curveStepBefore: curveStepBefore
};
const initPart = /^\s*init(?:ialize)?:\s*(\{.*})$/m;
const fullDirective = /%%\{(\w+)[:]?\s*(\{.*}(?!%%))?\s*}%%/;
const directiveWithoutOpen = /\{(\w+)[:]?\s*(\{.*}(?!%%))?\s*}%%/;
const commentWithoutDirectives = new RegExp(
`\\s*%%(?!${directiveWithoutOpen.source})(?=}%%).*\n`,
'gm'
);
const anyComment = new RegExp(`\\s*%%.*\n`, 'gm');
/**
* @function detectInit
* Detects the init config object from the text
* ```mermaid
* init: {"startOnLoad": true, "logLevel": 1 }
* %%{init: {"startOnLoad": true, "logLevel": 1 }}%%
* graph LR
* a-->b
* b-->c
@ -47,7 +52,7 @@ const initPart = /^\s*init(?:ialize)?:\s*(\{.*})$/m;
* ```
* or
* ```mermaid
* initialize: {"startOnLoad": true, logLevel: "fatal" }
* %%{initialize: {"startOnLoad": true, logLevel: "fatal" }}%%
* graph LR
* a-->b
* b-->c
@ -62,12 +67,12 @@ const initPart = /^\s*init(?:ialize)?:\s*(\{.*})$/m;
* @returns {object} An object representing the initialization to pass to mermaidAPI.initialize()
*/
export const detectInit = function(text) {
text = text.replace(/^\s*%%.*\n/g, '\n');
text = text.replace(commentWithoutDirectives, '\n');
logger.debug('Detecting diagram init based on the text ' + text);
if (text.match(initPart)) {
const matches = text.match(fullDirective);
if (matches && /init(?:ialize)?/.test(matches[1])) {
return JSON.parse(
text
.match(initPart)[1]
matches[2]
.trim()
.replace(/\\n/g, '\n')
.replace(/'/g, '"')
@ -77,8 +82,10 @@ export const detectInit = function(text) {
/**
* @function detectType
* Detects the type of the graph text.
* Detects the type of the graph text. Takes into consideration the possible existence of an %%init
* directive
* ```mermaid
* %%{initialize: {"startOnLoad": true, logLevel: "fatal" }}%%
* graph LR
* a-->b
* b-->c
@ -93,7 +100,7 @@ export const detectInit = function(text) {
* @returns {string} A graph definition key
*/
export const detectType = function(text) {
text = text.replace(/^\s*%%.*\n/g, '\n').replace(initPart, '');
text = text.replace(anyComment, '\n');
logger.debug('Detecting diagram type based on the text ' + text);
if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence';

View File

@ -8,12 +8,19 @@ describe('when detecting chart type ', function() {
expect(type).toBe('flowchart');
});
it('should handle an initialize defintion', function() {
const str = 'initialize: { "logLevel": 0, "theme": "dark" }\ngraph TB\nbfs1:queue';
const str = `
%%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%%
graph TB
bfs1:queue
`;
const init = JSON.stringify(utils.detectInit(str));
expect(init).toBe('{"logLevel":0,"theme":"dark"}');
});
it('should handle an init defintion', function() {
const str = 'init: { "logLevel": 0, "theme": "dark" }\ngraph TB\nbfs1:queue';
const str = `
%%{init: { 'logLevel': 0, 'theme': 'dark' }}%%
graph TB
bfs1:queue`;
const init = JSON.stringify(utils.detectInit(str));
expect(init).toBe('{"logLevel":0,"theme":"dark"}');
});