Merge pull request #6479 from Shahir-47/feature/3250/add_vertline_to_gantt_plot

feat: Add vertical line to gantt plot at specified time
This commit is contained in:
Sidharth Vinod
2025-04-29 06:25:57 +00:00
committed by GitHub
12 changed files with 114 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': minor
---
feat: Add Vertical Line To Gantt Plot At Specified Time

View File

@@ -358,6 +358,23 @@ describe('Gantt diagram', () => {
); );
}); });
it('should render a gantt diagram with a vert tag', () => {
imgSnapshotTest(
`
gantt
title A Gantt Diagram
dateFormat ss
axisFormat %Ss
section Section
A task : a1, 00, 6s
Milestone : vert, 01,
section Another
Task in sec : 06, 3s
another task : 3s
`
);
});
it('should render a gantt diagram with tick is 2 milliseconds', () => { it('should render a gantt diagram with tick is 2 milliseconds', () => {
imgSnapshotTest( imgSnapshotTest(
` `

View File

@@ -229,6 +229,30 @@ gantt
Final milestone : milestone, m2, 18:08, 4m Final milestone : milestone, m2, 18:08, 4m
``` ```
### Vertical Markers
The `vert` keyword lets you add vertical lines to your Gantt chart, making it easy to highlight important dates like deadlines, events, or checkpoints. These markers extend across the entire chart and are positioned based on the date you provide. Unlike milestones, vertical markers dont take up a row. Theyre purely visual reference points that help break up the timeline and make important moments easier to spot.
```mermaid-example
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial vert : vert, v1, 17:30, 2m
Task A : 3m
Task B : 8m
Final vert : vert, v2, 17:58, 4m
```
```mermaid
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial vert : vert, v1, 17:30, 2m
Task A : 3m
Task B : 8m
Final vert : vert, v2, 17:58, 4m
```
## Setting dates ## Setting dates
`dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`. `dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`.

View File

@@ -33,7 +33,7 @@ let sections = [];
let tasks = []; let tasks = [];
let currentSection = ''; let currentSection = '';
let displayMode = ''; let displayMode = '';
const tags = ['active', 'done', 'crit', 'milestone']; const tags = ['active', 'done', 'crit', 'milestone', 'vert'];
let funs = []; let funs = [];
let inclusiveEndDates = false; let inclusiveEndDates = false;
let topAxis = false; let topAxis = false;
@@ -422,7 +422,7 @@ const compileData = function (prevTask, dataStr) {
const task = {}; const task = {};
// Get tags like active, done, crit and milestone // Get tags like active, done, crit, milestone, and vert
getTaskTags(data, task, tags); getTaskTags(data, task, tags);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
@@ -470,7 +470,7 @@ const parseData = function (prevTaskId, dataStr) {
const task = {}; const task = {};
// Get tags like active, done, crit and milestone // Get tags like active, done, crit, milestone, and vert
getTaskTags(data, task, tags); getTaskTags(data, task, tags);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
@@ -538,6 +538,7 @@ 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.vert = taskInfo.vert;
rawTask.order = lastOrder; rawTask.order = lastOrder;
lastOrder++; lastOrder++;
@@ -570,6 +571,7 @@ export const addTaskOrg = function (descr, data) {
newTask.done = taskInfo.done; newTask.done = taskInfo.done;
newTask.crit = taskInfo.crit; newTask.crit = taskInfo.crit;
newTask.milestone = taskInfo.milestone; newTask.milestone = taskInfo.milestone;
newTask.vert = taskInfo.vert;
lastTask = newTask; lastTask = newTask;
tasks.push(newTask); tasks.push(newTask);
}; };

View File

@@ -231,10 +231,11 @@ export const draw = function (text, id, version, diagObj) {
* @param w * @param w
*/ */
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) { function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) {
// Sort theArray so that tasks with `vert` come last
theArray.sort((a, b) => (a.vert === b.vert ? 0 : a.vert ? 1 : -1));
// Get unique task orders. Required to draw the background rects when display mode is compact. // Get unique task orders. Required to draw the background rects when display mode is compact.
const uniqueTaskOrderIds = [...new Set(theArray.map((item) => item.order))]; const uniqueTaskOrderIds = [...new Set(theArray.map((item) => item.order))];
const uniqueTasks = uniqueTaskOrderIds.map((id) => theArray.find((item) => item.order === id)); const uniqueTasks = uniqueTaskOrderIds.map((id) => theArray.find((item) => item.order === id));
// Draw background rects covering the entire width of the graph, these form the section rows. // Draw background rects covering the entire width of the graph, these form the section rows.
svg svg
.append('g') .append('g')
@@ -259,7 +260,8 @@ export const draw = function (text, id, version, diagObj) {
} }
} }
return 'section section0'; return 'section section0';
}); })
.enter();
// Draw the rects representing the tasks // Draw the rects representing the tasks
const rectangles = svg.append('g').selectAll('rect').data(theArray).enter(); const rectangles = svg.append('g').selectAll('rect').data(theArray).enter();
@@ -289,15 +291,26 @@ export const draw = function (text, id, version, diagObj) {
.attr('y', function (d, i) { .attr('y', function (d, i) {
// Ignore the incoming i value and use our order instead // Ignore the incoming i value and use our order instead
i = d.order; i = d.order;
if (d.vert) {
return conf.gridLineStartPadding;
}
return i * theGap + theTopPad; return i * theGap + theTopPad;
}) })
.attr('width', function (d) { .attr('width', function (d) {
if (d.milestone) { if (d.milestone) {
return theBarHeight; return theBarHeight;
} }
if (d.vert) {
return 0.08 * theBarHeight;
}
return timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime); return timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime);
}) })
.attr('height', theBarHeight) .attr('height', function (d) {
if (d.vert) {
return taskArray.length * (conf.barHeight + conf.barGap) + conf.barHeight * 2;
}
return theBarHeight;
})
.attr('transform-origin', function (d, i) { .attr('transform-origin', function (d, i) {
// Ignore the incoming i value and use our order instead // Ignore the incoming i value and use our order instead
i = d.order; i = d.order;
@@ -354,6 +367,9 @@ export const draw = function (text, id, version, diagObj) {
if (d.milestone) { if (d.milestone) {
taskClass = ' milestone ' + taskClass; taskClass = ' milestone ' + taskClass;
} }
if (d.vert) {
taskClass = ' vert ' + taskClass;
}
taskClass += secNum; taskClass += secNum;
@@ -377,10 +393,13 @@ export const draw = function (text, id, version, diagObj) {
let endX = timeScale(d.renderEndTime || d.endTime); let endX = timeScale(d.renderEndTime || d.endTime);
if (d.milestone) { if (d.milestone) {
startX += 0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) - 0.5 * theBarHeight; startX += 0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) - 0.5 * theBarHeight;
}
if (d.milestone) {
endX = startX + theBarHeight; endX = startX + theBarHeight;
} }
if (d.vert) {
return timeScale(d.startTime) + theSidePad;
}
const textWidth = this.getBBox().width; const textWidth = this.getBBox().width;
// Check id text width > width of rectangle // Check id text width > width of rectangle
@@ -396,6 +415,9 @@ export const draw = function (text, id, version, diagObj) {
}) })
.attr('y', function (d, i) { .attr('y', function (d, i) {
// Ignore the incoming i value and use our order instead // Ignore the incoming i value and use our order instead
if (d.vert) {
return conf.gridLineStartPadding + taskArray.length * (conf.barHeight + conf.barGap) + 60;
}
i = d.order; 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;
}) })
@@ -406,6 +428,7 @@ export const draw = function (text, id, version, diagObj) {
if (d.milestone) { if (d.milestone) {
endX = startX + theBarHeight; endX = startX + theBarHeight;
} }
const textWidth = this.getBBox().width; const textWidth = this.getBBox().width;
let classStr = ''; let classStr = '';
@@ -445,6 +468,10 @@ export const draw = function (text, id, version, diagObj) {
taskType += ' milestoneText'; taskType += ' milestoneText';
} }
if (d.vert) {
taskType += ' vertText';
}
// Check id text width > width of rectangle // Check id text width > width of rectangle
if (textWidth > endX - startX) { if (textWidth > endX - startX) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) { if (endX + textWidth + 1.5 * conf.leftPadding > w) {

View File

@@ -237,6 +237,16 @@ const getStyles = (options) =>
fill: ${options.taskTextDarkColor} !important; fill: ${options.taskTextDarkColor} !important;
} }
.vert {
stroke: ${options.vertLineColor};
}
.vertText {
font-size: 15px;
text-anchor: middle;
fill: ${options.vertLineColor} !important;
}
.activeCritText0, .activeCritText0,
.activeCritText1, .activeCritText1,
.activeCritText2, .activeCritText2,

View File

@@ -150,6 +150,20 @@ gantt
Final milestone : milestone, m2, 18:08, 4m Final milestone : milestone, m2, 18:08, 4m
``` ```
### Vertical Markers
The `vert` keyword lets you add vertical lines to your Gantt chart, making it easy to highlight important dates like deadlines, events, or checkpoints. These markers extend across the entire chart and are positioned based on the date you provide. Unlike milestones, vertical markers dont take up a row. Theyre purely visual reference points that help break up the timeline and make important moments easier to spot.
```mermaid-example
gantt
dateFormat HH:mm
axisFormat %H:%M
Initial vert : vert, v1, 17:30, 2m
Task A : 3m
Task B : 8m
Final vert : vert, v2, 17:58, 4m
```
## Setting dates ## Setting dates
`dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`. `dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`.

View File

@@ -98,6 +98,7 @@ class Theme {
this.critBorderColor = this.critBorderColor || '#ff8888'; this.critBorderColor = this.critBorderColor || '#ff8888';
this.critBkgColor = this.critBkgColor || 'red'; this.critBkgColor = this.critBkgColor || 'red';
this.todayLineColor = this.todayLineColor || 'red'; this.todayLineColor = this.todayLineColor || 'red';
this.vertLineColor = this.vertLineColor || 'navy';
this.taskTextColor = this.taskTextColor || this.textColor; this.taskTextColor = this.taskTextColor || this.textColor;
this.taskTextOutsideColor = this.taskTextOutsideColor || this.textColor; this.taskTextOutsideColor = this.taskTextOutsideColor || this.textColor;
this.taskTextLightColor = this.taskTextLightColor || this.textColor; this.taskTextLightColor = this.taskTextLightColor || this.textColor;

View File

@@ -79,6 +79,7 @@ class Theme {
this.critBkgColor = '#E83737'; this.critBkgColor = '#E83737';
this.taskTextDarkColor = 'calculated'; this.taskTextDarkColor = 'calculated';
this.todayLineColor = '#DB5757'; this.todayLineColor = '#DB5757';
this.vertLineColor = '#00BFFF';
/* C4 Context Diagram variables */ /* C4 Context Diagram variables */
this.personBorder = this.primaryBorderColor; this.personBorder = this.primaryBorderColor;

View File

@@ -88,6 +88,7 @@ class Theme {
this.critBorderColor = 'calculated'; this.critBorderColor = 'calculated';
this.critBkgColor = 'calculated'; this.critBkgColor = 'calculated';
this.todayLineColor = 'calculated'; this.todayLineColor = 'calculated';
this.vertLineColor = 'calculated';
this.sectionBkgColor = rgba(102, 102, 255, 0.49); this.sectionBkgColor = rgba(102, 102, 255, 0.49);
this.altSectionBkgColor = 'white'; this.altSectionBkgColor = 'white';
@@ -107,6 +108,7 @@ class Theme {
this.critBorderColor = '#ff8888'; this.critBorderColor = '#ff8888';
this.critBkgColor = 'red'; this.critBkgColor = 'red';
this.todayLineColor = 'red'; this.todayLineColor = 'red';
this.vertLineColor = 'navy';
/* C4 Context Diagram variables */ /* C4 Context Diagram variables */
this.personBorder = this.primaryBorderColor; this.personBorder = this.primaryBorderColor;

View File

@@ -81,6 +81,7 @@ class Theme {
this.critBorderColor = '#ff8888'; this.critBorderColor = '#ff8888';
this.critBkgColor = 'red'; this.critBkgColor = 'red';
this.todayLineColor = 'red'; this.todayLineColor = 'red';
this.vertLineColor = '#00BFFF';
/* C4 Context Diagram variables */ /* C4 Context Diagram variables */
this.personBorder = this.primaryBorderColor; this.personBorder = this.primaryBorderColor;

View File

@@ -93,6 +93,7 @@ class Theme {
this.critBkgColor = 'calculated'; this.critBkgColor = 'calculated';
this.critBorderColor = 'calculated'; this.critBorderColor = 'calculated';
this.todayLineColor = 'calculated'; this.todayLineColor = 'calculated';
this.vertLineColor = 'calculated';
/* C4 Context Diagram variables */ /* C4 Context Diagram variables */
this.personBorder = this.primaryBorderColor; this.personBorder = this.primaryBorderColor;
@@ -209,6 +210,7 @@ class Theme {
this.critBorderColor = darken(this.critBkgColor, 10); this.critBorderColor = darken(this.critBkgColor, 10);
this.todayLineColor = this.critBkgColor; this.todayLineColor = this.critBkgColor;
this.vertLineColor = this.critBkgColor;
/* Architecture Diagram variables */ /* Architecture Diagram variables */
this.archEdgeColor = this.lineColor; this.archEdgeColor = this.lineColor;