From b3b7108d5903ed879e9300abea20dab116002cf5 Mon Sep 17 00:00:00 2001 From: Jeremy Funk Date: Wed, 22 Mar 2023 23:15:54 +0100 Subject: [PATCH 01/12] Implement basic repeating tasks --- packages/mermaid/src/config.type.ts | 1 + .../src/diagrams/gantt/ganttRenderer.js | 78 +++++++++++++++---- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 157304149..c1f94055b 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -335,6 +335,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig { axisFormat?: string; tickInterval?: string; topAxis?: boolean; + verticalDisplayMode?: 'default' | 'merged' | 'compact'; } export interface SequenceDiagramConfig extends BaseDiagramConfig { diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index 7a012beb5..53266a5f4 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -24,12 +24,31 @@ export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); }; +const getMaxIntersections = (tasks, orderOffset) => { + let timeline = [...tasks].map(() => 0); + 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 +75,41 @@ 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; + + conf.verticalDisplayMode = 'compact'; + + let categories = []; + + for (const element of taskArray) { + categories.push(element.type); + } + + const catsUnfiltered = categories; // for vert labels + + categories = checkUnique(categories); + const categoryHeights = {}; + + let h = 2 * conf.topPadding; + if (conf.verticalDisplayMode === undefined || conf.verticalDisplayMode === 'default') { + h = taskArray.length * (conf.barHeight + conf.barGap); + } else if (conf.verticalDisplayMode === '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; + } + } // Set viewBox elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h); @@ -74,16 +127,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 @@ -158,10 +201,14 @@ export const draw = function (text, id, version, diagObj) { */ function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) { // Draw background rects covering the entire width of the graph, these form the section rows. + + const uniqueTaskOrderIds = [...new Set(theArray.map((item) => item.order))]; + const uniqueTasks = uniqueTaskOrderIds.map((id) => theArray.find((item) => item.order === id)); + svg .append('g') .selectAll('rect') - .data(theArray) + .data(uniqueTasks) .enter() .append('rect') .attr('x', 0) @@ -582,12 +629,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 From a535fe1679944a1cb608b2aa9b909c10fe26af75 Mon Sep 17 00:00:00 2001 From: Jeremy Funk Date: Thu, 23 Mar 2023 22:38:04 +0100 Subject: [PATCH 02/12] Bugfixes, refactor, add compact --- demos/gantt.html | 33 +++++++- docs/config/setup/modules/defaultConfig.md | 2 +- packages/mermaid/src/config.type.ts | 2 +- packages/mermaid/src/defaultConfig.ts | 14 ++++ .../mermaid/src/diagrams/gantt/ganttDb.js | 12 +++ .../src/diagrams/gantt/ganttRenderer.js | 78 ++++++++----------- .../src/diagrams/gantt/parser/gantt.jison | 9 ++- 7 files changed, 96 insertions(+), 54 deletions(-) diff --git a/demos/gantt.html b/demos/gantt.html index 613dc8694..aa855a650 100644 --- a/demos/gantt.html +++ b/demos/gantt.html @@ -78,7 +78,7 @@ axisFormat %d/%m todayMarker off section Section1 - Today: 1, -01:00, 5min + Today: 1, 08-08-09-01:00, 5min
@@ -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
@@ -166,6 +166,35 @@
+
+      gantt
+        title GANTT compact
+        dateFormat  HH:mm:ss
+        axisFormat  %Hh%M
+        compact
+
+        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
+    
+
+