mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
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:
parent
22b0ddfb42
commit
bd11663e0a
747
dist/mermaid.core.js
vendored
747
dist/mermaid.core.js
vendored
File diff suppressed because one or more lines are too long
2
dist/mermaid.core.js.map
vendored
2
dist/mermaid.core.js.map
vendored
File diff suppressed because one or more lines are too long
753
dist/mermaid.js
vendored
753
dist/mermaid.js
vendored
File diff suppressed because one or more lines are too long
2
dist/mermaid.js.map
vendored
2
dist/mermaid.js.map
vendored
File diff suppressed because one or more lines are too long
12
dist/mermaid.min.js
vendored
12
dist/mermaid.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/mermaid.min.js.map
vendored
2
dist/mermaid.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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\*
|
||||
|
@ -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, "\""));} ;
|
||||
|
||||
%%
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 () {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
49
src/utils.js
49
src/utils.js
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user