From fcd212633093bb1a1fac84f5e9e970c2465bebec Mon Sep 17 00:00:00 2001 From: chris moran Date: Sun, 28 Jun 2020 08:37:26 -0400 Subject: [PATCH] Getting closer --- src/config.js | 25 ++-- src/diagrams/sequence/sequenceRenderer.js | 135 +++++++++++----------- src/diagrams/sequence/svgDraw.js | 70 +++++++---- src/utils.js | 30 +++-- 4 files changed, 144 insertions(+), 116 deletions(-) diff --git a/src/config.js b/src/config.js index f9ceb137d..c624c5731 100644 --- a/src/config.js +++ b/src/config.js @@ -292,9 +292,9 @@ const config = { wrap: false, /** * This sets the auto-wrap padding for the diagram (sides only) - * **Default value 15. + * **Default value 10. */ - wrapPadding: 15, + wrapPadding: 10, /** * This sets the width of the loop-box (loop, alt, opt, par) * **Default value 50. @@ -306,24 +306,27 @@ const config = { */ labelBoxHeight: 20, messageFont: () => { + const c = getConfig(); return { - fontFamily: config.messageFontFamily, - fontSize: config.messageFontSize, - fontWeight: config.messageFontWeight + fontFamily: c.messageFontFamily, + fontSize: c.messageFontSize, + fontWeight: c.messageFontWeight }; }, noteFont: () => { + const c = getConfig(); return { - fontFamily: config.noteFontFamily, - fontSize: config.noteFontSize, - fontWeight: config.noteFontWeight + fontFamily: c.noteFontFamily, + fontSize: c.noteFontSize, + fontWeight: c.noteFontWeight }; }, actorFont: () => { + const c = getConfig(); return { - fontFamily: config.actorFontFamily, - fontSize: config.actorFontSize, - fontWeight: config.actorFontWeight + fontFamily: c.actorFontFamily, + fontSize: c.actorFontSize, + fontWeight: c.actorFontWeight }; } }, diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index a05415f02..6bea8ad65 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -136,7 +136,7 @@ export const bounds = { }, newActivation: function(message, diagram, actors) { const actorRect = actors[message.from.actor]; - const stackedSize = actorActivations(message.from.actor).length; + const stackedSize = actorActivations(message.from.actor).length || 0; const x = actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2; this.activations.push({ startx: x, @@ -248,13 +248,16 @@ const drawNote = function(elem, noteModel) { * @param msgModel - the model containing fields describing a message */ const drawMessage = function(g, msgModel) { - bounds.bumpVerticalPos(conf.messageMargin); - msgModel.height += conf.messageMargin; - msgModel.starty = bounds.getVerticalPos(); - const { startx, stopx, starty: verticalPos, message, type, sequenceIndex, wrap } = msgModel; + const { startx, stopx, starty, message, type, sequenceIndex, wrap } = msgModel; + const lines = message.split(common.lineBreakRegex).length; + let textDims = utils.calculateTextDimensions(message, conf); + const lineHeight = textDims.height / lines; + msgModel.height += lineHeight; + + bounds.bumpVerticalPos(lineHeight); const textObj = svgDraw.getTextObj(); textObj.x = startx; - textObj.y = verticalPos; + textObj.y = starty; textObj.width = stopx - startx; textObj.class = 'messageText'; textObj.dy = '1em'; @@ -268,18 +271,11 @@ const drawMessage = function(g, msgModel) { textObj.tspan = false; textObj.wrap = wrap; - let textElem = drawText(g, textObj); - const lineHeight = (textElem[0]._groups || textElem[0])[0][0].getBBox().height; - textElem.forEach(te => te.attr('y', verticalPos - 7 - lineHeight / 2)); + drawText(g, textObj); - const lines = message.split(common.lineBreakRegex).length - 1; + let totalOffset = textDims.height; - let totalOffset = Math.round(lineHeight + lines * lineHeight); - - let textWidth = Math.max.apply( - null, - textElem.map(te => (te._groups || te)[0][0].getBBox().width) - ); + let textWidth = textDims.width; let line; if (startx === stopx) { @@ -288,7 +284,8 @@ const drawMessage = function(g, msgModel) { .append('path') .attr( 'd', - `M ${startx},${verticalPos + totalOffset} H ${startx + conf.width / 2} V ${verticalPos + + `M ${startx},${bounds.getVerticalPos() + totalOffset} H ${startx + + Math.max(conf.width / 2, textWidth / 2)} V ${bounds.getVerticalPos() + 25 + totalOffset} H ${startx}` ); @@ -302,49 +299,43 @@ const drawMessage = function(g, msgModel) { 'M ' + startx + ',' + - (verticalPos + totalOffset) + + (bounds.getVerticalPos() + totalOffset) + ' C ' + (startx + 60) + ',' + - (verticalPos - 10 + totalOffset) + + (bounds.getVerticalPos() - 10 + totalOffset) + ' ' + (startx + 60) + ',' + - (verticalPos + 30 + totalOffset) + + (bounds.getVerticalPos() + 30 + totalOffset) + ' ' + startx + ',' + - (verticalPos + 20 + totalOffset) + (bounds.getVerticalPos() + 20 + totalOffset) ); } - bounds.bumpVerticalPos(30); - msgModel.height += 30; - const dx = Math.max(textWidth / 2, 100); + totalOffset += 30; + const dx = Math.max(textWidth / 2, conf.width / 2); bounds.insert( startx - dx, bounds.getVerticalPos() - 10 + totalOffset, stopx + dx, bounds.getVerticalPos() + 30 + totalOffset ); - bounds.bumpVerticalPos(10); - msgModel.height += 10; } else { + totalOffset += conf.boxMargin; line = g.append('line'); line.attr('x1', startx); - line.attr('y1', verticalPos + totalOffset); + line.attr('y1', bounds.getVerticalPos() + totalOffset); line.attr('x2', stopx); - line.attr('y2', verticalPos + totalOffset); - bounds.bumpVerticalPos(10); - msgModel.height += 10; + line.attr('y2', bounds.getVerticalPos() + totalOffset); bounds.insert( startx, bounds.getVerticalPos() - 10 + totalOffset, stopx, bounds.getVerticalPos() + totalOffset ); - msgModel.height += 10; - bounds.bumpVerticalPos(10); } // Make an SVG Container // Draw the line @@ -387,7 +378,7 @@ const drawMessage = function(g, msgModel) { line.attr('marker-start', 'url(' + url + '#sequencenumber)'); g.append('text') .attr('x', startx) - .attr('y', verticalPos + 4 + totalOffset) + .attr('y', bounds.getVerticalPos() + 4 + totalOffset) .attr('font-family', 'sans-serif') .attr('font-size', '12px') .attr('text-anchor', 'middle') @@ -395,9 +386,10 @@ const drawMessage = function(g, msgModel) { .attr('class', 'sequenceNumber') .text(sequenceIndex); } + bounds.bumpVerticalPos(totalOffset); + msgModel.height += totalOffset; msgModel.stopy = msgModel.starty + msgModel.height; bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy); - logger.debug(`mm.h:${msgModel.height} vs c.h:${msgModel.stopy - msgModel.starty}`); }; export const drawActors = function(diagram, actors, actorKeys, verticalPos) { @@ -472,8 +464,11 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop msg.message = utils.wrapLabel(`[${msg.message}]`, loopWidth - 2 * conf.wrapPadding, textConf); msg.width = loopWidth; - const textHeight = utils.calculateTextHeight(msg.message, textConf); - heightAdjust += textHeight; + // const lines = msg.message.split(common.lineBreakRegex).length; + const textDims = utils.calculateTextDimensions(msg.message, textConf); + const totalOffset = textDims.height - conf.labelBoxHeight; + heightAdjust = postMargin + totalOffset; + logger.debug(`${totalOffset} - ${msg.message}`); } addLoopFn(msg); bounds.bumpVerticalPos(heightAdjust); @@ -488,8 +483,9 @@ export const draw = function(text, id) { parser.yy.clear(); parser.yy.setWrap(conf.wrap); parser.parse(text + '\n'); - bounds.init(); + logger.debug(`C:${JSON.stringify(conf, null, 2)}`); + const diagram = select(`[id="${id}"]`); // Fetch data from the parsing @@ -553,7 +549,7 @@ export const draw = function(text, id) { break; case parser.yy.LINETYPE.LOOP_END: loopModel = bounds.endLoop(); - svgDraw.drawLoop(diagram, loopModel, 'loop', conf, bounds); + svgDraw.drawLoop(diagram, loopModel, 'loop', conf); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.models.addLoop(loopModel); break; @@ -635,6 +631,7 @@ export const draw = function(text, id) { try { // lastMsg = msg msgModel = msg.msgModel; + msgModel.starty = bounds.getVerticalPos(); msgModel.sequenceIndex = sequenceIndex; drawMessage(diagram, msgModel); bounds.models.addMessage(msgModel); @@ -706,7 +703,7 @@ export const draw = function(text, id) { ' ' + (height + extraVertForTitle) ); - logger.debug(`models: ${JSON.stringify(bounds.models, null, 2)}`); + logger.debug(`models:`, bounds.models); }; /** @@ -767,6 +764,11 @@ const getMaxMessageWidthPerActor = function(actors, messages) { maxMessageWidthPerActor[msg.to] || 0, messageWidth ); + } else if (isMessage && msg.from === actor.prevActor) { + maxMessageWidthPerActor[msg.from] = Math.max( + maxMessageWidthPerActor[msg.from] || 0, + messageWidth + ); } else if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) { maxMessageWidthPerActor[msg.from] = Math.max( maxMessageWidthPerActor[msg.from] || 0, @@ -861,7 +863,6 @@ const buildNoteModel = function(msg, actors) { shouldWrap ? utils.wrapLabel(msg.message, conf.width, conf.noteFont()) : msg.message, conf.noteFont() ); - logger.debug(`TD:[${textDimensions.width},${textDimensions.height}]`); let noteModel = { width: shouldWrap ? conf.width @@ -892,14 +893,18 @@ const buildNoteModel = function(msg, actors) { } else if (msg.to === msg.from) { textDimensions = utils.calculateTextDimensions( shouldWrap - ? utils.wrapLabel(msg.message, Math.max(conf.width, actors[msg.to].width), conf.noteFont()) + ? utils.wrapLabel( + msg.message, + Math.max(conf.width, actors[msg.from].width), + conf.noteFont() + ) : msg.message, conf.noteFont() ); noteModel.width = shouldWrap - ? Math.max(conf.width, actors[msg.to].width) - : Math.max(actors[msg.to].width, conf.width, textDimensions.width + 2 * conf.noteMargin); - noteModel.startx = startx + (actors[msg.to].width - noteModel.width) / 2; + ? Math.max(conf.width, actors[msg.from].width) + : Math.max(actors[msg.from].width, conf.width, textDimensions.width + 2 * conf.noteMargin); + noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2; } else { noteModel.width = Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) + @@ -916,6 +921,9 @@ const buildNoteModel = function(msg, actors) { conf.noteFont() ); } + logger.debug( + `NM:[${noteModel.startx},${noteModel.stopx},${noteModel.starty},${noteModel.stopy}:${noteModel.width},${noteModel.height}=${msg.message}]` + ); return noteModel; }; @@ -957,7 +965,7 @@ const buildMessageModel = function(msg, actors) { if (msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message)) { msgModel.message = utils.wrapLabel( msg.message, - Math.max(msgModel.width, conf.width), + Math.max(msgModel.width - 2 * conf.wrapPadding, conf.width), conf.messageFont() ); } @@ -1028,44 +1036,31 @@ const calculateLoopBounds = function(messages, actors) { if (isNote) { noteModel = buildNoteModel(msg, actors); msg.noteModel = noteModel; - let depth = 0; stack.forEach(stk => { current = stk; current.from = Math.min(current.from, noteModel.startx); current.to = Math.max(current.to, noteModel.startx + noteModel.width); current.width = - Math.max(current.width, Math.abs(current.from - current.to)) - - 50 - - conf.boxMargin * depth; - depth++; + Math.max(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth; }); } else { msgModel = buildMessageModel(msg, actors); msg.msgModel = msgModel; - if (msg.from && msg.to && stack.length > 0) { - let depth = 0; + if (msgModel.startx && msgModel.stopx && stack.length > 0) { stack.forEach(stk => { current = stk; - let from = actors[msg.from]; - let to = actors[msg.to]; - 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) - 50 - conf.boxMargin * depth; - } else { - if (from.x < to.x) { - current.from = Math.min(current.from, from.x); - current.to = Math.max(current.to, to.x); - } else { - current.from = Math.min(current.from, to.x); - current.to = Math.max(current.to, from.x); - } + if (msgModel.startx === msgModel.stopx) { + let from = actors[msg.from]; + let to = actors[msg.to]; + current.from = Math.min(from.x - from.width / 2, current.from); + current.to = Math.max(to.x + from.width / 2, current.to); current.width = - Math.max(current.width, Math.abs(current.from - current.to)) - - 50 - - conf.boxMargin * depth; + Math.max(current.width, Math.abs(current.to - current.from)) - conf.labelBoxWidth; + } else { + current.from = Math.min(msgModel.startx, current.from); + current.to = Math.max(msgModel.stopx, current.to); + current.width = Math.max(current.width, msgModel.width) - conf.labelBoxWidth; } - depth++; }); } } diff --git a/src/diagrams/sequence/svgDraw.js b/src/diagrams/sequence/svgDraw.js index 1b326aa3c..ef461e54e 100644 --- a/src/diagrams/sequence/svgDraw.js +++ b/src/diagrams/sequence/svgDraw.js @@ -1,4 +1,6 @@ import common from '../common/common'; +import utils from '../../utils'; +import { logger } from '../../logger'; export const drawRect = function(elem, rectData) { const rectElem = elem.append('rect'); @@ -36,18 +38,21 @@ export const drawText = function(elem, textData) { switch (textData.valign) { case 'top': case 'start': - yfunc = () => textData.y + textData.textMargin; + yfunc = () => Math.round(textData.y + textData.textMargin); break; case 'middle': case 'center': - yfunc = () => textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2; + yfunc = () => + Math.round(textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2); break; case 'bottom': case 'end': yfunc = () => - textData.y + - (prevTextHeight + textHeight + 2 * textData.textMargin) - - textData.textMargin; + Math.round( + textData.y + + (prevTextHeight + textHeight + 2 * textData.textMargin) - + textData.textMargin + ); break; } } @@ -59,21 +64,21 @@ export const drawText = function(elem, textData) { switch (textData.anchor) { case 'left': case 'start': - textData.x = textData.x + textData.textMargin; + textData.x = Math.round(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.x = Math.round(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.x = Math.round(textData.x + textData.width - textData.textMargin); textData.anchor = 'end'; textData.dominantBaseline = 'text-before-edge'; textData.alignmentBaseline = 'middle'; @@ -256,6 +261,15 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct * @param conf - diagrom configuration */ export const drawLoop = function(elem, loopModel, labelText, conf) { + const { + boxMargin, + boxTextMargin, + labelBoxHeight, + labelBoxWidth, + messageFontFamily: fontFamily, + messageFontSize: fontSize, + messageFontWeight: fontWeight + } = conf; const g = elem.append('g'); const drawLoopLine = function(startx, starty, stopx, stopy) { return g @@ -283,45 +297,51 @@ export const drawLoop = function(elem, loopModel, labelText, conf) { txt.text = labelText; txt.x = loopModel.startx; txt.y = loopModel.starty; - const msgFont = conf.messageFont(); - txt.fontFamily = msgFont.fontFamily; - txt.fontSize = msgFont.fontSize; - txt.fontWeight = msgFont.fontWeight; + txt.fontFamily = fontFamily; + txt.fontSize = fontSize; + txt.fontWeight = 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.width = labelBoxWidth || 50; + txt.height = labelBoxHeight || 20; + txt.textMargin = boxTextMargin; txt.class = 'labelText'; drawLabel(g, txt); txt = getTextObj(); txt.text = loopModel.title; - txt.x = loopModel.startx + conf.labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2; - txt.y = loopModel.starty + conf.boxMargin + conf.boxTextMargin; + txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2; + txt.y = loopModel.starty + boxMargin + boxTextMargin; txt.anchor = 'middle'; + txt.valign = 'middle'; + txt.textMargin = boxTextMargin; txt.class = 'loopText'; - txt.fontFamily = msgFont.fontFamily; - txt.fontSize = msgFont.fontSize; - txt.fontWeight = msgFont.fontWeight; + txt.fontFamily = fontFamily; + txt.fontSize = fontSize; + txt.fontWeight = fontWeight; txt.wrap = true; let textElem = drawText(g, txt); + let textHeight = Math.round( + textElem.map(te => (te._groups || te)[0][0].getBBox().height).reduce((acc, curr) => acc + curr) + ); + const textDims = utils.calculateTextDimensions(txt.text, txt); + logger.debug(`loop: ${textHeight} vs ${textDims.height} ${txt.text}`, textDims); if (typeof loopModel.sectionTitles !== 'undefined') { loopModel.sectionTitles.forEach(function(item, idx) { if (item.message) { txt.text = item.message; txt.x = loopModel.startx + (loopModel.stopx - loopModel.startx) / 2; - txt.y = loopModel.sections[idx].y + conf.boxMargin + conf.boxTextMargin; + txt.y = loopModel.sections[idx].y + boxMargin + boxTextMargin; txt.class = 'loopText'; txt.anchor = 'middle'; txt.valign = 'middle'; txt.tspan = false; - txt.fontFamily = msgFont.fontFamily; - txt.fontSize = msgFont.fontSize; - txt.fontWeight = msgFont.fontWeight; + txt.fontFamily = fontFamily; + txt.fontSize = fontSize; + txt.fontWeight = fontWeight; txt.wrap = loopModel.wrap; textElem = drawText(g, txt); let sectionHeight = Math.round( @@ -329,7 +349,7 @@ export const drawLoop = function(elem, loopModel, labelText, conf) { .map(te => (te._groups || te)[0][0].getBBox().height) .reduce((acc, curr) => acc + curr) ); - loopModel.sections[idx].height += sectionHeight - (conf.boxMargin + conf.boxTextMargin); + loopModel.sections[idx].height += sectionHeight - (boxMargin + boxTextMargin); } }); } diff --git a/src/utils.js b/src/utils.js index a140fe9da..c115e3a38 100644 --- a/src/utils.js +++ b/src/utils.js @@ -627,21 +627,21 @@ export const calculateTextDimensions = function(text, config) { // of sans-serif. const fontFamilies = ['sans-serif', fontFamily]; const lines = text.split(common.lineBreakRegex); - let maxWidth = 0, - height = 0; + let dims = []; const body = select('body'); // 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 { width: 0, height: 0 }; + return { width: 0, height: 0, lineHeight: 0 }; } const g = body.append('svg'); - for (let line of lines) { + for (let fontFamily of fontFamilies) { let cheight = 0; - for (let fontFamily of fontFamilies) { + let dim = { width: 0, height: 0, lineHeight: 0 }; + for (let line of lines) { const textObj = getTextObj(); textObj.text = line; const textElem = drawSimpleText(g, textObj) @@ -650,16 +650,26 @@ export const calculateTextDimensions = function(text, config) { .style('font-family', fontFamily); let bBox = (textElem._groups || textElem)[0][0].getBBox(); - maxWidth = Math.max(maxWidth, bBox.width); - cheight = Math.max(bBox.height, cheight); + dim.width = Math.round(Math.max(dim.width, bBox.width)); + cheight = Math.round(bBox.height); + dim.height += cheight; + dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight)); } - height += cheight; + dims.push(dim); } g.remove(); - // Adds some padding, so the text won't sit exactly within the actor's borders - const result = { width: Math.round(maxWidth), height: Math.round(height) }; + let index = + isNaN(dims[1].height) || + isNaN(dims[1].width) || + isNaN(dims[1].lineHeight) || + (dims[0].height > dims[1].height && + dims[0].width > dims[1].width && + dims[0].lineHeight > dims[1].lineHeight) + ? 0 + : 1; + const result = dims[index]; calculateTextDimensions[cacheKey] = result; return result; };