mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-15 13:29:40 +02:00
addition: added bounds models for bounds checking in unit tests.
addition: bounds.init will clear models addition: added loop model widths id instead of using title as the id removed parseMessage debug message addition: added configuration labelBoxWidth and labelBoxHeight for loop label box width/height change: adjusted svgDraw drawText to support anchor and valign and whether to output a tspan change: drawText returns an array regardless of array size change: hardcoded label width/height uses conf.labelBoxWidth and conf.labelBoxHeight change: Math.round() on many of the calculations to clean up bounds values change: getTextObj anchor, width, height start as undefined
This commit is contained in:
@@ -508,7 +508,7 @@ context('Sequence diagram', () => {
|
||||
it('should overide config with directive settings', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { "sequence": { "mirrorActors": true }}}%%
|
||||
%%{init: { "config": { "mirrorActors": true }}}%%
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: config set to mirrorActors: false<br/>directive set to mirrorActors: true
|
||||
@@ -520,7 +520,7 @@ context('Sequence diagram', () => {
|
||||
it('should overide config with directive settings', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { "sequence": { "mirrorActors": false }}}%%
|
||||
%%{init: { "config": { "mirrorActors": false }}}%%
|
||||
sequenceDiagram
|
||||
%%{config: { "mirrorActors": false} }%%
|
||||
Alice->>Bob: I'm short
|
||||
|
@@ -202,7 +202,7 @@ export const clear = function() {
|
||||
|
||||
export const parseMessage = function(str) {
|
||||
const _str = str.trim();
|
||||
const retVal = {
|
||||
return {
|
||||
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
|
||||
wrap:
|
||||
_str.match(/^[:]?(?:no)?wrap:/) === null
|
||||
@@ -213,8 +213,6 @@ export const parseMessage = function(str) {
|
||||
? false
|
||||
: autoWrap()
|
||||
};
|
||||
logger.debug(`ParseMessage[${str}] [${JSON.stringify(retVal, null, 2)}`);
|
||||
return retVal;
|
||||
};
|
||||
|
||||
export const LINETYPE = {
|
||||
|
@@ -46,6 +46,8 @@ const conf = {
|
||||
|
||||
// width of activation box
|
||||
activationWidth: 10,
|
||||
labelBoxWidth: 50,
|
||||
labelBoxHeight: 20,
|
||||
|
||||
// text placement as: tspan | fo | old only text as before
|
||||
textPlacement: 'tspan',
|
||||
@@ -91,9 +93,19 @@ export const bounds = {
|
||||
|
||||
sequenceItems: [],
|
||||
activations: [],
|
||||
models: {
|
||||
loops: [],
|
||||
messages: [],
|
||||
notes: []
|
||||
},
|
||||
init: function() {
|
||||
this.sequenceItems = [];
|
||||
this.activations = [];
|
||||
this.models = {
|
||||
loops: [],
|
||||
messages: [],
|
||||
notes: []
|
||||
};
|
||||
this.data = {
|
||||
startx: undefined,
|
||||
stopx: undefined,
|
||||
@@ -181,6 +193,7 @@ export const bounds = {
|
||||
title: title.message,
|
||||
wrap: title.wrap,
|
||||
width: title.width,
|
||||
height: 0,
|
||||
fill: fill
|
||||
};
|
||||
},
|
||||
@@ -194,7 +207,7 @@ export const bounds = {
|
||||
const loop = this.sequenceItems.pop();
|
||||
loop.sections = loop.sections || [];
|
||||
loop.sectionTitles = loop.sectionTitles || [];
|
||||
loop.sections.push(bounds.getVerticalPos());
|
||||
loop.sections.push({ y: bounds.getVerticalPos(), height: 0 });
|
||||
loop.sectionTitles.push(message);
|
||||
this.sequenceItems.push(loop);
|
||||
},
|
||||
@@ -210,58 +223,8 @@ export const bounds = {
|
||||
}
|
||||
};
|
||||
|
||||
const drawLongText = (text, x, y, g, width) => {
|
||||
const alignmentToAnchor = {
|
||||
left: 'start',
|
||||
start: 'start',
|
||||
center: 'middle',
|
||||
middle: 'middle',
|
||||
right: 'end',
|
||||
end: 'end'
|
||||
};
|
||||
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
|
||||
const textObj = svgDraw.getTextObj();
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
textObj.x = x + conf.noteMargin;
|
||||
break;
|
||||
case 'middle':
|
||||
textObj.x = x + width / 2;
|
||||
break;
|
||||
case 'end':
|
||||
textObj.x = x + width - conf.noteMargin;
|
||||
break;
|
||||
}
|
||||
|
||||
textObj.y = y;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = text;
|
||||
textObj.class = 'noteText';
|
||||
textObj.fontFamily = conf.noteFontFamily;
|
||||
textObj.fontSize = conf.noteFontSize;
|
||||
textObj.fontWeight = conf.noteFontWeight;
|
||||
textObj.anchor = alignment;
|
||||
textObj.textMargin = conf.noteMargin;
|
||||
textObj.valign = alignment;
|
||||
textObj.wrap = true;
|
||||
|
||||
let textElem = drawText(g, textObj);
|
||||
|
||||
if (!Array.isArray(textElem)) {
|
||||
textElem = [textElem];
|
||||
}
|
||||
|
||||
textElem.forEach(te => {
|
||||
te.attr('dominant-baseline', 'central').attr('alignment-baseline', 'central');
|
||||
});
|
||||
|
||||
return textElem
|
||||
.map(te => (te._groups || te)[0][0].getBBox().height)
|
||||
.reduce((acc, curr) => acc + curr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an note in the diagram with the attaced line
|
||||
* Draws an note in the diagram with the attached line
|
||||
* @param elem - The diagram to draw to.
|
||||
* @param noteModel:{x: number, y: number, message: string, width: number} - startx: x axis start position, verticalPos: y axis position, messsage: the message to be shown, width: Set this with a custom width to override the default configured width.
|
||||
*/
|
||||
@@ -274,8 +237,26 @@ const drawNote = function(elem, noteModel) {
|
||||
|
||||
let g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.x = noteModel.x;
|
||||
textObj.y = noteModel.y;
|
||||
textObj.width = rect.width;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = noteModel.message;
|
||||
textObj.class = 'noteText';
|
||||
textObj.fontFamily = conf.noteFontFamily;
|
||||
textObj.fontSize = conf.noteFontSize;
|
||||
textObj.fontWeight = conf.noteFontWeight;
|
||||
textObj.anchor = conf.noteAlign;
|
||||
textObj.textMargin = conf.noteMargin;
|
||||
textObj.valign = conf.noteAlign;
|
||||
textObj.wrap = true;
|
||||
|
||||
const textHeight = drawLongText(noteModel.message, noteModel.x, noteModel.y, g, rect.width);
|
||||
let textElem = drawText(g, textObj);
|
||||
|
||||
let textHeight = Math.round(
|
||||
textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr)
|
||||
);
|
||||
|
||||
noteModel.height = textHeight + 2 * conf.noteMargin;
|
||||
|
||||
@@ -287,52 +268,41 @@ const drawNote = function(elem, noteModel) {
|
||||
|
||||
/**
|
||||
* Draws a message
|
||||
* @param elem
|
||||
* @param startx
|
||||
* @param stopx
|
||||
* @param verticalPos
|
||||
* @param msg
|
||||
* @param sequenceIndex
|
||||
* @param g - the parent of the message element
|
||||
* @param msgModel - the model containing fields describing a message
|
||||
*/
|
||||
const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceIndex) {
|
||||
const g = elem.append('g');
|
||||
const txtCenter = startx + (stopx - startx) / 2;
|
||||
const drawMessage = function(g, msgModel) {
|
||||
const { startx, stopx, starty: verticalPos, message, type, sequenceIndex, wrap } = msgModel;
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.x = startx;
|
||||
textObj.y = verticalPos - (conf.messageFontSize + 7);
|
||||
textObj.width = stopx - startx;
|
||||
textObj.class = 'messageText';
|
||||
textObj.dy = '1em';
|
||||
textObj.text = message;
|
||||
textObj.fontFamily = conf.messageFontFamily;
|
||||
textObj.fontSize = conf.messageFontSize;
|
||||
textObj.fontWeight = conf.messageFontWeight;
|
||||
textObj.anchor = conf.messageAlign;
|
||||
textObj.valign = conf.messageAlign;
|
||||
textObj.textMargin = conf.wrapPadding;
|
||||
textObj.tspan = false;
|
||||
textObj.wrap = wrap;
|
||||
|
||||
let textElems = [];
|
||||
let textElem = drawText(g, textObj);
|
||||
|
||||
let counterBreaklines = 0;
|
||||
let breaklineOffset = conf.messageFontSize;
|
||||
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 - 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())
|
||||
const lines = message.split(common.lineBreakRegex).length - 1 > 0 ? 1 : 0;
|
||||
|
||||
let totalOffset = Math.round(
|
||||
textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr)
|
||||
);
|
||||
counterBreaklines++;
|
||||
}
|
||||
const offsetLineCounter = counterBreaklines - 1;
|
||||
let totalOffset = offsetLineCounter * breaklineOffset;
|
||||
let textWidths = textElems.map(function(textElem) {
|
||||
return (textElem._groups || textElem)[0][0].getBBox().width;
|
||||
});
|
||||
let textWidth = Math.max(...textWidths);
|
||||
for (const textElem of textElems) {
|
||||
if (conf.messageAlign === 'left') {
|
||||
textElem.attr('x', txtCenter - textWidth / 2).style('text-anchor', 'start');
|
||||
} else if (conf.messageAlign === 'right') {
|
||||
textElem.attr('x', txtCenter + textWidth / 2).style('text-anchor', 'end');
|
||||
}
|
||||
}
|
||||
|
||||
bounds.bumpVerticalPos(totalOffset);
|
||||
let textWidth = Math.max.apply(
|
||||
null,
|
||||
textElem.map(te => (te._groups || te)[0][0].getBBox().width)
|
||||
);
|
||||
|
||||
bounds.bumpVerticalPos(totalOffset - lines * conf.messageFontSize);
|
||||
|
||||
let line;
|
||||
if (startx === stopx) {
|
||||
@@ -371,7 +341,7 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
);
|
||||
}
|
||||
|
||||
bounds.bumpVerticalPos(30 + totalOffset);
|
||||
bounds.bumpVerticalPos(30);
|
||||
const dx = Math.max(textWidth / 2, 100);
|
||||
bounds.insert(
|
||||
startx - dx,
|
||||
@@ -395,9 +365,9 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
// Make an SVG Container
|
||||
// Draw the line
|
||||
if (
|
||||
msg.type === parser.yy.LINETYPE.DOTTED ||
|
||||
msg.type === parser.yy.LINETYPE.DOTTED_CROSS ||
|
||||
msg.type === parser.yy.LINETYPE.DOTTED_OPEN
|
||||
type === parser.yy.LINETYPE.DOTTED ||
|
||||
type === parser.yy.LINETYPE.DOTTED_CROSS ||
|
||||
type === parser.yy.LINETYPE.DOTTED_OPEN
|
||||
) {
|
||||
line.style('stroke-dasharray', '3, 3');
|
||||
line.attr('class', 'messageLine1');
|
||||
@@ -420,11 +390,11 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
||||
line.attr('stroke-width', 2);
|
||||
line.attr('stroke', 'black');
|
||||
line.style('fill', 'none'); // remove any fill colour
|
||||
if (msg.type === parser.yy.LINETYPE.SOLID || msg.type === parser.yy.LINETYPE.DOTTED) {
|
||||
if (type === parser.yy.LINETYPE.SOLID || type === parser.yy.LINETYPE.DOTTED) {
|
||||
line.attr('marker-end', 'url(' + url + '#arrowhead)');
|
||||
}
|
||||
|
||||
if (msg.type === parser.yy.LINETYPE.SOLID_CROSS || msg.type === parser.yy.LINETYPE.DOTTED_CROSS) {
|
||||
if (type === parser.yy.LINETYPE.SOLID_CROSS || type === parser.yy.LINETYPE.DOTTED_CROSS) {
|
||||
line.attr('marker-end', 'url(' + url + '#crosshead)');
|
||||
}
|
||||
|
||||
@@ -452,8 +422,8 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
||||
const actor = actors[actorKeys[i]];
|
||||
|
||||
// Add some rendering data to the object
|
||||
actor.width = actor.width ? actor.width : conf.width;
|
||||
actor.height = conf.height;
|
||||
actor.width = actor.width || conf.width;
|
||||
actor.height = Math.max(actor.height || conf.height, conf.height);
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
@@ -491,9 +461,9 @@ const actorActivations = function(actor) {
|
||||
});
|
||||
};
|
||||
|
||||
const actorFlowVerticaBounds = function(actor) {
|
||||
const actorFlowVerticaBounds = function(actor, actors) {
|
||||
// handle multiple stacked activations for same actor
|
||||
const actorObj = parser.yy.getActors()[actor];
|
||||
const actorObj = actors[actor];
|
||||
const activations = actorActivations(actor);
|
||||
|
||||
const left = activations.reduce(function(acc, activation) {
|
||||
@@ -506,28 +476,23 @@ const actorFlowVerticaBounds = function(actor) {
|
||||
};
|
||||
|
||||
function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) {
|
||||
let heightAdjust = 0;
|
||||
bounds.bumpVerticalPos(preMargin);
|
||||
if (msg.message && loopWidths[msg.message]) {
|
||||
let loopWidth = loopWidths[msg.message].width;
|
||||
let heightAdjust = postMargin;
|
||||
if (msg.id && msg.message && loopWidths[msg.id]) {
|
||||
let loopWidth = loopWidths[msg.id].width;
|
||||
let textConf = conf.messageFont();
|
||||
msg.message = utils.wrapLabel(
|
||||
`[${msg.message}]`,
|
||||
loopWidth - 20 - 2 * conf.wrapPadding,
|
||||
textConf
|
||||
);
|
||||
msg.message = utils.wrapLabel(`[${msg.message}]`, loopWidth - 2 * conf.wrapPadding, textConf);
|
||||
msg.width = loopWidth;
|
||||
|
||||
heightAdjust = Math.max(
|
||||
0,
|
||||
utils.calculateTextHeight(msg.message, textConf) - (preMargin + postMargin)
|
||||
);
|
||||
const textHeight = utils.calculateTextHeight(msg.message, textConf);
|
||||
heightAdjust += textHeight;
|
||||
}
|
||||
addLoopFn(msg);
|
||||
bounds.bumpVerticalPos(heightAdjust + postMargin);
|
||||
bounds.bumpVerticalPos(heightAdjust);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
@@ -576,15 +541,15 @@ export const draw = function(text, id) {
|
||||
// Draw the messages/signals
|
||||
let sequenceIndex = 1;
|
||||
messages.forEach(function(msg) {
|
||||
let loopData, noteModel, msgModel;
|
||||
let loopModel, noteModel, msgModel;
|
||||
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.NOTE:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
noteModel = msg.noteModel;
|
||||
noteModel.y = bounds.getVerticalPos();
|
||||
logger.debug('noteModel', noteModel);
|
||||
drawNote(diagram, noteModel);
|
||||
bounds.models.notes.push(noteModel);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ACTIVE_START:
|
||||
bounds.newActivation(msg, diagram, actors);
|
||||
@@ -602,18 +567,21 @@ export const draw = function(text, id) {
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_END:
|
||||
loopData = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopData, 'loop', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'loop', conf);
|
||||
bounds.models.loops.push(loopModel);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.RECT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(undefined, msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
|
||||
bounds.newLoop(undefined, message.message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.RECT_END:
|
||||
svgDraw.drawBackgroundRect(diagram, bounds.endLoop());
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawBackgroundRect(diagram, loopModel);
|
||||
bounds.models.loops.push(loopModel);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
adjustLoopHeightForWrap(
|
||||
@@ -625,9 +593,10 @@ export const draw = function(text, id) {
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
loopData = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopData, 'opt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'opt', conf);
|
||||
bounds.models.loops.push(loopModel);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_START:
|
||||
adjustLoopHeightForWrap(
|
||||
@@ -639,14 +608,19 @@ export const draw = function(text, id) {
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_ELSE:
|
||||
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
|
||||
bounds.addSectionToLoop(message)
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
conf.boxMargin,
|
||||
message => bounds.addSectionToLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_END:
|
||||
loopData = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopData, 'alt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'alt', conf);
|
||||
bounds.models.loops.push(loopModel);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
adjustLoopHeightForWrap(
|
||||
@@ -658,14 +632,19 @@ export const draw = function(text, id) {
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
|
||||
bounds.addSectionToLoop(message)
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
conf.boxMargin,
|
||||
message => bounds.addSectionToLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
loopData = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopData, 'par', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopModel = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopModel, 'par', conf);
|
||||
bounds.models.loops.push(loopModel);
|
||||
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
@@ -673,14 +652,9 @@ export const draw = function(text, id) {
|
||||
bounds.bumpVerticalPos(conf.messageMargin);
|
||||
msgModel = msg.msgModel;
|
||||
msgModel.starty = bounds.getVerticalPos();
|
||||
drawMessage(
|
||||
diagram,
|
||||
msgModel.startx,
|
||||
msgModel.stopx,
|
||||
msgModel.starty,
|
||||
msgModel,
|
||||
sequenceIndex
|
||||
);
|
||||
msgModel.sequenceIndex = sequenceIndex;
|
||||
drawMessage(diagram, msgModel);
|
||||
bounds.models.messages.push(msgModel);
|
||||
bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.starty);
|
||||
} catch (e) {
|
||||
logger.error('error while drawing message', e);
|
||||
@@ -750,6 +724,7 @@ export const draw = function(text, id) {
|
||||
' ' +
|
||||
(height + extraVertForTitle)
|
||||
);
|
||||
logger.debug('bounds', bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -784,10 +759,10 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
||||
|
||||
const textFont = isNote ? conf.noteFont() : conf.messageFont();
|
||||
let wrappedMessage = msg.wrap
|
||||
? utils.wrapLabel(msg.message, conf.width - conf.noteMargin, textFont)
|
||||
? utils.wrapLabel(msg.message, conf.width - 2 * conf.wrapPadding, textFont)
|
||||
: msg.message;
|
||||
const messageDimensions = utils.calculateTextDimensions(wrappedMessage, textFont);
|
||||
const messageWidth = messageDimensions.width;
|
||||
const messageWidth = messageDimensions.width + 2 * conf.wrapPadding;
|
||||
|
||||
/*
|
||||
* The following scenarios should be supported:
|
||||
@@ -838,7 +813,7 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug('MaxMessages:', maxMessageWidthPerActor);
|
||||
logger.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor);
|
||||
return maxMessageWidthPerActor;
|
||||
};
|
||||
|
||||
@@ -915,7 +890,7 @@ const buildNoteModel = function(msg, actors) {
|
||||
};
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? conf.width
|
||||
? Math.max(conf.width, textDimensions.width)
|
||||
: Math.max(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
@@ -923,7 +898,7 @@ const buildNoteModel = function(msg, actors) {
|
||||
noteModel.x = startx + (actors[msg.from].width + conf.actorMargin) / 2;
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? conf.width
|
||||
? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin)
|
||||
: Math.max(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
@@ -959,7 +934,7 @@ const buildNoteModel = function(msg, actors) {
|
||||
return noteModel;
|
||||
};
|
||||
|
||||
const buildMessageModel = function(msg) {
|
||||
const buildMessageModel = function(msg, actors) {
|
||||
let process = false;
|
||||
if (
|
||||
[
|
||||
@@ -976,13 +951,13 @@ const buildMessageModel = function(msg) {
|
||||
if (!process) {
|
||||
return {};
|
||||
}
|
||||
const fromBounds = actorFlowVerticaBounds(msg.from);
|
||||
const toBounds = actorFlowVerticaBounds(msg.to);
|
||||
const fromBounds = actorFlowVerticaBounds(msg.from, actors);
|
||||
const toBounds = actorFlowVerticaBounds(msg.to, actors);
|
||||
const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0;
|
||||
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
|
||||
const allBounds = fromBounds.concat(toBounds);
|
||||
const msgModel = {
|
||||
width: Math.abs(toBounds[toIdx] - fromBounds[fromIdx]) + conf.messageMargin * 2,
|
||||
width: Math.abs(toBounds[toIdx] - fromBounds[fromIdx]),
|
||||
height: 0,
|
||||
startx: fromBounds[fromIdx],
|
||||
stopx: toBounds[toIdx],
|
||||
@@ -990,13 +965,14 @@ const buildMessageModel = function(msg) {
|
||||
stopy: 0,
|
||||
message: msg.message,
|
||||
type: msg.type,
|
||||
wrap: msg.wrap,
|
||||
fromBounds: Math.min.apply(null, allBounds),
|
||||
toBounds: Math.max.apply(null, allBounds)
|
||||
};
|
||||
if (msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message)) {
|
||||
msgModel.message = utils.wrapLabel(
|
||||
msg.message,
|
||||
Math.max(msgModel.width, conf.width + conf.messageMargin * 2),
|
||||
Math.max(msgModel.width, conf.width),
|
||||
conf.messageFont()
|
||||
);
|
||||
}
|
||||
@@ -1009,12 +985,14 @@ const calculateLoopBounds = function(messages, actors) {
|
||||
let current, noteModel, msgModel;
|
||||
|
||||
messages.forEach(function(msg) {
|
||||
msg.id = utils.generateId();
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.LOOP_START:
|
||||
case parser.yy.LINETYPE.ALT_START:
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
stack.push({
|
||||
id: msg.id,
|
||||
msg: msg.message,
|
||||
from: Number.MAX_SAFE_INTEGER,
|
||||
to: Number.MIN_SAFE_INTEGER,
|
||||
@@ -1025,7 +1003,8 @@ const calculateLoopBounds = function(messages, actors) {
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
if (msg.message) {
|
||||
current = stack.pop();
|
||||
loops[msg.message] = current;
|
||||
loops[current.id] = current;
|
||||
loops[msg.id] = current;
|
||||
stack.push(current);
|
||||
}
|
||||
break;
|
||||
@@ -1034,23 +1013,52 @@ const calculateLoopBounds = function(messages, actors) {
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
current = stack.pop();
|
||||
loops[current.msg] = current;
|
||||
loops[current.id] = current;
|
||||
break;
|
||||
case parser.yy.LINETYPE.ACTIVE_START:
|
||||
{
|
||||
const actorRect = actors[msg.from.actor];
|
||||
const stackedSize = actorActivations(msg.from.actor).length;
|
||||
const x =
|
||||
actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2;
|
||||
const toAdd = {
|
||||
startx: x,
|
||||
stopx: x + conf.activationWidth,
|
||||
actor: msg.from.actor,
|
||||
enabled: true
|
||||
};
|
||||
bounds.activations.push(toAdd);
|
||||
}
|
||||
break;
|
||||
case parser.yy.LINETYPE.ACTIVE_END:
|
||||
{
|
||||
const lastActorActivationIdx = bounds.activations
|
||||
.map(a => a.actor)
|
||||
.lastIndexOf(msg.from.actor);
|
||||
delete bounds.activations.splice(lastActorActivationIdx, 1)[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
const isNote = msg.placement !== undefined;
|
||||
if (isNote) {
|
||||
noteModel = buildNoteModel(msg, actors);
|
||||
msg.noteModel = noteModel;
|
||||
let depth = 0;
|
||||
stack.forEach(stk => {
|
||||
current = stk;
|
||||
current.from = Math.min(current.from, noteModel.x);
|
||||
current.to = Math.max(current.to, noteModel.x + noteModel.width);
|
||||
current.width = Math.max(current.width, Math.abs(current.from - current.to));
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.from - current.to)) -
|
||||
50 -
|
||||
conf.boxMargin * depth;
|
||||
depth++;
|
||||
});
|
||||
} else {
|
||||
msgModel = buildMessageModel(msg);
|
||||
msgModel = buildMessageModel(msg, actors);
|
||||
msg.msgModel = msgModel;
|
||||
if (msg.from && msg.to && stack.length > 0) {
|
||||
let depth = 0;
|
||||
stack.forEach(stk => {
|
||||
current = stk;
|
||||
let from = actors[msg.from];
|
||||
@@ -1058,7 +1066,7 @@ const calculateLoopBounds = function(messages, actors) {
|
||||
if (from.x === to.x) {
|
||||
current.from = Math.min(current.from, from.x);
|
||||
current.to = Math.max(current.to, to.x);
|
||||
current.width = Math.max(current.width, from.width);
|
||||
current.width = Math.max(current.width, from.width) - 50 - conf.boxMargin * depth;
|
||||
} else {
|
||||
if (from.x < to.x) {
|
||||
current.from = Math.min(current.from, from.x);
|
||||
@@ -1067,15 +1075,17 @@ const calculateLoopBounds = function(messages, actors) {
|
||||
current.from = Math.min(current.from, to.x);
|
||||
current.to = Math.max(current.to, from.x);
|
||||
}
|
||||
current.width = Math.max(
|
||||
current.width,
|
||||
Math.abs(current.from - current.to) - 20 + 2 * conf.wrapPadding
|
||||
);
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.from - current.to)) -
|
||||
50 -
|
||||
conf.boxMargin * depth;
|
||||
}
|
||||
depth++;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
bounds.activations = [];
|
||||
logger.debug('Loop type widths:', loops);
|
||||
return loops;
|
||||
};
|
||||
|
@@ -51,6 +51,35 @@ export const drawText = function(elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof textData.anchor !== 'undefined' &&
|
||||
typeof textData.textMargin !== 'undefined' &&
|
||||
typeof textData.width !== 'undefined'
|
||||
) {
|
||||
switch (textData.anchor) {
|
||||
case 'left':
|
||||
case 'start':
|
||||
textData.x = textData.x + textData.textMargin;
|
||||
textData.anchor = 'start';
|
||||
textData.dominantBaseline = 'text-after-edge';
|
||||
textData.alignmentBaseline = 'middle';
|
||||
break;
|
||||
case 'middle':
|
||||
case 'center':
|
||||
textData.x = textData.x + textData.width / 2;
|
||||
textData.anchor = 'middle';
|
||||
textData.dominantBaseline = 'middle';
|
||||
textData.alignmentBaseline = 'middle';
|
||||
break;
|
||||
case 'right':
|
||||
case 'end':
|
||||
textData.x = textData.x + textData.width - textData.textMargin;
|
||||
textData.anchor = 'end';
|
||||
textData.dominantBaseline = 'text-before-edge';
|
||||
textData.alignmentBaseline = 'middle';
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (
|
||||
@@ -65,7 +94,10 @@ export const drawText = function(elem, textData) {
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', yfunc());
|
||||
if (typeof textData.anchor !== 'undefined') {
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textElem
|
||||
.attr('text-anchor', textData.anchor)
|
||||
.attr('dominant-baseline', textData.dominantBaseline)
|
||||
.attr('alignment-baseline', textData.alignmentBaseline);
|
||||
}
|
||||
if (typeof textData.fontFamily !== 'undefined') {
|
||||
textElem.style('font-family', textData.fontFamily);
|
||||
@@ -88,13 +120,16 @@ export const drawText = function(elem, textData) {
|
||||
textElem.attr('dy', dy);
|
||||
}
|
||||
|
||||
if (textData.tspan) {
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x);
|
||||
if (typeof textData.fill !== 'undefined') {
|
||||
span.attr('fill', textData.fill);
|
||||
}
|
||||
span.text(line);
|
||||
|
||||
} else {
|
||||
textElem.text(line);
|
||||
}
|
||||
if (
|
||||
typeof textData.valign !== 'undefined' &&
|
||||
typeof textData.textMargin !== 'undefined' &&
|
||||
@@ -107,7 +142,7 @@ export const drawText = function(elem, textData) {
|
||||
textElems.push(textElem);
|
||||
}
|
||||
|
||||
return textElems.length === 1 ? textElems[0] : textElems;
|
||||
return textElems;
|
||||
};
|
||||
|
||||
export const drawLabel = function(elem, txtObject) {
|
||||
@@ -135,17 +170,18 @@ export const drawLabel = function(elem, txtObject) {
|
||||
);
|
||||
}
|
||||
const polygon = elem.append('polygon');
|
||||
polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7));
|
||||
polygon.attr('points', genPoints(txtObject.x, txtObject.y, txtObject.width, txtObject.height, 7));
|
||||
polygon.attr('class', 'labelBox');
|
||||
|
||||
txtObject.y = txtObject.y + txtObject.labelMargin;
|
||||
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin;
|
||||
return drawText(elem, txtObject);
|
||||
txtObject.y = txtObject.y + txtObject.height / 2;
|
||||
|
||||
drawText(elem, txtObject);
|
||||
return polygon;
|
||||
};
|
||||
|
||||
let actorCnt = -1;
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* Draws an actor in the diagram with the attached line
|
||||
* @param elem - The diagram we'll draw to.
|
||||
* @param actor - The actor to draw.
|
||||
* @param conf - drawText implementation discriminator object
|
||||
@@ -236,57 +272,67 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
|
||||
drawLoopLine(bounds.startx, bounds.starty, bounds.startx, bounds.stopy);
|
||||
if (typeof bounds.sections !== 'undefined') {
|
||||
bounds.sections.forEach(function(item) {
|
||||
drawLoopLine(bounds.startx, item, bounds.stopx, item).style('stroke-dasharray', '3, 3');
|
||||
drawLoopLine(bounds.startx, item.y, bounds.stopx, item.y).style('stroke-dasharray', '3, 3');
|
||||
});
|
||||
}
|
||||
|
||||
let minSize =
|
||||
Math.round((3 * conf.messageFontSize) / 4) < 10
|
||||
? conf.messageFontSize
|
||||
: Math.round((3 * conf.messageFontSize) / 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.messageFontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.messageFontWeight;
|
||||
txt.class = 'labelText'; // Its size & position are fixed.
|
||||
const msgFont = conf.messageFont();
|
||||
txt.fontFamily = msgFont.fontFamily;
|
||||
txt.fontSize = msgFont.fontSize;
|
||||
txt.fontWeight = msgFont.fontWeight;
|
||||
txt.anchor = 'middle';
|
||||
txt.valign = 'middle';
|
||||
txt.tspan = false;
|
||||
txt.width = conf.labelBoxWidth || 50;
|
||||
txt.height = conf.labelBoxHeight || 20;
|
||||
txt.textMargin = conf.boxTextMargin;
|
||||
txt.class = 'labelText';
|
||||
|
||||
let labelElem = drawLabel(g, txt);
|
||||
let labelBoxWidth = (labelElem._groups || labelElem)[0][0].getBBox().width;
|
||||
drawLabel(g, txt);
|
||||
txt = getTextObj();
|
||||
txt.text = bounds.title;
|
||||
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2 + labelBoxWidth;
|
||||
txt.x = bounds.startx + conf.labelBoxWidth / 2 + (bounds.stopx - bounds.startx) / 2;
|
||||
txt.y = bounds.starty + conf.boxMargin + conf.boxTextMargin;
|
||||
txt.anchor = 'middle';
|
||||
txt.class = 'loopText';
|
||||
txt.fontFamily = conf.messageFontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.messageFontWeight;
|
||||
txt.fontFamily = msgFont.fontFamily;
|
||||
txt.fontSize = msgFont.fontSize;
|
||||
txt.fontWeight = msgFont.fontWeight;
|
||||
txt.wrap = true;
|
||||
|
||||
drawText(g, txt);
|
||||
let textElem = drawText(g, txt);
|
||||
|
||||
if (typeof bounds.sectionTitles !== 'undefined') {
|
||||
bounds.sectionTitles.forEach(function(item, idx) {
|
||||
if (item.message) {
|
||||
txt.text = item.message;
|
||||
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2;
|
||||
txt.y = bounds.sections[idx] + conf.boxMargin + conf.boxTextMargin;
|
||||
txt.y = bounds.sections[idx].y + conf.boxMargin + conf.boxTextMargin;
|
||||
txt.class = 'loopText';
|
||||
txt.anchor = 'middle';
|
||||
txt.fontFamily = conf.messageFontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.messageFontWeight;
|
||||
txt.valign = 'middle';
|
||||
txt.tspan = false;
|
||||
txt.fontFamily = msgFont.fontFamily;
|
||||
txt.fontSize = msgFont.fontSize;
|
||||
txt.fontWeight = msgFont.fontWeight;
|
||||
txt.wrap = bounds.wrap;
|
||||
drawText(g, txt);
|
||||
textElem = drawText(g, txt);
|
||||
let sectionHeight = Math.round(
|
||||
textElem
|
||||
.map(te => (te._groups || te)[0][0].getBBox().height)
|
||||
.reduce((acc, curr) => acc + curr)
|
||||
);
|
||||
bounds.sections[idx].height += sectionHeight - (conf.boxMargin + conf.boxTextMargin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bounds.height = Math.round(bounds.stopy - bounds.starty);
|
||||
|
||||
return g;
|
||||
};
|
||||
|
||||
@@ -380,13 +426,14 @@ export const getTextObj = function() {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
anchor: 'start',
|
||||
anchor: undefined,
|
||||
style: '#666',
|
||||
width: 100,
|
||||
height: 100,
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined
|
||||
};
|
||||
};
|
||||
|
@@ -335,7 +335,17 @@ const config = {
|
||||
* This sets the auto-wrap padding for the diagram (sides only)
|
||||
* **Default value 15.
|
||||
*/
|
||||
wrapPadding: 15
|
||||
wrapPadding: 15,
|
||||
/**
|
||||
* This sets the width of the loop-box (loop, alt, opt, par)
|
||||
* **Default value 50.
|
||||
*/
|
||||
labelBoxWidth: 50,
|
||||
/**
|
||||
* This sets the height of the loop-box (loop, alt, opt, par)
|
||||
* **Default value 20.
|
||||
*/
|
||||
labelBoxHeight: 20
|
||||
},
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user