Merge pull request #4238 from JeremyFunk/feature/1618_repeating_tasks
Implement repeating tasks
This commit is contained in:
commit
3823ecafb1
|
@ -435,4 +435,39 @@ describe('Gantt diagram', () => {
|
|||
{ gantt: { topAxis: true } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render when compact is true', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title GANTT compact
|
||||
dateFormat HH:mm:ss
|
||||
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
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
axisFormat %d/%m
|
||||
todayMarker off
|
||||
section Section1
|
||||
Today: 1, -01:00, 5min
|
||||
Today: 1, 08-08-09-01:00, 5min
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
|
@ -89,7 +89,7 @@
|
|||
axisFormat %d/%m
|
||||
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
|
||||
section Section1
|
||||
Today: 1, -01:00, 5min
|
||||
Today: 1, 08-08-09-01:00, 5min
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
|
@ -166,6 +166,37 @@
|
|||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title GANTT compact
|
||||
dateFormat HH:mm:ss
|
||||
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
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<script>
|
||||
function ganttTestClick(a, b, c) {
|
||||
console.log('a:', a);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:2093](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2093)
|
||||
[defaultConfig.ts:2103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2103)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ const config = {
|
|||
numberSectionStyles: 4,
|
||||
axisFormat: '%Y-%m-%d',
|
||||
topAxis: false,
|
||||
displayMode: '',
|
||||
},
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
|
|
|
@ -257,9 +257,41 @@ The pattern is:
|
|||
|
||||
More info in: <https://github.com/d3/d3-time#interval_every>
|
||||
|
||||
## 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.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
|
|
|
@ -3,6 +3,7 @@ import { getConfig } from './config';
|
|||
let title = '';
|
||||
let diagramTitle = '';
|
||||
let description = '';
|
||||
|
||||
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
|
||||
|
||||
export const clear = function (): void {
|
||||
|
@ -36,10 +37,10 @@ export const getDiagramTitle = function (): string {
|
|||
};
|
||||
|
||||
export default {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setAccTitle,
|
||||
getDiagramTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle: getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear,
|
||||
|
|
|
@ -335,6 +335,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
|
|||
axisFormat?: string;
|
||||
tickInterval?: string;
|
||||
topAxis?: boolean;
|
||||
displayMode?: string;
|
||||
}
|
||||
|
||||
export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
||||
|
|
|
@ -659,6 +659,17 @@ const config: Partial<MermaidConfig> = {
|
|||
*/
|
||||
numberSectionStyles: 4,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | ------------------------- | ------ | -------- | --------- |
|
||||
* | displayMode | Controls the display mode | string | 4 | 'compact' |
|
||||
*
|
||||
* **Notes**:
|
||||
*
|
||||
* - **compact**: Enables displaying multiple tasks on the same row.
|
||||
*/
|
||||
displayMode: '',
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ---------- | ---------------------------- | ---- | -------- | ---------------- |
|
||||
|
@ -684,7 +695,6 @@ const config: Partial<MermaidConfig> = {
|
|||
* Default value: undefined
|
||||
*/
|
||||
tickInterval: undefined,
|
||||
|
||||
/**
|
||||
* | Parameter | Description | Type | Required | Values |
|
||||
* | ----------- | ----------- | ------- | -------- | ----------- |
|
||||
|
|
|
@ -11,6 +11,8 @@ export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
|||
|
||||
type FrontMatterMetadata = {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -33,6 +35,10 @@ export function extractFrontMatter(text: string, db: DiagramDb): string {
|
|||
db.setDiagramTitle?.(parsed.title);
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
db.setDisplayMode?.(parsed.displayMode);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
} else {
|
||||
return text;
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface InjectUtils {
|
|||
export interface DiagramDb {
|
||||
clear?: () => void;
|
||||
setDiagramTitle?: (title: string) => void;
|
||||
setDisplayMode?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
getAccDescription?: () => string;
|
||||
bindFunctions?: (element: Element) => void;
|
||||
|
|
|
@ -32,6 +32,7 @@ let links = {};
|
|||
let sections = [];
|
||||
let tasks = [];
|
||||
let currentSection = '';
|
||||
let displayMode = '';
|
||||
const tags = ['active', 'done', 'crit', 'milestone'];
|
||||
let funs = [];
|
||||
let inclusiveEndDates = false;
|
||||
|
@ -55,6 +56,7 @@ export const clear = function () {
|
|||
rawTasks = [];
|
||||
dateFormat = '';
|
||||
axisFormat = '';
|
||||
displayMode = '';
|
||||
tickInterval = undefined;
|
||||
todayMarker = '';
|
||||
includes = [];
|
||||
|
@ -110,6 +112,14 @@ export const topAxisEnabled = function () {
|
|||
return topAxis;
|
||||
};
|
||||
|
||||
export const setDisplayMode = function (txt) {
|
||||
displayMode = txt;
|
||||
};
|
||||
|
||||
export const getDisplayMode = function () {
|
||||
return displayMode;
|
||||
};
|
||||
|
||||
export const getDateFormat = function () {
|
||||
return dateFormat;
|
||||
};
|
||||
|
@ -143,11 +153,11 @@ export const getSections = function () {
|
|||
};
|
||||
|
||||
export const getTasks = function () {
|
||||
let allItemsPricessed = compileTasks();
|
||||
let allItemsProcessed = compileTasks();
|
||||
const maxDepth = 10;
|
||||
let iterationCount = 0;
|
||||
while (!allItemsPricessed && iterationCount < maxDepth) {
|
||||
allItemsPricessed = compileTasks();
|
||||
while (!allItemsProcessed && iterationCount < maxDepth) {
|
||||
allItemsProcessed = compileTasks();
|
||||
iterationCount++;
|
||||
}
|
||||
|
||||
|
@ -719,6 +729,8 @@ export default {
|
|||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setDisplayMode,
|
||||
getDisplayMode,
|
||||
setAccDescription,
|
||||
getAccDescription,
|
||||
addSection,
|
||||
|
|
|
@ -34,6 +34,7 @@ describe('when using the ganttDb', function () {
|
|||
beforeEach(function () {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.enableInclusiveEndDates();
|
||||
ganttDb.setDisplayMode('compact');
|
||||
ganttDb.setTodayMarker('off');
|
||||
ganttDb.setExcludes('weekends 2019-02-06,friday');
|
||||
ganttDb.addSection('weekends skip test');
|
||||
|
@ -53,6 +54,7 @@ describe('when using the ganttDb', function () {
|
|||
${'getExcludes'} | ${[]}
|
||||
${'getSections'} | ${[]}
|
||||
${'endDatesAreInclusive'} | ${false}
|
||||
${'getDisplayMode'} | ${''}
|
||||
`)('should clear $fn', ({ fn, expected }) => {
|
||||
expect(ganttDb[fn]()).toEqual(expected);
|
||||
});
|
||||
|
|
|
@ -24,12 +24,43 @@ export const setConf = function () {
|
|||
log.debug('Something is calling, setConf, remove the call');
|
||||
};
|
||||
|
||||
/**
|
||||
* For this issue:
|
||||
* https://github.com/mermaid-js/mermaid/issues/1618
|
||||
*
|
||||
* Finds the number of intersections between tasks that happen at any point in time.
|
||||
* Used to figure out how many rows are needed to display the tasks when the display
|
||||
* mode is set to 'compact'.
|
||||
*
|
||||
* @param tasks
|
||||
* @param orderOffset
|
||||
*/
|
||||
const getMaxIntersections = (tasks, orderOffset) => {
|
||||
let timeline = [...tasks].map(() => -Infinity);
|
||||
let sorted = [...tasks].sort((a, b) => a.startTime - b.startTime || a.order - b.order);
|
||||
let maxIntersections = 0;
|
||||
for (const element of sorted) {
|
||||
for (let j = 0; j < timeline.length; j++) {
|
||||
if (element.startTime >= timeline[j]) {
|
||||
timeline[j] = element.endTime;
|
||||
element.order = j + orderOffset;
|
||||
if (j > maxIntersections) {
|
||||
maxIntersections = j;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxIntersections;
|
||||
};
|
||||
|
||||
let w;
|
||||
export const draw = function (text, id, version, diagObj) {
|
||||
const conf = getConfig().gantt;
|
||||
|
||||
// diagObj.db.clear();
|
||||
// parser.parse(text);
|
||||
|
||||
const securityLevel = getConfig().securityLevel;
|
||||
// Handle root and Document for when rendering in sandbox mode
|
||||
let sandboxElement;
|
||||
|
@ -56,7 +87,40 @@ export const draw = function (text, id, version, diagObj) {
|
|||
const taskArray = diagObj.db.getTasks();
|
||||
|
||||
// Set height based on number of tasks
|
||||
const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
|
||||
|
||||
let categories = [];
|
||||
|
||||
for (const element of taskArray) {
|
||||
categories.push(element.type);
|
||||
}
|
||||
|
||||
categories = checkUnique(categories);
|
||||
const categoryHeights = {};
|
||||
|
||||
let h = 2 * conf.topPadding;
|
||||
if (diagObj.db.getDisplayMode() === 'compact' || conf.displayMode === 'compact') {
|
||||
const categoryElements = {};
|
||||
for (const element of taskArray) {
|
||||
if (categoryElements[element.section] === undefined) {
|
||||
categoryElements[element.section] = [element];
|
||||
} else {
|
||||
categoryElements[element.section].push(element);
|
||||
}
|
||||
}
|
||||
|
||||
let intersections = 0;
|
||||
for (const category of Object.keys(categoryElements)) {
|
||||
const categoryHeight = getMaxIntersections(categoryElements[category], intersections) + 1;
|
||||
intersections += categoryHeight;
|
||||
h += categoryHeight * (conf.barHeight + conf.barGap);
|
||||
categoryHeights[category] = categoryHeight;
|
||||
}
|
||||
} else {
|
||||
h += taskArray.length * (conf.barHeight + conf.barGap);
|
||||
for (const category of categories) {
|
||||
categoryHeights[category] = taskArray.filter((task) => task.type === category).length;
|
||||
}
|
||||
}
|
||||
|
||||
// Set viewBox
|
||||
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
||||
|
@ -74,16 +138,6 @@ export const draw = function (text, id, version, diagObj) {
|
|||
])
|
||||
.rangeRound([0, w - conf.leftPadding - conf.rightPadding]);
|
||||
|
||||
let categories = [];
|
||||
|
||||
for (const element of taskArray) {
|
||||
categories.push(element.type);
|
||||
}
|
||||
|
||||
const catsUnfiltered = categories; // for vert labels
|
||||
|
||||
categories = checkUnique(categories);
|
||||
|
||||
/**
|
||||
* @param a
|
||||
* @param b
|
||||
|
@ -157,11 +211,15 @@ export const draw = function (text, id, version, diagObj) {
|
|||
* @param w
|
||||
*/
|
||||
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) {
|
||||
// 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 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.
|
||||
svg
|
||||
.append('g')
|
||||
.selectAll('rect')
|
||||
.data(theArray)
|
||||
.data(uniqueTasks)
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('x', 0)
|
||||
|
@ -582,12 +640,9 @@ export const draw = function (text, id, version, diagObj) {
|
|||
* @param theTopPad
|
||||
*/
|
||||
function vertLabels(theGap, theTopPad) {
|
||||
const numOccurances = [];
|
||||
let prevGap = 0;
|
||||
|
||||
for (const [i, category] of categories.entries()) {
|
||||
numOccurances[i] = [category, getCount(category, catsUnfiltered)];
|
||||
}
|
||||
const numOccurances = Object.keys(categoryHeights).map((d) => [d, categoryHeights[d]]);
|
||||
|
||||
svg
|
||||
.append('g') // without doing this, impossible to put grid lines behind text
|
||||
|
@ -625,7 +680,6 @@ export const draw = function (text, id, version, diagObj) {
|
|||
}
|
||||
})
|
||||
.attr('font-size', conf.sectionFontSize)
|
||||
.attr('font-size', conf.sectionFontSize)
|
||||
.attr('class', function (d) {
|
||||
for (const [i, category] of categories.entries()) {
|
||||
if (d[0] === category) {
|
||||
|
@ -682,31 +736,6 @@ export const draw = function (text, id, version, diagObj) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* From this stack exchange question:
|
||||
* http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
|
||||
*
|
||||
* @param arr
|
||||
*/
|
||||
function getCounts(arr) {
|
||||
let i = arr.length; // const to loop over
|
||||
const obj = {}; // obj to store results
|
||||
while (i) {
|
||||
obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific from everything
|
||||
*
|
||||
* @param word
|
||||
* @param arr
|
||||
*/
|
||||
function getCount(word, arr) {
|
||||
return getCounts(arr)[word] || 0;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -133,7 +133,8 @@ statement
|
|||
| title {yy.setDiagramTitle($1.substr(6));$$=$1.substr(6);}
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
| section { yy.addSection($1.substr(8));$$=$1.substr(8); }
|
||||
| clickStatement
|
||||
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
|
||||
| directive
|
||||
|
|
|
@ -189,9 +189,27 @@ The pattern is:
|
|||
|
||||
More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/d3/d3-time#interval_every)
|
||||
|
||||
## 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.
|
||||
|
||||
```mmd
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
|
||||
|
||||
```mmd
|
||||
gantt
|
||||
|
|
|
@ -650,6 +650,7 @@ function addA11yInfo(
|
|||
* numberSectionStyles: 4,
|
||||
* axisFormat: '%Y-%m-%d',
|
||||
* topAxis: false,
|
||||
* displayMode: '',
|
||||
* },
|
||||
* };
|
||||
* mermaid.initialize(config);
|
||||
|
|
Loading…
Reference in New Issue