Merge branch 'develop' into feature/1483_long_messages

This commit is contained in:
chris moran
2020-06-30 03:50:04 -04:00
10 changed files with 351 additions and 153 deletions

View File

@@ -5,4 +5,5 @@
1. [How to bind an event?](https://github.com/knsv/mermaid/issues/372) 1. [How to bind an event?](https://github.com/knsv/mermaid/issues/372)
1. [How to add newline in the text?](https://github.com/knsv/mermaid/issues/384#issuecomment-281339381) 1. [How to add newline in the text?](https://github.com/knsv/mermaid/issues/384#issuecomment-281339381)
1. [How to have special characters in link text?](https://github.com/knsv/mermaid/issues/407#issuecomment-329944735) 1. [How to have special characters in link text?](https://github.com/knsv/mermaid/issues/407#issuecomment-329944735)
1. [How to change flowchart curve style?](https://github.com/knsv/mermaid/issues/580#issuecomment-373929046) 1. [How to change Flowchart curve style?](https://github.com/knsv/mermaid/issues/580#issuecomment-373929046)
1. [How to create a Flowchart end-Node that says "End"](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897)

View File

@@ -2,9 +2,9 @@
## Graph ## Graph
This statement declares a new graph and the direction of the graph layout. This statement declares the direction of the Flowchart.
This declares a graph oriented from top to bottom (`TD` or `TB`). This declares the graph is oriented from top to bottom (`TD` or `TB`).
``` ```
graph TD graph TD
@@ -15,7 +15,7 @@ graph TD
Start --> Stop Start --> Stop
``` ```
This declares a graph oriented from left to right (`LR`). This declares the graph is oriented from left to right (`LR`).
``` ```
graph LR graph LR
@@ -26,18 +26,26 @@ graph LR
Start --> Stop Start --> Stop
``` ```
Possible directions are: ## Flowchart Orientation
* TB - top bottom Possible FlowChart orientations are:
* BT - bottom top
* RL - right left
* LR - left right
* TD - same as TB * TB - top to bottom
* TD - top-down/ same as top to bottom
* BT - bottom to top
* RL - right to left
* LR - left to right
## flowchart
This renders a flowchart in the same way as a graph but with a new rendering method opening up for long requested features such as: more arrow types, multi directional arrows, and linking to and from subgraphs. Apart from the graph type, flowchart/graph, the syntax is the same. This is currently experimental but when the beta period is over both the graph and flowchart keywords will render in the new way. This means it is ok to start beta testing flowcharts.
## Flowcharts
This renders a flowchart that allows for features such as: more arrow types, multi directional arrows, and linking to and from subgraphs.
Apart from the graph type, the syntax is the same. This is currently experimental but when the beta period is over, both the graph and flowchart keywords will render in the new way. This means it is ok to start beta testing flowcharts.
## An important note on Flowchart nodes, do not type the word "end" as a Flowchart node. Capitalize all or any one the letters to keep the flowchart from breaking, i.e, "End" or "END". Or you can apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897).
## Nodes & shapes ## Nodes & shapes
@@ -52,7 +60,7 @@ graph LR
graph LR graph LR
id id
``` ```
Note that the id is what is displayed in the box. # Note that the id is what is displayed in the box.
### A node with text ### A node with text
@@ -69,6 +77,7 @@ graph LR
id1[This is the text in the box] id1[This is the text in the box]
``` ```
## Node Shapes
### A node with round edges ### A node with round edges

View File

@@ -292,9 +292,9 @@ const config = {
wrap: false, wrap: false,
/** /**
* This sets the auto-wrap padding for the diagram (sides only) * 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) * This sets the width of the loop-box (loop, alt, opt, par)
* **Default value 50. * **Default value 50.
@@ -306,24 +306,27 @@ const config = {
*/ */
labelBoxHeight: 20, labelBoxHeight: 20,
messageFont: () => { messageFont: () => {
const c = getConfig();
return { return {
fontFamily: config.messageFontFamily, fontFamily: c.messageFontFamily,
fontSize: config.messageFontSize, fontSize: c.messageFontSize,
fontWeight: config.messageFontWeight fontWeight: c.messageFontWeight
}; };
}, },
noteFont: () => { noteFont: () => {
const c = getConfig();
return { return {
fontFamily: config.noteFontFamily, fontFamily: c.noteFontFamily,
fontSize: config.noteFontSize, fontSize: c.noteFontSize,
fontWeight: config.noteFontWeight fontWeight: c.noteFontWeight
}; };
}, },
actorFont: () => { actorFont: () => {
const c = getConfig();
return { return {
fontFamily: config.actorFontFamily, fontFamily: c.actorFontFamily,
fontSize: config.actorFontSize, fontSize: c.actorFontSize,
fontWeight: config.actorFontWeight fontWeight: c.actorFontWeight
}; };
} }
}, },

View File

@@ -17,6 +17,9 @@ const tags = ['active', 'done', 'crit', 'milestone'];
let funs = []; let funs = [];
let inclusiveEndDates = false; let inclusiveEndDates = false;
// The serial order of the task in the script
let lastOrder = 0;
export const clear = function() { export const clear = function() {
sections = []; sections = [];
tasks = []; tasks = [];
@@ -32,6 +35,7 @@ export const clear = function() {
todayMarker = ''; todayMarker = '';
excludes = []; excludes = [];
inclusiveEndDates = false; inclusiveEndDates = false;
lastOrder = 0;
}; };
export const setAxisFormat = function(txt) { export const setAxisFormat = function(txt) {
@@ -374,6 +378,9 @@ export const addTask = function(descr, data) {
rawTask.done = taskInfo.done; rawTask.done = taskInfo.done;
rawTask.crit = taskInfo.crit; rawTask.crit = taskInfo.crit;
rawTask.milestone = taskInfo.milestone; rawTask.milestone = taskInfo.milestone;
rawTask.order = lastOrder;
lastOrder++;
const pos = rawTasks.push(rawTask); const pos = rawTasks.push(rawTask);

View File

@@ -176,6 +176,137 @@ describe('when using the ganttDb', function() {
expect(tasks[6].task).toEqual('test7'); expect(tasks[6].task).toEqual('test7');
}); });
it('should maintain the order in which tasks are created', function() {
ganttDb.setTitle('Project Execution');
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection('section A section');
ganttDb.addTask('Completed task', 'done, des1, 2014-01-06,2014-01-08');
ganttDb.addTask('Active task', 'active, des2, 2014-01-09, 3d');
ganttDb.addTask('Future task', 'des3, after des2, 5d');
ganttDb.addTask('Future task2', 'des4, after des3, 5d');
ganttDb.addSection('section Critical tasks');
ganttDb.addTask('Completed task in the critical line', 'crit, done, 2014-01-06,24h');
ganttDb.addTask('Implement parser and jison', 'crit, done, after des1, 2d');
ganttDb.addTask('Create tests for parser', 'crit, active, 3d');
ganttDb.addTask('Future task in critical line', 'crit, 5d');
ganttDb.addTask('Create tests for renderer', '2d');
ganttDb.addTask('Add to mermaid', '1d');
ganttDb.addSection('section Documentation');
ganttDb.addTask('Describe gantt syntax', 'active, a1, after des1, 3d');
ganttDb.addTask('Add gantt diagram to demo page', 'after a1 , 20h');
ganttDb.addTask('Add another diagram to demo page', 'doc1, after a1 , 48h');
ganttDb.addSection('section Last section');
ganttDb.addTask('Describe gantt syntax', 'after doc1, 3d');
ganttDb.addTask('Add gantt diagram to demo page', '20h');
ganttDb.addTask('Add another diagram to demo page', '48h');
const tasks = ganttDb.getTasks();
// Section - A section
expect(tasks[0].startTime).toEqual(moment('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[0].order).toEqual(0);
expect(tasks[0].id).toEqual('des1');
expect(tasks[0].task).toEqual('Completed task');
expect(tasks[1].startTime).toEqual(moment('2014-01-09', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[1].order).toEqual(1);
expect(tasks[1].id).toEqual('des2');
expect(tasks[1].task).toEqual('Active task');
expect(tasks[2].startTime).toEqual(moment('2014-01-12', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(moment('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[2].order).toEqual(2);
expect(tasks[2].id).toEqual('des3');
expect(tasks[2].task).toEqual('Future task');
expect(tasks[3].startTime).toEqual(moment('2014-01-17', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(moment('2014-01-22', 'YYYY-MM-DD').toDate());
expect(tasks[3].order).toEqual(3);
expect(tasks[3].id).toEqual('des4');
expect(tasks[3].task).toEqual('Future task2');
// Section - Critical tasks
expect(tasks[4].startTime).toEqual(moment('2014-01-06', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(moment('2014-01-07', 'YYYY-MM-DD').toDate());
expect(tasks[4].order).toEqual(4);
expect(tasks[4].id).toEqual('task1');
expect(tasks[4].task).toEqual('Completed task in the critical line');
expect(tasks[5].startTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(moment('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[5].order).toEqual(5);
expect(tasks[5].id).toEqual('task2');
expect(tasks[5].task).toEqual('Implement parser and jison');
expect(tasks[6].startTime).toEqual(moment('2014-01-10', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[6].order).toEqual(6);
expect(tasks[6].id).toEqual('task3');
expect(tasks[6].task).toEqual('Create tests for parser');
expect(tasks[7].startTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[7].endTime).toEqual(moment('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[7].order).toEqual(7);
expect(tasks[7].id).toEqual('task4');
expect(tasks[7].task).toEqual('Future task in critical line');
expect(tasks[8].startTime).toEqual(moment('2014-01-18', 'YYYY-MM-DD').toDate());
expect(tasks[8].endTime).toEqual(moment('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[8].order).toEqual(8);
expect(tasks[8].id).toEqual('task5');
expect(tasks[8].task).toEqual('Create tests for renderer');
expect(tasks[9].startTime).toEqual(moment('2014-01-20', 'YYYY-MM-DD').toDate());
expect(tasks[9].endTime).toEqual(moment('2014-01-21', 'YYYY-MM-DD').toDate());
expect(tasks[9].order).toEqual(9);
expect(tasks[9].id).toEqual('task6');
expect(tasks[9].task).toEqual('Add to mermaid');
// Section - Documentation
expect(tasks[10].startTime).toEqual(moment('2014-01-08', 'YYYY-MM-DD').toDate());
expect(tasks[10].endTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[10].order).toEqual(10);
expect(tasks[10].id).toEqual('a1');
expect(tasks[10].task).toEqual('Describe gantt syntax');
expect(tasks[11].startTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[11].endTime).toEqual(moment('2014-01-11 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[11].order).toEqual(11);
expect(tasks[11].id).toEqual('task7');
expect(tasks[11].task).toEqual('Add gantt diagram to demo page');
expect(tasks[12].startTime).toEqual(moment('2014-01-11', 'YYYY-MM-DD').toDate());
expect(tasks[12].endTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[12].order).toEqual(12);
expect(tasks[12].id).toEqual('doc1');
expect(tasks[12].task).toEqual('Add another diagram to demo page');
// Section - Last section
expect(tasks[13].startTime).toEqual(moment('2014-01-13', 'YYYY-MM-DD').toDate());
expect(tasks[13].endTime).toEqual(moment('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[13].order).toEqual(13);
expect(tasks[13].id).toEqual('task8');
expect(tasks[13].task).toEqual('Describe gantt syntax');
expect(tasks[14].startTime).toEqual(moment('2014-01-16', 'YYYY-MM-DD').toDate());
expect(tasks[14].endTime).toEqual(moment('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[14].order).toEqual(14);
expect(tasks[14].id).toEqual('task9');
expect(tasks[14].task).toEqual('Add gantt diagram to demo page');
expect(tasks[15].startTime).toEqual(moment('2014-01-16 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[15].endTime).toEqual(moment('2014-01-18 20:00:00', 'YYYY-MM-DD HH:mm:ss').toDate());
expect(tasks[15].order).toEqual(15);
expect(tasks[15].id).toEqual('task10');
expect(tasks[15].task).toEqual('Add another diagram to demo page');
});
it('should work when end date is the 31st', function() { it('should work when end date is the 31st', function() {
ganttDb.setDateFormat('YYYY-MM-DD'); ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection('Task endTime is on the 31st day of the month'); ganttDb.addSection('Task endTime is on the 31st day of the month');

View File

@@ -80,6 +80,22 @@ export const draw = function(text, id) {
categories = checkUnique(categories); categories = checkUnique(categories);
function taskCompare(a, b) {
const taskA = a.startTime;
const taskB = b.startTime;
let result = 0;
if (taskA > taskB) {
result = 1;
} else if (taskA < taskB) {
result = -1;
}
return result;
}
// Sort the task array using the above taskCompare() so that
// tasks are created based on their order of startTime
taskArray.sort(taskCompare);
makeGant(taskArray, w, h); makeGant(taskArray, w, h);
if (typeof conf.useWidth !== 'undefined') { if (typeof conf.useWidth !== 'undefined') {
elem.setAttribute('width', w); elem.setAttribute('width', w);
@@ -119,6 +135,8 @@ export const draw = function(text, id) {
.append('rect') .append('rect')
.attr('x', 0) .attr('x', 0)
.attr('y', function(d, i) { .attr('y', function(d, i) {
// Ignore the incoming i value and use our order instead
i = d.order;
return i * theGap + theTopPad - 2; return i * theGap + theTopPad - 2;
}) })
.attr('width', function() { .attr('width', function() {
@@ -160,6 +178,8 @@ export const draw = function(text, id) {
return timeScale(d.startTime) + theSidePad; return timeScale(d.startTime) + theSidePad;
}) })
.attr('y', function(d, i) { .attr('y', function(d, i) {
// Ignore the incoming i value and use our order instead
i = d.order;
return i * theGap + theTopPad; return i * theGap + theTopPad;
}) })
.attr('width', function(d) { .attr('width', function(d) {
@@ -263,6 +283,8 @@ export const draw = function(text, id) {
} }
}) })
.attr('y', function(d, i) { .attr('y', function(d, i) {
// Ignore the incoming i value and use our order instead
i = d.order;
return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad; return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad;
}) })
.attr('text-height', theBarHeight) .attr('text-height', theBarHeight)
@@ -280,6 +302,7 @@ export const draw = function(text, id) {
} }
let secNum = 0; let secNum = 0;
console.log(conf);
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d.type === categories[i]) { if (d.type === categories[i]) {
secNum = i % conf.numberSectionStyles; secNum = i % conf.numberSectionStyles;

View File

@@ -1,10 +1,9 @@
/* /*
* Parse following * Parse following
* gitGraph: * gitGraph:
* commit * commit
* commit * commit
* branch * branch
*/ */
%lex %lex
@@ -14,28 +13,28 @@
%% %%
(\r?\n)+ return 'NL'; (\r?\n)+ return 'NL';
\s+ /* skip all whitespace */ \s+ /* skip all whitespace */
\#[^\n]* /* skip comments */ \#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */ \%%[^\n]* /* skip comments */
"gitGraph" return 'GG'; "gitGraph" return 'GG';
"commit" return 'COMMIT'; "commit" return 'COMMIT';
"branch" return 'BRANCH'; "branch" return 'BRANCH';
"merge" return 'MERGE'; "merge" return 'MERGE';
"reset" return 'RESET'; "reset" return 'RESET';
"checkout" return 'CHECKOUT'; "checkout" return 'CHECKOUT';
"LR" return 'DIR'; "LR" return 'DIR';
"BT" return 'DIR'; "BT" return 'DIR';
":" return ':'; ":" return ':';
"^" return 'CARET' "^" return 'CARET'
"options"\r?\n this.begin("options"); "options"\r?\n this.begin("options");
<options>"end"\r?\n this.popState(); <options>"end"\r?\n this.popState();
<options>[^\n]+\r?\n return 'OPT'; <options>[^\n]+\r?\n return 'OPT';
["] this.begin("string"); ["] this.begin("string");
<string>["] this.popState(); <string>["] this.popState();
<string>[^"]* return 'STR'; <string>[^"]* return 'STR';
[a-zA-Z][a-zA-Z0-9_]+ return 'ID'; [a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9] return 'ID';
<<EOF>> return 'EOF'; <<EOF>> return 'EOF';
/lex /lex

View File

@@ -136,7 +136,7 @@ export const bounds = {
}, },
newActivation: function(message, diagram, actors) { newActivation: function(message, diagram, actors) {
const actorRect = actors[message.from.actor]; 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; const x = actorRect.x + actorRect.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2;
this.activations.push({ this.activations.push({
startx: x, startx: x,
@@ -248,13 +248,16 @@ const drawNote = function(elem, noteModel) {
* @param msgModel - the model containing fields describing a message * @param msgModel - the model containing fields describing a message
*/ */
const drawMessage = function(g, msgModel) { const drawMessage = function(g, msgModel) {
bounds.bumpVerticalPos(conf.messageMargin); const { startx, stopx, starty, message, type, sequenceIndex, wrap } = msgModel;
msgModel.height += conf.messageMargin; const lines = message.split(common.lineBreakRegex).length;
msgModel.starty = bounds.getVerticalPos(); let textDims = utils.calculateTextDimensions(message, conf);
const { startx, stopx, starty: verticalPos, message, type, sequenceIndex, wrap } = msgModel; const lineHeight = textDims.height / lines;
msgModel.height += lineHeight;
bounds.bumpVerticalPos(lineHeight);
const textObj = svgDraw.getTextObj(); const textObj = svgDraw.getTextObj();
textObj.x = startx; textObj.x = startx;
textObj.y = verticalPos; textObj.y = starty;
textObj.width = stopx - startx; textObj.width = stopx - startx;
textObj.class = 'messageText'; textObj.class = 'messageText';
textObj.dy = '1em'; textObj.dy = '1em';
@@ -268,18 +271,11 @@ const drawMessage = function(g, msgModel) {
textObj.tspan = false; textObj.tspan = false;
textObj.wrap = wrap; textObj.wrap = wrap;
let textElem = drawText(g, textObj); drawText(g, textObj);
const lineHeight = (textElem[0]._groups || textElem[0])[0][0].getBBox().height;
textElem.forEach(te => te.attr('y', verticalPos - 7 - lineHeight / 2));
const lines = message.split(common.lineBreakRegex).length - 1; let totalOffset = textDims.height;
let totalOffset = Math.round(lineHeight + lines * lineHeight); let textWidth = textDims.width;
let textWidth = Math.max.apply(
null,
textElem.map(te => (te._groups || te)[0][0].getBBox().width)
);
let line; let line;
if (startx === stopx) { if (startx === stopx) {
@@ -288,7 +284,8 @@ const drawMessage = function(g, msgModel) {
.append('path') .append('path')
.attr( .attr(
'd', '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 + 25 +
totalOffset} H ${startx}` totalOffset} H ${startx}`
); );
@@ -302,49 +299,43 @@ const drawMessage = function(g, msgModel) {
'M ' + 'M ' +
startx + startx +
',' + ',' +
(verticalPos + totalOffset) + (bounds.getVerticalPos() + totalOffset) +
' C ' + ' C ' +
(startx + 60) + (startx + 60) +
',' + ',' +
(verticalPos - 10 + totalOffset) + (bounds.getVerticalPos() - 10 + totalOffset) +
' ' + ' ' +
(startx + 60) + (startx + 60) +
',' + ',' +
(verticalPos + 30 + totalOffset) + (bounds.getVerticalPos() + 30 + totalOffset) +
' ' + ' ' +
startx + startx +
',' + ',' +
(verticalPos + 20 + totalOffset) (bounds.getVerticalPos() + 20 + totalOffset)
); );
} }
bounds.bumpVerticalPos(30); totalOffset += 30;
msgModel.height += 30; const dx = Math.max(textWidth / 2, conf.width / 2);
const dx = Math.max(textWidth / 2, 100);
bounds.insert( bounds.insert(
startx - dx, startx - dx,
bounds.getVerticalPos() - 10 + totalOffset, bounds.getVerticalPos() - 10 + totalOffset,
stopx + dx, stopx + dx,
bounds.getVerticalPos() + 30 + totalOffset bounds.getVerticalPos() + 30 + totalOffset
); );
bounds.bumpVerticalPos(10);
msgModel.height += 10;
} else { } else {
totalOffset += conf.boxMargin;
line = g.append('line'); line = g.append('line');
line.attr('x1', startx); line.attr('x1', startx);
line.attr('y1', verticalPos + totalOffset); line.attr('y1', bounds.getVerticalPos() + totalOffset);
line.attr('x2', stopx); line.attr('x2', stopx);
line.attr('y2', verticalPos + totalOffset); line.attr('y2', bounds.getVerticalPos() + totalOffset);
bounds.bumpVerticalPos(10);
msgModel.height += 10;
bounds.insert( bounds.insert(
startx, startx,
bounds.getVerticalPos() - 10 + totalOffset, bounds.getVerticalPos() - 10 + totalOffset,
stopx, stopx,
bounds.getVerticalPos() + totalOffset bounds.getVerticalPos() + totalOffset
); );
msgModel.height += 10;
bounds.bumpVerticalPos(10);
} }
// Make an SVG Container // Make an SVG Container
// Draw the line // Draw the line
@@ -387,7 +378,7 @@ const drawMessage = function(g, msgModel) {
line.attr('marker-start', 'url(' + url + '#sequencenumber)'); line.attr('marker-start', 'url(' + url + '#sequencenumber)');
g.append('text') g.append('text')
.attr('x', startx) .attr('x', startx)
.attr('y', verticalPos + 4 + totalOffset) .attr('y', bounds.getVerticalPos() + 4 + totalOffset)
.attr('font-family', 'sans-serif') .attr('font-family', 'sans-serif')
.attr('font-size', '12px') .attr('font-size', '12px')
.attr('text-anchor', 'middle') .attr('text-anchor', 'middle')
@@ -395,9 +386,10 @@ const drawMessage = function(g, msgModel) {
.attr('class', 'sequenceNumber') .attr('class', 'sequenceNumber')
.text(sequenceIndex); .text(sequenceIndex);
} }
bounds.bumpVerticalPos(totalOffset);
msgModel.height += totalOffset;
msgModel.stopy = msgModel.starty + msgModel.height; msgModel.stopy = msgModel.starty + msgModel.height;
bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy); 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) { 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.message = utils.wrapLabel(`[${msg.message}]`, loopWidth - 2 * conf.wrapPadding, textConf);
msg.width = loopWidth; msg.width = loopWidth;
const textHeight = utils.calculateTextHeight(msg.message, textConf); // const lines = msg.message.split(common.lineBreakRegex).length;
heightAdjust += textHeight; const textDims = utils.calculateTextDimensions(msg.message, textConf);
const totalOffset = textDims.height - conf.labelBoxHeight;
heightAdjust = postMargin + totalOffset;
logger.debug(`${totalOffset} - ${msg.message}`);
} }
addLoopFn(msg); addLoopFn(msg);
bounds.bumpVerticalPos(heightAdjust); bounds.bumpVerticalPos(heightAdjust);
@@ -488,8 +483,9 @@ export const draw = function(text, id) {
parser.yy.clear(); parser.yy.clear();
parser.yy.setWrap(conf.wrap); parser.yy.setWrap(conf.wrap);
parser.parse(text + '\n'); parser.parse(text + '\n');
bounds.init(); bounds.init();
logger.debug(`C:${JSON.stringify(conf, null, 2)}`);
const diagram = select(`[id="${id}"]`); const diagram = select(`[id="${id}"]`);
// Fetch data from the parsing // Fetch data from the parsing
@@ -553,7 +549,7 @@ export const draw = function(text, id) {
break; break;
case parser.yy.LINETYPE.LOOP_END: case parser.yy.LINETYPE.LOOP_END:
loopModel = bounds.endLoop(); loopModel = bounds.endLoop();
svgDraw.drawLoop(diagram, loopModel, 'loop', conf, bounds); svgDraw.drawLoop(diagram, loopModel, 'loop', conf);
bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos()); bounds.bumpVerticalPos(loopModel.stopy - bounds.getVerticalPos());
bounds.models.addLoop(loopModel); bounds.models.addLoop(loopModel);
break; break;
@@ -635,6 +631,7 @@ export const draw = function(text, id) {
try { try {
// lastMsg = msg // lastMsg = msg
msgModel = msg.msgModel; msgModel = msg.msgModel;
msgModel.starty = bounds.getVerticalPos();
msgModel.sequenceIndex = sequenceIndex; msgModel.sequenceIndex = sequenceIndex;
drawMessage(diagram, msgModel); drawMessage(diagram, msgModel);
bounds.models.addMessage(msgModel); bounds.models.addMessage(msgModel);
@@ -706,7 +703,7 @@ export const draw = function(text, id) {
' ' + ' ' +
(height + extraVertForTitle) (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, maxMessageWidthPerActor[msg.to] || 0,
messageWidth 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) { } else if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
maxMessageWidthPerActor[msg.from] = Math.max( maxMessageWidthPerActor[msg.from] = Math.max(
maxMessageWidthPerActor[msg.from] || 0, maxMessageWidthPerActor[msg.from] || 0,
@@ -861,7 +863,6 @@ const buildNoteModel = function(msg, actors) {
shouldWrap ? utils.wrapLabel(msg.message, conf.width, conf.noteFont()) : msg.message, shouldWrap ? utils.wrapLabel(msg.message, conf.width, conf.noteFont()) : msg.message,
conf.noteFont() conf.noteFont()
); );
logger.debug(`TD:[${textDimensions.width},${textDimensions.height}]`);
let noteModel = { let noteModel = {
width: shouldWrap width: shouldWrap
? conf.width ? conf.width
@@ -892,14 +893,18 @@ const buildNoteModel = function(msg, actors) {
} else if (msg.to === msg.from) { } else if (msg.to === msg.from) {
textDimensions = utils.calculateTextDimensions( textDimensions = utils.calculateTextDimensions(
shouldWrap 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, : msg.message,
conf.noteFont() conf.noteFont()
); );
noteModel.width = shouldWrap noteModel.width = shouldWrap
? Math.max(conf.width, actors[msg.to].width) ? Math.max(conf.width, actors[msg.from].width)
: Math.max(actors[msg.to].width, conf.width, textDimensions.width + 2 * conf.noteMargin); : Math.max(actors[msg.from].width, conf.width, textDimensions.width + 2 * conf.noteMargin);
noteModel.startx = startx + (actors[msg.to].width - noteModel.width) / 2; noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2;
} else { } else {
noteModel.width = noteModel.width =
Math.abs(startx + actors[msg.from].width / 2 - (stopx + actors[msg.to].width / 2)) + 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() conf.noteFont()
); );
} }
logger.debug(
`NM:[${noteModel.startx},${noteModel.stopx},${noteModel.starty},${noteModel.stopy}:${noteModel.width},${noteModel.height}=${msg.message}]`
);
return noteModel; return noteModel;
}; };
@@ -957,7 +965,7 @@ const buildMessageModel = function(msg, actors) {
if (msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message)) { if (msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message)) {
msgModel.message = utils.wrapLabel( msgModel.message = utils.wrapLabel(
msg.message, msg.message,
Math.max(msgModel.width, conf.width), Math.max(msgModel.width - 2 * conf.wrapPadding, conf.width),
conf.messageFont() conf.messageFont()
); );
} }
@@ -1028,44 +1036,31 @@ const calculateLoopBounds = function(messages, actors) {
if (isNote) { if (isNote) {
noteModel = buildNoteModel(msg, actors); noteModel = buildNoteModel(msg, actors);
msg.noteModel = noteModel; msg.noteModel = noteModel;
let depth = 0;
stack.forEach(stk => { stack.forEach(stk => {
current = stk; current = stk;
current.from = Math.min(current.from, noteModel.startx); current.from = Math.min(current.from, noteModel.startx);
current.to = Math.max(current.to, noteModel.startx + noteModel.width); current.to = Math.max(current.to, noteModel.startx + noteModel.width);
current.width = current.width =
Math.max(current.width, Math.abs(current.from - current.to)) - Math.max(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
50 -
conf.boxMargin * depth;
depth++;
}); });
} else { } else {
msgModel = buildMessageModel(msg, actors); msgModel = buildMessageModel(msg, actors);
msg.msgModel = msgModel; msg.msgModel = msgModel;
if (msg.from && msg.to && stack.length > 0) { if (msgModel.startx && msgModel.stopx && stack.length > 0) {
let depth = 0;
stack.forEach(stk => { stack.forEach(stk => {
current = stk; current = stk;
let from = actors[msg.from]; if (msgModel.startx === msgModel.stopx) {
let to = actors[msg.to]; let from = actors[msg.from];
if (from.x === to.x) { let to = actors[msg.to];
current.from = Math.min(current.from, from.x); current.from = Math.min(from.x - from.width / 2, current.from);
current.to = Math.max(current.to, to.x); current.to = Math.max(to.x + from.width / 2, current.to);
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);
}
current.width = current.width =
Math.max(current.width, Math.abs(current.from - current.to)) - Math.max(current.width, Math.abs(current.to - current.from)) - conf.labelBoxWidth;
50 - } else {
conf.boxMargin * depth; 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++;
}); });
} }
} }

View File

@@ -1,4 +1,6 @@
import common from '../common/common'; import common from '../common/common';
import utils from '../../utils';
import { logger } from '../../logger';
export const drawRect = function(elem, rectData) { export const drawRect = function(elem, rectData) {
const rectElem = elem.append('rect'); const rectElem = elem.append('rect');
@@ -36,18 +38,21 @@ export const drawText = function(elem, textData) {
switch (textData.valign) { switch (textData.valign) {
case 'top': case 'top':
case 'start': case 'start':
yfunc = () => textData.y + textData.textMargin; yfunc = () => Math.round(textData.y + textData.textMargin);
break; break;
case 'middle': case 'middle':
case 'center': case 'center':
yfunc = () => textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2; yfunc = () =>
Math.round(textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2);
break; break;
case 'bottom': case 'bottom':
case 'end': case 'end':
yfunc = () => yfunc = () =>
textData.y + Math.round(
(prevTextHeight + textHeight + 2 * textData.textMargin) - textData.y +
textData.textMargin; (prevTextHeight + textHeight + 2 * textData.textMargin) -
textData.textMargin
);
break; break;
} }
} }
@@ -59,21 +64,21 @@ export const drawText = function(elem, textData) {
switch (textData.anchor) { switch (textData.anchor) {
case 'left': case 'left':
case 'start': case 'start':
textData.x = textData.x + textData.textMargin; textData.x = Math.round(textData.x + textData.textMargin);
textData.anchor = 'start'; textData.anchor = 'start';
textData.dominantBaseline = 'text-after-edge'; textData.dominantBaseline = 'text-after-edge';
textData.alignmentBaseline = 'middle'; textData.alignmentBaseline = 'middle';
break; break;
case 'middle': case 'middle':
case 'center': case 'center':
textData.x = textData.x + textData.width / 2; textData.x = Math.round(textData.x + textData.width / 2);
textData.anchor = 'middle'; textData.anchor = 'middle';
textData.dominantBaseline = 'middle'; textData.dominantBaseline = 'middle';
textData.alignmentBaseline = 'middle'; textData.alignmentBaseline = 'middle';
break; break;
case 'right': case 'right':
case 'end': case 'end':
textData.x = textData.x + textData.width - textData.textMargin; textData.x = Math.round(textData.x + textData.width - textData.textMargin);
textData.anchor = 'end'; textData.anchor = 'end';
textData.dominantBaseline = 'text-before-edge'; textData.dominantBaseline = 'text-before-edge';
textData.alignmentBaseline = 'middle'; textData.alignmentBaseline = 'middle';
@@ -256,6 +261,15 @@ export const drawActivation = function(elem, bounds, verticalPos, conf, actorAct
* @param conf - diagrom configuration * @param conf - diagrom configuration
*/ */
export const drawLoop = function(elem, loopModel, labelText, conf) { 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 g = elem.append('g');
const drawLoopLine = function(startx, starty, stopx, stopy) { const drawLoopLine = function(startx, starty, stopx, stopy) {
return g return g
@@ -283,45 +297,51 @@ export const drawLoop = function(elem, loopModel, labelText, conf) {
txt.text = labelText; txt.text = labelText;
txt.x = loopModel.startx; txt.x = loopModel.startx;
txt.y = loopModel.starty; txt.y = loopModel.starty;
const msgFont = conf.messageFont(); txt.fontFamily = fontFamily;
txt.fontFamily = msgFont.fontFamily; txt.fontSize = fontSize;
txt.fontSize = msgFont.fontSize; txt.fontWeight = fontWeight;
txt.fontWeight = msgFont.fontWeight;
txt.anchor = 'middle'; txt.anchor = 'middle';
txt.valign = 'middle'; txt.valign = 'middle';
txt.tspan = false; txt.tspan = false;
txt.width = conf.labelBoxWidth || 50; txt.width = labelBoxWidth || 50;
txt.height = conf.labelBoxHeight || 20; txt.height = labelBoxHeight || 20;
txt.textMargin = conf.boxTextMargin; txt.textMargin = boxTextMargin;
txt.class = 'labelText'; txt.class = 'labelText';
drawLabel(g, txt); drawLabel(g, txt);
txt = getTextObj(); txt = getTextObj();
txt.text = loopModel.title; txt.text = loopModel.title;
txt.x = loopModel.startx + conf.labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2; txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2;
txt.y = loopModel.starty + conf.boxMargin + conf.boxTextMargin; txt.y = loopModel.starty + boxMargin + boxTextMargin;
txt.anchor = 'middle'; txt.anchor = 'middle';
txt.valign = 'middle';
txt.textMargin = boxTextMargin;
txt.class = 'loopText'; txt.class = 'loopText';
txt.fontFamily = msgFont.fontFamily; txt.fontFamily = fontFamily;
txt.fontSize = msgFont.fontSize; txt.fontSize = fontSize;
txt.fontWeight = msgFont.fontWeight; txt.fontWeight = fontWeight;
txt.wrap = true; txt.wrap = true;
let textElem = drawText(g, txt); 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') { if (typeof loopModel.sectionTitles !== 'undefined') {
loopModel.sectionTitles.forEach(function(item, idx) { loopModel.sectionTitles.forEach(function(item, idx) {
if (item.message) { if (item.message) {
txt.text = item.message; txt.text = item.message;
txt.x = loopModel.startx + (loopModel.stopx - loopModel.startx) / 2; 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.class = 'loopText';
txt.anchor = 'middle'; txt.anchor = 'middle';
txt.valign = 'middle'; txt.valign = 'middle';
txt.tspan = false; txt.tspan = false;
txt.fontFamily = msgFont.fontFamily; txt.fontFamily = fontFamily;
txt.fontSize = msgFont.fontSize; txt.fontSize = fontSize;
txt.fontWeight = msgFont.fontWeight; txt.fontWeight = fontWeight;
txt.wrap = loopModel.wrap; txt.wrap = loopModel.wrap;
textElem = drawText(g, txt); textElem = drawText(g, txt);
let sectionHeight = Math.round( 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) .map(te => (te._groups || te)[0][0].getBBox().height)
.reduce((acc, curr) => acc + curr) .reduce((acc, curr) => acc + curr)
); );
loopModel.sections[idx].height += sectionHeight - (conf.boxMargin + conf.boxTextMargin); loopModel.sections[idx].height += sectionHeight - (boxMargin + boxTextMargin);
} }
}); });
} }

View File

@@ -627,21 +627,21 @@ export const calculateTextDimensions = function(text, config) {
// of sans-serif. // of sans-serif.
const fontFamilies = ['sans-serif', fontFamily]; const fontFamilies = ['sans-serif', fontFamily];
const lines = text.split(common.lineBreakRegex); const lines = text.split(common.lineBreakRegex);
let maxWidth = 0, let dims = [];
height = 0;
const body = select('body'); const body = select('body');
// We don't 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. // for any reason, do not continue.
if (!body.remove) { if (!body.remove) {
return { width: 0, height: 0 }; return { width: 0, height: 0, lineHeight: 0 };
} }
const g = body.append('svg'); const g = body.append('svg');
for (let line of lines) { for (let fontFamily of fontFamilies) {
let cheight = 0; let cheight = 0;
for (let fontFamily of fontFamilies) { let dim = { width: 0, height: 0, lineHeight: 0 };
for (let line of lines) {
const textObj = getTextObj(); const textObj = getTextObj();
textObj.text = line; textObj.text = line;
const textElem = drawSimpleText(g, textObj) const textElem = drawSimpleText(g, textObj)
@@ -650,16 +650,26 @@ export const calculateTextDimensions = function(text, config) {
.style('font-family', fontFamily); .style('font-family', fontFamily);
let bBox = (textElem._groups || textElem)[0][0].getBBox(); let bBox = (textElem._groups || textElem)[0][0].getBBox();
maxWidth = Math.max(maxWidth, bBox.width); dim.width = Math.round(Math.max(dim.width, bBox.width));
cheight = Math.max(bBox.height, cheight); cheight = Math.round(bBox.height);
dim.height += cheight;
dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
} }
height += cheight; dims.push(dim);
} }
g.remove(); g.remove();
// Adds some padding, so the text won't sit exactly within the actor's borders let index =
const result = { width: Math.round(maxWidth), height: Math.round(height) }; 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; calculateTextDimensions[cacheKey] = result;
return result; return result;
}; };