Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Custom Date Range to Gantt Charts Part 2 #5220

Open
wants to merge 34 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d40023e
Add date range parser
paldeep-singh Jan 20, 2024
4ef5798
Add db functions
paldeep-singh Jan 20, 2024
6f7cc40
Add db tests
paldeep-singh Jan 20, 2024
43dba1c
Add parser tests
paldeep-singh Jan 20, 2024
6389c59
Rework renderer timescale to use new db functions
paldeep-singh Jan 20, 2024
a37cb06
Add docs update
paldeep-singh Jan 20, 2024
281edc9
Add snapshot tests
paldeep-singh Jan 20, 2024
1bf4121
Compute start date if end-only dateRange provided
paldeep-singh Jan 20, 2024
62d5871
Add docs for verifying snapshots
paldeep-singh Jan 20, 2024
d0bba86
Ensure tasks do not spill out of date range
paldeep-singh Jan 20, 2024
c475e55
Remove eslint disables
paldeep-singh Jan 20, 2024
1cb58a3
Fix unit tests and add tests for out of bounds dates
paldeep-singh Jan 21, 2024
7cd30b5
add release version to docs
paldeep-singh Jan 21, 2024
f175a23
Make docs clearer
paldeep-singh Jan 21, 2024
9cb06bb
Update packages/mermaid/src/diagrams/gantt/ganttDb.js
paldeep-singh Mar 10, 2024
5bdec31
Fix dateRange parsing to not skip first character
paldeep-singh Mar 10, 2024
7480eec
Add additional tests for valid date ranges
paldeep-singh Mar 10, 2024
0444adb
Add tests for invalid date ranges and invalid range syntax
paldeep-singh Mar 10, 2024
81b57d6
Always throw error if dayjs determines date is invalid
paldeep-singh Mar 10, 2024
1071afc
Improve e2e test names for time-only date ranges
paldeep-singh Mar 10, 2024
d8b07bf
Add dateRange e2e tests for date-only ranges
paldeep-singh Mar 10, 2024
5ff31bd
Remove compact declaration in dateRange cypress tests
paldeep-singh Mar 10, 2024
db4a31a
Add test with tasks outside of specified date range
paldeep-singh Mar 10, 2024
fe77471
Merge branch 'develop' into feature/1530_gantt_chart_custom_range_2
paldeep-singh Mar 10, 2024
ec5b9b2
Fix syntax for title and dateFormat in theme tests for gantt charts
paldeep-singh Mar 15, 2024
6a3b4a0
Add tasks that partially exist within date range
paldeep-singh Mar 22, 2024
a4c03f1
Add time-only date range tests with tasks outside range
paldeep-singh Mar 22, 2024
6ca3fd0
Remove tasks outside of date range in tests not testing that behaviour
paldeep-singh Mar 22, 2024
1d0efd1
Default date format to YYYY-MM-DD
paldeep-singh Mar 22, 2024
e945fbf
Make chart titles in date range tests more descriptive
paldeep-singh Mar 22, 2024
424dac9
Add e2e test for time and date dateRange with tasks extending outside…
paldeep-singh Mar 22, 2024
00ddb98
Fix axis ticks for time-only date range test
paldeep-singh Mar 22, 2024
c644fb8
Merge branch 'develop' into feature/1530_gantt_chart_custom_range_2
paldeep-singh Mar 22, 2024
1ba0cb8
Merge branch 'develop' into feature/1530_gantt_chart_custom_range_2
paldeep-singh Mar 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions cypress/integration/rendering/gantt.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,108 @@ describe('Gantt diagram', () => {
);
});

it('should render when there is a dateRange', () => {
imgSnapshotTest(
`
---
displayMode: compact
---
gantt
title GANTT compact
dateFormat HH:mm:ss
dateRange 12:30:00,13:30:00
axisFormat %Hh%M
section DB Clean
paldeep-singh marked this conversation as resolved.
Show resolved Hide resolved
Clean: 12:00:00, 10m
Clean: 12:30:00, 12m
Clean: 13:00:00, 8m
Clean: 13:30:00, 9m
Clean: 14:00:00, 13m
Clean: 14:30:00, 10m
Clean: 15:00:00, 11m
section Sessions
A: 12:00:00, 63m
B: 12:30:00, 12m
C: 13:05:00, 12m
D: 13:06:00, 33m
E: 13:15:00, 55m
F: 13:20:00, 12m
G: 13:32:00, 18m
H: 13:50:00, 20m
I: 14:10:00, 10m
`,
{}
);
});

it('should render with a start-only dateRange', () => {
imgSnapshotTest(
`
---
displayMode: compact
---
gantt
title GANTT compact
dateFormat HH:mm:ss
dateRange 12:30:00
axisFormat %Hh%M
section DB Clean
Clean: 12:00:00, 10m
Clean: 12:30:00, 12m
Clean: 13:00:00, 8m
Clean: 13:30:00, 9m
Clean: 14:00:00, 13m
Clean: 14:30:00, 10m
Clean: 15:00:00, 11m
section Sessions
A: 12:00:00, 63m
B: 12:30:00, 12m
C: 13:05:00, 12m
D: 13:06:00, 33m
E: 13:15:00, 55m
F: 13:20:00, 12m
G: 13:32:00, 18m
H: 13:50:00, 20m
I: 14:10:00, 10m
`,
{}
);
});

it('should render with an end-only dateRange', () => {
imgSnapshotTest(
`
---
displayMode: compact
---
gantt
title GANTT compact
dateFormat HH:mm:ss
dateRange ,13:30:00
axisFormat %Hh%M
section DB Clean
Clean: 12:00:00, 10m
Clean: 12:30:00, 12m
Clean: 13:00:00, 8m
Clean: 13:30:00, 9m
Clean: 14:00:00, 13m
Clean: 14:30:00, 10m
Clean: 15:00:00, 11m
section Sessions
A: 12:00:00, 63m
B: 12:30:00, 12m
C: 13:05:00, 12m
D: 13:06:00, 33m
E: 13:15:00, 55m
F: 13:20:00, 12m
G: 13:32:00, 18m
H: 13:50:00, 20m
I: 14:10:00, 10m
`,
{}
);
});

// TODO: fix it
//
// This test is skipped deliberately
Expand Down
6 changes: 6 additions & 0 deletions docs/community/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ it('should render forks and joins', () => {
});
```

**Verifying Snapshots**

```sh
pnpm e2e open cypress/snapshots
```

<!-- **_[TODO - running the tests against what is expected in development. ]_** -->

<!-- **_[TODO - how to generate new screenshots]_** -->
Expand Down
30 changes: 30 additions & 0 deletions docs/syntax/gantt.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,36 @@ gantt

> **Warning** > `millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION

### Date Ranges (v\<MERMAID_RELEASE_VERSION>+)

The default behavior is to set the x range to include all of the data in the chart. You can override
the range by specifying `dateRange`. Any tasks falling outside the specified range will be removed
from the plot, and tasks overlapping with the boundaries will be truncated. The `dateRange` command
should follow the `dateFormat` and use the same format.

```mermaid-example
gantt
dateFormat HH:mm
dateRange 13:00, 14:00
axisFormat %H:%M
Task A : 13:24, 13:39
Task B : 13:50, 14:35
Dropped : 14:01, 14:20
```

```mermaid
gantt
dateFormat HH:mm
dateRange 13:00, 14:00
axisFormat %H:%M
Task A : 13:24, 13:39
Task B : 13:50, 14:35
Dropped : 14:01, 14:20
```

It is also possible to specify only a lower bound using `dateRange <date>` or only an upper bound
using `dateRange ,<date>`.

## Output in compact mode

The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceeding YAML settings.
Expand Down
84 changes: 82 additions & 2 deletions packages/mermaid/src/diagrams/gantt/ganttDb.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
dayjs.extend(dayjsAdvancedFormat);

let dateFormat = '';
let dateRange = '';
let startDateRange = '';
let endDateRange = '';
let axisFormat = '';
let tickInterval = undefined;
let todayMarker = '';
Expand Down Expand Up @@ -51,6 +54,9 @@
lastTaskID = undefined;
rawTasks = [];
dateFormat = '';
dateRange = '';
startDateRange = '';
endDateRange = '';
axisFormat = '';
displayMode = '';
tickInterval = undefined;
Expand Down Expand Up @@ -101,6 +107,22 @@
return inclusiveEndDates;
};

export const setDateRange = function (txt) {
dateRange = txt;

if (!dateRange) {
return;
}
const [startStr, endStr] = dateRange.split(',');

if (startStr) {
startDateRange = getStartDate(undefined, dateFormat, startStr);
}
if (endStr) {
endDateRange = getEndDate(undefined, dateFormat, endStr);
}
};

export const enableTopAxis = function () {
topAxis = true;
};
Expand All @@ -121,6 +143,34 @@
return dateFormat;
};

export const getDateRange = function () {
return dateRange;
};

export const getStartRange = function () {
if (startDateRange) {
return startDateRange;
}
if (getTasks().length > 0) {
return getTasks().reduce((min, task) => {
return task.startTime < min ? task.startTime : min;
}, Infinity);
}
return '';
};

export const getEndRange = function () {
if (endDateRange) {
return endDateRange;
}
if (getTasks().length > 0) {
return getTasks().reduce((max, task) => {
return task.endTime > max ? task.endTime : max;
}, -Infinity);
}
return '';
};

export const setIncludes = function (txt) {
includes = txt.toLowerCase().split(/[\s,]+/);
};
Expand Down Expand Up @@ -158,7 +208,27 @@
iterationCount++;
}

tasks = rawTasks;
if (dateRange === '') {
tasks = rawTasks;

return tasks;
}
paldeep-singh marked this conversation as resolved.
Show resolved Hide resolved

const filteredTasks = rawTasks.filter(function (task) {
if (
(startDateRange && task.endTime <= startDateRange) ||
(endDateRange && task.startTime >= endDateRange)
) {
return false;
}
return true;
});

tasks = filteredTasks.map((task) => ({
...task,
startTime: startDateRange && task.startTime < startDateRange ? startDateRange : task.startTime,
endTime: endDateRange && task.endTime > endDateRange ? endDateRange : task.endTime,
}));

return tasks;
};
Expand Down Expand Up @@ -284,6 +354,12 @@
}
}

if (str === '') {
// If only an end-only dateRange is provided, we determine the start date
// by comparing the start times of the tasks.

Check warning on line 359 in packages/mermaid/src/diagrams/gantt/ganttDb.js

View check run for this annotation

Codecov / codecov/patch

packages/mermaid/src/diagrams/gantt/ganttDb.js#L358-L359

Added lines #L358 - L359 were not covered by tests
return getStartRange();
}

Check warning on line 361 in packages/mermaid/src/diagrams/gantt/ganttDb.js

View check run for this annotation

Codecov / codecov/patch

packages/mermaid/src/diagrams/gantt/ganttDb.js#L361

Added line #L361 was not covered by tests

// Check for actual date set
let mDate = dayjs(str, dateFormat.trim(), true);
if (mDate.isValid()) {
Expand All @@ -303,7 +379,7 @@
d.getFullYear() < -10000 ||
d.getFullYear() > 10000
) {
throw new Error('Invalid date:' + str);
throw new Error(`Invalid date: '${str}' with date format: '${dateFormat}'`);
}
return d;
}
Expand Down Expand Up @@ -730,6 +806,10 @@
setDateFormat,
getDateFormat,
enableInclusiveEndDates,
setDateRange,
getDateRange,
getStartRange,
getEndRange,
endDatesAreInclusive,
enableTopAxis,
topAxisEnabled,
Expand Down
66 changes: 65 additions & 1 deletion packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('when using the ganttDb', function () {
beforeEach(function () {
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.enableInclusiveEndDates();
ganttDb.setDateRange('2019-02-01, 2019-03-01');
ganttDb.setDisplayMode('compact');
ganttDb.setTodayMarker('off');
ganttDb.setExcludes('weekends 2019-02-06,friday');
Expand All @@ -50,6 +51,9 @@ describe('when using the ganttDb', function () {
${'getAccDescription'} | ${''}
${'getDateFormat'} | ${''}
${'getAxisFormat'} | ${''}
${'getDateRange'} | ${''}
${'getStartRange'} | ${''}
${'getEndRange'} | ${''}
${'getTodayMarker'} | ${''}
${'getExcludes'} | ${[]}
${'getSections'} | ${[]}
Expand Down Expand Up @@ -158,6 +162,42 @@ 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 fixed date ranges', function () {
const DATE_FORMAT = 'YYYY-MM-DD';
ganttDb.setDateFormat(DATE_FORMAT);
ganttDb.setDateRange('2023-07-01, 2023-07-30');
ganttDb.addSection('testa1');
ganttDb.addTask('test1', 'id1,2013-07-01,2w');
ganttDb.addTask('test2', 'id2,2023-07-02,2w');
ganttDb.addSection('testa2');
ganttDb.addTask('test3', 'id3,after id2,2d');
ganttDb.addSection('testa3');
ganttDb.addTask('test4', 'id4,2023-06-25,2w');
ganttDb.addTask('test5', 'id5,2023-07-25,2w');

const tasks = ganttDb.getTasks();

expect(tasks.length).toEqual(4);
expect(tasks[0].id).toEqual('id2');
expect(tasks[0].task).toEqual('test2');
expect(tasks[0].startTime).toEqual(dayjs('2023-07-02', DATE_FORMAT, true).toDate());
expect(tasks[0].endTime).toEqual(dayjs('2023-07-16', DATE_FORMAT, true).toDate());

expect(tasks[1].id).toEqual('id3');
expect(tasks[1].task).toEqual('test3');
expect(tasks[1].startTime).toEqual(dayjs('2023-07-16', DATE_FORMAT, true).toDate());
expect(tasks[1].endTime).toEqual(dayjs('2023-07-18', DATE_FORMAT, true).toDate());

expect(tasks[2].id).toEqual('id4');
expect(tasks[2].task).toEqual('test4');
expect(tasks[2].startTime).toEqual(dayjs('2023-07-01', DATE_FORMAT, true).toDate());
expect(tasks[2].endTime).toEqual(dayjs('2023-07-09', DATE_FORMAT, true).toDate());

expect(tasks[3].id).toEqual('id5');
expect(tasks[3].task).toEqual('test5');
expect(tasks[3].startTime).toEqual(dayjs('2023-07-25', DATE_FORMAT, true).toDate());
expect(tasks[3].endTime).toEqual(dayjs('2023-07-30', DATE_FORMAT, true).toDate());
});
it('should ignore weekends', function () {
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.setExcludes('weekends 2019-02-06,friday');
Expand Down Expand Up @@ -436,6 +476,30 @@ describe('when using the ganttDb', function () {
it('should reject dates with ridiculous years', function () {
ganttDb.setDateFormat('YYYYMMDD');
ganttDb.addTask('test1', 'id1,202304,1d');
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
expect(() => ganttDb.getTasks()).toThrowError(
"Invalid date: '202304' with date format: 'YYYYMMDD'"
);
});

it.each(convert`
testName | dateRange | expStartRange | expEndRange | expTasksLength
${'No dateRange'} | ${''} | ${'2023-06-01'} | ${'2023-07-07'} | ${2}
${'Wide dateRange'} | ${'2023-01-01, 2023-12-31'} | ${'2023-01-01'} | ${'2023-12-31'} | ${2}
${'Narrow dateRange'} | ${'2023-06-29, 2023-06-30'} | ${'2023-06-29'} | ${'2023-06-30'} | ${0}
${'Overlapping dateRange'} | ${'2023-06-06, 2023-07-03'} | ${'2023-06-06'} | ${'2023-07-03'} | ${2}
${'Starting dateRange'} | ${'2023-06-06'} | ${'2023-06-06'} | ${'2023-07-07'} | ${2}
${'Ending dateRange'} | ${',2023-06-06'} | ${'2023-06-01'} | ${'2023-06-06'} | ${1}
`)('$testName', ({ dateFormat, dateRange, expStartRange, expEndRange, expTasksLength }) => {
ganttDb.setDateFormat('YYYY-MM-DD');
expect('').toEqual(ganttDb.getDateRange());
ganttDb.setDateRange(dateRange);
ganttDb.addTask('task1', 't1, 2023-06-01, 2023-06-07');
ganttDb.addTask('task2', 't2, 2023-07-02, 2023-07-07');
const tasks = ganttDb.getTasks();
const startRange = ganttDb.getStartRange();
const endRange = ganttDb.getEndRange();
expect(expTasksLength).toEqual(tasks.length);
expect(dayjs(expStartRange, 'YYYY-MM-DD').toDate()).toEqual(startRange);
expect(dayjs(expEndRange, 'YYYY-MM-DD').toDate()).toEqual(endRange);
});
});
Loading
Loading