From c00b204259a4bc8aa650f1bcc2830270237fdb9c Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sat, 20 Jun 2020 11:58:38 +0800 Subject: [PATCH 1/9] gitGraph: :hammer: dot and minus should be valid branch name --- src/diagrams/git/parser/gitGraph.jison | 47 +++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/diagrams/git/parser/gitGraph.jison b/src/diagrams/git/parser/gitGraph.jison index e675a56e4..d14069b6c 100644 --- a/src/diagrams/git/parser/gitGraph.jison +++ b/src/diagrams/git/parser/gitGraph.jison @@ -1,10 +1,9 @@ - /* * Parse following * gitGraph: * commit * commit - * branch + * branch */ %lex @@ -14,28 +13,28 @@ %% -(\r?\n)+ return 'NL'; -\s+ /* skip all whitespace */ -\#[^\n]* /* skip comments */ -\%%[^\n]* /* skip comments */ -"gitGraph" return 'GG'; -"commit" return 'COMMIT'; -"branch" return 'BRANCH'; -"merge" return 'MERGE'; -"reset" return 'RESET'; -"checkout" return 'CHECKOUT'; -"LR" return 'DIR'; -"BT" return 'DIR'; -":" return ':'; -"^" return 'CARET' -"options"\r?\n this.begin("options"); -"end"\r?\n this.popState(); -[^\n]+\r?\n return 'OPT'; -["] this.begin("string"); -["] this.popState(); -[^"]* return 'STR'; -[a-zA-Z][a-zA-Z0-9_]+ return 'ID'; -<> return 'EOF'; +(\r?\n)+ return 'NL'; +\s+ /* skip all whitespace */ +\#[^\n]* /* skip comments */ +\%%[^\n]* /* skip comments */ +"gitGraph" return 'GG'; +"commit" return 'COMMIT'; +"branch" return 'BRANCH'; +"merge" return 'MERGE'; +"reset" return 'RESET'; +"checkout" return 'CHECKOUT'; +"LR" return 'DIR'; +"BT" return 'DIR'; +":" return ':'; +"^" return 'CARET' +"options"\r?\n this.begin("options"); +"end"\r?\n this.popState(); +[^\n]+\r?\n return 'OPT'; +["] this.begin("string"); +["] this.popState(); +[^"]* return 'STR'; +[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9] return 'ID'; +<> return 'EOF'; /lex From 5f56db6f6e85e61d72f3c275b3649d7b3ec1d1c2 Mon Sep 17 00:00:00 2001 From: Abhijeet Pathak Date: Sat, 20 Jun 2020 18:03:48 +0530 Subject: [PATCH 2/9] Fix for the issue 1005 (https://github.com/mermaid-js/mermaid/issues/1005) A new attribute 'order' has been introduced in the task which records the serial number of task in the script. In ganttRenderer.js, the tasks are sorted by stratTime attribute. The function which calculates 'y' for task rectangles, lables etc. has been modified to correctly position it. --- src/diagrams/gantt/ganttDb.js | 7 +++++++ src/diagrams/gantt/ganttRenderer.js | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/diagrams/gantt/ganttDb.js b/src/diagrams/gantt/ganttDb.js index 0c01ab257..c8e138526 100644 --- a/src/diagrams/gantt/ganttDb.js +++ b/src/diagrams/gantt/ganttDb.js @@ -17,6 +17,9 @@ const tags = ['active', 'done', 'crit', 'milestone']; let funs = []; let inclusiveEndDates = false; +// The serial order of the task in the script +let lastOrder = 0; + export const clear = function() { sections = []; tasks = []; @@ -32,6 +35,7 @@ export const clear = function() { todayMarker = ''; excludes = []; inclusiveEndDates = false; + lastOrder = 0; }; export const setAxisFormat = function(txt) { @@ -374,6 +378,9 @@ export const addTask = function(descr, data) { rawTask.done = taskInfo.done; rawTask.crit = taskInfo.crit; rawTask.milestone = taskInfo.milestone; + rawTask.order = lastOrder; + + lastOrder++; const pos = rawTasks.push(rawTask); diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js index ad902bf20..99d0c5642 100644 --- a/src/diagrams/gantt/ganttRenderer.js +++ b/src/diagrams/gantt/ganttRenderer.js @@ -80,6 +80,23 @@ export const draw = function(text, id) { 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); if (typeof conf.useWidth !== 'undefined') { elem.setAttribute('width', w); @@ -119,7 +136,7 @@ export const draw = function(text, id) { .append('rect') .attr('x', 0) .attr('y', function(d, i) { - return i * theGap + theTopPad - 2; + return d.order * theGap + theTopPad - 2; }) .attr('width', function() { return w - conf.rightPadding / 2; @@ -160,7 +177,7 @@ export const draw = function(text, id) { return timeScale(d.startTime) + theSidePad; }) .attr('y', function(d, i) { - return i * theGap + theTopPad; + return d.order * theGap + theTopPad; }) .attr('width', function(d) { if (d.milestone) { @@ -263,7 +280,7 @@ export const draw = function(text, id) { } }) .attr('y', function(d, i) { - return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad; + return d.order * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad; }) .attr('text-height', theBarHeight) .attr('class', function(d) { @@ -280,6 +297,7 @@ export const draw = function(text, id) { } let secNum = 0; + console.log(conf); for (let i = 0; i < categories.length; i++) { if (d.type === categories[i]) { secNum = i % conf.numberSectionStyles; From e515c7beb16047cb5f54e11599445a27f946e039 Mon Sep 17 00:00:00 2001 From: Abhijeet Pathak Date: Sat, 20 Jun 2020 18:39:19 +0530 Subject: [PATCH 3/9] Fxed liniting issues. --- src/diagrams/gantt/ganttRenderer.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js index 99d0c5642..98e6391a9 100644 --- a/src/diagrams/gantt/ganttRenderer.js +++ b/src/diagrams/gantt/ganttRenderer.js @@ -83,7 +83,6 @@ export const draw = function(text, id) { function taskCompare(a, b) { const taskA = a.startTime; const taskB = b.startTime; - let result = 0; if (taskA > taskB) { result = 1; @@ -136,7 +135,9 @@ export const draw = function(text, id) { .append('rect') .attr('x', 0) .attr('y', function(d, i) { - return d.order * theGap + theTopPad - 2; + // Ignore the incoming i value and use our order instead + i = d.order; + return i * theGap + theTopPad - 2; }) .attr('width', function() { return w - conf.rightPadding / 2; @@ -177,7 +178,9 @@ export const draw = function(text, id) { return timeScale(d.startTime) + theSidePad; }) .attr('y', function(d, i) { - return d.order * theGap + theTopPad; + // Ignore the incoming i value and use our order instead + i = d.order; + return i * theGap + theTopPad; }) .attr('width', function(d) { if (d.milestone) { @@ -280,7 +283,9 @@ export const draw = function(text, id) { } }) .attr('y', function(d, i) { - return d.order * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad; + // Ignore the incoming i value and use our order instead + i = d.order; + return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad; }) .attr('text-height', theBarHeight) .attr('class', function(d) { From 224d6dc93fc552a1eb1cb01d90ef09a4298d24cd Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sun, 21 Jun 2020 10:03:21 -0700 Subject: [PATCH 4/9] Update faq.md --- docs/faq.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 038a3ae1d..7abb5bccc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -5,4 +5,5 @@ 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 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 From e7784e3c347a50acff9d7bc7054a7cc70ddcf7f7 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sun, 21 Jun 2020 11:41:46 -0700 Subject: [PATCH 5/9] Update faq.md --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 7abb5bccc..c6924b3c2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -6,4 +6,4 @@ 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 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 +1. [How to create a Flowchart end-Node that says "End"](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897) From 9da3402997b8b6bafc776f3f9ae9685a43238e85 Mon Sep 17 00:00:00 2001 From: Neil Cuzon <58763315+NeilCuzon@users.noreply.github.com> Date: Sun, 21 Jun 2020 15:19:45 -0700 Subject: [PATCH 6/9] Update flowchart.md -added a few workarounds, I found for writing "end" as a node name alongside the workaround found in the FAQ. -also made a few minor edits to the contents. --- docs/flowchart.md | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/flowchart.md b/docs/flowchart.md index be15559de..26b230ff1 100644 --- a/docs/flowchart.md +++ b/docs/flowchart.md @@ -2,9 +2,9 @@ ## 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 @@ -15,7 +15,7 @@ graph TD 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 @@ -26,18 +26,26 @@ graph LR Start --> Stop ``` -Possible directions are: +## Flowchart Orientation -* TB - top bottom -* BT - bottom top -* RL - right left -* LR - left right +Possible FlowChart orientations are: -* 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 @@ -52,7 +60,7 @@ graph LR graph LR 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 @@ -69,6 +77,7 @@ graph LR id1[This is the text in the box] ``` +## Node Shapes ### A node with round edges From 2840c98bb029f2200cc78ecdb857626146da85e3 Mon Sep 17 00:00:00 2001 From: Abhijeet Pathak Date: Tue, 23 Jun 2020 22:32:49 +0530 Subject: [PATCH 7/9] Added test case for preserving creation order of tasks --- src/diagrams/gantt/ganttDb.spec.js | 131 +++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/diagrams/gantt/ganttDb.spec.js b/src/diagrams/gantt/ganttDb.spec.js index 67e150c15..c655889f8 100644 --- a/src/diagrams/gantt/ganttDb.spec.js +++ b/src/diagrams/gantt/ganttDb.spec.js @@ -176,6 +176,137 @@ describe('when using the ganttDb', function() { expect(tasks[6].task).toEqual('test7'); }); + it('should create tasks in the order days - top to bottom and letft to right', 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() { ganttDb.setDateFormat('YYYY-MM-DD'); ganttDb.addSection('Task endTime is on the 31st day of the month'); From 3c727bc46f167932082497e5ba946a57edd9b518 Mon Sep 17 00:00:00 2001 From: abhijeet-pathak <56287990+abhijeet-pathak@users.noreply.github.com> Date: Wed, 24 Jun 2020 14:51:08 +0530 Subject: [PATCH 8/9] Corrected the test case description --- src/diagrams/gantt/ganttDb.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagrams/gantt/ganttDb.spec.js b/src/diagrams/gantt/ganttDb.spec.js index c655889f8..4ece88fa3 100644 --- a/src/diagrams/gantt/ganttDb.spec.js +++ b/src/diagrams/gantt/ganttDb.spec.js @@ -176,7 +176,7 @@ describe('when using the ganttDb', function() { expect(tasks[6].task).toEqual('test7'); }); - it('should create tasks in the order days - top to bottom and letft to right', function() { + 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'); From fcd212633093bb1a1fac84f5e9e970c2465bebec Mon Sep 17 00:00:00 2001 From: chris moran Date: Sun, 28 Jun 2020 08:37:26 -0400 Subject: [PATCH 9/9] 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; };