mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-07 06:14:14 +01:00
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:
5
.changeset/add-vert-tag-bar-chart.md
Normal file
5
.changeset/add-vert-tag-bar-chart.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Add Vertical Line To Gantt Plot At Specified Time
|
||||||
@@ -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(
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -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 don’t take up a row. They’re 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`.
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 don’t take up a row. They’re 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`.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user