Added auto wrap option (and grammar) for sequenceDiagrams

Added inline config and init(ialization) grammar
Added reinitialize functionality to mermaidAPI (not to be confused with initialize)
Added actorFontWeight, noteFontWeight, messageFontWeight, wrapEnabled, wrapPadding
Added wrapLabel and breakWord functions to intelligently wrap text based on a pixel-based width instead of column-based
  - The implementation is largely from Carys Mills: https://medium.com/@CarysMills/wrapping-svg-text-without-svg-2-ecbfb58f7ba4
  - Made slight modifications for mermaid-js
Fixed dark theme color inconsistencies for sequence diagrams
Removed !important from sequence scss as this prevents any client overrides
Fixed various invalid css values in sequence scss which prevented proper rendering of various elements
Added detectInit to utils for initialization json detection
Updated detectType to support the existence or absence of the intialization configuration
Updated calculateTextWidth to include fontWeight
This commit is contained in:
Chris Moran 2020-06-08 14:48:03 -04:00
parent 22b0ddfb42
commit bd11663e0a
No known key found for this signature in database
GPG Key ID: FBD13F2A0E1B9152
15 changed files with 1192 additions and 741 deletions

747
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

753
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

@ -219,6 +219,11 @@ This sets the font size of the actor's description
This sets the font family of the actor's description
**Default value "Open-Sans", "sans-serif"**.
### actorFontWeight
This sets the font weight of the actor's description
\*\*Default value 400.
### noteFontSize
This sets the font size of actor-attached notes.
@ -229,6 +234,11 @@ This sets the font size of actor-attached notes.
This sets the font family of actor-attached notes.
**Default value "trebuchet ms", verdana, arial**.
### noteFontWeight
This sets the font weight of the note's description
\*\*Default value 400.
### noteAlign
This sets the text alignment of actor-attached notes.
@ -244,6 +254,21 @@ This sets the font size of actor messages.
This sets the font family of actor messages.
**Default value "trebuchet ms", verdana, arial**.
### messageFontWeight
This sets the font weight of the message's description
\*\*Default value 400.
### wrapEnabled
This sets the auto-wrap state for the diagram
\*\*Default value false.
### wrapPadding
This sets the auto-wrap padding for the diagram (sides only)
\*\*Default value 15.
## gantt
The object containing configurations specific for gantt diagrams\*

View File

@ -48,6 +48,14 @@
"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'; }
@ -74,6 +82,7 @@
start
: SPACE start
| NL start
| INIT text3 start
| SD document { yy.apply($2);return $2; }
;
@ -85,7 +94,7 @@ document
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$=[];}
| NL { $$=[]; }
;
statement
@ -93,9 +102,12 @@ statement
| 'participant' actor 'NL' {$$=$2;}
| signal 'NL'
| autonumber {yy.enableSequenceNumbers()}
| wrap {yy.enableWrap()}
| nowrap {yy.disableWrap()}
| '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
{
@ -197,4 +209,6 @@ signaltype
text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
text3: TXT {$$ = JSON.parse($1.substring(1).trim().replace(/\\n/gm, "\n").replace(/'/gm, "\""));} ;
%%

View File

@ -1,4 +1,5 @@
import { logger } from '../../logger';
import { getConfig, setConfig } from '../../config';
let prevActor = undefined;
let actors = {};
@ -6,6 +7,7 @@ let messages = [];
const notes = [];
let title = '';
let sequenceNumbersEnabled = false;
let wrapEnabled = false;
export const addActor = function(id, name, description) {
// Don't allow description nulling
@ -92,6 +94,16 @@ export const enableSequenceNumbers = function() {
};
export const showSequenceNumbers = () => sequenceNumbersEnabled;
export const enableWrap = function() {
wrapEnabled = true;
};
export const disableWrap = function() {
wrapEnabled = false;
};
export const autoWrap = () => wrapEnabled;
export const clear = function() {
actors = {};
messages = [];
@ -213,6 +225,16 @@ 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;
}
}
};
@ -221,8 +243,11 @@ export default {
addActor,
addMessage,
addSignal,
enableWrap,
disableWrap,
enableSequenceNumbers,
showSequenceNumbers,
autoWrap,
getMessages,
getActors,
getActor,

View File

@ -4,6 +4,7 @@ import { logger } from '../../logger';
import { parser } from './parser/sequenceDiagram';
import common from '../common/common';
import sequenceDb from './sequenceDb';
import { getConfig } from '../../config';
parser.yy = sequenceDb;
@ -18,13 +19,17 @@ const conf = {
height: 65,
actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"',
// 400 = normal
actorFontWeight: 400,
// Note font settings
noteFontSize: 14,
noteFontFamily: '"trebuchet ms", verdana, arial',
noteFontWeight: 400,
noteAlign: 'center',
// Message font settings
messageFontSize: 16,
messageFontFamily: '"trebuchet ms", verdana, arial',
messageFontWeight: 400,
// Margin around loop boxes
boxMargin: 10,
boxTextMargin: 5,
@ -45,7 +50,12 @@ const conf = {
// text placement as: tspan | fo | old only text as before
textPlacement: 'tspan',
showSequenceNumbers: false
showSequenceNumbers: false,
// wrap text
wrapEnabled: false,
// padding for wrapped text
wrapPadding: 15
};
export const bounds = {
@ -138,8 +148,7 @@ export const bounds = {
return activation.actor;
})
.lastIndexOf(message.from.actor);
const activation = this.activations.splice(lastActorActivationIdx, 1)[0];
return activation;
return this.activations.splice(lastActorActivationIdx, 1)[0];
},
newLoop: function(title, fill) {
this.sequenceItems.push({
@ -152,8 +161,7 @@ export const bounds = {
});
},
endLoop: function() {
const loop = this.sequenceItems.pop();
return loop;
return this.sequenceItems.pop();
},
addSectionToLoop: function(message) {
const loop = this.sequenceItems.pop();
@ -174,6 +182,50 @@ export const bounds = {
return this.data;
}
};
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
const words = label.split(' ');
const completedLines = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `);
const nextLineLength = calculateTextWidth(nextLine);
if (wordLength > maxWidth) {
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth);
completedLines.push(nextLine, ...hyphenatedStrings);
nextLine = remainingWord;
} else if (nextLineLength + wordLength >= maxWidth) {
completedLines.push(nextLine);
nextLine = word;
} else {
nextLine = [nextLine, word].filter(Boolean).join(' ');
}
const currentWord = index + 1;
const isLastWord = currentWord === words.length;
if (isLastWord) {
completedLines.push(nextLine);
}
});
return completedLines.filter(line => line !== '').join(joinWith);
};
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
const characters = word.split('');
const lines = [];
let currentLine = '';
characters.forEach((character, index) => {
const nextLine = `${currentLine}${character}`;
const lineWidth = calculateTextWidth(nextLine);
if (lineWidth >= maxWidth) {
const currentCharacter = index + 1;
const isLastLine = characters.length === currentCharacter;
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
currentLine = '';
} else {
currentLine = nextLine;
}
});
return { hyphenatedStrings: lines, remainingWord: currentLine };
};
const _drawLongText = (text, x, y, g, width) => {
let textHeight = 0;
@ -187,7 +239,6 @@ const _drawLongText = (text, x, y, g, width) => {
right: 'end',
end: 'end'
};
const lines = text.split(common.lineBreakRegex);
for (const line of lines) {
const textObj = svgDraw.getTextObj();
@ -263,8 +314,8 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
* @param startx
* @param stopx
* @param verticalPos
* @param txtCenter
* @param msg
* @param sequenceIndex
*/
const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceIndex) {
const g = elem.append('g');
@ -450,7 +501,13 @@ export const setConf = function(cnf) {
});
if (cnf.fontFamily) {
conf.actorFontFamily = conf.noteFontFamily = cnf.fontFamily;
conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily;
}
if (cnf.fontSize) {
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
}
if (cnf.fontWeight) {
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
}
};
@ -491,7 +548,12 @@ const calculateActorWidth = function(actor) {
return Math.max(
conf.width,
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
};
@ -501,14 +563,16 @@ const calculateActorWidth = function(actor) {
* @param text - The text to calculate the width of
* @param fontSize - The font size of the given text
* @param fontFamily - The font family (one, or more fonts) to render
* @param fontWeight - The font weight (normal, bold, italics)
*/
export const calculateTextWidth = function(text, fontSize, fontFamily) {
export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeight) {
if (!text) {
return 0;
}
fontSize = fontSize ? fontSize : conf.actorFontSize;
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
fontWeight = fontWeight ? fontWeight : conf.actorFontWeight;
// We can't really know if the user supplied font family will render on the user agent;
// thus, we'll take the max width between the user supplied font family, and a default
@ -518,7 +582,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
let maxWidth = 0;
const body = select('body');
// We don'y want to leak DOM elements - if a removal operation isn't available
// We don't want to leak DOM elements - if a removal operation isn't available
// for any reason, do not continue.
if (!body.remove) {
return 0;
@ -533,6 +597,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
const textElem = svgDraw
.drawText(g, textObj)
.style('font-size', fontSize)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
@ -542,7 +607,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders
return maxWidth + 35;
return maxWidth + conf.wrapPadding * 2;
};
/**
@ -551,8 +616,11 @@ export const calculateTextWidth = function(text, fontSize, fontFamily) {
* @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}"]`);
@ -597,11 +665,18 @@ export const draw = function(text, id) {
// Draw the messages/signals
let sequenceIndex = 1;
messages.forEach(function(msg) {
let loopData;
const noteWidth = Math.max(
conf.width,
calculateTextWidth(msg.message, conf.noteFontSize, conf.noteFontFamily)
);
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);
switch (msg.type) {
case parser.yy.LINETYPE.NOTE:
@ -609,8 +684,12 @@ export const draw = function(text, id) {
startx = actors[msg.from].x;
stopx = actors[msg.to].x;
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);
}
drawNote(
diagram,
startx + (actors[msg.from].width + conf.actorMargin) / 2,
@ -619,6 +698,9 @@ export const draw = function(text, id) {
noteWidth
);
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth - conf.noteMargin * 2);
}
drawNote(
diagram,
startx - noteWidth + (actors[msg.from].width - conf.actorMargin) / 2,
@ -628,6 +710,9 @@ 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);
}
drawNote(
diagram,
startx + (actors[msg.to].width - noteWidth) / 2,
@ -638,6 +723,11 @@ 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;
if (shouldWrap) {
msg.message = wrapLabel(msg.message, noteWidth);
}
let x =
startx < stopx
? startx + (actors[msg.from].width - conf.actorMargin) / 2
@ -726,6 +816,9 @@ export const draw = function(text, id) {
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
startx = fromBounds[fromIdx];
stopx = toBounds[toIdx];
if (shouldWrap) {
msg.message = wrapLabel(msg.message, Math.abs(stopx - startx - conf.messageMargin));
}
const verticalPos = bounds.getVerticalPos();
drawMessage(diagram, startx, stopx, verticalPos, msg, sequenceIndex);
@ -823,21 +916,34 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
const actor = actors[msg.to];
// If this is the first actor, and the message is left of it, no need to calculate the margin
if (msg.placement == parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
if (msg.placement === parser.yy.PLACEMENT.LEFTOF && !actor.prevActor) {
return;
}
// If this is the last actor, and the message is right of it, no need to calculate the margin
if (msg.placement == parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF && !actor.nextActor) {
return;
}
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 messageWidth = calculateTextWidth(msg.message, fontSize, fontFamily);
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,
fontSize,
fontFamily,
fontWeight
);
/*
* The following scenarios should be supported:
@ -855,25 +961,25 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
* - If the note is on the right of the actor, we should define the current actor
* margin
*/
if (isMessage && msg.from == actor.nextActor) {
if (isMessage && msg.from === actor.nextActor) {
maxMessageWidthPerActor[msg.to] = Math.max(
maxMessageWidthPerActor[msg.to] || 0,
messageWidth
);
} else if (
(isMessage && msg.from == actor.prevActor) ||
msg.placement == parser.yy.PLACEMENT.RIGHTOF
(isMessage && msg.from === actor.prevActor) ||
msg.placement === parser.yy.PLACEMENT.RIGHTOF
) {
maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] || 0,
messageWidth
);
} else if (msg.placement == parser.yy.PLACEMENT.LEFTOF) {
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] || 0,
messageWidth
);
} else if (msg.placement == parser.yy.PLACEMENT.OVER) {
} else if (msg.placement === parser.yy.PLACEMENT.OVER) {
if (actor.prevActor) {
maxMessageWidthPerActor[actor.prevActor] = Math.max(
maxMessageWidthPerActor[actor.prevActor] || 0,
@ -891,6 +997,7 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
}
});
logger.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor);
return maxMessageWidthPerActor;
};
@ -919,15 +1026,31 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
continue;
}
actor.width = Math.max(
conf.width,
calculateTextWidth(actor.description, conf.actorFontSize, conf.actorFontFamily)
);
const wrapped = sequenceDb.autoWrap() || conf.wrapEnabled;
nextActor.width = Math.max(
conf.width,
calculateTextWidth(nextActor.description, conf.actorFontSize, conf.actorFontFamily)
);
actor.width = wrapped
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
actor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
nextActor.width = wrapped
? conf.width
: Math.max(
conf.width,
calculateTextWidth(
nextActor.description,
conf.actorFontSize,
conf.actorFontFamily,
conf.actorFontWeight
)
);
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;

View File

@ -284,6 +284,11 @@ const config = {
* **Default value "Open-Sans", "sans-serif"**.
*/
actorFontFamily: '"Open-Sans", "sans-serif"',
/**
* This sets the font weight of the actor's description
* **Default value 400.
*/
actorFontWeight: 400,
/**
* This sets the font size of actor-attached notes.
* **Default value 14**.
@ -294,6 +299,11 @@ const config = {
* **Default value "trebuchet ms", verdana, arial**.
*/
noteFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the note's description
* **Default value 400.
*/
noteFontWeight: 400,
/**
* This sets the text alignment of actor-attached notes.
* **Default value center**.
@ -308,7 +318,22 @@ const config = {
* This sets the font family of actor messages.
* **Default value "trebuchet ms", verdana, arial**.
*/
messageFontFamily: '"trebuchet ms", verdana, arial'
messageFontFamily: '"trebuchet ms", verdana, arial',
/**
* This sets the font weight of the message's description
* **Default value 400.
*/
messageFontWeight: 400,
/**
* This sets the auto-wrap state for the diagram
* **Default value false.
*/
wrapEnabled: false,
/**
* This sets the auto-wrap padding for the diagram (sides only)
* **Default value 15.
*/
wrapPadding: 15
},
/**
@ -710,6 +735,10 @@ const render = function(id, _txt, cb, container) {
txt = encodeEntities(txt);
const element = select('#d' + id).node();
const graphInit = utils.detectInit(txt);
if (graphInit) {
reinitialize(graphInit);
}
const graphType = utils.detectType(txt);
// insert inline style into svg
@ -912,15 +941,27 @@ const setConf = function(cnf) {
}
};
function initialize(options) {
logger.debug('Initializing mermaidAPI ', pkg.version);
function reinitialize(options) {
let _config = getConfig();
if (typeof options === 'object') {
_config = Object.assign(_config, options);
setConf(_config);
}
setConfig(_config);
setLogLevel(_config.logLevel);
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, _config });
}
function initialize(options) {
let _config = config;
logger.debug('Initializing mermaidAPI ', { version: pkg.version, options, _config });
// Update default config with options supplied at initialization
if (typeof options === 'object') {
setConf(options);
_config = Object.assign(_config, options);
setConf(_config);
}
setConfig(config);
setLogLevel(config.logLevel);
setConfig(_config);
setLogLevel(_config.logLevel);
}
// function getConfig () {

View File

@ -1,5 +1,5 @@
$mainBkg: #BDD5EA;
$secondBkg: #6D6D65;
$mainBkg: #1f2020;
$secondBkg: lighten(#1f2020, 100);
$mainContrastColor: lightgrey;
$darkTextColor: #323D47;
$lineColor: $mainContrastColor;
@ -21,13 +21,13 @@ $edgeLabelBackground: #e8e8e8;
$actorBorder: $border1;
$actorBkg: $mainBkg;
$actorTextColor: black;
$actorTextColor: $mainContrastColor;
$actorLineColor: $mainContrastColor;
$signalColor: $mainContrastColor;
$signalTextColor: $mainContrastColor;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $darkTextColor;
$labelTextColor: $mainContrastColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;

View File

@ -3,7 +3,7 @@
fill: $actorBkg;
}
text.actor {
text.actor > tspan {
fill: $actorTextColor;
stroke: none;
}
@ -14,18 +14,19 @@ text.actor {
.messageLine0 {
stroke-width: 1.5;
stroke-dasharray: '2 2';
stroke-dasharray: none;
stroke: $signalColor;
}
.messageLine1 {
stroke-width: 1.5;
stroke-dasharray: '2 2';
stroke-dasharray: 2, 2;
stroke: $signalColor;
}
#arrowhead {
#arrowhead path {
fill: $signalColor;
stroke: $signalColor;
}
.sequenceNumber {
@ -37,13 +38,13 @@ text.actor {
}
#crosshead path {
fill: $signalColor !important;
stroke: $signalColor !important;
fill: $signalColor;
stroke: $signalColor;
}
.messageText {
fill: $signalTextColor;
stroke: none;
stroke: $signalTextColor;
}
.labelBox {
@ -51,20 +52,21 @@ text.actor {
fill: $labelBoxBkgColor;
}
.labelText {
.labelText, .labelText > tspan {
fill: $labelTextColor;
stroke: none;
}
.loopText {
.loopText, .loopText > tspan {
fill: $loopTextColor;
stroke: none;
}
.loopLine {
stroke-width: 2;
stroke-dasharray: '2 2';
stroke-width: 2px;
stroke-dasharray: 2, 2;
stroke: $labelBoxBorderColor;
fill: $labelBoxBorderColor;
}
.note {
@ -74,10 +76,9 @@ text.actor {
}
.noteText {
fill: black;
fill: $actorBkg;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-family: 'trebuchet ms', verdana, arial, var(--mermaid-font-family);
font-size: 14px;
}
@ -95,3 +96,14 @@ text.actor {
fill: $activationBkgColor;
stroke: $activationBorderColor;
}
g > rect[class="note"] {
stroke: $noteBorderColor;
fill: $noteBkgColor;
}
g > text[class="noteText"] > tspan {
stroke: $actorBkg;
fill: $actorBkg;
stroke-width: 1px;
}

View File

@ -29,6 +29,52 @@ const d3CurveTypes = {
curveStepBefore: curveStepBefore
};
const initPart = /^\s*init(?:ialize)?:\s*(\{.*})$/m;
/**
* @function detectInit
* Detects the init config object from the text
* ```mermaid
* init: {"startOnLoad": true, "logLevel": 1 }
* graph LR
* a-->b
* b-->c
* c-->d
* d-->e
* e-->f
* f-->g
* g-->h
* ```
* or
* ```mermaid
* initialize: {"startOnLoad": true, logLevel: "fatal" }
* graph LR
* a-->b
* b-->c
* c-->d
* d-->e
* e-->f
* f-->g
* g-->h
* ```
*
* @param {string} text The text defining the graph
* @returns {object} An object representing the initialization to pass to mermaidAPI.initialize()
*/
export const detectInit = function(text) {
text = text.replace(/^\s*%%.*\n/g, '\n');
logger.debug('Detecting diagram init based on the text ' + text);
if (text.match(initPart)) {
return JSON.parse(
text
.match(initPart)[1]
.trim()
.replace(/\\n/g, '\n')
.replace(/'/g, '"')
);
}
};
/**
* @function detectType
* Detects the type of the graph text.
@ -47,7 +93,7 @@ const d3CurveTypes = {
* @returns {string} A graph definition key
*/
export const detectType = function(text) {
text = text.replace(/^\s*%%.*\n/g, '\n');
text = text.replace(/^\s*%%.*\n/g, '\n').replace(initPart, '');
logger.debug('Detecting diagram type based on the text ' + text);
if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence';
@ -255,6 +301,7 @@ export const generateId = () => {
};
export default {
detectInit,
detectType,
isSubstringInArray,
interpolateToCurve,

View File

@ -7,6 +7,16 @@ describe('when detecting chart type ', function() {
const type = utils.detectType(str);
expect(type).toBe('flowchart');
});
it('should handle an initialize defintion', function() {
const str = 'initialize: { "logLevel": 0, "theme": "dark" }\ngraph TB\nbfs1: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 init = JSON.stringify(utils.detectInit(str));
expect(init).toBe('{"logLevel":0,"theme":"dark"}');
});
it('should handle a graph defintion with leading spaces', function() {
const str = ' graph TB\nbfs1:queue';
const type = utils.detectType(str);