diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 4abde9d446..7566110082 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -92,6 +92,31 @@ describe('Gantt diagram', () => { {} ); }); + it('should handle multiple dependencies syntax with after and until', () => { + imgSnapshotTest( + ` + gantt + dateFormat YYYY-MM-DD + axisFormat %d/%m + title Adding GANTT diagram to mermaid + excludes weekdays 2014-01-10 + todayMarker off + + section team's critical event + deadline A :milestone, crit, deadlineA, 2024-02-01, 0 + deadline B :milestone, crit, deadlineB, 2024-02-15, 0 + boss on leave :bossaway, 2024-01-28, 2024-02-11 + + section new intern + onboarding :onboarding, 2024-01-02, 1w + literature review :litreview, 2024-01-02, 10d + project A :projectA, after onboarding litreview, until deadlineA bossaway + chilling :chilling, after projectA, until deadlineA + project B :projectB, after deadlineA, until deadlineB + `, + {} + ); + }); it('should FAIL redering a gantt chart for issue #1060 with invalid date', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js index 19ddde31d4..60800e809b 100644 --- a/cypress/integration/rendering/gitGraph.spec.js +++ b/cypress/integration/rendering/gitGraph.spec.js @@ -943,4 +943,74 @@ gitGraph TB: { gitGraph: { parallelCommits: true } } ); }); + it('46: should render GitGraph with merge back and merge forward', () => { + imgSnapshotTest( + `gitGraph LR: + commit + + branch branch-A + branch branch-B + commit + + checkout branch-A + merge branch-B + + checkout branch-B + merge branch-A + `, + { gitGraph: { parallelCommits: true } } + ); + }); + it('47: should render GitGraph with merge back and merge forward | Vertical Branch', () => { + imgSnapshotTest( + `gitGraph TB: + commit + + branch branch-A + branch branch-B + commit + + checkout branch-A + merge branch-B + + checkout branch-B + merge branch-A + `, + { gitGraph: { parallelCommits: true } } + ); + }); + it('48: should render GitGraph with merge on a new branch | Vertical Branch', () => { + imgSnapshotTest( + `gitGraph LR: + commit + + branch branch-B order: 2 + commit + + branch branch-A + merge main + + checkout branch-B + merge branch-A + `, + { gitGraph: { parallelCommits: true } } + ); + }); + it('49: should render GitGraph with merge on a new branch | Vertical Branch', () => { + imgSnapshotTest( + `gitGraph TB: + commit + + branch branch-B order: 2 + commit + + branch branch-A + merge main + + checkout branch-B + merge branch-A + `, + { gitGraph: { parallelCommits: true } } + ); + }); }); diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md index c96de3f3f8..d1a13c6fa5 100644 --- a/docs/syntax/gantt.md +++ b/docs/syntax/gantt.md @@ -67,8 +67,8 @@ gantt Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d - Add to mermaid :1d - Functionality added :milestone, 2014-01-25, 0d + Add to mermaid :until isadded + Functionality added :milestone, isadded, 2014-01-25, 0d section Documentation Describe gantt syntax :active, a1, after des1, 3d @@ -100,8 +100,8 @@ gantt Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d - Add to mermaid :1d - Functionality added :milestone, 2014-01-25, 0d + Add to mermaid :until isadded + Functionality added :milestone, isadded, 2014-01-25, 0d section Documentation Describe gantt syntax :active, a1, after des1, 3d @@ -124,18 +124,26 @@ After processing the tags, the remaining metadata items are interpreted as follo 2. If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task. 3. If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later ` syntax. -| Metadata syntax | Start date | End date | ID | -| ------------------------------------------ | --------------------------------------------------- | ------------------------------------------- | -------- | -| `, , ` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` | -| `, , ` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` | -| `, after , ` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | -| `, after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` | -| `, ` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a | -| `, ` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a | -| `after , ` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a | -| `after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a | -| `` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a | -| `` | End date of preceding task | Start date + `length` | n/a | +| Metadata syntax | Start date | End date | ID | +| ---------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | -------- | +| `, , ` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` | +| `, , ` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` | +| `, after , ` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | +| `, after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` | +| `, , until ` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | `taskID` | +| `, after , until ` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | `taskID` | +| `, ` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a | +| `, ` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a | +| `after , ` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a | +| `after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a | +| `, until ` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | n/a | +| `after , until ` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | n/a | +| `` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a | +| `` | End date of preceding task | Start date + `length` | n/a | +| `until ` | End date of preceding task | Start date of previously specified task `otherTaskID` | n/a | + +> **Note** +> Support for keyword `until` was added in (v\+). This can be used to define a task which is running until some other specific task or milestone starts. For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted: @@ -144,6 +152,7 @@ gantt apple :a, 2017-07-20, 1w banana :crit, b, 2017-07-23, 1d cherry :active, c, after b a, 1d + kiwi :d, 2017-07-20, until b c ``` ```mermaid @@ -151,6 +160,7 @@ gantt apple :a, 2017-07-20, 1w banana :crit, b, 2017-07-23, 1d cherry :active, c, after b a, 1d + kiwi :d, 2017-07-20, until b c ``` ### Title @@ -549,3 +559,5 @@ gantt section Issue1300 5 : 0, 5 ``` + + diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index 86a2452e69..b0058ff8d6 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -256,32 +256,25 @@ const getStartDate = function (prevTime, dateFormat, str) { str = str.trim(); // Test for after - const re = /^after\s+([\d\w- ]+)/; - const afterStatement = re.exec(str.trim()); + const afterRePattern = /^after\s+(?[\d\w- ]+)/; + const afterStatement = afterRePattern.exec(str); if (afterStatement !== null) { // check all after ids and take the latest - let latestEndingTask = null; - afterStatement[1].split(' ').forEach(function (id) { + let latestTask = null; + for (const id of afterStatement.groups.ids.split(' ')) { let task = findTaskById(id); - if (task !== undefined) { - if (!latestEndingTask) { - latestEndingTask = task; - } else { - if (task.endTime > latestEndingTask.endTime) { - latestEndingTask = task; - } - } + if (task !== undefined && (!latestTask || task.endTime > latestTask.endTime)) { + latestTask = task; } - }); + } - if (!latestEndingTask) { - const dt = new Date(); - dt.setHours(0, 0, 0, 0); - return dt; - } else { - return latestEndingTask.endTime; + if (latestTask) { + return latestTask.endTime; } + const today = new Date(); + today.setHours(0, 0, 0, 0); + return today; } // Check for actual date set @@ -344,13 +337,35 @@ const parseDuration = function (str) { const getEndDate = function (prevTime, dateFormat, str, inclusive = false) { str = str.trim(); - // Check for actual date - let mDate = dayjs(str, dateFormat.trim(), true); - if (mDate.isValid()) { + // test for until + const untilRePattern = /^until\s+(?[\d\w- ]+)/; + const untilStatement = untilRePattern.exec(str); + + if (untilStatement !== null) { + // check all until ids and take the earliest + let earliestTask = null; + for (const id of untilStatement.groups.ids.split(' ')) { + let task = findTaskById(id); + if (task !== undefined && (!earliestTask || task.startTime < earliestTask.startTime)) { + earliestTask = task; + } + } + + if (earliestTask) { + return earliestTask.startTime; + } + const today = new Date(); + today.setHours(0, 0, 0, 0); + return today; + } + + // check for actual date + let parsedDate = dayjs(str, dateFormat.trim(), true); + if (parsedDate.isValid()) { if (inclusive) { - mDate = mDate.add(1, 'd'); + parsedDate = parsedDate.add(1, 'd'); } - return mDate.toDate(); + return parsedDate.toDate(); } let endTime = dayjs(prevTime); diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts index 416368e8f9..96aae0b89c 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts @@ -140,10 +140,10 @@ describe('when using the ganttDb', function () { it('should handle relative start date based on id regardless of sections', function () { ganttDb.setDateFormat('YYYY-MM-DD'); - ganttDb.addSection('testa1'); + ganttDb.addSection('sec1'); ganttDb.addTask('test1', 'id1,2013-01-01,2w'); ganttDb.addTask('test2', 'id2,after id3,1d'); - ganttDb.addSection('testa2'); + ganttDb.addSection('sec2'); ganttDb.addTask('test3', 'id3,after id1,2d'); const tasks = ganttDb.getTasks(); @@ -158,6 +158,58 @@ describe('when using the ganttDb', function () { expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15)); expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17)); }); + + it('should handle relative end date based on id regardless of sections', function () { + ganttDb.setDateFormat('YYYY-MM-DD'); + ganttDb.addSection('sec1'); + ganttDb.addTask('task1', 'id1,2013-01-01,until id3'); + ganttDb.addSection('sec2'); + ganttDb.addTask('task2', 'id2,2013-01-10,until id3'); + ganttDb.addTask('task3', 'id3,2013-02-01,2d'); + + const tasks = ganttDb.getTasks(); + + expect(tasks[0].startTime).toEqual(new Date(2013, 0, 1)); + expect(tasks[0].endTime).toEqual(new Date(2013, 1, 1)); + expect(tasks[0].id).toEqual('id1'); + expect(tasks[0].task).toEqual('task1'); + + expect(tasks[1].id).toEqual('id2'); + expect(tasks[1].task).toEqual('task2'); + expect(tasks[1].startTime).toEqual(new Date(2013, 0, 10)); + expect(tasks[1].endTime).toEqual(new Date(2013, 1, 1)); + }); + + it('should handle relative start date based on multiple id', function () { + ganttDb.setDateFormat('YYYY-MM-DD'); + ganttDb.addSection('sec1'); + ganttDb.addTask('task1', 'id1,after id2 id3 id4,1d'); + ganttDb.addTask('task2', 'id2,2013-01-01,1d'); + ganttDb.addTask('task3', 'id3,2013-02-01,3d'); + ganttDb.addTask('task4', 'id4,2013-02-01,2d'); + + const tasks = ganttDb.getTasks(); + + expect(tasks[0].endTime).toEqual(new Date(2013, 1, 5)); + expect(tasks[0].id).toEqual('id1'); + expect(tasks[0].task).toEqual('task1'); + }); + + it('should handle relative end date based on multiple id', function () { + ganttDb.setDateFormat('YYYY-MM-DD'); + ganttDb.addSection('sec1'); + ganttDb.addTask('task1', 'id1,2013-01-01,until id2 id3 id4'); + ganttDb.addTask('task2', 'id2,2013-01-11,1d'); + ganttDb.addTask('task3', 'id3,2013-02-10,1d'); + ganttDb.addTask('task4', 'id4,2013-02-12,1d'); + + const tasks = ganttDb.getTasks(); + + expect(tasks[0].endTime).toEqual(new Date(2013, 0, 11)); + expect(tasks[0].id).toEqual('id1'); + expect(tasks[0].task).toEqual('task1'); + }); + it('should ignore weekends', function () { ganttDb.setDateFormat('YYYY-MM-DD'); ganttDb.setExcludes('weekends 2019-02-06,friday'); diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js b/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js index ae5f74249c..6665765a4b 100644 --- a/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js +++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.spec.js @@ -118,6 +118,38 @@ describe('when parsing a gantt diagram it', function () { expect(tasks[0].id).toEqual('des1'); expect(tasks[0].task).toEqual('Design jison grammar'); }); + it('should handle a task with start/end time relative to other tasks', function () { + const str = + 'gantt\n' + + 'dateFormat YYYY-MM-DD\n' + + 'title Adding gantt diagram functionality to mermaid\n' + + 'section Documentation\n' + + 'task A: a, 2024-01-27, 2024-01-28\n' + + 'task B: b, after a, 2024-01-30\n' + + 'task C: c, 2024-01-20, until a\n' + + 'task D: d, after c, until b'; + + expect(parserFnConstructor(str)).not.toThrow(); + + const tasks = parser.yy.getTasks(); + + expect(tasks[0].startTime).toEqual(new Date(2024, 0, 27)); + expect(tasks[0].endTime).toEqual(new Date(2024, 0, 28)); + expect(tasks[0].id).toEqual('a'); + expect(tasks[0].task).toEqual('task A'); + expect(tasks[1].startTime).toEqual(new Date(2024, 0, 28)); + expect(tasks[1].endTime).toEqual(new Date(2024, 0, 30)); + expect(tasks[1].id).toEqual('b'); + expect(tasks[1].task).toEqual('task B'); + expect(tasks[2].startTime).toEqual(new Date(2024, 0, 20)); + expect(tasks[2].endTime).toEqual(new Date(2024, 0, 27)); + expect(tasks[2].id).toEqual('c'); + expect(tasks[2].task).toEqual('task C'); + expect(tasks[3].startTime).toEqual(new Date(2024, 0, 27)); + expect(tasks[3].endTime).toEqual(new Date(2024, 0, 28)); + expect(tasks[3].id).toEqual('d'); + expect(tasks[3].task).toEqual('task D'); + }); it.each(convert` tags | milestone | done | crit | active ${'milestone'} | ${true} | ${false} | ${false} | ${false} diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 84b8f41a16..fbcaa547a4 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -456,6 +456,10 @@ const drawArrow = (svg, commitA, commitB, allCommits) => { let radius = 0; let offset = 0; let colorClassNum = branchPos[commitB.branch].index; + if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) { + colorClassNum = branchPos[commitA.branch].index; + } + let lineDef; if (arrowNeedsRerouting) { arc = 'A 10 10, 0, 0, 0,'; @@ -470,7 +474,6 @@ const drawArrow = (svg, commitA, commitB, allCommits) => { if (p1.x < p2.x) { // Source commit is on branch position left of destination commit // so render arrow rightward with colour of destination branch - colorClassNum = branchPos[commitB.branch].index; lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${ p1.y + offset } L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`; @@ -486,7 +489,6 @@ const drawArrow = (svg, commitA, commitB, allCommits) => { if (p1.y < p2.y) { // Source commit is on branch positioned above destination commit // so render arrow downward with colour of destination branch - colorClassNum = branchPos[commitB.branch].index; lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${ p1.x + offset } ${lineY} L ${p2.x - radius} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`; @@ -500,19 +502,22 @@ const drawArrow = (svg, commitA, commitB, allCommits) => { } } } else { + arc = 'A 20 20, 0, 0, 0,'; + arc2 = 'A 20 20, 0, 0, 1,'; + radius = 20; + offset = 20; + if (dir === 'TB') { if (p1.x < p2.x) { - arc = 'A 20 20, 0, 0, 0,'; - arc2 = 'A 20 20, 0, 0, 1,'; - radius = 20; - offset = 20; - - // Figure out the color of the arrow,arrows going down take the color from the destination branch - colorClassNum = branchPos[commitB.branch].index; - - lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${ - p1.y + offset - } L ${p2.x} ${p2.y}`; + if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) { + lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${ + p2.y + } L ${p2.x} ${p2.y}`; + } else { + lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${ + p1.y + offset + } L ${p2.x} ${p2.y}`; + } } if (p1.x > p2.x) { arc = 'A 20 20, 0, 0, 0,'; @@ -520,46 +525,46 @@ const drawArrow = (svg, commitA, commitB, allCommits) => { radius = 20; offset = 20; - // Arrows going up take the color from the source branch - colorClassNum = branchPos[commitA.branch].index; - lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${ - p2.y - } L ${p2.x} ${p2.y}`; + if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) { + lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${ + p2.y + } L ${p2.x} ${p2.y}`; + } else { + lineDef = `M ${p1.x} ${p1.y} L ${p2.x + radius} ${p1.y} ${arc} ${p2.x} ${ + p1.y + offset + } L ${p2.x} ${p2.y}`; + } } if (p1.x === p2.x) { - colorClassNum = branchPos[commitA.branch].index; - lineDef = `M ${p1.x} ${p1.y} L ${p1.x + radius} ${p1.y} ${arc} ${p1.x + offset} ${ - p2.y + radius - } L ${p2.x} ${p2.y}`; + lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`; } } else { if (p1.y < p2.y) { - arc = 'A 20 20, 0, 0, 0,'; - radius = 20; - offset = 20; - // Arrows going up take the color from the target branch - colorClassNum = branchPos[commitB.branch].index; - lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${ - p2.x - } ${p2.y}`; + if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) { + lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${ + p1.y + offset + } L ${p2.x} ${p2.y}`; + } else { + lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${ + p2.y + } L ${p2.x} ${p2.y}`; + } } if (p1.y > p2.y) { - arc = 'A 20 20, 0, 0, 0,'; - radius = 20; - offset = 20; - // Arrows going up take the color from the source branch - colorClassNum = branchPos[commitA.branch].index; - lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${p1.y - offset} L ${ - p2.x - } ${p2.y}`; + if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) { + lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${ + p1.y - offset + } L ${p2.x} ${p2.y}`; + } else { + lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc2} ${p1.x + offset} ${ + p2.y + } L ${p2.x} ${p2.y}`; + } } if (p1.y === p2.y) { - colorClassNum = branchPos[commitA.branch].index; - lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${ - p2.x - } ${p2.y}`; + lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`; } } } diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md index 15cdc6a09c..a1139d3789 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -49,8 +49,8 @@ gantt Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d - Add to mermaid :1d - Functionality added :milestone, 2014-01-25, 0d + Add to mermaid :until isadded + Functionality added :milestone, isadded, 2014-01-25, 0d section Documentation Describe gantt syntax :active, a1, after des1, 3d @@ -73,18 +73,27 @@ After processing the tags, the remaining metadata items are interpreted as follo 2. If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task. 3. If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later ` syntax. -| Metadata syntax | Start date | End date | ID | -| ------------------------------------------ | --------------------------------------------------- | ------------------------------------------- | -------- | -| `, , ` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` | -| `, , ` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` | -| `, after , ` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | -| `, after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` | -| `, ` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a | -| `, ` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a | -| `after , ` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a | -| `after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a | -| `` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a | -| `` | End date of preceding task | Start date + `length` | n/a | +| Metadata syntax | Start date | End date | ID | +| ---------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | -------- | +| `, , ` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` | +| `, , ` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` | +| `, after , ` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | +| `, after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` | +| `, , until ` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | `taskID` | +| `, after , until ` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | `taskID` | +| `, ` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a | +| `, ` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a | +| `after , ` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a | +| `after , ` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a | +| `, until ` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | n/a | +| `after , until ` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | n/a | +| `` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a | +| `` | End date of preceding task | Start date + `length` | n/a | +| `until ` | End date of preceding task | Start date of previously specified task `otherTaskID` | n/a | + +```note +Support for keyword `until` was added in (v+). This can be used to define a task which is running until some other specific task or milestone starts. +``` For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted: @@ -93,6 +102,7 @@ gantt apple :a, 2017-07-20, 1w banana :crit, b, 2017-07-23, 1d cherry :active, c, after b a, 1d + kiwi :d, 2017-07-20, until b c ``` ### Title @@ -439,3 +449,5 @@ gantt section Issue1300 5 : 0, 5 ``` + +